GithubHelp home page GithubHelp logo

niuqinghua / kolich-httpclient4-closure Goto Github PK

View Code? Open in Web Editor NEW

This project forked from markkolich/kolich-httpclient4-closure

0.0 1.0 0.0 238 KB

A Java 8 wrapper around the synchronous Apache Commons HttpClient 4.

Home Page: http://markkolich.github.com/repo/com/kolich/kolich-httpclient4-closure

License: Other

Java 100.00%

kolich-httpclient4-closure's Introduction

kolich-httpclient4-closure

A Java 8 wrapper around the synchronous Apache Commons HttpClient 4.

For most applications, the synchronous HttpClient closure is acceptable — the thread making the request will block, waiting for the request to complete before continuing. If you need a pure asynchronous solution, please see the Async Http Client.

Overview

Using HttpClient directly is often cumbersome for vanilla HEAD, GET, POST, PUT and DELETE requests. For example, it often takes multiple lines of boiler plate Java to send a simple GET request, check the resulting status code, read a response (if any), and release the connection back into the connection pool.

In most implementations, the typical HttpClient usage pattern almost always involves:

  1. Creating a new HttpHead, HttpGet, HttpPost, HttpPut, or HttpDelete instance specific to the operation.
  2. Setting an request body ("entity") to be sent with the request, if any.
  3. Actually sending the request.
  4. Checking the HTTP response status code. If successful, do something. If not successful, do something else.
  5. Reading a response entity, if any. If one exists, convert the entity to something useful so you can do something with it.
  6. Freeing the response entity and releasing the connection back into the underlying connection pool.

The intent of this library is to let you do all of this in a cleaner, repeatable and more understandable manner.

Many would argue that this library simply trades one set of "boiler plate" for another. True. However, the patterns used here are much easier to grasp and they help you prevent obvious mistakes — like forgetting to close an InputStream when you're done with a response entity, which almost always manifests itself as a nasty leak under a heavy load.

Latest Version

See the Releases page for the latest version.

Resolvers

If you wish to use this artifact, you can easily add it to your existing Maven or Gradle project using my GitHub hosted Maven2 repository.

Maven

<repository>
  <id>Kolichrepo</id>
  <name>Kolich repo</name>
  <url>http://markkolich.github.io/repo/</url>
  <layout>default</layout>
</repository>

<dependency>
  <groupId>com.kolich</groupId>
  <artifactId>kolich-httpclient4-closure</artifactId>
  <version>3.2</version>
  <scope>compile</scope>
</dependency>

Gradle

compile 'com.kolich:kolich-httpclient4-closure:3.2'

Functional Concepts

Formally speaking, this library does not use "closures" (Lambda expressions) but rather a well defined pattern with anonymous classes.

Some concepts in this library, like the com.kolich.common.either.Either<F,S>, were borrowed directly from Scala. In this case, Either<F,S> uses Java generics so that this library can return either a left type F indicating failure, or a right type S indicating success. It's up to you, the developer, to define what these types are when you define your (anonymous) class — the definition of a "successful" return type varies from application to application.

When you receive an Either<F,S> you can check for success by calling the success method on the result.

final Either<Exception,String> result =
  new HttpClient4Closure<Exception,String>(client) {
    // ...
  }.get("http://example.com"); // blocks

if(result.success()) {
  // It worked!
}

Using Either<F,S>

When presented with an Either<F,S>, you can extract the return value of type S on success by calling right.

final String s = result.right();

On the other hand, to extract the return value of type F on failure, call left.

final Exception cause = result.left();

Note that if you call right on a request that failed, expect a null return value. Likewise, if you call left on a request that succeeded, also expect a null return value.

Usage Details

A few other details you'll probably be interested in:

  • This library automatically releases/frees all connection resources allocated/opened by the underlying HTTP client when a request has finished, either successfully or unsuccessfully. You don't have to worry about closing any internal HTTP client entity streams, that's done for you.
  • All return types are developer-defined based on how you parameterize your Either<F,S>. It's up to you to write a success method which converts an HttpSuccess object to your desired success type S.
  • The default definition of "success" is any request that 1) completes without Exception and 2) receives an HTTP status code that is less than (<) 400 Bad Request. You can easily override this default behavior by implementing a custom check method as needed.
  • If you need to manipulate the request immediately before execution, you should override the before method. This lets you do things like sign the request, or add the right authentication headers before the request is sent.
  • If you need to examine the raw response immediately after execution, before the result is checked for success, you should override the after method.

There are a number of examples highlighting these closure entry points in the content below.

