Package se.wollan.httpwrapper

Source Code of se.wollan.httpwrapper.Http

/*
* Copyright (c) 2013 Andreas Wohlén
* MIT License
*/
package se.wollan.httpwrapper;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import javax.xml.bind.DatatypeConverter;

/**
* Version 1.3
* <p>
* Wrapper class for {@link HttpURLConnection}.
* Use directly to send/receive text data (assuming UTF-8 everywhere)
* supporting common request parameters without streaming, or
* extend class to access to the current {@link HttpURLConnection} instance.
* Override {@link #setRequestParams(HttpURLConnection)} to set additional params,
* {@link #sendRequestData(HttpURLConnection)} to write output stream or
* {@link #receiveResponse(HttpURLConnection)} to read input stream.
* <p>
* Usage:
* <ol>
* <li>Format the request: <code>Http conn =
* new Http().method("POST").url("http://localhost/").data("My post data!");</code></li>
* <li>Execute request: <code>String respData = conn.exec();</code></li>
* <li>Optional. Check response: <code>conn.respCode(), conn.respHeaders(), ...</code></li>
* <li>Optional. Clear data: <code>conn.clear(), conn.clearReq(), conn.clearResp()</code></li>
* </ol>
* <p>
* Example:<br>
* <code>String resp = new Http("http://www.google.com").exec();<code>
*/
/*
* TODO:
*  - Multiple encodings. Including read charset from header?
*/
public class Http {
 
  private final static String CHARSET = "UTF-8";
 
  /** Convenience method for simply getting an URL. */
  public static String get(String url) throws IOException {
    return new Http(url).exec();
  }

  //request params
  private String url;
  private String method;
  private int timeout;
  private List<String> reqHeaderFieldNames;
  private List<String> reqHeaderFieldValues;
  private String reqData;
  private Map<String, String> reqForm;
 
  //response data
  private int respCode;
  private String respData;
  private Map<String, List<String>> respHeaders;
  private IOException ioException;
 
  /** Create an empty http object. */
  public Http() {
    clear();
  }
 
  /** Same as <code>new Http().url(myurl)</code>. */
  public Http(String urlToExecute) {
    clear();
    url = urlToExecute;
  }

  /** Resets both req and resp to default values effectively recycling this instance. */
  public Http clear() {
    clearReq();
    clearResp();
    return this;
  }
 
  /** Reset req parameters to default values. */
  public Http clearReq() {
    url = null;
    method = null;
    timeout = -1;
    reqHeaderFieldNames = null;
    reqHeaderFieldValues = null;
    reqData = null;
    reqForm = null;
    return this;
  }
 
  /** Removes latest response releasing potentially large resources like respData. */
  public Http clearResp() {   
    respCode = -1;
    respData = null;
    respHeaders = null;
    ioException = null;
    return this;
  }

  /** Set the url. Wrapper for {@link URL#URL(String)}. */
  public Http url(String urlToExecute) {
    url = urlToExecute;
    return this;
  }
 
  /** Wrapper for {@link HttpURLConnection#getURL()} */
  public String url() {
    return url;
  }
 
  /** Wrapper for {@link HttpURLConnection#setRequestMethod(String)}.
   * Default is "GET". */
  public Http method(String httpMethodToUse) {
    method = httpMethodToUse;
    return this;
  }
 
  /** Wrapper for {@link HttpURLConnection#getRequestMethod()} */
  public String method() {
    return method;
  }
 
  /** Wrapper for {@link HttpURLConnection#setConnectTimeout(int)}. */
  public Http timeout(int newTimeoutMillis) {
    timeout = newTimeoutMillis;
    return this;
  }
 
  /** Wrapper for {@link HttpURLConnection#getConnectTimeout()} */
  public int timeout() {
    return timeout;
  }
 
