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

com.sun.jersey.client.non.blocking.NonBlockingClientHandler Maven / Gradle / Ivy

There is a newer version: 1.19.4
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.jersey.client.non.blocking;

import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.Cookie;
import com.ning.http.client.FluentCaseInsensitiveStringsMap;
import com.ning.http.client.ProxyServer;
import com.ning.http.client.Realm;
import com.ning.http.client.Request;
import com.ning.http.client.RequestBuilder;
import com.ning.http.client.Response;
import com.ning.http.client.filter.RequestFilter;
import com.ning.http.client.filter.ResponseFilter;
import com.ning.http.client.providers.grizzly.GrizzlyAsyncHttpProvider;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.TerminatingClientHandler;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.client.non.blocking.config.NonBlockingClientConfig;
import com.sun.jersey.client.urlconnection.HTTPSProperties;
import com.sun.jersey.core.header.InBoundHeaders;
import com.sun.jersey.core.util.ReaderWriter;

import javax.net.ssl.HostnameVerifier;
import javax.ws.rs.core.MultivaluedMap;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * A root handler with Ning Async HTTP client acting as a backend.
 * 

* Client operations are thread safe, the HTTP connection may * be shared between different threads. *

* If a response entity is obtained that is an instance of {@link Closeable} * then the instance MUST be closed after processing the entity to release * connection-based resources. *