Get an HttpClient

Before you can make HTTP requests, you need an HttpClient instance. You can use your own HttpClient (as instantiated elsewhere by another class), or you can use the HttpClient4ClosureBuilder.Factory class packaged with this library to snag a pre-configured HttpClient.

import com.kolich.http.HttpClient4ClosureBuilder.Factory;

final HttpClient client = HttpClient4ClosureBuilder.Factory.getNewInstanceWithProxySelector();

Or, pass a String to the factory method to set the HTTP User-Agent on your new HttpClient instance.

final HttpClient client = HttpClient4ClosureBuilder.Factory.getNewInstanceWithProxySelector("IE6, srsly");

By default, the HttpClient4ClosureBuilder.Factory always returns an HttpClient instance backed by a thread-safe PoolingClientConnectionManager. There's currently no support for passing your own connection manager to the HttpClient4ClosureBuilder.Factory — if you need to use your own connection manager, it's safest to just build your own HttpClient instance elsewhere.

HttpClient Factory for Beans

You can use the HttpClient4ClosureBuilder.Factory to also instantiate an HttpClient as a bean:

<bean id="YourHttpClient"
  class="com.kolich.http.HttpClient4ClosureBuilder.Factory"
  factory-method="getNewInstanceWithProxySelector">
  <!-- Set a custom User-Agent string too, if you want. -->
  <constructor-arg><value>IE6, srsly</value></constructor-arg>
</bean>

<bean id="SomeBean" class="com.foo.bar.SomeBean">
  <property name="httpClient" ref="YourHttpClient" />			    	
</bean>

Web-Proxy Support

Some environments require outgoing HTTP/HTTPS connections to use a web-proxy.

Fortunately, HttpClient integrates nicely with java.net.ProxySelector which makes it possible to automatically detect proxy settings across platforms.

That said, you can easily create a proxy-aware HttpClient instance using the right factory method.

Get a new HttpClient from the HttpClient4ClosureBuilder.Factory.

import com.kolich.http.HttpClient4ClosureBuilder.Factory;

final HttpClient usesProxy = HttpClient4ClosureBuilder.Factory.getNewInstanceWithProxySelector();

final HttpClient noProxy = HttpClient4ClosureBuilder.Factory.getNewInstanceNoProxySelector();

When using the getNewInstanceWithProxySelector static factory methods, the underlying client will automatically use the JVM's java.net.ProxySelector to discover what web-proxy to use when establishing outgoing HTTP connections. On all platforms, you can manually tell the JVM's default java.net.ProxySelector what web-proxy to use by setting the http.proxyHost and http.proxyPort VM arguments for vanilla HTTP connections. For outgoing HTTPS connections, use https.proxyHost and https.proxyPort.

java -Dhttp.proxyHost=proxy.example.com -Dhttp.proxyPort=3128 \
     -Dhttps.proxyHost=proxy.example.com -Dhttps.proxyPort=3128 \
     -jar runme.jar

Examples

Synchronous, or blocking, HTTP requests block the thread of execution until the request has finished, either successfully or unsuccessfully. When making synchronous requests, the execution thread waits for the HTTP transaction to complete. Generally speaking, this model is acceptable for your everyday "vanilla" application. However, in some applications this may be suboptimal, given that the requesting thread is blocked waiting on the HTTP transaction to finish, and consequently cannot do any additional work.

HEAD

Send a HEAD request and expect back an array of HTTP response headers on success. Drop any failures on the floor — expect a null return value in place of success type S if anything went wrong.

final Either<Void,Header[]> result =
  new HttpClient4Closure<Void,Header[]>(client) {
  @Override
  public Header[] success(final HttpSuccess success) {
    return success.getResponse().getAllHeaders();
  }
}.head("http://example.com");

final Header[] headers = result.right();

GET

Send a GET request expecting either a String back on success, or an Exception on failure — this is represented by the Either<Exception,String> return type.

final Either<Exception,String> result =
  new HttpClient4Closure<Exception,String>(client) {
  @Override
  public String success(final HttpSuccess success) throws Exception {
    return EntityUtils.toString(success.getEntity(), "UTF-8");
  }
  @Override
  public Exception failure(final HttpFailure failure) {
    return failure.getCause();
  }
}.get("http://example.com");

Or, send a GET request still expecting a String on success, but drop any failures on the floor — expect a null return value in place of success type S if anything went wrong.

