001package edu.pdx.cs410J.web; 002 003import com.google.common.annotations.VisibleForTesting; 004 005import java.io.*; 006import java.net.HttpURLConnection; 007import java.net.URL; 008import java.net.URLEncoder; 009import java.nio.charset.StandardCharsets; 010import java.util.HashMap; 011import java.util.Iterator; 012import java.util.Map; 013 014/** 015 * A helper class that provides methods for requesting resources via HTTP 016 */ 017public class HttpRequestHelper { 018 019 private final String urlString; 020 021 public HttpRequestHelper(String urlString) { 022 this.urlString = urlString; 023 } 024 025 /** 026 * Performs an HTTP GET 027 * 028 * @param parameters The key/value query parameters 029 * @return A <code>Response</code> summarizing the result of the GET 030 */ 031 public Response get(Map<String, String> parameters) throws IOException { 032 StringBuilder query = encodeParameters(parameters); 033 if (query.length() > 0) { 034 query.insert(0, '?'); 035 } 036 037 URL url = new URL(urlString + query); 038 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 039 conn.setRequestMethod("GET"); 040 conn.setDoOutput(true); 041 conn.setDoInput(true); 042 043 return new Response(conn); 044 045 } 046 047 /** 048 * Performs an HTTP POST 049 * 050 * @param parameters The key/value parameters 051 * @return A <code>Response</code> summarizing the result of the POST 052 */ 053 public Response post(Map<String, String> parameters) throws IOException { 054 return sendEncodedRequest(urlString, "POST", parameters); 055 } 056 057 /** 058 * Performs an HTTP DELETE 059 * 060 * @param parameters The key/value parameters 061 * @return A <code>Response</code> summarizing the result of the POST 062 */ 063 public Response delete(Map<String, String> parameters) throws IOException { 064 return sendEncodedRequest(urlString, "DELETE", parameters); 065 } 066 067 private Response sendEncodedRequest(String urlString, String requestMethod, Map<String, String> parameters) throws IOException { 068 StringBuilder data = encodeParameters(parameters); 069 070 URL url = new URL(urlString); 071 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 072 conn.setRequestMethod(requestMethod); 073 conn.setDoOutput(true); 074 075 OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8); 076 wr.write(data.toString()); 077 wr.flush(); 078 079 Response response = new Response(conn); 080 wr.close(); 081 082 return response; 083 } 084 085 /** 086 * Encodes parameters to be sent to the server via an HTTP GET, POST, or DELETE 087 * @param parameters The parameter key/value pairs 088 * @return The encoded parameters 089 */ 090 private StringBuilder encodeParameters(Map<String, String> parameters) { 091 StringBuilder query = new StringBuilder(); 092 for (Iterator<Map.Entry<String, String>> iter = parameters.entrySet().iterator(); iter.hasNext(); ) { 093 Map.Entry<String, String> pair = iter.next(); 094 String key = pair.getKey(); 095 String value = pair.getValue(); 096 query.append(URLEncoder.encode(key, StandardCharsets.UTF_8)); 097 query.append("="); 098 query.append(URLEncoder.encode(value, StandardCharsets.UTF_8)); 099 if (iter.hasNext()) { 100 query.append("&"); 101 } 102 } 103 return query; 104 } 105 106 /** 107 * Performs an HTTP PUT on the given URL 108 * 109 * @param parameters key/value parameters to the put 110 * @return A <code>Response</code> summarizing the result of the PUT 111 */ 112 public Response put(Map<String, String> parameters) throws IOException { 113 StringBuilder data = new StringBuilder(); 114 parameters.forEach((key, value) -> { 115 data.append(key); 116 data.append("="); 117 data.append(value); 118 data.append("\n"); 119 }); 120 121 URL url = new URL(urlString); 122 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 123 conn.setRequestMethod("PUT"); 124 conn.setDoOutput(true); 125 conn.setDoInput(true); 126 conn.setRequestProperty("Content-Type", "text/plain"); 127 conn.setRequestProperty("Context-Length", String.valueOf(data.length())); 128 129 OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream(), StandardCharsets.UTF_8); 130 wr.write(data.toString()); 131 wr.flush(); 132 133 Response response = new Response(conn); 134 wr.close(); 135 136 return response; 137 } 138 139 /** 140 * Encapsulates a response to an HTTP request 141 */ 142 public static class Response { 143 144 private final int httpStatusCode; 145 146 private final String content; 147 148 private int contentLines = 0; 149 150 private Response(HttpURLConnection conn) throws IOException { 151 this.httpStatusCode = conn.getResponseCode(); 152 InputStream stream; 153 if (this.httpStatusCode != java.net.HttpURLConnection.HTTP_OK) { 154 stream = conn.getErrorStream(); 155 156 } else { 157 stream = conn.getInputStream(); 158 } 159 160 StringBuilder content = new StringBuilder(); 161 if (stream != null) { 162 BufferedReader rd = new BufferedReader(new InputStreamReader(stream)); 163 String line; 164 while ((line = rd.readLine()) != null) { 165 content.append(line); 166 content.append("\n"); 167 contentLines++; 168 } 169 rd.close(); 170 } 171 172 this.content = content.toString().trim(); 173 } 174 175 @VisibleForTesting 176 public Response(String content) { 177 this.content = content; 178 this.httpStatusCode = HttpURLConnection.HTTP_OK; 179 } 180 181 /** 182 * Returns the HTTP status code of the response 183 * @see java.net.HttpURLConnection#getResponseCode() 184 */ 185 public int getHttpStatusCode() { 186 return httpStatusCode; 187 } 188 189 /** 190 * Returns the (presumably textual) response from the URL 191 */ 192 public String getContent() { 193 return content; 194 } 195 196 /** 197 * Returns the number of lines in the response's content 198 */ 199 public int getContentLines() { 200 return contentLines; 201 } 202 } 203 204 205 private static Map<String, String> arrayToMap(String[] parameters) { 206 Map<String, String> params = new HashMap<>(); 207 for (int i = 0; i < parameters.length; i++) { 208 String key = parameters[i]; 209 i++; 210 String value = parameters[i]; 211 params.put(key, value); 212 } 213 return params; 214 } 215 216 /** 217 * A main method that requests a resource from a URL using a given HTTP method 218 */ 219 public static void main(String[] args) throws IOException { 220 String method = args[0]; 221 String url = args[1]; 222 String[] parameters = new String[args.length - 2]; 223 System.arraycopy(args, 2, parameters, 0, parameters.length); 224 Map<String, String> map = arrayToMap(parameters); 225 226 HttpRequestHelper helper = new HttpRequestHelper(url); 227 228 Response response; 229 if (method.equalsIgnoreCase("PUT")) { 230 response = helper.put(map); 231 232 } else if (method.equalsIgnoreCase("GET")) { 233 response = helper.get(map); 234 235 } else if (method.equalsIgnoreCase("POST")) { 236 response = helper.post(map); 237 238 } else { 239 System.err.println("** Unknown method: " + method); 240 return; 241 } 242 243 System.out.println("Returned code " + response.getHttpStatusCode() + " and " + 244 response.getContentLines() + " lines of content\n"); 245 System.out.println(response.getContent()); 246 247 } 248 249 public static class RestException extends RuntimeException { 250 251 private final int httpStatusCode; 252 253 public RestException(int httpStatusCode, String message) { 254 super(message); 255 256 this.httpStatusCode = httpStatusCode; 257 } 258 259 public int getHttpStatusCode() { 260 return this.httpStatusCode; 261 } 262 } 263}