  /** Adds a request header. Wrapper for
   * {@link HttpURLConnection#addRequestProperty(String, String)}. */
  public Http header(String fieldName, String fieldValue) { 
    if(reqHeaderFieldNames == null) {
      reqHeaderFieldNames = new ArrayList<String>();
      reqHeaderFieldValues = new ArrayList<String>();
    }
    reqHeaderFieldNames.add(fieldName);
    reqHeaderFieldValues.add(fieldValue);
    return this;
  }
 
  /** Add a basic authentication header. */
  public Http basicAuth(String username, String password) {
    if(username != null && password != null) {
      header("Authorization", "Basic " +
          DatatypeConverter.printBase64Binary(
          (username+":"+username).getBytes()));
    }
    return this;
  }
 
  /**
   * Add a cookie to request headers. About allowed
   * characters: http://stackoverflow.com/a/1969339/1593797.
   * No cookie session management.
   */
  public Http cookie(String name, String value) {
    if(name != null && value != null) {
      header("Cookie", name + "=" + value);
    }
    return this;
  }
 
  /**
   * Set arbitrary text data to send with this request.
   * Any form data will be deleted, see {@link #form(String, String)}.
   *
   * @param dataToSend null disables sending
   * @return this to allow chaining
   */
  public Http data(String dataToSend) {
    reqData = dataToSend;
    if(dataToSend != null) {
      reqForm = null;
    }
    return this;
  }
 
  /** @return request data set by {@link #data(String)}. */
  public String data() {
    return reqData;
  }
 
  /**
   * Set a HTML form name-value-pair to send with this request
   * assuming UTF-8.<br>
   * Call multiple times to set a whole form.<br>
   * Any data set by {@link #data(String)} will be deleted,
   * as it's not possible to send both form and arbitrary data
   * with the same request.<br>
   * The Content-Type <tt>application/x-www-form-urlencoded</tt> is not
   * set automatically. Do so using {@link #header(String, String)} if
   * necessary.
   *
   * @param fieldName null fieldName disables the whole form
   * @param fieldValue to disable one previously set field, call this
   * with same fieldName and fieldValue=null
   * @return this to allow chaining
   */
  public Http form(String fieldName, String fieldValue) {
    if(fieldName == null) {
      reqForm = null;
      return this;
    }
    if(reqForm == null) {
      reqForm = new HashMap<String, String>();
    }
    reqForm.put(fieldName, fieldValue);
    reqData = null;
    return this;
  }
 
  /** @return request form data set by {@link #form(String, String)}. */
  public Map<String, String> form() {
    return reqForm;
  }
 
  /**
   * Execute request silently. Check exceptions
   * by {@link #ioException()}.
   * 
   * @return the response data including error data.
   * (Same as {@link #respData()}.)
   */
  public String execSilently() {
    try {
      httpExec();
      return respData;
    } catch (IOException e) {
      ioException = e;
      return respData;
    }
  }
 
  /**
   * Execute request.
   *
   * @return the response data on a successful request.
   * (Retrieve response data for http codes above 400 by {@link #respData()}
   * after caught exception).
   * @throws IOException
   */
  public String exec() throws IOException {
    httpExec();
    return respData;
  }
 
  /** Checks whether we got a response code from server.
   * If false, check {@link #ioException}. */
  public boolean gotResp() {
    return respCode != -1;
  }
 
  /** Wrapper for {@link HttpURLConnection#getResponseCode()}. */
  public int respCode() {
    return respCode;
  }
 
  /** @return The latest response from server including error data
   * (eg. http 404 response). */
  public String respData() {
    return respData;
  }
 
  /** Wrapper for {@link URLConnection#getHeaderFields()}. */
  public Map<String, List<String>> respHeaders() {
    return respHeaders;
  }
 
  /** @return saved exception from {@link #execSilently()}.  */
  public IOException ioException() {
    return ioException;
  }
 
  private void httpExec() throws IOException {
    clearResp();
   
    final HttpURLConnection httpConn = (HttpURLConnection)
        new URL(url).openConnection();
    try {
      setRequestParams(httpConn);     
      sendRequestData(httpConn);
     
      respCode = httpConn.getResponseCode();
      respHeaders = httpConn.getHeaderFields();
      receiveResponse(httpConn);
    } finally {
      httpConn.disconnect();
    }
  }