final Either<Void,String> result =
  new HttpClient4Closure<Void,String>(client) {
  @Override
  public String success(final HttpSuccess success) throws Exception {
    return EntityUtils.toString(success.getEntity(), "UTF-8");
  }
}.get(new HttpGet("http://example.com/sdljflk8831sdflk")); // Obvious 404

System.out.println(result.right()); // Prints "null"

Send a GET request and stream the result on success to an existing and open OutputStream.

import org.apache.commons.io.IOUtils;

final OutputStream os = ...; // Existing and open output stream

final Either<Void,Long> result =
  new HttpClient4Closure<Void,Long>(client) {
  @Override
  public Long success(final HttpSuccess success) throws Exception {
    return IOUtils.copyLarge(success.getContent(), os);
  }
}.get("http://api.example.com/path/to/big/resource");

// Success result is the number of bytes copied.
System.out.println("I copied " + result.right() + " total bytes.");

Send a GET request and extract a List<Cookie> (list of Cookie's) from the response, using the default HttpContext and a per-request BasicCookieStore.

import static org.apache.http.client.protocol.ClientContext.COOKIE_STORE;
import org.apache.http.client.CookieStore;
import org.apache.http.impl.client.BasicCookieStore;

final Either<Integer,List<Cookie>> mmmm =
  new HttpClient4Closure<Integer,List<Cookie>>(client) {
  @Override
  public void before(final HttpRequestBase request, final HttpContext context) {
    context.setAttribute(COOKIE_STORE, new BasicCookieStore());
  }
  @Override
  public List<Cookie> success(final HttpSuccess success) {
    // Extract a list of cookies from the request.
    // Might be empty.
    return ((CookieStore)success.getContext()
      .getAttribute(COOKIE_STORE)).getCookies();
  }
  @Override
  public Integer failure(final HttpFailure failure) {
    return failure.getStatusCode();
  }
}.get("http://example.com");

// Get the list of extracted cookies from the response
// and print them out.  Mmmm, cookies.
final List<Cookie> cookies;
if((cookies = mmmm.right()) != null) {
  for(final Cookie c : cookies) {
    System.out.println(c.getName() + " -> " + c.getValue());
  }
}

POST

Send a POST request but manipulate the HttpBaseRequest object before execution by overriding the before method. Expect a String on success, and an Integer on failure.

final Either<Integer,String> result =
  new HttpClient4Closure<Integer,String>(client) {
  @Override
  public void before(final HttpRequestBase request) {
    request.addHeader("Authorization", "super-secret-password!");
  }
  @Override
  public String success(final HttpSuccess success) throws Exception {
    return EntityUtils.toString(success.getEntity(), "UTF-8");
  }
  @Override
  public Integer failure(final HttpFailure failure) {
    // Return the HTTP status code if anything went wrong.
    return failure.getStatusCode();
  }
}.post("http://api.example.com");

Or, send a POST with some form variables, ignoring failures, and convert a successful response to some custom entity using Google's GSON toolkit.

import com.google.gson.GsonBuilder;

final HttpPost request = new HttpPost("http://api.example.com");
// Set a POST-body on the request.
final List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("foo", "bar"));
params.add(new BasicNameValuePair("cat", "dog"));
request.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));

final Either<Void,MyClazz> result =
  new HttpClient4Closure<Void,MyClazz>(client) {
  @Override
  public MyClazz success(final HttpSuccess success) throws Exception {
    // Expects JSON from the server in response to the POST.
    return new GsonBuilder().create().fromJson(
      EntityUtils.toString(success.getEntity(), "UTF-8"),
      MyClazz.class);
  }
}.post(request);

final MyClazz entity = result.right(); // Kewl

PUT

Send a PUT request with an existing and open InputStream from another source. Expect an Integer back on success and nothing on failure.

final InputStream is = ...; // Existing and open input stream

final Either<Void,Integer> result =
  new HttpClient4Closure<Void,Integer>(client) {
  @Override
  public void before(final HttpRequestBase request) {
    ((HttpPut)request).setEntity(new InputStreamEntity(is, contentLength));
  }
  @Override
  public Integer success(final HttpSuccess success) throws Exception {
    return success.getStatusCode();
  }
}.put("http://api.example.com/upload");

if(result.success()) {
  System.out.println("PUT resulted in a " + result.right() + " status.");
}

DELETE

Send a DELETE request with a custom success check — in this example, the server returns a 410 Gone when the resource is deleted successfully but we don't want a 410 response to indicate failure.

