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.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
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.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
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;

/**
* Lightweight wrapper class for {@link HttpURLConnection}.
*
* @see https://bitbucket.org/wollan/httpwrapper
*/
/*
* TODO:
*  - Multiple encodings. Including read charset from header?
*  - Optimize for android: http://developer.android.com/reference/java/net/HttpURLConnection.html
*  - respLength() for number of bytes
*  - srcFile, srcStream as well.
*  - more efficient string reading
*/
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;
 
  //destinations
  private File destFile, destErrorFile;
  private OutputStream destStream, destErrorStream;
 
  //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;
  }
 
  /** Set default basic authentication. */
  public Http basicAuth(final String username, final String password) {
    Authenticator.setDefault(new Authenticator() {
      protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication(username,
            (password == null ? "" : password).toCharArray());
      }
    });
    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;
  }
 
  /**
   * Set the destination file.
   *
   * <p>The response bytes will be written to this file during execution, creating file if it doesn't exist.
   * Any <code>IOException</code> related file operations will be thrown later when executed.
   * This is for status codes below 400, for error codes use {@link #destErrorFile(File)}.
   *
   * <p><b>Note:</b> This does not interfere with any other <code>dest*()</code>-method
   * (multiple outputs are allowed), but it will disable the default string decoder.
   * Meaning {@link #exec()} will henceforth return <code>null</code>.
   */
  public Http destFile(File file) {
    destFile = file;
    return this;
  }
 
  /** Same as calling {@link #destFile(File)} with <code>new File(filePath)</code>. */
  public Http destFile(String filePath) {
    return destFile(new File(filePath));
  }
 
  /** @return the destination file set by {@link #destFile(File)}. */
  public File destFile() {
    return destFile;
  }
 
  /** Same as {@link #destFile(File)} but for error responses, ie. http codes >= 400. May be same file. */
  public Http destErrorFile(File errorFile) {
    destErrorFile = errorFile;
    return this;
  }
 
  /** @return the destination error file set by {@link #destErrorFile(File)}. */
  public File destErrorFile() {
    return destErrorFile;
  }
 
  /**
   * Set the destination stream.
   *
   * <p>The response bytes will be written to this output stream during execution, closing it when done.
   * Any relating <code>IOException</code> will be thrown later when executed.
   * This is for status codes below 400, for error codes use {@link #destErrorStream(OutputStream)}.
   *
   * <p><b>Note:</b> This does not interfere with any other <code>dest*()</code>-method
   * (multiple outputs are allowed), but it will disable the default string decoder.
   * Meaning {@link #exec()} will henceforth return <code>null</code>.
   */
  public Http destStream(OutputStream stream) {
    destStream = stream;
    return this;
  }
 
  /** @return the destination stream set by {@link #destStream(OutputStream)}. */
  public OutputStream destStream() {
    return destStream;
  }
 
  /** Same as {@link #destStream(OutputStream)} but for error responses, ie. http codes >= 400. May be same stream. */
  public Http destErrorStream(OutputStream errorStream) {
    destErrorStream = errorStream;
    return this;
  }
 
  /** @return the destination error stream set by {@link #destErrorStream(OutputStream)}. */
  public OutputStream destErrorStream() {
    return destErrorStream;
  }
 
  /**
   * 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()} and
   * {@link HttpURLConnection#getErrorStream()} yourself.
   *
   * @param httpConn the current instance
   * @throws IOException
   */
  protected void receiveResponse(HttpURLConnection httpConn) throws IOException {
    try {
      decodeRespInputStream(respCode(), httpConn.getInputStream());
    } catch (IOException e) {
      decodeRespErrorStream(respCode(), httpConn.getErrorStream());
      throw e;
    }
  } 
 
  /**
   * Called during execution when decoding {@link HttpURLConnection#getInputStream()}
   * (status codes below 400). Override this can be useful if you need to switch on
   * status code and decode some responses differently.
   * Don't forget to close the stream when done.
   *
   * @see #destStream(OutputStream)
   */
  protected void decodeRespInputStream(int statusCode, InputStream inputStream) throws IOException {
    decodeRespStream(inputStream, destFile, destStream);
  }
 
  /**
   * Same as {@link #decodeRespInputStream(InputStream)} but for
   * {@link HttpURLConnection#getErrorStream()} (codes >=400).
   *
   * @see #destErrorStream(OutputStream)
   */
  protected void decodeRespErrorStream(int statusCode, InputStream errorStream) throws IOException {
    decodeRespStream(errorStream, destErrorFile, destErrorStream);
  }
 
  private void decodeRespStream(InputStream is, File f, OutputStream os) throws IOException {
   
    //use default string decoder if there are no destinations
    if(f == null && os == null) {
      respData = readStream(is);
      return;
    }
   
    //create outputs
    OutputStream fos = f != null ? new FileOutputStream(f) : null;
   
    try {   
   
      //stream data to all non-null outputs
      byte[] buffer = new byte[8192]; //same size as bufferedInputStream  
      int n;
      while((n = is.read(buffer)) >= 0) {
        if(fos != null) {
          fos.write(buffer, 0, n);
        }
        if(os != null) {
          os.write(buffer, 0, n);
        }
      }
    } finally {
      //close all inputs and outputs
      is.close();
      if(fos != null) fos.close();
      if(os != null) os.close();
    }
  }

  /**
   * 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 and close it when done.
   */
  public static String readStream(InputStream is) throws IOException {
    final StringBuilder sb = new StringBuilder(is.available());
    final byte[] buffer = new byte[8192];
    int n;
    try {
      while((n = is.read(buffer)) >= 0) {       
        sb.append(new String(buffer, 0, n, CHARSET));
      }
      return sb.toString();
    } finally {
      is.close();
    }
  }
 
  /**
   * @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.