  /**
   * Called during execution. Sets request parameters prior to actual connection.
   * Override to add custom properties to the {@link HttpURLConnection}
   * not present as public methods.
   *
   * @param httpConn the current instance
   */
  protected void setRequestParams(HttpURLConnection httpConn) throws ProtocolException {
    if(method != null) {
      httpConn.setRequestMethod(method);
    }
    if(timeout >= 0) httpConn.setConnectTimeout(timeout);
    if(reqHeaderFieldNames != null) {
      for(int i=0; i<reqHeaderFieldNames.size(); i++) {
        httpConn.addRequestProperty(reqHeaderFieldNames.get(i), reqHeaderFieldValues.get(i));
      }
    }
  }
 
  /**
   * Called during execution. Override to write the
   * {@link HttpURLConnection#getOutputStream()} yourself.
   *
   * @param httpConn the current instance
   * @return the raw data to send
   * @throws UnsupportedEncodingException
   */
  protected void sendRequestData(HttpURLConnection httpConn) throws IOException {
    final String outData = encodeRequestData();   
    if(outData != null) {
      httpConn.setDoOutput(true);
      writeStream(httpConn.getOutputStream(), outData);
    }
  }
 
  /**
   * Called during execution when receiving response. Override to
   * read the {@link HttpURLConnection#getInputStream()} yourself.
   *
   * @param httpConn the current instance
   * @throws IOException
   */
  protected void receiveResponse(HttpURLConnection httpConn) throws IOException {
    try {
      respData = readStream(httpConn.getInputStream());
    } catch (IOException e) {
      respData = readStream(httpConn.getErrorStream());
      throw e;
    }
  }

  /**
   * Write an output stream to a string assuming UTF-8.
   * It will internally wrap the stream with a {@link BufferedOutputStream}
   * and close it when done.
   */
  public static void writeStream(OutputStream os, String data) throws IOException {
    final OutputStreamWriter out = new OutputStreamWriter(
        new BufferedOutputStream(os), CHARSET);
    try {
      out.write(data);
    } finally {
      out.close();
    }
  }
 
  /**
   * Read an input stream into a string assuming UTF-8.
   * It will internally wrap the stream with a {@link BufferedInputStream}
   * and close it when done.
   */
  public static String readStream(InputStream is) throws IOException {
    if(is == null) return null;
    final Scanner scanner = new Scanner(new BufferedInputStream(is), CHARSET);
    scanner.useDelimiter("\\A");
    final String content = scanner.hasNext() ? scanner.next() : "";
    final IOException readException = scanner.ioException();
    scanner.close();
    if(readException != null) {
      throw new IOException(readException);
    }
    return content;
  }
 
  /**
   * @return a hexadecimal md5 hash of the provided string assuming UTF-8.
   */
  public static String md5(String toHash) {     
    try {
      return new BigInteger(1,
          MessageDigest.getInstance("MD5").digest(
          String.valueOf(toHash).getBytes(CHARSET)))
          .toString(16);
    } catch (UnsupportedEncodingException e) {
    } catch (NoSuchAlgorithmException e) {}
    return null; //will never happen
  }
 
  private String encodeRequestData() throws UnsupportedEncodingException {
    if(reqData != null) {
      return reqData;
    }
    if(reqForm != null) {     
      //URL encode the form data
      final StringBuilder result = new StringBuilder();
      boolean first = true;
      for(Map.Entry<String, String> entry : reqForm.entrySet()) {
        if(entry.getValue() != null) {         
          if(first) {
            first = false;
          } else {
            result.append("&")
          }
          result.append(URLEncoder.encode(entry.getKey(), CHARSET));
              result.append("=");
              result.append(URLEncoder.encode(entry.getValue(), CHARSET));
        }
      }
      return result.toString();
    }
    return null;
  }

}
TOP

Related Classes of se.wollan.httpwrapper.Http

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.