Package play.libs.ws

Source Code of play.libs.ws.WSAsync$WSOAuthConsumer

package play.libs.ws;

import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.ning.http.client.*;
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 org.apache.commons.lang.NotImplementedException;

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;

import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
import com.ning.http.client.AsyncHttpClientConfig.Builder;
import com.ning.http.client.FilePart;
import com.ning.http.client.PerRequestConfig;
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;

/**
* Simple HTTP client to make webservices requests.
*
* <p/>
* Get latest BBC World news as a RSS content
* <pre>
*    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...
* </pre>
* <p/>
*
* Search what Yahoo! thinks of google (starting from the 30th result).
* <pre>
*    HttpResponse response = WS.url("http://search.yahoo.com/search?p=<em>%s</em>&pstart=1&b=<em>%s</em>", "Google killed me", "30").get();
*    if( response.getStatus() == 200 ) {
*       html = response.getString();
*    }
* </pre>
*/
public class WSAsync implements WSImpl {

    private AsyncHttpClient httpClient;

    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 userAgent = Play.configuration.getProperty("http.userAgent");

        Builder confBuilder = new AsyncHttpClientConfig.Builder();
        if (proxyHost != null) {
            int proxyPortInt = 0;
            try {
                proxyPortInt = Integer.parseInt(proxyPort);
            } catch (NumberFormatException e) {
                Logger.error("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);
            confBuilder.setProxyServer(proxy);
        }
        if (userAgent != null) {
            confBuilder.setUserAgent(userAgent);
        }
        // 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.setUseRawUrl(true);
        httpClient = new AsyncHttpClient(confBuilder.build());
    }

    public void stop() {
        Logger.trace("Releasing http client connections...");
        httpClient.close();
    }

    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()
         */
        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
         */
        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.addQueryParameter(URLEncoder.encode(name, encoding), null);
                        } else {
                            requestBuilder.addQueryParameter(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 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) {
                Logger.error(e.toString());
                throw new RuntimeException(e);
            }
        }

        /** Execute a GET request asynchronously. */
        @Override
        public Promise<HttpResponse> getAsync() {
            this.type = "GET";
            sign();
            return execute(prepareGet());
        }


        /** 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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<HttpResponse> 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);
            PerRequestConfig perRequestConfig = new PerRequestConfig();
            perRequestConfig.setRequestTimeoutInMs(this.timeout * 1000);
            builder.setPerRequestConfig(perRequestConfig);
            return builder;
        }

        private Promise<HttpResponse> execute(BoundRequestBuilder builder) {
            try {
                final Promise<HttpResponse> smartFuture = new Promise<HttpResponse>();
                prepare(builder).execute(new AsyncCompletionHandler<HttpResponse>() {
                    @Override
                    public HttpResponse onCompleted(Response response) throws Exception {
                        HttpResponse httpResponse = new HttpAsyncResponse(response);
                        smartFuture.invoke(httpResponse);
                        return httpResponse;
                    }
                    @Override
                    public void onThrowable(Throwable t) {
                        throw new RuntimeException(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()),
                            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, null, v.toString().getBytes(encoding), "text/plain", encoding);
                                    builder.addBodyPart( part );
                                }
                            } else {
                                Part part = new ByteArrayPart(key, null, value.toString().getBytes(encoding), "text/plain", encoding);
                                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 );
                        InputStream bodyInStream = new ByteArrayInputStream( bodyBytes );
                        builder.setBody( bodyInStream );
                    } 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.addQueryParameter(encode(key), encode(v.toString()));
                            }
                        } else {
                            // must encode it since AHC uses raw urls
                            builder.addQueryParameter(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 );
                        InputStream bodyInStream = new ByteArrayInputStream( bodyBytes );
                        builder.setBody( bodyInStream );
                    } 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 shouldnt have to create an HttpResponse yourself
         * @param method
         */
        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();
        }

        @Override
        public String getHeader(String key) {
            return response.getHeader(key);
        }

        @Override
        public List<Header> getHeaders() {
            Map<String, List<String>> hdrs = response.getHeaders();
            List<Header> result = new ArrayList<Header>();
            for (String key: hdrs.keySet()) {
                result.add(new Header(key, hdrs.get(key)));
            }
            return result;
        }

        /**
         * 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;
            }

            public Map<String, String> getAllHeaders() {
                return request.headers;
            }

            public String getContentType() {
                return request.mimeType;
            }

            public String getHeader(String name) {
                return request.headers.get(name);
            }

            public InputStream getMessagePayload() throws IOException {
                return null;
            }

            public String getMethod() {
                return this.method;
            }

            private void setMethod(String method) {
                this.method = method;
            }

            public String getRequestUrl() {
                return request.url;
            }

            public void setHeader(String name, String value) {
                request.setHeader(name, value);
            }

            public void setRequestUrl(String url) {
                request.url = url;
            }

        }

    }

}
TOP

Related Classes of play.libs.ws.WSAsync$WSOAuthConsumer

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.