final Either<Integer,Void> result =
  new HttpClient4Closure<Integer,Void>(client) {
  @Override
  public boolean check(final HttpResponse response, final HttpContext context) {
    // Success is 410, any other status code is failure.
    return (response.getStatusLine().getStatusCode() == 410);
  }
  @Override
  public Void success(final HttpSuccess success) throws Exception {
    return null; // Meh, for Void
  }
  @Override
  public Integer failure(final HttpFailure failure) {
    // Return the HTTP status code if anything went "wrong".
    return failure.getStatusCode();
  }
}.delete("http://api.example.com/go/away");

if(result.success()) {
  // Got 401 Gone response from server, resource is "history".
}

Helpers

To ease development, a number of helper closures are available out-of-the-box as found in the com.kolich.http.helpers package. These helpers are packaged and shipped with this library and are intended to help developers avoid much of the closure boiler plate for the most common operations.

Below are several examples using these helper closures.

StringOrNullClosure

Send a GET and if the request was successful extract the response body as a UTF-8 encoded String. If unsuccessful, return null.

import com.kolich.http.helpers.StringClosures.StringOrNullClosure;

final Either<Void,String> s = new StringOrNullClosure(client)
  .get("http://google.com");

final String html;
if((html = s.right()) != null) {
  System.out.println("Your HTML: " + html);
}

ByteArrayOrHttpFailureClosure

Send a POST and if the request was successful extract the response body as a byte[] array. If unsuccessful, return an HttpFailure object.

import com.kolich.http.helpers.ByteArrayClosures.ByteArrayOrHttpFailureClosure;

final Either<HttpFailure,byte[]> r = new ByteArrayOrHttpFailureClosure(client)
  .post("http://api.example.com/resource");

final byte[] bytes = r.right();

StatusCodeAndHeadersClosure

Send a GET and blindly ignore if the request was "successful" or not. Extract the resulting HTTP status code and headers on the response — even if the server responded with an "unsuccessful" status code.

import com.kolich.http.helpers.StatusCodeAndHeaderClosures.StatusCodeAndHeadersClosure;

// Tip: Use URIBuilder to add query parameters to the URI of a GET.
// (see example usage below)
import org.apache.http.client.utils.URIBuilder;

final StatusCodeAndHeadersClosure sah = new StatusCodeAndHeadersClosure(client) {
  @Override
  public void before(final HttpRequestBase request) throws Exception {
    // Add some query parameters to the request URI before its sent.
    final URIBuilder b = new URIBuilder(request.getURI());
    b.addParameter("foo", "bar");
    request.setURI(b.build());
  }
};

// Send the request.
sah.get("https://example.com/get/status/and/headers");

System.out.println("Got status " + sah.getStatusCode());
System.out.println("Found " + sah.getHeaderList().size() + " headers too!");

GsonOrHttpFailureClosure<S>

Send a POST and if the request succeeded, use GSON to convert the response body (a blob of JSON) to successful type S. If the request failed, expect an HttpFailure object.

Note, YourType below is assumed to be an entity class (a domain object) defined by your application or schema.

import com.kolich.http.helpers.GsonClosures.GsonOrHttpFailureClosure;

import com.google.gson.Gson;

final Gson gson = ...; // Get a GSON instance.

// Send the POST, and if the request is successful, use the GSON instance
// to unmarshall the response body to a valid YourType object.
final Either<HttpFailure,YourType> g =
  new GsonOrHttpFailureClosure<YourType>(client, gson, YourType.class)
    .post("https://api.example.com/resource.json");

// Will be null if the request failed.
final YourType t = g.right();

Or, use a GSON TypeToken if your expected successful type S is a generic type, and consequently, you cannot use .class due to Java's type erasure.

import com.kolich.http.helpers.GsonClosures.GsonOrHttpFailureClosure;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

final Gson gson = ...; // Get a GSON instance.

// Send the POST, and if the request is successful, use the GSON instance
// to unmarshall the response body to a valid List<YourType> object.
final Either<HttpFailure,List<YourType>> g =
  new GsonOrHttpFailureClosure<List<YourType>>(
    client, gson, new TypeToken<List<YourType>>(){}.getType()
  ).post("https://api.example.com/resource.json");

// Will be null if the request failed.
final List<YourType> lt = g.right();

Building

Clone or fork the repository.

#~> git clone https://github.com/markkolich/kolich-httpclient4-closure.git

To package the JAR, run mvn package:

mvn package

The resulting JAR is placed into the dist directory.

Licensing

Copyright (c) 2015 Mark S. Kolich

All code in this artifact is freely available for use and redistribution under the MIT License.

See LICENSE for details.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.