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}