* If a {@link ClientResponse} is obtained and an entity is not read from the * response then {@link ClientResponse#close() } MUST be called after processing * the response to release connection-based resources. *

* The following methods are currently supported: HEAD, GET, POST, PUT, DELETE * and OPTIONS. *

* Chunked transfer encoding can be enabled or disabled but configuration of * the chunked encoding size is not possible. If the * {@link ClientConfig#PROPERTY_CHUNKED_ENCODING_SIZE} property is set * to a non-null value then chunked transfer encoding is enabled. * * @author [email protected] * @author [email protected] * @author [email protected] */ public final class NonBlockingClientHandler extends TerminatingClientHandler { private final AsyncHttpClient client; private final ExecutorService executorService; private final ClientConfig clientConfig; private final ThreadLocal> cookieStore = new ThreadLocal>() { @Override protected Collection initialValue() { return new HashSet(); } }; public NonBlockingClientHandler(final ClientConfig cc) { AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder(); if(cc != null) { Object executorService = cc.getProperty(NonBlockingClientConfig.PROPERTY_EXECUTOR_SERVICE); if(executorService != null && (executorService instanceof ExecutorService)) { builder = builder.setExecutorService((ExecutorService)executorService); this.executorService = (ExecutorService)executorService; } else { final Object threadpoolSize = cc.getProperties().get(ClientConfig.PROPERTY_THREADPOOL_SIZE); if(threadpoolSize != null && threadpoolSize instanceof Integer && (Integer)threadpoolSize > 0) { this.executorService = Executors.newFixedThreadPool((Integer) threadpoolSize); } else { this.executorService = Executors.newCachedThreadPool(); } builder = builder.setExecutorService(this.executorService); } Integer timeout = (Integer)cc.getProperties().get(ClientConfig.PROPERTY_CONNECT_TIMEOUT); if(timeout != null) builder = builder.setConnectionTimeoutInMs(timeout); Object username = cc.getProperties().get(NonBlockingClientConfig.PROPERTY_AUTH_USERNAME); Object password = cc.getProperties().get(NonBlockingClientConfig.PROPERTY_AUTH_PASSWORD); if((username != null) && (password != null)) { boolean preemptiveAuth = cc.getPropertyAsFeature(NonBlockingClientConfig.PROPERTY_AUTH_PREEMPTIVE); Object scheme = cc.getProperties().get(NonBlockingClientConfig.PROPERTY_AUTH_SCHEME); Realm.AuthScheme authScheme = Realm.AuthScheme.NONE; if(scheme != null) { if(scheme.equals("DIGEST")) { authScheme = Realm.AuthScheme.DIGEST; } else if(scheme.equals("KERBEROS")) { authScheme = Realm.AuthScheme.KERBEROS; } else if(scheme.equals("NTLM")) { authScheme = Realm.AuthScheme.NTLM; } else if(scheme.equals("SPNEGO")) { authScheme = Realm.AuthScheme.SPNEGO; } } else { authScheme = Realm.AuthScheme.BASIC; } Realm realm = new Realm.RealmBuilder() .setPrincipal(username.toString()) .setPassword(password.toString()) .setUsePreemptiveAuth(preemptiveAuth) .setScheme(authScheme).build(); builder = builder.setRealm(realm); } Object httpsPropertiesProperty = cc.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES); if(httpsPropertiesProperty != null && httpsPropertiesProperty instanceof HTTPSProperties) { HTTPSProperties httpsProperties = (HTTPSProperties)httpsPropertiesProperty; final HostnameVerifier hostnameVerifier = httpsProperties.getHostnameVerifier(); if(hostnameVerifier != null) { builder = builder.setHostnameVerifier(hostnameVerifier); } builder = builder.setSSLContext(httpsProperties.getSSLContext()); } Object requestFilters = cc.getProperties().get(NonBlockingClientConfig.PROPERTY_REQUEST_FILTERS); if(requestFilters != null) { if(requestFilters instanceof RequestFilter) { builder.addRequestFilter((RequestFilter) requestFilters); } else if(requestFilters instanceof List) { List requestFilterList = (List)requestFilters; for (ListIterator iterator = requestFilterList.listIterator(requestFilterList.size()); iterator.hasPrevious();) { final Object listElement = iterator.previous(); if(listElement instanceof RequestFilter) { builder.addRequestFilter((RequestFilter)listElement); } } } } Object responseFilters = cc.getProperties().get(NonBlockingClientConfig.PROPERTY_RESPONSE_FILTERS); if(responseFilters != null) { if(responseFilters instanceof ResponseFilter) { builder.addResponseFilter((ResponseFilter) responseFilters); } else if(responseFilters instanceof List) { List responseFiltersList = (List)responseFilters; for (ListIterator iterator = responseFiltersList.listIterator(responseFiltersList.size()); iterator.hasPrevious();) { final Object listElement = iterator.previous(); if(listElement instanceof ResponseFilter) { builder.addResponseFilter((ResponseFilter) listElement); } } } } } else { this.executorService = Executors.newCachedThreadPool(); builder.setExecutorService(this.executorService); } final AsyncHttpClientConfig config = builder.build(); this.client = new AsyncHttpClient(new GrizzlyAsyncHttpProvider(config), config); this.clientConfig = cc; } public AsyncHttpClient getHttpClient() { return client; } public final ExecutorService getExecutorService() { return executorService; } public ClientResponse handle(final ClientRequest cr) throws ClientHandlerException { final Request request = getRequest(cr); try { Future response; response = getHttpClient().executeRequest(request); return getClientResponse(response.get()); } catch (Exception e) { throw new ClientHandlerException(e); } } ClientResponse getClientResponse(Response response) { if(this.clientConfig != null && (!this.clientConfig.getPropertyAsFeature(NonBlockingClientConfig.PROPERTY_DISABLE_COOKIES))) cookieStore.get().addAll(response.getCookies()); try { ClientResponse r = new ClientResponse(response.getStatusCode(), getInBoundHeaders(response), new HttpClientResponseInputStream(response), getMessageBodyWorkers()); if (!r.hasEntity()) { r.bufferEntity(); r.close(); } return r; } catch (Exception e) { throw new ClientHandlerException(e); } } Request getRequest(final ClientRequest cr) { final String strMethod = cr.getMethod(); final URI uri = cr.getURI(); RequestBuilder builder = new RequestBuilder(strMethod).setUrl(uri.toString()); final Request.EntityWriter entity = getHttpEntity(cr); if(entity != null) { builder = builder.setBody(entity); } ProxyServer proxyServer = createProxyServer(cr); if(proxyServer != null) builder.setProxyServer(proxyServer); if(this.clientConfig != null) { if(!this.clientConfig.getPropertyAsFeature(NonBlockingClientConfig.PROPERTY_DISABLE_COOKIES)) { for(Cookie cookie : cookieStore.get()) builder = builder.addCookie(cookie); cookieStore.remove(); } if(this.clientConfig.getPropertyAsFeature(ClientConfig.PROPERTY_FOLLOW_REDIRECTS)) builder = builder.setFollowRedirects(true); else builder = builder.setFollowRedirects(false); } Request request = builder.build(); // /* extremely ugly, unnecessary and inefficient. Unfortunately it is needed // because client side providers which modifies header values when entity is // being written. Ning Client writes is too late, so header is ClientRequest is // not yet changed and not propagated into Nings Request. // Question is - should we support this case? Yes - for now. // */ // try { // if(request.getEntityWriter() != null) // request.getEntityWriter().writeEntity(new OutputStream() { // @Override // public void write(int i) throws IOException { // } // }); // } catch (IOException ignored) { // } writeOutBoundHeaders(cr.getHeaders(), request); return request; } private ProxyServer createProxyServer(ClientRequest cr) { Object proxyHost = cr.getProperties().get(NonBlockingClientConfig.PROPERTY_PROXY_HOST); if(proxyHost != null) { ProxyServerBuilder proxyBuilder = new ProxyServerBuilder((String) proxyHost); Integer proxyPort = (Integer)cr.getProperties().get(NonBlockingClientConfig.PROPERTY_PROXY_PORT); if(proxyPort != null) proxyBuilder.setPort(proxyPort); String proxyUser = (String)cr.getProperties().get(NonBlockingClientConfig.PROPERTY_PROXY_USERNAME); if(proxyUser != null) { String proxyPass = (String)cr.getProperties().get(NonBlockingClientConfig.PROPERTY_PROXY_PASSWORD); if(proxyPass != null) { proxyBuilder.setUsername(proxyUser); proxyBuilder.setPassword(proxyPass); } } String proxyProto = (String)cr.getProperties().get(NonBlockingClientConfig.PROPERTY_PROXY_PROTOCOL); if(proxyProto != null) { if(proxyProto.equals("HTTP")) { proxyBuilder.setProtocol(ProxyServer.Protocol.HTTP); } else if(proxyProto.equals("HTTPS")) { proxyBuilder.setProtocol(ProxyServer.Protocol.HTTPS); } else if(proxyProto.equals("NTLM")) { proxyBuilder.setProtocol(ProxyServer.Protocol.NTLM); } else if(proxyProto.equals("SPNEGO")) { proxyBuilder.setProtocol(ProxyServer.Protocol.SPNEGO); } else if(proxyProto.equals("KERBEROS")) { proxyBuilder.setProtocol(ProxyServer.Protocol.KERBEROS); } } return proxyBuilder.build(); } else { return null; } } private Request.EntityWriter getHttpEntity(final ClientRequest cr) { final Object entity = cr.getEntity(); if(entity == null) return null; final RequestEntityWriter requestEntityWriter = getRequestEntityWriter(cr); return new Request.EntityWriter() { @Override public void writeEntity(OutputStream out) throws IOException { requestEntityWriter.writeRequestEntity(out); } }; } protected static void writeOutBoundHeaders(final MultivaluedMap headers, final Request request) { for (Map.Entry> e : headers.entrySet()) { List vs = e.getValue(); if (vs.size() == 1) { request.getHeaders().add(e.getKey(), ClientRequest.getHeaderValue(vs.get(0))); } else { StringBuilder b = new StringBuilder(); for (Object v : e.getValue()) { if (b.length() > 0) { b.append(','); } b.append(ClientRequest.getHeaderValue(v)); } request.getHeaders().add(e.getKey(), b.toString()); } } } private InBoundHeaders getInBoundHeaders(final Response response) throws ExecutionException, InterruptedException { final InBoundHeaders headers = new InBoundHeaders(); FluentCaseInsensitiveStringsMap responseHeaders = response.getHeaders(); for (FluentCaseInsensitiveStringsMap.Entry> header : responseHeaders) { headers.put(header.getKey(), header.getValue()); } return headers; } static final class HttpClientResponseInputStream extends FilterInputStream { HttpClientResponseInputStream(Response response) throws IOException, ExecutionException, InterruptedException { super(getInputStream(response)); } @Override public void close() throws IOException { super.close(); } } private static InputStream getInputStream(final Response response) throws IOException, ExecutionException, InterruptedException { if(!response.hasResponseBody()) { return new ByteArrayInputStream(new byte[0]); } else { final InputStream i = response.getResponseBodyAsStream(); if(i.markSupported()) return i; return new BufferedInputStream(i, ReaderWriter.BUFFER_SIZE); } } private static class ProxyServerBuilder { private String host = null; private Integer port = null; private String username = null; private String password = null; private ProxyServer.Protocol protocol = null; public ProxyServerBuilder(String host) { this.host = host; } public ProxyServerBuilder setPort(int port) { this.port = port; return this; } public ProxyServerBuilder setUsername(String username) { this.username = username; return this; } public ProxyServerBuilder setPassword(String password) { this.password = password; return this; } public ProxyServerBuilder setProtocol(ProxyServer.Protocol protocol) { this.protocol = protocol; return this; } public ProxyServer build() { return new ProxyServer(protocol, host, port, username, password); } } }