All Downloads are FREE. Search and download functionalities are using the official Maven repository.

play.libs.ws.WSAsync Maven / Gradle / Ivy

The newest version!
package play.libs.ws;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;

import org.apache.commons.lang.NotImplementedException;

import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.AsyncHttpClientConfig.Builder;
import com.ning.http.client.ProxyServer;
import com.ning.http.client.Realm.AuthScheme;
import com.ning.http.client.Realm.RealmBuilder;
import com.ning.http.client.Response;
import com.ning.http.client.multipart.ByteArrayPart;
import com.ning.http.client.multipart.FilePart;
import com.ning.http.client.multipart.Part;

import oauth.signpost.AbstractOAuthConsumer;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.http.HttpRequest;
import play.Logger;
import play.Play;
import play.libs.F.Promise;
import play.libs.MimeTypes;
import play.libs.OAuth.ServiceInfo;
import play.libs.WS.HttpResponse;
import play.libs.WS.WSImpl;
import play.libs.WS.WSRequest;
import play.mvc.Http.Header;

/**
 * Simple HTTP client to make webservices requests.
 * 
 * 

* Get latest BBC World news as a RSS content * *

 * HttpResponse response = WS.url("http://newsrss.bbc.co.uk/rss/newsonline_world_edition/front_page/rss.xml").get();
 * Document xmldoc = response.getXml();
 * // the real pain begins here...
 * 
*

* * Search what Yahoo! thinks of google (starting from the 30th result). * *

 * HttpResponse response = WS.url("http://search.yahoo.com/search?p=%s&pstart=1&b=%s", "Google killed me", "30").get();
 * if (response.getStatus() == 200) {
 *     html = response.getString();
 * }
 * 
*/ public class WSAsync implements WSImpl { private AsyncHttpClient httpClient; private static SSLContext sslCTX = null; public WSAsync() { String proxyHost = Play.configuration.getProperty("http.proxyHost", System.getProperty("http.proxyHost")); String proxyPort = Play.configuration.getProperty("http.proxyPort", System.getProperty("http.proxyPort")); String proxyUser = Play.configuration.getProperty("http.proxyUser", System.getProperty("http.proxyUser")); String proxyPassword = Play.configuration.getProperty("http.proxyPassword", System.getProperty("http.proxyPassword")); String nonProxyHosts = Play.configuration.getProperty("http.nonProxyHosts", System.getProperty("http.nonProxyHosts")); String userAgent = Play.configuration.getProperty("http.userAgent"); String keyStore = Play.configuration.getProperty("ssl.keyStore", System.getProperty("javax.net.ssl.keyStore")); String keyStorePass = Play.configuration.getProperty("ssl.keyStorePassword", System.getProperty("javax.net.ssl.keyStorePassword")); Boolean CAValidation = Boolean.parseBoolean(Play.configuration.getProperty("ssl.cavalidation", "true")); Builder confBuilder = new AsyncHttpClientConfig.Builder(); if (proxyHost != null) { int proxyPortInt = 0; try { proxyPortInt = Integer.parseInt(proxyPort); } catch (NumberFormatException e) { Logger.error(e, "Cannot parse the proxy port property '%s'. Check property http.proxyPort either in System configuration or in Play config file.", proxyPort); throw new IllegalStateException("WS proxy is misconfigured -- check the logs for details"); } ProxyServer proxy = new ProxyServer(proxyHost, proxyPortInt, proxyUser, proxyPassword); if (nonProxyHosts != null) { String[] strings = nonProxyHosts.split("\\|"); for (String uril : strings) { proxy.addNonProxyHost(uril); } } confBuilder.setProxyServer(proxy); } if (userAgent != null) { confBuilder.setUserAgent(userAgent); } if (keyStore != null && !keyStore.equals("")) { Logger.info("Keystore configured, loading from '%s', CA validation enabled : %s", keyStore, CAValidation); if (Logger.isTraceEnabled()) { Logger.trace("Keystore password : %s, SSLCTX : %s", keyStorePass, sslCTX); } if (sslCTX == null) { sslCTX = WSSSLContext.getSslContext(keyStore, keyStorePass, CAValidation); confBuilder.setSSLContext(sslCTX); } } // when using raw urls, AHC does not encode the params in url. // this means we can/must encode it(with correct encoding) before // passing it to AHC confBuilder.setDisableUrlEncodingForBoundedRequests(true); httpClient = new AsyncHttpClient(confBuilder.build()); } @Override public void stop() { Logger.trace("Releasing http client connections..."); httpClient.close(); } @Override public WSRequest newRequest(String url, String encoding) { return new WSAsyncRequest(url, encoding); } public class WSAsyncRequest extends WSRequest { protected String type = null; private String generatedContentType = null; protected WSAsyncRequest(String url, String encoding) { super(url, encoding); } /** * Returns the URL but removed the queryString-part of it The QueryString-info is later added with * addQueryString() * * @return The URL without the queryString-part */ protected String getUrlWithoutQueryString() { int i = url.indexOf('?'); if (i > 0) { return url.substring(0, i); } else { return url; } } /** * Adds the queryString-part of the url to the BoundRequestBuilder * * @param requestBuilder * : The request buider to add the queryString-part */ protected void addQueryString(BoundRequestBuilder requestBuilder) { // AsyncHttpClient is by default encoding everything in utf-8 so for // us to be able to use // different encoding we have configured AHC to use raw urls. When // using raw urls, // AHC does not encode url and QueryParam with utf-8 - but there is // another problem: // If we send raw (none-encoded) url (with queryString) to AHC, it // does not url-encode it, // but transform all illegal chars to '?'. // If we pre-encoded the url with QueryString before sending it to // AHC, ahc will decode it, and then // later break it with '?'. // This method basically does the same as // RequestBuilderBase.buildUrl() except from destroying the // pre-encoding // does url contain query_string? int i = url.indexOf('?'); if (i > 0) { try { // extract query-string-part String queryPart = url.substring(i + 1); // parse queryPart - and decode it... (it is going to be // re-encoded later) for (String param : queryPart.split("&")) { i = param.indexOf('='); String name; String value = null; if (i <= 0) { // only a flag name = URLDecoder.decode(param, encoding); } else { name = URLDecoder.decode(param.substring(0, i), encoding); value = URLDecoder.decode(param.substring(i + 1), encoding); } if (value == null) { requestBuilder.addQueryParam(URLEncoder.encode(name, encoding), null); } else { requestBuilder.addQueryParam(URLEncoder.encode(name, encoding), URLEncoder.encode(value, encoding)); } } } catch (UnsupportedEncodingException e) { throw new RuntimeException("Error parsing query-part of url", e); } } } private BoundRequestBuilder prepareAll(BoundRequestBuilder requestBuilder) { checkFileBody(requestBuilder); addQueryString(requestBuilder); addGeneratedContentType(requestBuilder); return requestBuilder; } public BoundRequestBuilder prepareGet() { return prepareAll(httpClient.prepareGet(getUrlWithoutQueryString())); } public BoundRequestBuilder prepareOptions() { return prepareAll(httpClient.prepareOptions(getUrlWithoutQueryString())); } public BoundRequestBuilder prepareHead() { return prepareAll(httpClient.prepareHead(getUrlWithoutQueryString())); } public BoundRequestBuilder preparePatch() { return prepareAll(httpClient.preparePatch(getUrlWithoutQueryString())); } public BoundRequestBuilder preparePost() { return prepareAll(httpClient.preparePost(getUrlWithoutQueryString())); } public BoundRequestBuilder preparePut() { return prepareAll(httpClient.preparePut(getUrlWithoutQueryString())); } public BoundRequestBuilder prepareDelete() { return prepareAll(httpClient.prepareDelete(getUrlWithoutQueryString())); } /** Execute a GET request synchronously. */ @Override public HttpResponse get() { this.type = "GET"; sign(); try { return new HttpAsyncResponse(prepare(prepareGet()).execute().get()); } catch (Exception e) { throw new RuntimeException(e); } } /** Execute a GET request asynchronously. */ @Override public Promise getAsync() { this.type = "GET"; sign(); return execute(prepareGet()); } /** Execute a PATCH request. */ @Override public HttpResponse patch() { this.type = "PATCH"; sign(); try { return new HttpAsyncResponse(prepare(preparePatch()).execute().get()); } catch (Exception e) { throw new RuntimeException(e); } } /** Execute a PATCH request asynchronously. */ @Override public Promise patchAsync() { this.type = "PATCH"; sign(); return execute(preparePatch()); } /** Execute a POST request. */ @Override public HttpResponse post() { this.type = "POST"; sign(); try { return new HttpAsyncResponse(prepare(preparePost()).execute().get()); } catch (Exception e) { throw new RuntimeException(e); } } /** Execute a POST request asynchronously. */ @Override public Promise postAsync() { this.type = "POST"; sign(); return execute(preparePost()); } /** Execute a PUT request. */ @Override public HttpResponse put() { this.type = "PUT"; try { return new HttpAsyncResponse(prepare(preparePut()).execute().get()); } catch (Exception e) { throw new RuntimeException(e); } } /** Execute a PUT request asynchronously. */ @Override public Promise putAsync() { this.type = "PUT"; return execute(preparePut()); } /** Execute a DELETE request. */ @Override public HttpResponse delete() { this.type = "DELETE"; try { return new HttpAsyncResponse(prepare(prepareDelete()).execute().get()); } catch (Exception e) { throw new RuntimeException(e); } } /** Execute a DELETE request asynchronously. */ @Override public Promise deleteAsync() { this.type = "DELETE"; return execute(prepareDelete()); } /** Execute a OPTIONS request. */ @Override public HttpResponse options() { this.type = "OPTIONS"; try { return new HttpAsyncResponse(prepare(prepareOptions()).execute().get()); } catch (Exception e) { throw new RuntimeException(e); } } /** Execute a OPTIONS request asynchronously. */ @Override public Promise optionsAsync() { this.type = "OPTIONS"; return execute(prepareOptions()); } /** Execute a HEAD request. */ @Override public HttpResponse head() { this.type = "HEAD"; try { return new HttpAsyncResponse(prepare(prepareHead()).execute().get()); } catch (Exception e) { throw new RuntimeException(e); } } /** Execute a HEAD request asynchronously. */ @Override public Promise headAsync() { this.type = "HEAD"; return execute(prepareHead()); } /** Execute a TRACE request. */ @Override public HttpResponse trace() { this.type = "TRACE"; throw new NotImplementedException(); } /** Execute a TRACE request asynchronously. */ @Override public Promise traceAsync() { this.type = "TRACE"; throw new NotImplementedException(); } private WSRequest sign() { if (this.oauthToken != null && this.oauthSecret != null) { WSOAuthConsumer consumer = new WSOAuthConsumer(oauthInfo, oauthToken, oauthSecret); try { consumer.sign(this, this.type); } catch (Exception e) { throw new RuntimeException(e); } } return this; } private BoundRequestBuilder prepare(BoundRequestBuilder builder) { if (this.username != null && this.password != null && this.scheme != null) { AuthScheme authScheme; switch (this.scheme) { case DIGEST: authScheme = AuthScheme.DIGEST; break; case NTLM: authScheme = AuthScheme.NTLM; break; case KERBEROS: authScheme = AuthScheme.KERBEROS; break; case SPNEGO: authScheme = AuthScheme.SPNEGO; break; case BASIC: authScheme = AuthScheme.BASIC; break; default: throw new RuntimeException("Scheme " + this.scheme + " not supported by the UrlFetch WS backend."); } builder.setRealm((new RealmBuilder()).setScheme(authScheme).setPrincipal(this.username).setPassword(this.password) .setUsePreemptiveAuth(true).build()); } for (String key : this.headers.keySet()) { builder.addHeader(key, headers.get(key)); } builder.setFollowRedirects(this.followRedirects); builder.setRequestTimeout(this.timeout * 1000); if (this.virtualHost != null) { builder.setVirtualHost(this.virtualHost); } return builder; } private Promise execute(BoundRequestBuilder builder) { try { final Promise smartFuture = new Promise<>(); prepare(builder).execute(new AsyncCompletionHandler() { @Override public HttpResponse onCompleted(Response response) throws Exception { HttpResponse httpResponse = new HttpAsyncResponse(response); smartFuture.invoke(httpResponse); return httpResponse; } @Override public void onThrowable(Throwable t) { // An error happened - must "forward" the exception to // the one waiting for the result smartFuture.invokeWithException(t); } }); return smartFuture; } catch (Exception e) { throw new RuntimeException(e); } } private void checkFileBody(BoundRequestBuilder builder) { setResolvedContentType(null); if (this.fileParams != null) { // could be optimized, we know the size of this array. for (int i = 0; i < this.fileParams.length; i++) { builder.addBodyPart(new FilePart(this.fileParams[i].paramName, this.fileParams[i].file, MimeTypes.getMimeType(this.fileParams[i].file.getName()), Charset.forName(encoding))); } if (this.parameters != null) { try { // AHC only supports ascii chars in keys in multipart for (String key : this.parameters.keySet()) { Object value = this.parameters.get(key); if (value instanceof Collection || value.getClass().isArray()) { Collection values = value.getClass().isArray() ? Arrays.asList((Object[]) value) : (Collection) value; for (Object v : values) { Part part = new ByteArrayPart(key, v.toString().getBytes(encoding), "text/plain", Charset.forName(encoding), null); builder.addBodyPart(part); } } else { Part part = new ByteArrayPart(key, value.toString().getBytes(encoding), "text/plain", Charset.forName(encoding), null); builder.addBodyPart(part); } } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } // Don't have to set content-type: AHC will automatically choose // multipart return; } if (this.parameters != null && !this.parameters.isEmpty()) { boolean isPostPut = "POST".equals(this.type) || ("PUT".equals(this.type)); if (isPostPut) { // Since AHC is hard-coded to encode to use UTF-8, we must // build // the content ourself.. StringBuilder sb = new StringBuilder(); for (String key : this.parameters.keySet()) { Object value = this.parameters.get(key); if (value == null) continue; if (value instanceof Collection || value.getClass().isArray()) { Collection values = value.getClass().isArray() ? Arrays.asList((Object[]) value) : (Collection) value; for (Object v : values) { if (sb.length() > 0) { sb.append('&'); } sb.append(encode(key)); sb.append('='); sb.append(encode(v.toString())); } } else { // Since AHC is hard-coded to encode using UTF-8, we // must build // the content ourself.. if (sb.length() > 0) { sb.append('&'); } sb.append(encode(key)); sb.append('='); sb.append(encode(value.toString())); } } try { byte[] bodyBytes = sb.toString().getBytes(this.encoding); builder.setBody(bodyBytes); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } setResolvedContentType("application/x-www-form-urlencoded; charset=" + encoding); } else { for (String key : this.parameters.keySet()) { Object value = this.parameters.get(key); if (value == null) continue; if (value instanceof Collection || value.getClass().isArray()) { Collection values = value.getClass().isArray() ? Arrays.asList((Object[]) value) : (Collection) value; for (Object v : values) { // must encode it since AHC uses raw urls builder.addQueryParam(encode(key), encode(v.toString())); } } else { // must encode it since AHC uses raw urls builder.addQueryParam(encode(key), encode(value.toString())); } } setResolvedContentType("text/html; charset=" + encoding); } } if (this.body != null) { if (this.parameters != null && !this.parameters.isEmpty()) { throw new RuntimeException("POST or PUT method with parameters AND body are not supported."); } if (this.body instanceof InputStream) { builder.setBody((InputStream) this.body); } else { try { byte[] bodyBytes = this.body.toString().getBytes(this.encoding); builder.setBody(bodyBytes); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } setResolvedContentType("text/html; charset=" + encoding); } if (this.mimeType != null) { // User has specified mimeType this.headers.put("Content-Type", this.mimeType); } } /** * Sets the resolved Content-type - This is added as Content-type-header to AHC if ser has not specified * Content-type or mimeType manually (Cannot add it directly to this.header since this cause problem when * Request-object is used multiple times with first GET, then POST) */ private void setResolvedContentType(String contentType) { generatedContentType = contentType; } /** * If generatedContentType is present AND if Content-type header is not already present, add * generatedContentType as Content-Type to headers in requestBuilder */ private void addGeneratedContentType(BoundRequestBuilder requestBuilder) { if (!headers.containsKey("Content-Type") && generatedContentType != null) { requestBuilder.addHeader("Content-Type", generatedContentType); } } } /** * An HTTP response wrapper */ public static class HttpAsyncResponse extends HttpResponse { private Response response; /** * You shouldn't have to create an HttpResponse yourself * * @param response * The given response */ public HttpAsyncResponse(Response response) { this.response = response; } /** * The HTTP status code * * @return the status code of the http response */ @Override public Integer getStatus() { return this.response.getStatusCode(); } /** * the HTTP status text * * @return the status text of the http response */ @Override public String getStatusText() { return this.response.getStatusText(); } @Override public String getHeader(String key) { return response.getHeader(key); } @Override public List
getHeaders() { Map> hdrs = response.getHeaders(); List
result = new ArrayList<>(); for (String key : hdrs.keySet()) { result.add(new Header(key, hdrs.get(key))); } return result; } @Override public String getString() { try { return response.getResponseBody(getEncoding()); } catch (Exception e) { throw new RuntimeException(e); } } @Override public String getString(String encoding) { try { return response.getResponseBody(encoding); } catch (Exception e) { throw new RuntimeException(e); } } /** * get the response as a stream * * @return an inputstream */ @Override public InputStream getStream() { try { return response.getResponseBodyAsStream(); } catch (IllegalStateException e) { return new ByteArrayInputStream(new byte[] {}); // Workaround // AHC's bug on // empty // responses } catch (Exception e) { throw new RuntimeException(e); } } } private static class WSOAuthConsumer extends AbstractOAuthConsumer { public WSOAuthConsumer(String consumerKey, String consumerSecret) { super(consumerKey, consumerSecret); } public WSOAuthConsumer(ServiceInfo info, String token, String secret) { super(info.consumerKey, info.consumerSecret); setTokenWithSecret(token, secret); } @Override protected HttpRequest wrap(Object request) { if (!(request instanceof WSRequest)) { throw new IllegalArgumentException("WSOAuthConsumer expects requests of type play.libs.WS.WSRequest"); } return new WSRequestAdapter((WSRequest) request); } public WSRequest sign(WSRequest request, String method) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException { WSRequestAdapter req = (WSRequestAdapter) wrap(request); req.setMethod(method); sign(req); return request; } public class WSRequestAdapter implements HttpRequest { private WSRequest request; private String method; public WSRequestAdapter(WSRequest request) { this.request = request; } @Override public Map getAllHeaders() { return request.headers; } @Override public String getContentType() { return request.mimeType; } @Override public Object unwrap() { return null; } @Override public String getHeader(String name) { return request.headers.get(name); } @Override public InputStream getMessagePayload() throws IOException { return null; } @Override public String getMethod() { return this.method; } private void setMethod(String method) { this.method = method; } @Override public String getRequestUrl() { return request.url; } @Override public void setHeader(String name, String value) { request.setHeader(name, value); } @Override public void setRequestUrl(String url) { request.url = url; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy