add async http client

This commit is contained in:
kimchy 2010-05-24 23:27:07 +03:00
parent b7d11f1303
commit 32e4c405de
48 changed files with 6400 additions and 0 deletions

View File

@ -62,6 +62,8 @@ import org.elasticsearch.util.Tuple;
import org.elasticsearch.util.component.Lifecycle;
import org.elasticsearch.util.component.LifecycleComponent;
import org.elasticsearch.util.guice.Injectors;
import org.elasticsearch.util.http.HttpClientModule;
import org.elasticsearch.util.http.HttpClientService;
import org.elasticsearch.util.inject.Guice;
import org.elasticsearch.util.inject.Injector;
import org.elasticsearch.util.inject.Module;
@ -133,6 +135,7 @@ public final class InternalNode implements Node {
modules.add(new MonitorModule(settings));
modules.add(new GatewayModule(settings));
modules.add(new NodeClientModule());
modules.add(new HttpClientModule());
pluginsService.processModules(modules);
@ -249,6 +252,7 @@ public final class InternalNode implements Node {
injector.getInstance(IndicesService.class).close();
injector.getInstance(RestController.class).close();
injector.getInstance(TransportService.class).close();
injector.getInstance(HttpClientService.class).close();
for (Class<? extends LifecycleComponent> plugin : pluginsService.services()) {
injector.getInstance(plugin).close();

View File

@ -0,0 +1,32 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search licenses this
* file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http;
import org.elasticsearch.util.inject.AbstractModule;
/**
* @author kimchy (shay.banon)
*/
public class HttpClientModule extends AbstractModule {
@Override protected void configure() {
bind(HttpClientService.class).asEagerSingleton();
}
}

View File

@ -0,0 +1,63 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search licenses this
* file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http;
import org.elasticsearch.ElasticSearchException;
import org.elasticsearch.util.component.AbstractLifecycleComponent;
import org.elasticsearch.util.http.client.AsyncHttpClient;
import org.elasticsearch.util.inject.Inject;
import org.elasticsearch.util.settings.Settings;
/**
* @author kimchy (shay.banon)
*/
public class HttpClientService extends AbstractLifecycleComponent<HttpClientService> {
private volatile AsyncHttpClient asyncHttpClient;
@Inject public HttpClientService(Settings settings) {
super(settings);
}
public AsyncHttpClient asyncHttpClient() {
if (asyncHttpClient != null) {
return asyncHttpClient;
}
synchronized (this) {
if (asyncHttpClient != null) {
return asyncHttpClient;
}
asyncHttpClient = new AsyncHttpClient();
return asyncHttpClient;
}
}
@Override protected void doStart() throws ElasticSearchException {
}
@Override protected void doStop() throws ElasticSearchException {
}
@Override protected void doClose() throws ElasticSearchException {
if (asyncHttpClient != null) {
asyncHttpClient.close();
}
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.logging.ESLogger;
import org.elasticsearch.util.logging.Loggers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
* An {@link AsyncHandler} augmented with an {@link #onCompleted(Response)} convenience method which gets called
* when the {@link Response} has been fully received.
*
* @param <T> Type of the value that will be returned by the associated {@link java.util.concurrent.Future}
*/
public abstract class AsyncCompletionHandler<T> implements AsyncHandler<T> {
private final static ESLogger log = Loggers.getLogger(AsyncCompletionHandlerBase.class);
private final Collection<HttpResponseBodyPart<T>> bodies =
Collections.synchronizedCollection(new ArrayList<HttpResponseBodyPart<T>>());
private HttpResponseStatus<T> status;
private HttpResponseHeaders<T> headers;
/**
* {@inheritDoc}
*/
/* @Override */
public STATE onBodyPartReceived(final HttpResponseBodyPart<T> content) throws Exception {
bodies.add(content);
return STATE.CONTINUE;
}
/**
* {@inheritDoc}
*/
/* @Override */
public final STATE onStatusReceived(final HttpResponseStatus<T> status) throws Exception {
this.status = status;
return STATE.CONTINUE;
}
/**
* {@inheritDoc}
*/
/* @Override */
public final STATE onHeadersReceived(final HttpResponseHeaders<T> headers) throws Exception {
this.headers = headers;
return STATE.CONTINUE;
}
/**
* {@inheritDoc}
*/
/* @Override */
public final T onCompleted() throws Exception {
return onCompleted(status == null ? null : status.provider().prepareResponse(status, headers, bodies));
}
/**
* {@inheritDoc}
*/
/* @Override */
public void onThrowable(Throwable t) {
if (log.isDebugEnabled())
log.debug(t.getMessage(), t);
}
/**
* Invoked once the HTTP response has been fully read.
*
* @param response The {@link Response}
* @return Type of the value that will be returned by the associated {@link java.util.concurrent.Future}
*/
abstract public T onCompleted(Response response) throws Exception;
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.logging.ESLogger;
import org.elasticsearch.util.logging.Loggers;
/**
* Simple {@link AsyncHandler} of type {@link Response}
*/
public class AsyncCompletionHandlerBase extends AsyncCompletionHandler<Response> {
private final static ESLogger log = Loggers.getLogger(AsyncCompletionHandlerBase.class);
@Override
public Response onCompleted(Response response) throws Exception {
return response;
}
/* @Override */
public void onThrowable(Throwable t) {
if (log.isDebugEnabled()) {
log.debug(t.getMessage(), t);
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
/**
* An asynchronous handler or callback which gets invoked as soon as some data are available when
* processing an asynchronous response. Callbacks method gets invoked in the following order:
* (1) {@link #onStatusReceived(HttpResponseStatus)}
* (2) {@link #onHeadersReceived(HttpResponseHeaders)}
* (3) {@link #onBodyPartReceived(HttpResponseBodyPart)}, which could be invoked multiple times
* (4) {@link #onCompleted()}, once the response has been fully read.
*
* Interrupting the process of the asynchronous response can be achieved by
* returning a {@link AsyncHandler.STATE#ABORT} at any moment during the
* processing of the asynchronous response.
*
* @param <T> Type of object returned by the {@link java.util.concurrent.Future#get}
*/
public interface AsyncHandler<T> {
public static enum STATE {
ABORT, CONTINUE
}
/**
* Invoked when an unexpected exception occurs during the processing of the response
*
* @param t a {@link Throwable}
*/
void onThrowable(Throwable t);
/**
* Invoked as soon as some response body part are received.
*
* @param bodyPart response's body part.
* @throws Exception
*/
STATE onBodyPartReceived(HttpResponseBodyPart<T> bodyPart) throws Exception;
/**
* Invoked as soon as the HTTP status line has been received
*
* @param responseStatus the status code and test of the response
* @throws Exception
*/
STATE onStatusReceived(HttpResponseStatus<T> responseStatus) throws Exception;
/**
* Invoked as soon as the HTTP headers has been received.
*
* @param headers the HTTP headers.
* @throws Exception
*/
STATE onHeadersReceived(HttpResponseHeaders<T> headers) throws Exception;
/**
* Invoked once the HTTP response has been fully received
*
* @return T Type of the value that will be returned by the associated {@link java.util.concurrent.Future}
* @throws Exception
*/
T onCompleted() throws Exception;
}

View File

@ -0,0 +1,423 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.collect.Multimap;
import org.elasticsearch.util.http.client.Request.EntityWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.Future;
/**
* This class support asynchronous and synchronous HTTP request.
*
* To execute synchronous HTTP request, you just need to do
* {@code
* AsyncHttpClient c = new AsyncHttpClient();
* Future<Response> f = c.prepareGet("http://www.ning.com/").execute();
* }
*
* The code above will block until the response is fully received. To execute asynchronous HTTP request, you
* create an {@link AsyncHandler} or its abstract implementation, {@link org.elasticsearch.util.http.client.AsyncCompletionHandler}
*
* {@code
* AsyncHttpClient c = new AsyncHttpClient();
* Future<Response> f = c.prepareGet("http://www.ning.com/").execute(new AsyncCompletionHandler<Response>() &#123;
*
* &#64;Override
* public Response onCompleted(Response response) throws IOException &#123;
* // Do something
* return response;
* &#125;
*
* &#64;Override
* public void onThrowable(Throwable t) &#123;
* &#125;
* &#125;);
* Response response = f.get();
*
* // We are just interested to retrieve the status code.
* Future<Integer> f = c.prepareGet("http://www.ning.com/").execute(new AsyncCompletionHandler<Integer>() &#123;
*
* &#64;Override
* public Integer onCompleted(Response response) throws IOException &#123;
* // Do something
* return response.getStatusCode();
* &#125;
*
* &#64;Override
* public void onThrowable(Throwable t) &#123;
* &#125;
* &#125;);
* Integer statusCode = f.get();
* }
* The {@link AsyncCompletionHandler#onCompleted(org.elasticsearch.util.http.client.Response)} will be invoked once the http response has been fully read, which include
* the http headers and the response body. Note that the entire response will be buffered in memory.
*
* You can also have more control about the how the response is asynchronously processed by using a {@link AsyncHandler}
* {@code
* AsyncHttpClient c = new AsyncHttpClient();
* Future<String> f = c.prepareGet("http://www.ning.com/").execute(new AsyncHandler<String>() &#123;
* private StringBuilder builder = new StringBuilder();
*
* &#64;Override
* public void onStatusReceived(HttpResponseStatus s) throws Exception &#123;
* // The Status have been read
* // If you don't want to read the headers,body, or stop processing the response
* throw new ResponseComplete();
* }
*
* &#64;Override
* public void onHeadersReceived(HttpResponseHeaders bodyPart) throws Exception &#123;
* // The headers have been read
* // If you don't want to read the body, or stop processing the response
* throw new ResponseComplete();
* }
* &#64;Override
*
* public void onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception &#123;
* builder.append(new String(bodyPart));
* &#125;
*
* &#64;Override
* public String onCompleted() throws Exception &#123;
* // Will be invoked once the response has been fully read or a ResponseComplete exception
* // has been thrown.
* return builder.toString();
* &#125;
*
* &#64;Override
* public void onThrowable(Throwable t) &#123;
* &#125;
* &#125;);
*
* String bodyResponse = f.get();
* }
* From any {@link HttpContent} sub classses, you can asynchronously process the response status,headers and body and decide when to
* stop the processing the response by throwing a new {link ResponseComplete} at any moment.
*
* This class can also be used without the need of {@link AsyncHandler}</p>
* {@code
* AsyncHttpClient c = new AsyncHttpClient();
* Future<Response> f = c.prepareGet(TARGET_URL).execute();
* Response r = f.get();
* }
*
* Finally, you can configure the AsyncHttpClient using an {@link AsyncHttpClientConfig} instance</p>
* {@code
* AsyncHttpClient c = new AsyncHttpClient(new AsyncHttpClientConfig.Builder().setRequestTimeoutInMs(...).build());
* Future<Response> f = c.prepareGet(TARGET_URL).execute();
* Response r = f.get();
* }
*
* An instance of this class will cache every HTTP 1.1 connections and close them when the {@link AsyncHttpClientConfig#getIdleConnectionTimeoutInMs()}
* expires. This object can hold many persistent connections to different host.
*/
public class AsyncHttpClient {
private final static String DEFAULT_PROVIDER = "org.elasticsearch.util.http.client.providers.NettyAsyncHttpProvider";
private final AsyncHttpProvider<?> httpProvider;
private final AsyncHttpClientConfig config;
/**
* Create a new HTTP Asynchronous Client using the default {@link AsyncHttpClientConfig} configuration. The
* default {@link AsyncHttpProvider} will be used ({@link org.elasticsearch.util.http.client.providers.NettyAsyncHttpProvider}
*/
public AsyncHttpClient() {
this(new AsyncHttpClientConfig.Builder().build());
}
/**
* Create a new HTTP Asynchronous Client using an implementation of {@link AsyncHttpProvider} and
* the default {@link AsyncHttpClientConfig} configuration.
*
* @param provider a {@link AsyncHttpProvider}
*/
public AsyncHttpClient(AsyncHttpProvider<?> provider) {
this(provider, new AsyncHttpClientConfig.Builder().build());
}
/**
* Create a new HTTP Asynchronous Client using a {@link AsyncHttpClientConfig} configuration and the
* {@link #DEFAULT_PROVIDER}
*
* @param config a {@link AsyncHttpClientConfig}
*/
public AsyncHttpClient(AsyncHttpClientConfig config) {
this(loadDefaultProvider(DEFAULT_PROVIDER, config), config);
}
/**
* Create a new HTTP Asynchronous Client using a {@link AsyncHttpClientConfig} configuration and
* and a {@link AsyncHttpProvider}.
*
* @param config a {@link AsyncHttpClientConfig}
* @param httpProvider a {@link AsyncHttpProvider}
*/
public AsyncHttpClient(AsyncHttpProvider<?> httpProvider, AsyncHttpClientConfig config) {
this.config = config;
this.httpProvider = httpProvider;
}
/**
* Create a new HTTP Asynchronous Client using a {@link AsyncHttpClientConfig} configuration and
* and a AsyncHttpProvider class' name.
*
* @param config a {@link AsyncHttpClientConfig}
* @param providerClass a {@link AsyncHttpProvider}
*/
public AsyncHttpClient(String providerClass, AsyncHttpClientConfig config) {
this.config = new AsyncHttpClientConfig.Builder().build();
this.httpProvider = loadDefaultProvider(providerClass, config);
}
public class BoundRequestBuilder extends RequestBuilderBase<BoundRequestBuilder> {
private BoundRequestBuilder(RequestType type) {
super(BoundRequestBuilder.class, type);
}
private BoundRequestBuilder(Request prototype) {
super(BoundRequestBuilder.class, prototype);
}
public <T> Future<T> execute(AsyncHandler<T> handler) throws IOException {
return AsyncHttpClient.this.executeRequest(build(), handler);
}
public Future<Response> execute() throws IOException {
return AsyncHttpClient.this.executeRequest(build(), new AsyncCompletionHandlerBase());
}
// Note: For now we keep the delegates in place even though they are not needed
// since otherwise Clojure (and maybe other languages) won't be able to
// access these methods - see Clojure tickets 126 and 259
@Override
public BoundRequestBuilder addBodyPart(Part part) throws IllegalArgumentException {
return super.addBodyPart(part);
}
@Override
public BoundRequestBuilder addCookie(Cookie cookie) {
return super.addCookie(cookie);
}
@Override
public BoundRequestBuilder addHeader(String name, String value) {
return super.addHeader(name, value);
}
@Override
public BoundRequestBuilder addParameter(String key, String value) throws IllegalArgumentException {
return super.addParameter(key, value);
}
@Override
public BoundRequestBuilder addQueryParameter(String name, String value) {
return super.addQueryParameter(name, value);
}
@Override
public Request build() {
return super.build();
}
@Override
public BoundRequestBuilder setBody(byte[] data) throws IllegalArgumentException {
return super.setBody(data);
}
@Override
public BoundRequestBuilder setBody(EntityWriter dataWriter, long length) throws IllegalArgumentException {
return super.setBody(dataWriter, length);
}
@Override
public BoundRequestBuilder setBody(EntityWriter dataWriter) {
return super.setBody(dataWriter);
}
@Override
public BoundRequestBuilder setBody(InputStream stream) throws IllegalArgumentException {
return super.setBody(stream);
}
@Override
public BoundRequestBuilder setBody(String data) throws IllegalArgumentException {
return super.setBody(data);
}
@Override
public BoundRequestBuilder setHeader(String name, String value) {
return super.setHeader(name, value);
}
@Override
public BoundRequestBuilder setHeaders(Headers headers) {
return super.setHeaders(headers);
}
@Override
public BoundRequestBuilder setParameters(Map<String, String> parameters) throws IllegalArgumentException {
return super.setParameters(parameters);
}
@Override
public BoundRequestBuilder setParameters(Multimap<String, String> parameters) throws IllegalArgumentException {
return super.setParameters(parameters);
}
@Override
public BoundRequestBuilder setUrl(String url) {
return super.setUrl(url);
}
@Override
public BoundRequestBuilder setVirtualHost(String virtualHost) {
return super.setVirtualHost(virtualHost);
}
}
/**
* Return the asynchronouys {@link org.elasticsearch.util.http.client.AsyncHttpProvider}
*
* @return an {@link org.elasticsearch.util.http.client.AsyncHttpProvider}
*/
public AsyncHttpProvider<?> getProvider() {
return httpProvider;
}
/**
* Close the underlying connections.
*/
public void close() {
httpProvider.close();
}
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
/**
* Return the {@link org.elasticsearch.util.http.client.AsyncHttpClientConfig}
*
* @return {@link org.elasticsearch.util.http.client.AsyncHttpClientConfig}
*/
public AsyncHttpClientConfig getConfig() {
return config;
}
/**
* Prepare an HTTP client GET request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareGet(String url) {
return new BoundRequestBuilder(RequestType.GET).setUrl(url);
}
/**
* Prepare an HTTP client HEAD request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareHead(String url) {
return new BoundRequestBuilder(RequestType.HEAD).setUrl(url);
}
/**
* Prepare an HTTP client POST request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder preparePost(String url) {
return new BoundRequestBuilder(RequestType.POST).setUrl(url);
}
/**
* Prepare an HTTP client PUT request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder preparePut(String url) {
return new BoundRequestBuilder(RequestType.PUT).setUrl(url);
}
/**
* Prepare an HTTP client DELETE request.
*
* @param url A well formed URL.
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareDelete(String url) {
return new BoundRequestBuilder(RequestType.DELETE).setUrl(url);
}
/**
* Construct a {@link RequestBuilder} using a {@link Request}
*
* @param request a {@link Request}
* @return {@link RequestBuilder}
*/
public BoundRequestBuilder prepareRequest(Request request) {
return new BoundRequestBuilder(request);
}
/**
* Execute an HTTP request.
*
* @param request {@link Request}
* @param handler an instance of {@link AsyncHandler}
* @param <T> Type of the value that will be returned by the associated {@link java.util.concurrent.Future}
* @return a {@link Future} of type T
* @throws IOException
*/
public <T> Future<T> executeRequest(Request request, AsyncHandler<T> handler) throws IOException {
return httpProvider.execute(request, handler);
}
/**
* Execute an HTTP request.
*
* @param request {@link Request}
* @return a {@link Future} of type Response
* @throws IOException
*/
public Future<Response> executeRequest(Request request) throws IOException {
return httpProvider.execute(request, new AsyncCompletionHandlerBase());
}
@SuppressWarnings("unchecked")
private final static AsyncHttpProvider<?> loadDefaultProvider(String className, AsyncHttpClientConfig config) {
try {
Class<AsyncHttpProvider<?>> providerClass = (Class<AsyncHttpProvider<?>>) Thread.currentThread()
.getContextClassLoader().loadClass(className);
return (AsyncHttpProvider<?>) providerClass.getDeclaredConstructor(
new Class[]{AsyncHttpClientConfig.class}).newInstance(new Object[]{config});
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}

View File

@ -0,0 +1,435 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
import javax.net.ssl.SSLEngine;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
/**
* Configuration class to use with a {@link AsyncHttpClient}. System property can be also used to configure this
* object default behavior by doing:
* <p/>
* -Dcom.ning.http.client.AsyncHttpClientConfig.nameOfTheProperty
* ex:
* <p/>
* -Dcom.ning.http.client.AsyncHttpClientConfig.defaultMaxTotalConnections
* -Dcom.ning.http.client.AsyncHttpClientConfig.defaultMaxTotalConnections
* -Dcom.ning.http.client.AsyncHttpClientConfig.defaultMaxConnectionsPerHost
* -Dcom.ning.http.client.AsyncHttpClientConfig.defaultConnectionTimeoutInMS
* -Dcom.ning.http.client.AsyncHttpClientConfig.defaultIdleConnectionTimeoutInMS
* -Dcom.ning.http.client.AsyncHttpClientConfig.defaultRequestTimeoutInMS
* -Dcom.ning.http.client.AsyncHttpClientConfig.defaultRedirectsEnabled
* -Dcom.ning.http.client.AsyncHttpClientConfig.defaultMaxRedirects
*/
public class AsyncHttpClientConfig {
private final static String ASYNC_CLIENT = AsyncHttpClient.class.getName() + ".";
private final int maxTotalConnections;
private final int maxConnectionPerHost;
private final int connectionTimeOutInMs;
private final int idleConnectionTimeoutInMs;
private final int requestTimeoutInMs;
private final boolean redirectEnabled;
private final int maxDefaultRedirects;
private final boolean compressionEnabled;
private final String userAgent;
private final boolean keepAlive;
private final ScheduledExecutorService reaper;
private final ExecutorService applicationThreadPool;
private final ProxyServer proxyServer;
private final SSLEngine sslEngine;
private AsyncHttpClientConfig(int maxTotalConnections,
int maxConnectionPerHost,
int connectionTimeOutInMs,
int idleConnectionTimeoutInMs,
int requestTimeoutInMs,
boolean redirectEnabled,
int maxDefaultRedirects,
boolean compressionEnabled,
String userAgent,
boolean keepAlive,
ScheduledExecutorService reaper,
ExecutorService applicationThreadPool,
ProxyServer proxyServer,
SSLEngine sslEngine) {
this.maxTotalConnections = maxTotalConnections;
this.maxConnectionPerHost = maxConnectionPerHost;
this.connectionTimeOutInMs = connectionTimeOutInMs;
this.idleConnectionTimeoutInMs = idleConnectionTimeoutInMs;
this.requestTimeoutInMs = requestTimeoutInMs;
this.redirectEnabled = redirectEnabled;
this.maxDefaultRedirects = maxDefaultRedirects;
this.compressionEnabled = compressionEnabled;
this.userAgent = userAgent;
this.keepAlive = keepAlive;
this.sslEngine = sslEngine;
if (reaper == null) {
this.reaper = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncHttpClient-Reaper");
}
});
} else {
this.reaper = reaper;
}
if (applicationThreadPool == null) {
this.applicationThreadPool = Executors.newCachedThreadPool();
} else {
this.applicationThreadPool = applicationThreadPool;
}
this.proxyServer = proxyServer;
}
/**
* A {@link ScheduledExecutorService} used to expire idle connections.
*
* @return {@link ScheduledExecutorService}
*/
public ScheduledExecutorService reaper() {
return reaper;
}
/**
* Return the maximum number of connections an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can handle.
*
* @return the maximum number of connections an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can handle.
*/
public int getMaxTotalConnections() {
return maxTotalConnections;
}
/**
* Return the maximum number of connections per hosts an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can handle.
*
* @return the maximum number of connections per host an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can handle.
*/
public int getMaxConnectionPerHost() {
return maxConnectionPerHost;
}
/**
* Return the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can wait when connecting to a remote host
*
* @return the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can wait when connecting to a remote host
*/
public int getConnectionTimeoutInMs() {
return connectionTimeOutInMs;
}
/**
* Return the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can stay idle.
*
* @return the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can stay idle.
*/
public int getIdleConnectionTimeoutInMs() {
return idleConnectionTimeoutInMs;
}
/**
* Return the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} wait for a response
*
* @return the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} wait for a response
*/
public int getRequestTimeoutInMs() {
return requestTimeoutInMs;
}
/**
* Is HTTP redirect enabled
*
* @return true if enabled.
*/
public boolean isRedirectEnabled() {
return redirectEnabled;
}
/**
* Get the maximum number of HTTP redirect
*
* @return the maximum number of HTTP redirect
*/
public int getMaxRedirects() {
return maxDefaultRedirects;
}
/**
* Is HTTP keep-alive enabled.
*
* @return true if keep-alive is enabled
*/
public boolean getKeepAlive() {
return keepAlive;
}
/**
* Return the USER_AGENT header value
*
* @return the USER_AGENT header value
*/
public String getUserAgent() {
return userAgent;
}
/**
* Is HTTP compression enabled.
*
* @return true if compression is enabled
*/
public boolean isCompressionEnabled() {
return compressionEnabled;
}
/**
* Return the {@link java.util.concurrent.ExecutorService} an {@link AsyncHttpClient} use for handling
* asynchronous response.
*
* @return the {@link java.util.concurrent.ExecutorService} an {@link AsyncHttpClient} use for handling
* asynchronous response.
*/
public ExecutorService executorService() {
return applicationThreadPool;
}
/**
* An instance of {@link org.elasticsearch.util.http.client.ProxyServer} used by an {@link AsyncHttpClient}
*
* @return instance of {@link org.elasticsearch.util.http.client.ProxyServer}
*/
public ProxyServer getProxyServer() {
return proxyServer;
}
/**
* Return an instance of {@link SSLEngine} used for SSL connection.
*
* @return an instance of {@link SSLEngine} used for SSL connection.
*/
public SSLEngine getSSLEngine() {
return sslEngine;
}
/**
* Builder for an {@link AsyncHttpClient}
*/
public static class Builder {
private int defaultMaxTotalConnections = Integer.getInteger(ASYNC_CLIENT + "defaultMaxTotalConnections", 2000);
private int defaultMaxConnectionPerHost = Integer.getInteger(ASYNC_CLIENT + "defaultMaxConnectionsPerHost", 2000);
private int defaultConnectionTimeOutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultConnectionTimeoutInMS", 60 * 1000);
private int defaultIdleConnectionTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultIdleConnectionTimeoutInMS", 60 * 1000);
private int defaultRequestTimeoutInMs = Integer.getInteger(ASYNC_CLIENT + "defaultRequestTimeoutInMS", 60 * 1000);
private boolean redirectEnabled = Boolean.getBoolean(ASYNC_CLIENT + "defaultRedirectsEnabled");
private int maxDefaultRedirects = Integer.getInteger(ASYNC_CLIENT + "defaultMaxRedirects", 5);
private boolean compressionEnabled = Boolean.getBoolean(ASYNC_CLIENT + "compressionEnabled");
private String userAgent = System.getProperty(ASYNC_CLIENT + "userAgent", "ES/1.0");
private boolean keepAlive = true;
private ScheduledExecutorService reaper = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
private ExecutorService applicationThreadPool = Executors.newCachedThreadPool();
private ProxyServer proxyServer = null;
private SSLEngine sslEngine;
public Builder() {
}
/**
* Set the maximum number of connections an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can handle.
*
* @param defaultMaxTotalConnections the maximum number of connections an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can handle.
* @return a {@link Builder}
*/
public Builder setMaximumConnectionsTotal(int defaultMaxTotalConnections) {
this.defaultMaxTotalConnections = defaultMaxTotalConnections;
return this;
}
/**
* Set the maximum number of connections per hosts an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can handle.
*
* @param defaultMaxConnectionPerHost the maximum number of connections per host an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can handle.
* @return a {@link Builder}
*/
public Builder setMaximumConnectionsPerHost(int defaultMaxConnectionPerHost) {
this.defaultMaxConnectionPerHost = defaultMaxConnectionPerHost;
return this;
}
/**
* Set the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can wait when connecting to a remote host
*
* @param defaultConnectionTimeOutInMs the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can wait when connecting to a remote host
* @return a {@link Builder}
*/
public Builder setConnectionTimeoutInMs(int defaultConnectionTimeOutInMs) {
this.defaultConnectionTimeOutInMs = defaultConnectionTimeOutInMs;
return this;
}
/**
* Set the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can stay idle.
*
* @param defaultIdleConnectionTimeoutInMs
* the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} can stay idle.
* @return a {@link Builder}
*/
public Builder setIdleConnectionTimeoutInMs(int defaultIdleConnectionTimeoutInMs) {
this.defaultIdleConnectionTimeoutInMs = defaultIdleConnectionTimeoutInMs;
return this;
}
/**
* Set the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} wait for a response
*
* @param defaultRequestTimeoutInMs the maximum time in millisecond an {@link org.elasticsearch.util.http.client.AsyncHttpClient} wait for a response
* @return a {@link Builder}
*/
public Builder setRequestTimeoutInMs(int defaultRequestTimeoutInMs) {
this.defaultRequestTimeoutInMs = defaultRequestTimeoutInMs;
return this;
}
/**
* Set to true to enable HTTP redirect
*
* @param redirectEnabled true if enabled.
* @return a {@link Builder}
*/
public Builder setFollowRedirects(boolean redirectEnabled) {
this.redirectEnabled = redirectEnabled;
return this;
}
/**
* Set the maximum number of HTTP redirect
*
* @param maxDefaultRedirects the maximum number of HTTP redirect
* @return a {@link Builder}
*/
public Builder setMaximumNumberOfRedirects(int maxDefaultRedirects) {
this.maxDefaultRedirects = maxDefaultRedirects;
return this;
}
/**
* Enable HTTP compression.
*
* @param compressionEnabled true if compression is enabled
* @return a {@link Builder}
*/
public Builder setCompressionEnabled(boolean compressionEnabled) {
this.compressionEnabled = compressionEnabled;
return this;
}
/**
* Set the USER_AGENT header value
*
* @param userAgent the USER_AGENT header value
* @return a {@link Builder}
*/
public Builder setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
/**
* Set HTTP keep-alive value.
*
* @param keepAlive true if keep-alive is enabled
* @return a {@link Builder}
*/
public Builder setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
return this;
}
/**
* Set the{@link ScheduledExecutorService} used to expire idle connections.
*
* @param reaper the{@link ScheduledExecutorService} used to expire idle connections.
* @return a {@link Builder}
*/
public Builder setScheduledExecutorService(ScheduledExecutorService reaper) {
this.reaper = reaper;
return this;
}
/**
* Set the {@link java.util.concurrent.ExecutorService} an {@link AsyncHttpClient} use for handling
* asynchronous response.
*
* @param applicationThreadPool the {@link java.util.concurrent.ExecutorService} an {@link AsyncHttpClient} use for handling
* asynchronous response.
* @return a {@link Builder}
*/
public Builder setExecutorService(ExecutorService applicationThreadPool) {
this.applicationThreadPool = applicationThreadPool;
return this;
}
/**
* Set an instance of {@link org.elasticsearch.util.http.client.ProxyServer} used by an {@link AsyncHttpClient}
*
* @param proxyServer instance of {@link org.elasticsearch.util.http.client.ProxyServer}
* @return a {@link Builder}
*/
public Builder setProxyServer(ProxyServer proxyServer) {
this.proxyServer = proxyServer;
return this;
}
/**
* Set the {@link SSLEngine} for secure connection.
*
* @param sslEngine the {@link SSLEngine} for secure connection
* @return a {@link Builder}
*/
public Builder setSSLEngine(SSLEngine sslEngine) {
this.sslEngine = sslEngine;
return this;
}
/**
* Build an {@link AsyncHttpClientConfig}
*
* @return an {@link AsyncHttpClientConfig}
*/
public AsyncHttpClientConfig build() {
return new AsyncHttpClientConfig(defaultMaxTotalConnections,
defaultMaxConnectionPerHost,
defaultConnectionTimeOutInMs,
defaultIdleConnectionTimeoutInMs,
defaultRequestTimeoutInMs,
redirectEnabled,
maxDefaultRedirects,
compressionEnabled,
userAgent,
keepAlive,
reaper,
applicationThreadPool,
proxyServer,
sslEngine);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Future;
/**
* Interface to be used when implementing custom asynchronous I/O HTTP client.
* By default, the {@link org.elasticsearch.util.http.client.providers.NettyAsyncHttpProvider} is used.
*/
public interface AsyncHttpProvider<A> {
/**
* Execute the request and invoke the {@link AsyncHandler} when the response arrive.
*
* @param handler an instance of {@link AsyncHandler}
* @return a {@link java.util.concurrent.Future} of Type T.
* @throws IOException
*/
public <T> Future<T> execute(Request request, AsyncHandler<T> handler) throws IOException;
/**
* Close the current underlying TCP/HTTP connection.s
*/
public void close();
/**
* Prepare a {@link Response}
*
* @param status {@link HttpResponseStatus}
* @param headers {@link HttpResponseHeaders}
* @param bodyParts list of {@link HttpResponseBodyPart}
* @return a {@link Response}
*/
public Response prepareResponse(HttpResponseStatus<A> status,
HttpResponseHeaders<A> headers,
Collection<HttpResponseBodyPart<A>> bodyParts);
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
public class ByteArrayPart implements Part {
private String name;
private String fileName;
private byte[] data;
private String mimeType;
private String charSet;
public ByteArrayPart(String name, String fileName, byte[] data, String mimeType, String charSet) {
this.name = name;
this.fileName = fileName;
this.data = data;
this.mimeType = mimeType;
this.charSet = charSet;
}
public String getName() {
return name;
}
public String getFileName() {
return fileName;
}
public byte[] getData() {
return data;
}
public String getMimeType() {
return mimeType;
}
public String getCharSet() {
return charSet;
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
public class Cookie {
private String domain;
private String name;
private String value;
private String path;
private int maxAge;
private boolean secure;
public Cookie(String domain, String name, String value, String path, int maxAge, boolean secure) {
this.domain = domain;
this.name = name;
this.value = value;
this.path = path;
this.maxAge = maxAge;
this.secure = secure;
}
public String getDomain() {
return domain;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
public String getPath() {
return path;
}
public int getMaxAge() {
return maxAge;
}
public boolean isSecure() {
return secure;
}
@Override
public String toString() {
return String.format("Cookie: domain=%s, name=%s, value=%s, path=%s, maxAge=%d, secure=%s",
domain, name, value, path, maxAge, secure);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import java.io.File;
/**
* A file multipart part.
*/
public class FilePart implements Part {
private String name;
private File file;
private String mimeType;
private String charSet;
public FilePart(String name, File file, String mimeType, String charSet) {
this.name = name;
this.file = file;
this.mimeType = mimeType;
this.charSet = charSet;
}
public String getName() {
return name;
}
public File getFile() {
return file;
}
public String getMimeType() {
return mimeType;
}
public String getCharSet() {
return charSet;
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
import java.util.concurrent.Future;
/**
* Extended {@link Future}
*
* @param <V> Type of the value that will be returned.
*/
public interface FutureImpl<V> extends Future<V> {
void done();
void abort(Throwable t);
}

View File

@ -0,0 +1,233 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.http.collection.Pair;
import java.util.*;
public class Headers implements Iterable<Pair<String, String>> {
public static final String CONTENT_TYPE = "Content-Type";
private List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
public static Headers unmodifiableHeaders(Headers headers) {
return new UnmodifiableHeaders(headers);
}
public Headers() {
}
public Headers(Headers src) {
if (src != null) {
for (Pair<String, String> header : src) {
add(header);
}
}
}
public Headers(Map<String, Collection<String>> headers) {
for (Map.Entry<String, Collection<String>> entry : headers.entrySet()) {
for (String value : entry.getValue()) {
add(entry.getKey(), value);
}
}
}
/**
* Adds the specified header and returns this headers object.
*
* @param name The header name
* @param value The header value
* @return This object
*/
public Headers add(String name, String value) {
headers.add(new Pair<String, String>(name, value));
return this;
}
/**
* Adds the specified header and returns this headers object.
*
* @param header The name / value pair
* @return This object
*/
public Headers add(Pair<String, String> header) {
headers.add(new Pair<String, String>(header.getFirst(), header.getSecond()));
return this;
}
/**
* Adds all headers from the given headers object to this object and returns this headers object.
*
* @param srcHeaders The source headers object
* @return This object
*/
public Headers addAll(Headers srcHeaders) {
for (Pair<String, String> entry : srcHeaders.headers) {
headers.add(new Pair<String, String>(entry.getFirst(), entry.getSecond()));
}
return this;
}
/**
* Convenience method to add a Content-type header
*
* @param contentType content type to set
* @return This object
*/
public Headers addContentTypeHeader(String contentType) {
return add(CONTENT_TYPE, contentType);
}
/**
* Replaces all existing headers with the header given.
*
* @param header The header name.
* @param value The new header value.
*/
public void replace(final String header, final String value) {
remove(header);
add(header, value);
}
/**
* {@inheritDoc}
*/
public Iterator<Pair<String, String>> iterator() {
return headers.iterator();
}
/**
* Returns the value of first header of the given name.
*
* @param name The header's name
* @return The value
*/
public String getHeaderValue(String name) {
for (Pair<String, String> header : this) {
if (name.equalsIgnoreCase(header.getFirst())) {
return header.getSecond();
}
}
return null;
}
/**
* Returns the values of all header of the given name.
*
* @param name The header name
* @return The values, will not be <code>null</code>
*/
public List<String> getHeaderValues(String name) {
ArrayList<String> values = new ArrayList<String>();
for (Pair<String, String> header : this) {
if (name.equalsIgnoreCase(header.getFirst())) {
values.add(header.getSecond());
}
}
return values;
}
/**
* Adds the specified header(s) and returns this headers object.
*
* @param name The header name
* @return This object
*/
public Headers remove(String name) {
for (Iterator<Pair<String, String>> it = headers.iterator(); it.hasNext();) {
Pair<String, String> header = it.next();
if (name.equalsIgnoreCase(header.getFirst())) {
it.remove();
}
}
return this;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Headers other = (Headers) obj;
if (headers == null) {
if (other.headers != null)
return false;
} else if (!headers.equals(other.headers))
return false;
return true;
}
private static class UnmodifiableHeaders extends Headers {
final Headers headers;
UnmodifiableHeaders(Headers headers) {
this.headers = headers;
}
@Override
public Headers add(Pair<String, String> header) {
throw new UnsupportedOperationException();
}
@Override
public Headers add(String name, String value) {
throw new UnsupportedOperationException();
}
@Override
public Headers addAll(Headers srcHeaders) {
throw new UnsupportedOperationException();
}
@Override
public Headers addContentTypeHeader(String contentType) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object obj) {
return headers.equals(obj);
}
@Override
public String getHeaderValue(String name) {
return headers.getHeaderValue(name);
}
@Override
public List<String> getHeaderValues(String name) {
return headers.getHeaderValues(name);
}
@Override
public Iterator<Pair<String, String>> iterator() {
return headers.iterator();
}
@Override
public Headers remove(String name) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.http.url.Url;
/**
* Base class for callback class used by {@link org.elasticsearch.util.http.client.AsyncHandler}
*/
public class HttpContent<R> {
protected final R response;
protected final AsyncHttpProvider<R> provider;
protected final Url url;
protected HttpContent(Url url, R response, AsyncHttpProvider<R> provider) {
this.response = response;
this.provider = provider;
this.url = url;
}
protected final AsyncHttpProvider<R> provider() {
return provider;
}
public final Url getUrl() {
return url;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.http.url.Url;
/**
* A callback class used when an HTTP response body is received.
*/
public abstract class HttpResponseBodyPart<R> extends HttpContent<R> {
public HttpResponseBodyPart(Url url, R response, AsyncHttpProvider<R> provider) {
super(url, response, provider);
}
/**
* Return the response body's part bytes received.
*
* @return the response body's part bytes received.
*/
abstract public byte[] getBodyPartBytes();
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.http.url.Url;
/**
* A class that represent the HTTP headers.
*/
public abstract class HttpResponseHeaders<R> extends HttpContent<R> {
private final boolean traillingHeaders;
public HttpResponseHeaders(Url url, R response, AsyncHttpProvider<R> provider) {
super(url, response, provider);
this.traillingHeaders = false;
}
public HttpResponseHeaders(Url url, R response, AsyncHttpProvider<R> provider, boolean traillingHeaders) {
super(url, response, provider);
this.traillingHeaders = traillingHeaders;
}
/**
* Return the HTTP header
*
* @return an {@link Headers}
*/
abstract public Headers getHeaders();
/**
* Return true is headers has been received after the response body.
*
* @return true is headers has been received after the response body.
*/
public boolean isTraillingHeadersReceived() {
return traillingHeaders;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.http.url.Url;
/**
* A class that represent the HTTP response' status line (code + text)
*/
public abstract class HttpResponseStatus<R> extends HttpContent<R> {
public HttpResponseStatus(Url url, R response, AsyncHttpProvider<R> provider) {
super(url, response, provider);
}
/**
* Return the response status code
*
* @return the response status code
*/
abstract public int getStatusCode();
/**
* Return the response status text
*
* @return the response status text
*/
abstract public String getStatusText();
/**
* Protocol name from status line.
*
* @return Protocol name.
*/
abstract public String getProtocolName();
/**
* Protocol major version.
*
* @return Major version.
*/
abstract public int getProtocolMajorVersion();
/**
* Protocol minor version.
*
* @return Minor version.
*/
abstract public int getProtocolMinorVersion();
/**
* Full protocol name + version
*
* @return protocol name + version
*/
abstract public String getProtocolText();
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
/**
* Thrown when the {@link AsyncHttpClientConfig#getMaxRedirects()} has been reached.
*/
public class MaxRedirectException extends Exception {
private static final long serialVersionUID = 1L;
public MaxRedirectException() {
super();
}
public MaxRedirectException(String msg) {
super(msg);
}
public MaxRedirectException(Throwable cause) {
super(cause);
}
public MaxRedirectException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
/**
* Interface for the parts in a multipart request.
*/
public interface Part {
public String getName();
}

View File

@ -0,0 +1,111 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
/**
* Represents a proxy server.
*/
public class ProxyServer {
public enum Protocol {
HTTP("http"), HTTPS("https");
private final String protocol;
private Protocol(final String protocol) {
this.protocol = protocol;
}
public String getProtocol() {
return protocol;
}
@Override
public String toString() {
return getProtocol();
}
}
private final Protocol protocol;
private final String host;
private int port;
public ProxyServer(final Protocol protocol, final String host, final int port) {
this.protocol = protocol;
this.host = host;
this.port = port;
}
public ProxyServer(final String host, final int port) {
this(Protocol.HTTP, host, port);
}
public Protocol getProtocol() {
return protocol;
}
public String getProtocolAsString() {
return protocol.toString();
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
/**
* Convert from Java java.net.Proxy object.
*
* @param proxy
* @return A ProxyServer object or null if the proxy object can not converted.
*/
public static final ProxyServer fromProxy(final Proxy proxy) {
if (proxy == null || proxy.type() == Proxy.Type.DIRECT) {
return null;
}
if (proxy.type() != Proxy.Type.HTTP) {
throw new IllegalArgumentException("Only DIRECT and HTTP Proxies are supported!");
}
final SocketAddress sa = proxy.address();
if (!(sa instanceof InetSocketAddress)) {
throw new IllegalArgumentException("Only Internet Address sockets are supported!");
}
InetSocketAddress isa = (InetSocketAddress) sa;
if (isa.isUnresolved()) {
return new ProxyServer(isa.getHostName(), isa.getPort());
} else {
return new ProxyServer(isa.getAddress().getHostAddress(), isa.getPort());
}
}
@Override
public String toString() {
return String.format("%s://%s:%d", protocol.toString(), host, port);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.collect.Multimap;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.List;
public interface Request {
public static interface EntityWriter {
public void writeEntity(OutputStream out) throws IOException;
}
public RequestType getType();
public String getUrl();
public Headers getHeaders();
public Collection<Cookie> getCookies();
public byte[] getByteData();
public String getStringData();
public InputStream getStreamData();
public EntityWriter getEntityWriter();
public long getLength();
public Multimap<String, String> getParams();
public List<Part> getParts();
public String getVirtualHost();
public Multimap<String, String> getQueryParams();
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.collect.Multimap;
import org.elasticsearch.util.http.client.Request.EntityWriter;
import java.io.InputStream;
import java.util.Map;
/**
* Builder for a {@link Request}.
*/
public class RequestBuilder extends RequestBuilderBase<RequestBuilder> {
public RequestBuilder(RequestType type) {
super(RequestBuilder.class, type);
}
public RequestBuilder(Request prototype) {
super(RequestBuilder.class, prototype);
}
// Note: For now we keep the delegates in place even though they are not needed
// since otherwise Clojure (and maybe other languages) won't be able to
// access these methods - see Clojure tickets 126 and 259
@Override
public RequestBuilder addBodyPart(Part part) throws IllegalArgumentException {
return super.addBodyPart(part);
}
@Override
public RequestBuilder addCookie(Cookie cookie) {
return super.addCookie(cookie);
}
@Override
public RequestBuilder addHeader(String name, String value) {
return super.addHeader(name, value);
}
@Override
public RequestBuilder addParameter(String key, String value) throws IllegalArgumentException {
return super.addParameter(key, value);
}
@Override
public RequestBuilder addQueryParameter(String name, String value) {
return super.addQueryParameter(name, value);
}
@Override
public Request build() {
return super.build();
}
@Override
public RequestBuilder setBody(byte[] data) throws IllegalArgumentException {
return super.setBody(data);
}
@Override
public RequestBuilder setBody(EntityWriter dataWriter, long length) throws IllegalArgumentException {
return super.setBody(dataWriter, length);
}
@Override
public RequestBuilder setBody(EntityWriter dataWriter) {
return super.setBody(dataWriter);
}
@Override
public RequestBuilder setBody(InputStream stream) throws IllegalArgumentException {
return super.setBody(stream);
}
@Override
public RequestBuilder setBody(String data) throws IllegalArgumentException {
return super.setBody(data);
}
@Override
public RequestBuilder setHeader(String name, String value) {
return super.setHeader(name, value);
}
@Override
public RequestBuilder setHeaders(Headers headers) {
return super.setHeaders(headers);
}
@Override
public RequestBuilder setParameters(Map<String, String> parameters) throws IllegalArgumentException {
return super.setParameters(parameters);
}
@Override
public RequestBuilder setParameters(Multimap<String, String> parameters) throws IllegalArgumentException {
return super.setParameters(parameters);
}
@Override
public RequestBuilder setUrl(String url) {
return super.setUrl(url);
}
@Override
public RequestBuilder setVirtualHost(String virtualHost) {
return super.setVirtualHost(virtualHost);
}
}

View File

@ -0,0 +1,358 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.collect.LinkedListMultimap;
import org.elasticsearch.util.collect.Multimap;
import org.elasticsearch.util.collect.Multimaps;
import org.elasticsearch.util.http.client.Request.EntityWriter;
import org.elasticsearch.util.http.collection.Pair;
import org.elasticsearch.util.http.url.Url;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.*;
/**
* Builder for {@link Request}
*
* @param <T>
*/
abstract class RequestBuilderBase<T extends RequestBuilderBase<T>> {
private static final class RequestImpl implements Request {
private RequestType type;
private String url;
private Headers headers = new Headers();
private Collection<Cookie> cookies = new ArrayList<Cookie>();
private byte[] byteData;
private String stringData;
private InputStream streamData;
private EntityWriter entityWriter;
private Multimap<String, String> params;
private List<Part> parts;
private String virtualHost;
private long length = -1;
public Multimap<String, String> queryParams;
public RequestImpl() {
}
public RequestImpl(Request prototype) {
if (prototype != null) {
this.type = prototype.getType();
this.url = prototype.getUrl();
this.headers = new Headers(prototype.getHeaders());
this.cookies = new ArrayList<Cookie>(prototype.getCookies());
this.byteData = prototype.getByteData();
this.stringData = prototype.getStringData();
this.streamData = prototype.getStreamData();
this.entityWriter = prototype.getEntityWriter();
this.params = (prototype.getParams() == null ? null : LinkedListMultimap.create(prototype.getParams()));
this.queryParams = (prototype.getQueryParams() == null ? null : LinkedListMultimap.create(prototype.getQueryParams()));
this.parts = (prototype.getParts() == null ? null : new ArrayList<Part>(prototype.getParts()));
this.virtualHost = prototype.getVirtualHost();
this.length = prototype.getLength();
}
}
/* @Override */
public RequestType getType() {
return type;
}
/* @Override */
public String getUrl() {
try {
Url url = Url.valueOf(this.url);
if (queryParams != null) {
for (Map.Entry<String, String> entry : queryParams.entries()) {
url.addParameter(entry.getKey(), entry.getValue());
}
}
return url.toString();
}
catch (MalformedURLException e) {
throw new IllegalArgumentException("Illegal URL", e);
}
}
/* @Override */
public Headers getHeaders() {
return Headers.unmodifiableHeaders(headers);
}
/* @Override */
public Collection<Cookie> getCookies() {
return Collections.unmodifiableCollection(cookies);
}
/* @Override */
public byte[] getByteData() {
return byteData;
}
/* @Override */
public String getStringData() {
return stringData;
}
/* @Override */
public InputStream getStreamData() {
return streamData;
}
/* @Override */
public EntityWriter getEntityWriter() {
return entityWriter;
}
/* @Override */
public long getLength() {
return length;
}
/* @Override */
public Multimap<String, String> getParams() {
return params == null ? null : Multimaps.unmodifiableMultimap(params);
}
/* @Override */
public List<Part> getParts() {
return parts == null ? null : Collections.unmodifiableList(parts);
}
/* @Override */
public String getVirtualHost() {
return virtualHost;
}
public Multimap<String, String> getQueryParams() {
return queryParams == null ? null : Multimaps.unmodifiableMultimap(queryParams);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(url);
sb.append("\t");
sb.append(type);
for (Pair<String, String> header : headers) {
sb.append("\t");
sb.append(header.getFirst());
sb.append(":");
sb.append(header.getSecond());
}
return sb.toString();
}
}
private final Class<T> derived;
private final RequestImpl request;
RequestBuilderBase(Class<T> derived, RequestType type) {
this.derived = derived;
request = new RequestImpl();
request.type = type;
}
RequestBuilderBase(Class<T> derived, Request prototype) {
this.derived = derived;
request = new RequestImpl(prototype);
}
public T setUrl(String url) {
request.url = url;
return derived.cast(this);
}
public T setVirtualHost(String virtualHost) {
request.virtualHost = virtualHost;
return derived.cast(this);
}
public T setHeader(String name, String value) {
request.headers.replace(name, value);
return derived.cast(this);
}
public T addHeader(String name, String value) {
request.headers.add(name, value);
return derived.cast(this);
}
public T setHeaders(Headers headers) {
request.headers = (headers == null ? new Headers() : new Headers(headers));
return derived.cast(this);
}
public T addCookie(Cookie cookie) {
request.cookies.add(cookie);
return derived.cast(this);
}
private void resetParameters() {
request.params = null;
}
private void resetNonMultipartData() {
request.byteData = null;
request.stringData = null;
request.streamData = null;
request.entityWriter = null;
request.length = -1;
}
private void resetMultipartData() {
request.parts = null;
}
public T setBody(byte[] data) throws IllegalArgumentException {
if ((request.type != RequestType.POST) && (request.type != RequestType.PUT)) {
throw new IllegalArgumentException("Request type has to POST or PUT for content");
}
resetParameters();
resetNonMultipartData();
resetMultipartData();
request.byteData = data;
return derived.cast(this);
}
public T setBody(String data) throws IllegalArgumentException {
if ((request.type != RequestType.POST) && (request.type != RequestType.PUT)) {
throw new IllegalArgumentException("Request type has to POST or PUT for content");
}
resetParameters();
resetNonMultipartData();
resetMultipartData();
request.stringData = data;
return derived.cast(this);
}
public T setBody(InputStream stream) throws IllegalArgumentException {
if ((request.type != RequestType.POST) && (request.type != RequestType.PUT)) {
throw new IllegalArgumentException("Request type has to POST or PUT for content");
}
resetParameters();
resetNonMultipartData();
resetMultipartData();
request.streamData = stream;
return derived.cast(this);
}
public T setBody(EntityWriter dataWriter) {
return setBody(dataWriter, -1);
}
public T setBody(EntityWriter dataWriter, long length) throws IllegalArgumentException {
if ((request.type != RequestType.POST) && (request.type != RequestType.PUT)) {
throw new IllegalArgumentException("Request type has to POST or PUT for content");
}
resetParameters();
resetNonMultipartData();
resetMultipartData();
request.entityWriter = dataWriter;
request.length = length;
return derived.cast(this);
}
public T addQueryParameter(String name, String value) {
if (request.queryParams == null) {
request.queryParams = LinkedListMultimap.create();
}
request.queryParams.put(name, value);
return derived.cast(this);
}
public T addParameter(String key, String value) throws IllegalArgumentException {
if ((request.type != RequestType.POST) && (request.type != RequestType.PUT)) {
throw new IllegalArgumentException("Request type has to POST or PUT for form parameters");
}
resetNonMultipartData();
resetMultipartData();
if (request.params == null) {
request.params = LinkedListMultimap.create();
}
request.params.put(key, value);
return derived.cast(this);
}
public T setParameters(Multimap<String, String> parameters) throws IllegalArgumentException {
if ((request.type != RequestType.POST) && (request.type != RequestType.PUT)) {
throw new IllegalArgumentException("Request type has to POST or PUT for form parameters");
}
resetNonMultipartData();
resetMultipartData();
request.params = LinkedListMultimap.create(parameters);
return derived.cast(this);
}
public T setParameters(Map<String, String> parameters) throws IllegalArgumentException {
if ((request.type != RequestType.POST) && (request.type != RequestType.PUT)) {
throw new IllegalArgumentException("Request type has to POST or PUT for form parameters");
}
resetNonMultipartData();
resetMultipartData();
request.params = LinkedListMultimap.create(Multimaps.forMap(parameters));
return derived.cast(this);
}
public T addBodyPart(Part part) throws IllegalArgumentException {
if ((request.type != RequestType.POST) && (request.type != RequestType.PUT)) {
throw new IllegalArgumentException("Request type has to POST or PUT for parts");
}
resetParameters();
resetNonMultipartData();
if (request.parts == null) {
request.parts = new ArrayList<Part>();
}
request.parts.add(part);
return derived.cast(this);
}
public Request build() {
if ((request.length < 0) && (request.streamData == null) &&
((request.type == RequestType.POST) || (request.type == RequestType.PUT))) {
String contentLength = request.headers.getHeaderValue("Content-Length");
if (contentLength != null) {
try {
request.length = Long.parseLong(contentLength);
}
catch (NumberFormatException e) {
// NoOp -- we wdn't specify length so it will be chunked?
}
}
}
return request;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
public enum RequestType {
GET,
POST,
PUT,
DELETE,
HEAD
}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
import org.elasticsearch.util.http.url.Url;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.List;
/**
* Represents the asynchronous HTTP response callback for an {@link org.elasticsearch.util.http.client.AsyncCompletionHandler}
*/
public interface Response {
/**
* Returns the status code for the request.
*
* @return The status code
*/
public int getStatusCode();
/**
* Returns the status text for the request.
*
* @return The status text
*/
public String getStatusText();
/**
* Returns an input stream for the response body. Note that you should not try to get this more than once,
* and that you should not close the stream.
*
* @return The input stream
* @throws java.io.IOException
*/
public InputStream getResponseBodyAsStream() throws IOException;
/**
* Returns the first maxLength bytes of the response body as a string. Note that this does not check
* whether the content type is actually a textual one, but it will use the charset if present in the content
* type header.
*
* @param maxLength The maximum number of bytes to read
* @return The response body
* @throws java.io.IOException
*/
public String getResponseBodyExcerpt(int maxLength) throws IOException;
/**
* Return the entire response body as a String.
*
* @return the entire response body as a String.
* @throws IOException
*/
public String getResponseBody() throws IOException;
/**
* Return the request {@link Url}. Note that if the request got redirected, the value of the {@link Url} will be
* the last valid redirect url.
*
* @return the request {@link Url}.
* @throws MalformedURLException
*/
public Url getUrl() throws MalformedURLException;
/**
* Return the content-type header value.
*
* @return the content-type header value.
*/
public String getContentType();
/**
* Return the response header
*
* @return the response header
*/
public String getHeader(String name);
/**
* Return a {@link List} of the response header value.
*
* @return the response header
*/
public List<String> getHeaders(String name);
public Headers getHeaders();
/**
* Return true if the response redirects to another object.
*
* @return True if the response redirects to another object.
*/
boolean isRedirected();
/**
* Subclasses SHOULD implement toString() in a way that identifies the request for logging.
*
* @return The textual representation
*/
public String toString();
/**
* Return the list of {@link Cookie}.
*/
public List<Cookie> getCookies();
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client;
/**
* A string multipart part.
*/
public class StringPart implements Part {
private String name;
private String value;
public StringPart(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
}

View File

@ -0,0 +1,776 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client.providers;
import org.elasticsearch.util.collect.Multimap;
import org.elasticsearch.util.http.client.*;
import org.elasticsearch.util.http.client.AsyncHandler.STATE;
import org.elasticsearch.util.http.client.Cookie;
import org.elasticsearch.util.http.client.HttpResponseStatus;
import org.elasticsearch.util.http.collection.Pair;
import org.elasticsearch.util.http.multipart.ByteArrayPartSource;
import org.elasticsearch.util.http.multipart.MultipartRequestEntity;
import org.elasticsearch.util.http.multipart.PartSource;
import org.elasticsearch.util.http.url.Url;
import org.elasticsearch.util.http.url.Url.Protocol;
import org.elasticsearch.util.http.util.SslUtils;
import org.elasticsearch.util.logging.ESLogger;
import org.elasticsearch.util.logging.Loggers;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferOutputStream;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.http.*;
import org.jboss.netty.handler.ssl.SslHandler;
import org.jboss.netty.handler.timeout.IdleStateEvent;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.jboss.netty.util.HashedWheelTimer;
import org.jboss.netty.util.internal.ConcurrentHashMap;
import javax.net.ssl.SSLEngine;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.jboss.netty.channel.Channels.*;
public class NettyAsyncHttpProvider extends SimpleChannelUpstreamHandler implements AsyncHttpProvider<HttpResponse> {
private final static ESLogger log = Loggers.getLogger(NettyAsyncHttpProvider.class);
private final ClientBootstrap bootstrap;
private final static int MAX_BUFFERRED_BYTES = 8192;
private final AsyncHttpClientConfig config;
private final ConcurrentHashMap<String, Channel> connectionsPool = new ConcurrentHashMap<String, Channel>();
private volatile int maxConnectionsPerHost;
private final HashedWheelTimer timer = new HashedWheelTimer();
private final AtomicBoolean isClose = new AtomicBoolean(false);
private final NioClientSocketChannelFactory socketChannelFactory;
private final ChannelGroup openChannels = new DefaultChannelGroup("asyncHttpClient");
public NettyAsyncHttpProvider(AsyncHttpClientConfig config) {
socketChannelFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
config.executorService());
bootstrap = new ClientBootstrap(socketChannelFactory);
this.config = config;
}
void configure(final boolean useSSL, final ConnectListener<?> cl) {
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
/* @Override */
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = pipeline();
if (useSSL) {
try {
SSLEngine sslEngine = config.getSSLEngine();
if (sslEngine == null) {
sslEngine = SslUtils.getSSLEngine();
}
pipeline.addLast("ssl", new SslHandler(sslEngine));
} catch (Throwable ex) {
cl.future().abort(ex);
}
}
pipeline.addLast("codec", new HttpClientCodec());
if (config.isCompressionEnabled()) {
pipeline.addLast("inflater", new HttpContentDecompressor());
}
IdleStateHandler h = new IdleStateHandler(timer, 0, 0, config.getIdleConnectionTimeoutInMs(), TimeUnit.MILLISECONDS) {
@SuppressWarnings("unused")
public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) throws MalformedURLException {
e.getChannel().close();
removeFromCache(ctx, e);
}
};
pipeline.addLast("timeout", h);
pipeline.addLast("httpProcessor", NettyAsyncHttpProvider.this);
return pipeline;
}
});
}
private Channel lookupInCache(Url url) {
Channel channel = connectionsPool.get(url.getBaseUrl());
if (channel != null) {
/**
* The Channel will eventually be closed by Netty and will becomes invalid.
* We might suffer a memory leak if we don't scan for closed channel. The
* AsyncHttpClientConfig.reaper() will always make sure those are cleared.
*/
if (channel.isOpen()) {
channel.setReadable(true);
} else {
connectionsPool.remove(url.getBaseUrl());
}
}
return channel;
}
/**
* Non Blocking connect.
*/
private final static class ConnectListener<T> implements ChannelFutureListener {
private final AsyncHttpClientConfig config;
private final AsyncHandler<T> asyncHandler;
private final NettyResponseFuture<T> future;
private final HttpRequest nettyRequest;
private ConnectListener(AsyncHttpClientConfig config,
AsyncHandler<T> asyncHandler,
NettyResponseFuture<T> future,
HttpRequest nettyRequest) {
this.config = config;
this.asyncHandler = asyncHandler;
this.future = future;
this.nettyRequest = nettyRequest;
}
public NettyResponseFuture<T> future() {
return future;
}
public final void operationComplete(ChannelFuture f) throws Exception {
try {
executeRequest(f.getChannel(), asyncHandler, config, future, nettyRequest);
} catch (ConnectException ex) {
future.abort(ex);
}
}
public static class Builder<T> {
private final AsyncHttpClientConfig config;
private final Request request;
private final AsyncHandler<T> asyncHandler;
private NettyResponseFuture<T> future;
public Builder(AsyncHttpClientConfig config, Request request, AsyncHandler<T> asyncHandler) {
this.config = config;
this.request = request;
this.asyncHandler = asyncHandler;
this.future = null;
}
public Builder(AsyncHttpClientConfig config, Request request, AsyncHandler<T> asyncHandler, NettyResponseFuture<T> future) {
this.config = config;
this.request = request;
this.asyncHandler = asyncHandler;
this.future = future;
}
public ConnectListener<T> build() throws IOException {
Url url = createUrl(request.getUrl());
HttpRequest nettyRequest = buildRequest(config, request, url);
if (log.isDebugEnabled())
log.debug("Executing the doConnect operation: " + asyncHandler);
if (future == null) {
future = new NettyResponseFuture<T>(url, request, asyncHandler,
nettyRequest, config.getRequestTimeoutInMs());
}
return new ConnectListener<T>(config, asyncHandler, future, nettyRequest);
}
}
}
private final static <T> void executeRequest(final Channel channel,
final AsyncHandler<T> asyncHandler,
final AsyncHttpClientConfig config,
final NettyResponseFuture<T> future,
final HttpRequest nettyRequest) throws ConnectException {
if (!channel.isConnected()) {
throw new ConnectException("Connection refused to " + channel.getRemoteAddress());
}
channel.getPipeline().getContext(NettyAsyncHttpProvider.class).setAttachment(future);
channel.write(nettyRequest);
try {
future.setReaperFuture(config.reaper().schedule(new Callable<Object>() {
public Object call() {
if (!future.isDone() && !future.isCancelled()) {
future.abort(new TimeoutException());
channel.getPipeline().getContext(NettyAsyncHttpProvider.class).setAttachment(ClosedEvent.class);
channel.close();
}
return null;
}
}, config.getRequestTimeoutInMs(), TimeUnit.MILLISECONDS));
} catch (RejectedExecutionException ex) {
future.abort(ex);
}
}
private final static HttpRequest buildRequest(AsyncHttpClientConfig config, Request request, Url url) throws IOException {
HttpRequest nettyRequest = null;
switch (request.getType()) {
case GET:
nettyRequest = construct(config, request, HttpMethod.GET, url);
break;
case POST:
nettyRequest = construct(config, request, HttpMethod.POST, url);
break;
case DELETE:
nettyRequest = construct(config, request, HttpMethod.DELETE, url);
break;
case PUT:
nettyRequest = construct(config, request, HttpMethod.PUT, url);
break;
case HEAD:
nettyRequest = construct(config, request, HttpMethod.HEAD, url);
break;
}
return nettyRequest;
}
private final static Url createUrl(String u) {
URI uri = URI.create(u);
final String scheme = uri.getScheme().toLowerCase();
if (scheme == null || !scheme.equals("http") && !scheme.equals("https")) {
throw new IllegalArgumentException("The URI scheme, of the URI " + u
+ ", must be equal (ignoring case) to 'http'");
}
String path = uri.getPath();
if (path == null) {
throw new IllegalArgumentException("The URI path, of the URI " + uri
+ ", must be non-null");
} else if (path.length() > 0 && path.charAt(0) != '/') {
throw new IllegalArgumentException("The URI path, of the URI " + uri
+ ". must start with a '/'");
}
int port = uri.getPort();
if (port == -1)
port = scheme.equals("http") ? 80 : 443;
return new Url(uri.getScheme(), uri.getHost(), port, uri.getPath(), uri.getQuery());
}
@SuppressWarnings("deprecation")
private static HttpRequest construct(AsyncHttpClientConfig config,
Request request,
HttpMethod m,
Url url) throws IOException {
String host = url.getHost();
if (request.getVirtualHost() != null) {
host = request.getVirtualHost();
}
HttpRequest nettyRequest;
String queryString = url.getQueryString();
// does this request have a query string
if (queryString != null) {
nettyRequest = new DefaultHttpRequest(
HttpVersion.HTTP_1_1, m, url.getUri());
} else {
nettyRequest = new DefaultHttpRequest(
HttpVersion.HTTP_1_1, m, url.getPath());
}
nettyRequest.setHeader(HttpHeaders.Names.HOST, host + ":" + url.getPort());
Headers h = request.getHeaders();
if (h != null) {
Iterator<Pair<String, String>> i = h.iterator();
Pair<String, String> p;
while (i.hasNext()) {
p = i.next();
if ("host".equalsIgnoreCase(p.getFirst())) {
continue;
}
String key = p.getFirst() == null ? "" : p.getFirst();
String value = p.getSecond() == null ? "" : p.getSecond();
nettyRequest.setHeader(key, value);
}
}
String ka = config.getKeepAlive() ? "keep-alive" : "close";
nettyRequest.setHeader(HttpHeaders.Names.CONNECTION, ka);
if (config.getProxyServer() != null) {
nettyRequest.setHeader("Proxy-Connection", ka);
}
if (config.getUserAgent() != null) {
nettyRequest.setHeader("User-Agent", config.getUserAgent());
}
if (request.getCookies() != null && !request.getCookies().isEmpty()) {
CookieEncoder httpCookieEncoder = new CookieEncoder(false);
Iterator<Cookie> ic = request.getCookies().iterator();
Cookie c;
org.jboss.netty.handler.codec.http.Cookie cookie;
while (ic.hasNext()) {
c = ic.next();
cookie = new DefaultCookie(c.getName(), c.getValue());
cookie.setPath(c.getPath());
cookie.setMaxAge(c.getMaxAge());
cookie.setDomain(c.getDomain());
httpCookieEncoder.addCookie(cookie);
}
nettyRequest.setHeader(HttpHeaders.Names.COOKIE, httpCookieEncoder.encode());
}
if (config.isCompressionEnabled()) {
nettyRequest.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
}
switch (request.getType()) {
case POST:
case PUT:
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, "0");
if (request.getByteData() != null) {
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(request.getByteData().length));
nettyRequest.setContent(ChannelBuffers.copiedBuffer(request.getByteData()));
} else if (request.getStringData() != null) {
// TODO: Not sure we need to reconfigure that one.
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(request.getStringData().length()));
nettyRequest.setContent(ChannelBuffers.copiedBuffer(request.getStringData(), "UTF-8"));
} else if (request.getStreamData() != null) {
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(request.getStreamData().available()));
byte[] b = new byte[(int) request.getStreamData().available()];
request.getStreamData().read(b);
nettyRequest.setContent(ChannelBuffers.copiedBuffer(b));
} else if (request.getParams() != null) {
StringBuilder sb = new StringBuilder();
for (final Entry<String, String> param : request.getParams().entries()) {
sb.append(param.getKey());
sb.append("=");
sb.append(param.getValue());
sb.append("&");
}
sb.deleteCharAt(sb.length() - 1);
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(sb.length()));
nettyRequest.setContent(ChannelBuffers.copiedBuffer(sb.toString().getBytes()));
if (request.getHeaders().getHeaderValues(Headers.CONTENT_TYPE).isEmpty()
&& request.getHeaders().getHeaderValue(Headers.CONTENT_TYPE) == null) {
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/x-www-form-urlencoded");
}
} else if (request.getParts() != null) {
int lenght = computeAndSetContentLength(request, nettyRequest);
if (lenght == -1) {
lenght = MAX_BUFFERRED_BYTES;
}
MultipartRequestEntity mre = createMultipartRequestEntity(request.getParts(), request.getParams());
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_TYPE, mre.getContentType());
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(mre.getContentLength()));
ChannelBuffer b = ChannelBuffers.dynamicBuffer((int) lenght);
mre.writeRequest(new ChannelBufferOutputStream(b));
nettyRequest.setContent(b);
} else if (request.getEntityWriter() != null) {
int lenght = computeAndSetContentLength(request, nettyRequest);
if (lenght == -1) {
lenght = MAX_BUFFERRED_BYTES;
}
ChannelBuffer b = ChannelBuffers.dynamicBuffer((int) lenght);
request.getEntityWriter().writeEntity(new ChannelBufferOutputStream(b));
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_LENGTH, b.writerIndex());
nettyRequest.setContent(b);
}
break;
}
if (nettyRequest.getHeader(HttpHeaders.Names.CONTENT_TYPE) == null) {
nettyRequest.setHeader(HttpHeaders.Names.CONTENT_TYPE, "txt/html; charset=utf-8");
}
if (log.isDebugEnabled())
log.debug("Constructed request: " + nettyRequest);
return nettyRequest;
}
public void close() {
isClose.set(true);
connectionsPool.clear();
openChannels.close();
timer.stop();
config.reaper().shutdown();
config.executorService().shutdown();
socketChannelFactory.releaseExternalResources();
bootstrap.releaseExternalResources();
}
/* @Override */
public Response prepareResponse(final HttpResponseStatus<HttpResponse> status,
final HttpResponseHeaders<HttpResponse> headers,
final Collection<HttpResponseBodyPart<HttpResponse>> bodyParts) {
return new NettyAsyncResponse(status, headers, bodyParts);
}
/* @Override */
public <T> Future<T> execute(final Request request, final AsyncHandler<T> asyncHandler) throws IOException {
return doConnect(request, asyncHandler, null);
}
private <T> void execute(final Request request, final NettyResponseFuture<T> f) throws IOException {
doConnect(request, f.getAsyncHandler(), f);
return;
}
private <T> Future<T> doConnect(final Request request, final AsyncHandler asyncHandler, NettyResponseFuture<T> f) throws IOException {
if (isClose.get()) {
throw new IOException("Closed");
}
if (connectionsPool.size() >= config.getMaxTotalConnections()) {
throw new IOException("Too many connections");
}
Url url = createUrl(request.getUrl());
if (log.isDebugEnabled())
log.debug("Lookup cache: " + url.toString());
Channel channel = lookupInCache(url);
if (channel != null && channel.isOpen()) {
HttpRequest nettyRequest = buildRequest(config, request, url);
if (f == null) {
f = new NettyResponseFuture<T>(url, request, asyncHandler,
nettyRequest, config.getRequestTimeoutInMs());
}
executeRequest(channel, asyncHandler, config, f, nettyRequest);
return f;
}
ConnectListener<T> c = new ConnectListener.Builder<T>(config, request, asyncHandler, f).build();
configure(url.getProtocol().compareTo(Protocol.HTTPS) == 0, c);
ChannelFuture channelFuture = null;
try {
if (config.getProxyServer() == null) {
channelFuture = bootstrap.connect(new InetSocketAddress(url.getHost(), url.getPort()));
} else {
channelFuture = bootstrap.connect(
new InetSocketAddress(config.getProxyServer().getHost(), config.getProxyServer().getPort()));
}
bootstrap.setOption("connectTimeout", (int) config.getConnectionTimeoutInMs());
} catch (Throwable t) {
log.error(t.getMessage(), t);
c.future().abort(t.getCause());
return c.future();
}
channelFuture.addListener(c);
openChannels.add(channelFuture.getChannel());
return c.future();
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
/**
* Discard in memory bytes if the HttpContent.interrupt() has been invoked.
*/
if (ctx.getAttachment() instanceof DiscardEvent) {
ctx.getChannel().setReadable(false);
return;
}
NettyResponseFuture<?> future = (NettyResponseFuture<?>) ctx.getAttachment();
HttpRequest nettyRequest = future.getNettyRequest();
AsyncHandler<?> handler = future.getAsyncHandler();
try {
if (e.getMessage() instanceof HttpResponse) {
HttpResponse response = (HttpResponse) e.getMessage();
// Required if there is some trailing headers.
future.setHttpResponse(response);
String ka = response.getHeader("Connection");
future.setKeepAlive(ka == null || ka.toLowerCase().equals("keep-alive"));
if (config.isRedirectEnabled()
&& (response.getStatus().getCode() == 302 || response.getStatus().getCode() == 301)) {
if (future.incrementAndGetCurrentRedirectCount() < config.getMaxRedirects()) {
String location = response.getHeader(HttpHeaders.Names.LOCATION);
if (location.startsWith("/")) {
location = future.getUrl().getBaseUrl() + location;
}
Url url = createUrl(location);
RequestBuilder builder = new RequestBuilder(future.getRequest());
future.setUrl(url);
ctx.setAttachment(new DiscardEvent());
try {
ctx.getChannel().setReadable(false);
} catch (Exception ex) {
if (log.isTraceEnabled()) {
log.trace(ex.getMessage(), ex);
}
}
execute(builder.setUrl(url.toString()).build(), future);
return;
} else {
throw new MaxRedirectException("Maximum redirect reached: " + config.getMaxRedirects());
}
}
if (log.isDebugEnabled()) {
log.debug("Status: " + response.getStatus());
log.debug("Version: " + response.getProtocolVersion());
log.debug("\"");
if (!response.getHeaderNames().isEmpty()) {
for (String name : response.getHeaderNames()) {
log.debug("Header: " + name + " = " + response.getHeaders(name));
}
log.debug("\"");
}
}
if (updateStatusAndInterrupt(handler, new ResponseStatus(future.getUrl(), response, this))) {
finishUpdate(future, ctx);
return;
} else if (updateHeadersAndInterrupt(handler, new ResponseHeaders(future.getUrl(), response, this))) {
finishUpdate(future, ctx);
return;
} else if (!response.isChunked()) {
updateBodyAndInterrupt(handler, new ResponseBodyPart(future.getUrl(), response, this));
finishUpdate(future, ctx);
return;
}
if (response.getStatus().getCode() != 200 || nettyRequest.getMethod().equals(HttpMethod.HEAD)) {
markAsDoneAndCacheConnection(future, ctx.getChannel());
}
} else if (e.getMessage() instanceof HttpChunk) {
HttpChunk chunk = (HttpChunk) e.getMessage();
if (handler != null) {
if (updateBodyAndInterrupt(handler, new ResponseBodyPart(future.getUrl(), null, this, chunk)) || chunk.isLast()) {
if (chunk instanceof HttpChunkTrailer) {
updateHeadersAndInterrupt(handler, new ResponseHeaders(future.getUrl(),
future.getHttpResponse(), this, (HttpChunkTrailer) chunk));
}
finishUpdate(future, ctx);
}
}
}
} catch (Exception t) {
future.abort(t);
finishUpdate(future, ctx);
throw t;
}
}
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
if (log.isDebugEnabled())
log.debug("Channel closed: " + e.getState().toString());
if (!isClose.get() && ctx.getAttachment() instanceof NettyResponseFuture<?>) {
NettyResponseFuture<?> future = (NettyResponseFuture<?>) ctx.getAttachment();
if (future != null && !future.isDone() && !future.isCancelled()) {
future.getAsyncHandler().onThrowable(new IOException("No response received. Connection timed out"));
}
}
removeFromCache(ctx, e);
ctx.sendUpstream(e);
}
private void removeFromCache(ChannelHandlerContext ctx, ChannelEvent e) throws MalformedURLException {
if (ctx.getAttachment() instanceof NettyResponseFuture<?>) {
NettyResponseFuture<?> future = (NettyResponseFuture<?>) ctx.getAttachment();
connectionsPool.remove(future.getUrl());
}
}
private void markAsDoneAndCacheConnection(final NettyResponseFuture<?> future, final Channel channel) throws MalformedURLException {
if (future.getKeepAlive() && maxConnectionsPerHost++ < config.getMaxConnectionPerHost()) {
connectionsPool.put(future.getUrl().getBaseUrl(), channel);
} else {
connectionsPool.remove(future.getUrl());
}
future.done();
}
private void finishUpdate(NettyResponseFuture<?> future, ChannelHandlerContext ctx) throws IOException {
ctx.setAttachment(new DiscardEvent());
markAsDoneAndCacheConnection(future, ctx.getChannel());
// Catch any unexpected exception when marking the channel.
try {
ctx.getChannel().setReadable(false);
} catch (Exception ex) {
if (log.isTraceEnabled()) {
log.trace(ex.getMessage(), ex);
}
}
}
@SuppressWarnings("unchecked")
private final boolean updateStatusAndInterrupt(AsyncHandler handler, HttpResponseStatus c) throws Exception {
return (handler.onStatusReceived(c) == STATE.CONTINUE ? false : true);
}
@SuppressWarnings("unchecked")
private final boolean updateHeadersAndInterrupt(AsyncHandler handler, HttpResponseHeaders c) throws Exception {
return (handler.onHeadersReceived(c) == STATE.CONTINUE ? false : true);
}
@SuppressWarnings("unchecked")
private final boolean updateBodyAndInterrupt(AsyncHandler handler, HttpResponseBodyPart c) throws Exception {
return (handler.onBodyPartReceived(c) == STATE.CONTINUE ? false : true);
}
//Simple marker for stopping publishing bytes.
private final static class DiscardEvent {
}
//Simple marker for closed events
private final static class ClosedEvent {
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
Channel ch = e.getChannel();
Throwable cause = e.getCause();
if (log.isDebugEnabled())
log.debug("I/O Exception during read or doConnect: ", e.getCause());
if (ctx.getAttachment() instanceof NettyResponseFuture<?>) {
NettyResponseFuture<?> future = (NettyResponseFuture<?>) ctx.getAttachment();
if (future != null) {
future.getAsyncHandler().onThrowable(cause);
}
}
if (log.isDebugEnabled()) {
log.debug(e.toString());
log.debug(ch.toString());
}
}
private final static int computeAndSetContentLength(Request request, HttpRequest r) {
int lenght = (int) request.getLength();
if (lenght == -1 && r.getHeader(HttpHeaders.Names.CONTENT_LENGTH) != null) {
lenght = Integer.valueOf(r.getHeader(HttpHeaders.Names.CONTENT_LENGTH));
}
if (lenght != -1) {
r.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(lenght));
}
return lenght;
}
/**
* Map CommonsHttp Method to Netty Method.
*
* @param type
* @return
*/
private final static HttpMethod map(RequestType type) {
switch (type) {
case GET:
return HttpMethod.GET;
case POST:
return HttpMethod.POST;
case DELETE:
return HttpMethod.DELETE;
case PUT:
return HttpMethod.PUT;
case HEAD:
return HttpMethod.HEAD;
default:
throw new IllegalStateException();
}
}
/**
* This is quite ugly as our internal names are duplicated, but we build on top of HTTP Client implementation.
*
* @param params
* @param methodParams
* @return
* @throws java.io.FileNotFoundException
*/
private final static MultipartRequestEntity createMultipartRequestEntity(List<Part> params, Multimap<String, String> methodParams) throws FileNotFoundException {
org.elasticsearch.util.http.multipart.Part[] parts = new org.elasticsearch.util.http.multipart.Part[params.size()];
int i = 0;
for (Part part : params) {
if (part instanceof StringPart) {
parts[i] = new org.elasticsearch.util.http.multipart.StringPart(part.getName(),
((StringPart) part).getValue(),
"UTF-8");
} else if (part instanceof FilePart) {
parts[i] = new org.elasticsearch.util.http.multipart.FilePart(part.getName(),
((FilePart) part).getFile(),
((FilePart) part).getMimeType(),
((FilePart) part).getCharSet());
} else if (part instanceof ByteArrayPart) {
PartSource source = new ByteArrayPartSource(((ByteArrayPart) part).getFileName(), ((ByteArrayPart) part).getData());
parts[i] = new org.elasticsearch.util.http.multipart.FilePart(part.getName(),
source,
((ByteArrayPart) part).getMimeType(),
((ByteArrayPart) part).getCharSet());
} else if (part == null) {
throw new NullPointerException("Part cannot be null");
} else {
throw new IllegalArgumentException(String.format("Unsupported part type for multipart parameter %s",
part.getName()));
}
++i;
}
return new MultipartRequestEntity(parts, methodParams);
}
}

View File

@ -0,0 +1,193 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client.providers;
import org.elasticsearch.util.http.client.*;
import org.elasticsearch.util.http.collection.Pair;
import org.elasticsearch.util.http.url.Url;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferInputStream;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.handler.codec.http.HttpResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.*;
/**
* Wrapper around the {@link org.elasticsearch.util.http.client.Response} API.
*/
public class NettyAsyncResponse implements Response {
private final Url url;
private final Collection<HttpResponseBodyPart<HttpResponse>> bodyParts;
private final HttpResponseHeaders<HttpResponse> headers;
private final HttpResponseStatus<HttpResponse> status;
private final List<Cookie> cookies = new ArrayList<Cookie>();
public NettyAsyncResponse(HttpResponseStatus<HttpResponse> status,
HttpResponseHeaders<HttpResponse> headers,
Collection<HttpResponseBodyPart<HttpResponse>> bodyParts) {
this.status = status;
this.headers = headers;
this.bodyParts = bodyParts;
url = status.getUrl();
}
/* @Override */
public int getStatusCode() {
return status.getStatusCode();
}
/* @Override */
public String getStatusText() {
return status.getStatusText();
}
/* @Override */
public String getResponseBody() throws IOException {
String contentType = getContentType();
String charset = "UTF-8";
if (contentType != null) {
for (String part : contentType.split(";")) {
if (part.startsWith("charset=")) {
charset = part.substring("charset=".length());
}
}
}
return contentToString(charset);
}
String contentToString(String charset) throws UnsupportedEncodingException {
StringBuilder b = new StringBuilder();
for (HttpResponseBodyPart<?> bp : bodyParts) {
b.append(new String(bp.getBodyPartBytes(), charset));
}
return b.toString();
}
/* @Override */
public InputStream getResponseBodyAsStream() throws IOException {
ChannelBuffer buf = ChannelBuffers.dynamicBuffer();
for (HttpResponseBodyPart<?> bp : bodyParts) {
// Ugly. TODO
// (1) We must remove the downcast,
// (2) we need a CompositeByteArrayInputStream to avoid
// copying the bytes.
if (bp.getClass().isAssignableFrom(ResponseBodyPart.class)) {
buf.writeBytes(bp.getBodyPartBytes());
}
}
return new ChannelBufferInputStream(buf);
}
/* @Override */
public String getResponseBodyExcerpt(int maxLength) throws IOException {
String contentType = getContentType();
String charset = "UTF-8";
if (contentType != null) {
for (String part : contentType.split(";")) {
if (part.startsWith("charset=")) {
charset = part.substring("charset=".length());
}
}
}
String response = contentToString(charset);
return response.length() <= maxLength ? response : response.substring(0, maxLength);
}
/* @Override */
public Url getUrl() throws MalformedURLException {
return url;
}
/* @Override */
public String getContentType() {
return headers.getHeaders().getHeaderValue("Content-Type");
}
/* @Override */
public String getHeader(String name) {
return headers.getHeaders().getHeaderValue(name);
}
/* @Override */
public List<String> getHeaders(String name) {
return headers.getHeaders().getHeaderValues(name);
}
/* @Override */
public Headers getHeaders() {
return headers.getHeaders();
}
/* @Override */
public boolean isRedirected() {
return (status.getStatusCode() >= 300) && (status.getStatusCode() <= 399);
}
/* @Override */
public List<Cookie> getCookies() {
if (cookies.isEmpty()) {
Iterator<Pair<String, String>> i = headers.getHeaders().iterator();
Pair<String, String> p;
while (i.hasNext()) {
p = i.next();
if (p.getFirst().equalsIgnoreCase("Set-Cookie")) {
String[] fields = p.getSecond().split(";\\s*");
String[] cookieValue = fields[0].split("=");
String name = cookieValue[0];
String value = cookieValue[1];
String expires = "-1";
String path = null;
String domain = null;
boolean secure = false; // Parse each field
for (int j = 1; j < fields.length; j++) {
if ("secure".equalsIgnoreCase(fields[j])) {
secure = true;
} else if (fields[j].indexOf('=') > 0) {
String[] f = fields[j].split("=");
if ("expires".equalsIgnoreCase(f[0])) {
expires = f[1];
} else if ("domain".equalsIgnoreCase(f[0])) {
domain = f[1];
} else if ("path".equalsIgnoreCase(f[0])) {
path = f[1];
}
}
}
cookies.add(new Cookie(domain, name, value, path, Integer.valueOf(expires), secure));
}
}
}
return Collections.unmodifiableList(cookies);
}
}

View File

@ -0,0 +1,207 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client.providers;
import org.elasticsearch.util.http.client.AsyncHandler;
import org.elasticsearch.util.http.client.FutureImpl;
import org.elasticsearch.util.http.client.Request;
import org.elasticsearch.util.http.url.Url;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import java.net.MalformedURLException;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* A {@link Future} that can be used to track when an asynchronous HTTP request has been fully processed.
*
* @param <V>
*/
public final class NettyResponseFuture<V> implements FutureImpl<V> {
private final CountDownLatch latch = new CountDownLatch(1);
private final AtomicBoolean isDone = new AtomicBoolean(false);
private final AtomicBoolean isCancelled = new AtomicBoolean(false);
private final AsyncHandler<V> asyncHandler;
private final int responseTimeoutInMs;
private final Request request;
private final HttpRequest nettyRequest;
private final AtomicReference<V> content = new AtomicReference<V>();
private Url url;
private boolean keepAlive = true;
private HttpResponse httpResponse;
private final AtomicReference<ExecutionException> exEx = new AtomicReference<ExecutionException>();
private final AtomicInteger redirectCount = new AtomicInteger();
private Future<Object> reaperFuture;
public NettyResponseFuture(Url url,
Request request,
AsyncHandler<V> asyncHandler,
HttpRequest nettyRequest,
int responseTimeoutInMs) {
this.asyncHandler = asyncHandler;
this.responseTimeoutInMs = responseTimeoutInMs;
this.request = request;
this.nettyRequest = nettyRequest;
this.url = url;
}
public Url getUrl() throws MalformedURLException {
return url;
}
public void setUrl(Url url) {
this.url = url;
}
/**
* {@inheritDoc}
*/
/* @Override */
public boolean isDone() {
return isDone.get();
}
/**
* {@inheritDoc}
*/
/* @Override */
public boolean isCancelled() {
return isCancelled.get();
}
/**
* {@inheritDoc}
*/
/* @Override */
public boolean cancel(boolean force) {
latch.countDown();
isCancelled.set(true);
return true;
}
/**
* {@inheritDoc}
*/
/* @Override */
public V get() throws InterruptedException, ExecutionException {
try {
return get(responseTimeoutInMs, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
}
/**
* {@inheritDoc}
*/
/* @Override */
public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, ExecutionException {
if (!isDone() && !isCancelled()) {
if (!latch.await(l, tu)) {
isCancelled.set(true);
TimeoutException te = new TimeoutException("No response received");
onThrowable(te);
throw te;
}
isDone.set(true);
if (exEx.get() != null) {
throw exEx.getAndSet(null);
}
}
return (V) getContent();
}
private void onThrowable(Throwable t) {
asyncHandler.onThrowable(t);
}
V getContent() {
if (content.get() == null) {
try {
content.set(asyncHandler.onCompleted());
} catch (Throwable ex) {
onThrowable(ex);
throw new RuntimeException(ex);
}
}
return content.get();
}
public final void done() {
if (exEx.get() != null) {
return;
}
if (reaperFuture != null) reaperFuture.cancel(true);
isDone.set(true);
getContent();
latch.countDown();
}
public final void abort(final Throwable t) {
if (isDone.get() || isCancelled.get()) return;
if (reaperFuture != null) reaperFuture.cancel(true);
if (exEx.get() == null) {
exEx.set(new ExecutionException(t));
}
asyncHandler.onThrowable(t);
isDone.set(true);
latch.countDown();
}
public final Request getRequest() {
return request;
}
public final HttpRequest getNettyRequest() {
return nettyRequest;
}
public final AsyncHandler<V> getAsyncHandler() {
return asyncHandler;
}
public final boolean getKeepAlive() {
return keepAlive;
}
public final void setKeepAlive(final boolean keepAlive) {
this.keepAlive = keepAlive;
}
public final HttpResponse getHttpResponse() {
return httpResponse;
}
public final void setHttpResponse(final HttpResponse httpResponse) {
this.httpResponse = httpResponse;
}
public int incrementAndGetCurrentRedirectCount() {
return redirectCount.incrementAndGet();
}
public void setReaperFuture(Future<Object> reaperFuture) {
this.reaperFuture = reaperFuture;
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client.providers;
import org.elasticsearch.util.http.client.AsyncHttpProvider;
import org.elasticsearch.util.http.client.HttpResponseBodyPart;
import org.elasticsearch.util.http.url.Url;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpResponse;
/**
* A callback class used when an HTTP response body is received.
*/
public class ResponseBodyPart extends HttpResponseBodyPart<HttpResponse> {
private final HttpChunk chunk;
public ResponseBodyPart(Url url, HttpResponse response, AsyncHttpProvider<HttpResponse> provider) {
super(url, response, provider);
this.chunk = null;
}
public ResponseBodyPart(Url url, HttpResponse response, AsyncHttpProvider<HttpResponse> provider, HttpChunk chunk) {
super(url, response, provider);
this.chunk = chunk;
}
/**
* Return the response body's part bytes received.
*
* @return the response body's part bytes received.
*/
public byte[] getBodyPartBytes() {
if (chunk != null) {
return chunk.getContent().array();
} else {
return response.getContent().array();
}
}
protected HttpChunk chunk() {
return chunk;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.client.providers;
import org.elasticsearch.util.http.client.AsyncHttpProvider;
import org.elasticsearch.util.http.client.Headers;
import org.elasticsearch.util.http.client.HttpResponseHeaders;
import org.elasticsearch.util.http.url.Url;
import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
import org.jboss.netty.handler.codec.http.HttpResponse;
/**
* A class that represent the HTTP headers.
*/
public class ResponseHeaders extends HttpResponseHeaders<HttpResponse> {
private final HttpChunkTrailer trailingHeaders;
public ResponseHeaders(Url url, HttpResponse response, AsyncHttpProvider<HttpResponse> provider) {
super(url, response, provider, false);
this.trailingHeaders = null;
}
public ResponseHeaders(Url url, HttpResponse response, AsyncHttpProvider<HttpResponse> provider, HttpChunkTrailer traillingHeaders) {
super(url, response, provider, true);
this.trailingHeaders = traillingHeaders;
}
/**
* Return the HTTP header
*
* @return an {@link org.elasticsearch.util.http.client.Headers}
*/
public Headers getHeaders() {
Headers h = new Headers();
for (String s : response.getHeaderNames()) {
for (String header : response.getHeaders(s)) {
h.add(s, header);
}
}
if (trailingHeaders != null && trailingHeaders.getHeaderNames().size() > 0) {
for (final String s : trailingHeaders.getHeaderNames()) {
for (String header : response.getHeaders(s)) {
h.add(s, header);
}
}
}
return Headers.unmodifiableHeaders(h);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.client.providers;
import org.elasticsearch.util.http.client.AsyncHttpProvider;
import org.elasticsearch.util.http.client.HttpResponseStatus;
import org.elasticsearch.util.http.url.Url;
import org.jboss.netty.handler.codec.http.HttpResponse;
/**
* A class that represent the HTTP response' status line (code + text)
*/
public class ResponseStatus extends HttpResponseStatus<HttpResponse> {
public ResponseStatus(Url url, HttpResponse response, AsyncHttpProvider<HttpResponse> provider) {
super(url, response, provider);
}
/**
* Return the response status code
*
* @return the response status code
*/
public int getStatusCode() {
return response.getStatus().getCode();
}
/**
* Return the response status text
*
* @return the response status text
*/
public String getStatusText() {
return response.getStatus().getReasonPhrase();
}
@Override
public String getProtocolName() {
return response.getProtocolVersion().getProtocolName();
}
@Override
public int getProtocolMajorVersion() {
return response.getProtocolVersion().getMajorVersion();
}
@Override
public int getProtocolMinorVersion() {
return response.getProtocolVersion().getMinorVersion();
}
@Override
public String getProtocolText() {
return response.getProtocolVersion().getText();
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.collection;
import java.io.Serializable;
public class Pair<FirstType, SecondType> implements Serializable {
private static final long serialVersionUID = -4403264592023348398L;
private final FirstType firstValue;
private final SecondType secondValue;
public Pair(FirstType v1, SecondType v2) {
firstValue = v1;
secondValue = v2;
}
public FirstType getFirst() {
return firstValue;
}
public SecondType getSecond() {
return secondValue;
}
public String toString() {
return "Pair(" + firstValue + ", " + secondValue + ")";
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Pair<?, ?> pair = (Pair<?, ?>) o;
return (firstValue != null ? firstValue.equals(pair.firstValue) : pair.firstValue == null)
&& (secondValue != null ? secondValue.equals(pair.secondValue) : pair.secondValue == null);
}
public int hashCode() {
int result;
result = (firstValue != null ? firstValue.hashCode() : 0);
result = 29 * result + (secondValue != null ? secondValue.hashCode() : 0);
return result;
}
public static <K, V> Pair<K, V> of(K k, V v) {
return new Pair<K, V>(k, v);
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public class ByteArrayPartSource implements PartSource {
/**
* Name of the source file.
*/
private String fileName;
/**
* Byte array of the source file.
*/
private byte[] bytes;
/**
* Constructor for ByteArrayPartSource.
*
* @param fileName the name of the file these bytes represent
* @param bytes the content of this part
*/
public ByteArrayPartSource(String fileName, byte[] bytes) {
this.fileName = fileName;
this.bytes = bytes;
}
/**
* @see PartSource#getLength()
*/
public long getLength() {
return bytes.length;
}
/**
* @see PartSource#getFileName()
*/
public String getFileName() {
return fileName;
}
/**
* @see PartSource#createInputStream()
*/
public InputStream createInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
}

View File

@ -0,0 +1,233 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import org.elasticsearch.util.logging.ESLogger;
import org.elasticsearch.util.logging.Loggers;
import java.io.*;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public class FilePart extends PartBase {
/**
* Default content encoding of file attachments.
*/
public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
/**
* Default charset of file attachments.
*/
public static final String DEFAULT_CHARSET = "ISO-8859-1";
/**
* Default transfer encoding of file attachments.
*/
public static final String DEFAULT_TRANSFER_ENCODING = "binary";
/**
* Log object for this class.
*/
private final static ESLogger LOG = Loggers.getLogger(FilePart.class);
/**
* Attachment's file name
*/
protected static final String FILE_NAME = "; filename=";
/**
* Attachment's file name as a byte array
*/
private static final byte[] FILE_NAME_BYTES =
MultipartEncodingUtil.getAsciiBytes(FILE_NAME);
/**
* Source of the file part.
*/
private PartSource source;
/**
* FilePart Constructor.
*
* @param name the name for this part
* @param partSource the source for this part
* @param contentType the content type for this part, if <code>null</code> the
* {@link #DEFAULT_CONTENT_TYPE default} is used
* @param charset the charset encoding for this part, if <code>null</code> the
* {@link #DEFAULT_CHARSET default} is used
*/
public FilePart(String name, PartSource partSource, String contentType, String charset) {
super(
name,
contentType == null ? DEFAULT_CONTENT_TYPE : contentType,
charset == null ? "ISO-8859-1" : charset,
DEFAULT_TRANSFER_ENCODING
);
if (partSource == null) {
throw new IllegalArgumentException("Source may not be null");
}
this.source = partSource;
}
/**
* FilePart Constructor.
*
* @param name the name for this part
* @param partSource the source for this part
*/
public FilePart(String name, PartSource partSource) {
this(name, partSource, null, null);
}
/**
* FilePart Constructor.
*
* @param name the name of the file part
* @param file the file to post
* @throws java.io.FileNotFoundException if the <i>file</i> is not a normal
* file or if it is not readable.
*/
public FilePart(String name, File file)
throws FileNotFoundException {
this(name, new FilePartSource(file), null, null);
}
/**
* FilePart Constructor.
*
* @param name the name of the file part
* @param file the file to post
* @param contentType the content type for this part, if <code>null</code> the
* {@link #DEFAULT_CONTENT_TYPE default} is used
* @param charset the charset encoding for this part, if <code>null</code> the
* {@link #DEFAULT_CHARSET default} is used
* @throws FileNotFoundException if the <i>file</i> is not a normal
* file or if it is not readable.
*/
public FilePart(String name, File file, String contentType, String charset)
throws FileNotFoundException {
this(name, new FilePartSource(file), contentType, charset);
}
/**
* FilePart Constructor.
*
* @param name the name of the file part
* @param fileName the file name
* @param file the file to post
* @throws FileNotFoundException if the <i>file</i> is not a normal
* file or if it is not readable.
*/
public FilePart(String name, String fileName, File file)
throws FileNotFoundException {
this(name, new FilePartSource(fileName, file), null, null);
}
/**
* FilePart Constructor.
*
* @param name the name of the file part
* @param fileName the file name
* @param file the file to post
* @param contentType the content type for this part, if <code>null</code> the
* {@link #DEFAULT_CONTENT_TYPE default} is used
* @param charset the charset encoding for this part, if <code>null</code> the
* {@link #DEFAULT_CHARSET default} is used
* @throws FileNotFoundException if the <i>file</i> is not a normal
* file or if it is not readable.
*/
public FilePart(String name, String fileName, File file, String contentType, String charset)
throws FileNotFoundException {
this(name, new FilePartSource(fileName, file), contentType, charset);
}
/**
* Write the disposition header to the output stream
*
* @param out The output stream
* @throws java.io.IOException If an IO problem occurs
*/
protected void sendDispositionHeader(OutputStream out)
throws IOException {
LOG.trace("enter sendDispositionHeader(OutputStream out)");
super.sendDispositionHeader(out);
String filename = this.source.getFileName();
if (filename != null) {
out.write(FILE_NAME_BYTES);
out.write(QUOTE_BYTES);
out.write(MultipartEncodingUtil.getAsciiBytes(filename));
out.write(QUOTE_BYTES);
}
}
/**
* Write the data in "source" to the specified stream.
*
* @param out The output stream.
* @throws IOException if an IO problem occurs.
*/
protected void sendData(OutputStream out) throws IOException {
LOG.trace("enter sendData(OutputStream out)");
if (lengthOfData() == 0) {
// this file contains no data, so there is nothing to send.
// we don't want to create a zero length buffer as this will
// cause an infinite loop when reading.
LOG.debug("No data to send.");
return;
}
byte[] tmp = new byte[4096];
InputStream instream = source.createInputStream();
try {
int len;
while ((len = instream.read(tmp)) >= 0) {
out.write(tmp, 0, len);
}
} finally {
// we're done with the stream, close it
instream.close();
}
}
/**
* Returns the source of the file part.
*
* @return The source.
*/
protected PartSource getSource() {
LOG.trace("enter getSource()");
return this.source;
}
/**
* Return the length of the data.
*
* @return The length.
* @throws IOException if an IO problem occurs
*/
protected long lengthOfData() throws IOException {
LOG.trace("enter lengthOfData()");
return source.getLength();
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import java.io.*;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public class FilePartSource implements PartSource {
/**
* File part file.
*/
private File file = null;
/**
* File part file name.
*/
private String fileName = null;
/**
* Constructor for FilePartSource.
*
* @param file the FilePart source File.
* @throws java.io.FileNotFoundException if the file does not exist or
* cannot be read
*/
public FilePartSource(File file) throws FileNotFoundException {
this.file = file;
if (file != null) {
if (!file.isFile()) {
throw new FileNotFoundException("File is not a normal file.");
}
if (!file.canRead()) {
throw new FileNotFoundException("File is not readable.");
}
this.fileName = file.getName();
}
}
/**
* Constructor for FilePartSource.
*
* @param fileName the file name of the FilePart
* @param file the source File for the FilePart
* @throws FileNotFoundException if the file does not exist or
* cannot be read
*/
public FilePartSource(String fileName, File file)
throws FileNotFoundException {
this(file);
if (fileName != null) {
this.fileName = fileName;
}
}
/**
* Return the length of the file
*
* @return the length of the file.
* @see PartSource#getLength()
*/
public long getLength() {
if (this.file != null) {
return this.file.length();
} else {
return 0;
}
}
/**
* Return the current filename
*
* @return the filename.
* @see PartSource#getFileName()
*/
public String getFileName() {
return (fileName == null) ? "noname" : fileName;
}
/**
* Return a new {@link java.io.FileInputStream} for the current filename.
*
* @return the new input stream.
* @throws java.io.IOException If an IO problem occurs.
* @see PartSource#createInputStream()
*/
public InputStream createInputStream() throws IOException {
if (this.file != null) {
return new FileInputStream(this.file);
} else {
return new ByteArrayInputStream(new byte[]{});
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import org.elasticsearch.util.logging.ESLogger;
import org.elasticsearch.util.logging.Loggers;
import java.io.UnsupportedEncodingException;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public class MultipartEncodingUtil {
/**
* Log object for this class.
*/
private final static ESLogger LOG = Loggers.getLogger(MultipartEncodingUtil.class);
public static byte[] getAsciiBytes(String data) {
try {
return data.getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String getAsciiString(final byte[] data) {
if (data == null) {
throw new IllegalArgumentException("Parameter may not be null");
}
try {
return new String(data, "US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static byte[] getBytes(final String data, String charset) {
if (data == null) {
throw new IllegalArgumentException("data may not be null");
}
if (charset == null || charset.length() == 0) {
throw new IllegalArgumentException("charset may not be null or empty");
}
try {
return data.getBytes(charset);
} catch (UnsupportedEncodingException e) {
if (LOG.isDebugEnabled()) {
LOG.warn("Unsupported encoding: " + charset + ". System encoding used.");
}
return data.getBytes();
}
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import org.elasticsearch.util.collect.ArrayListMultimap;
import org.elasticsearch.util.collect.Multimap;
import org.elasticsearch.util.logging.ESLogger;
import org.elasticsearch.util.logging.Loggers;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public class MultipartRequestEntity implements RequestEntity {
private final static ESLogger log = Loggers.getLogger(MultipartRequestEntity.class);
/**
* The Content-Type for multipart/form-data.
*/
private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data";
/**
* The pool of ASCII chars to be used for generating a multipart boundary.
*/
private static byte[] MULTIPART_CHARS = MultipartEncodingUtil.getAsciiBytes(
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
/**
* Generates a random multipart boundary string.
*
* @return
*/
private static byte[] generateMultipartBoundary() {
Random rand = new Random();
byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to 40
for (int i = 0; i < bytes.length; i++) {
bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)];
}
return bytes;
}
/**
* The MIME parts as set by the constructor
*/
protected Part[] parts;
private byte[] multipartBoundary;
private Multimap<String, String> methodParams;
/**
* Creates a new multipart entity containing the given parts.
*
* @param parts The parts to include.
* @param methodParams The params of the HttpMethod using this entity.
*/
public MultipartRequestEntity(Part[] parts, Multimap<String, String> methodParams) {
if (parts == null) {
throw new IllegalArgumentException("parts cannot be null");
}
if (methodParams == null) {
methodParams = ArrayListMultimap.create();
}
this.parts = parts;
this.methodParams = methodParams;
}
/**
* Returns the MIME boundary string that is used to demarcate boundaries of
* this part. The first call to this method will implicitly create a new
* boundary string. To create a boundary string first the
* HttpMethodParams.MULTIPART_BOUNDARY parameter is considered. Otherwise
* a random one is generated.
*
* @return The boundary string of this entity in ASCII encoding.
*/
protected byte[] getMultipartBoundary() {
if (multipartBoundary == null) {
String temp = methodParams.get("").isEmpty() ? null : methodParams.get("").iterator().next();
if (temp != null) {
multipartBoundary = MultipartEncodingUtil.getAsciiBytes(temp);
} else {
multipartBoundary = generateMultipartBoundary();
}
}
return multipartBoundary;
}
/**
* Returns <code>true</code> if all parts are repeatable, <code>false</code> otherwise.
*/
public boolean isRepeatable() {
for (int i = 0; i < parts.length; i++) {
if (!parts[i].isRepeatable()) {
return false;
}
}
return true;
}
/* (non-Javadoc)
* @see org.apache.commons.httpclient.methods.RequestEntity#writeRequest(java.io.OutputStream)
*/
public void writeRequest(OutputStream out) throws IOException {
Part.sendParts(out, parts, getMultipartBoundary());
}
/* (non-Javadoc)
* @see org.apache.commons.httpclient.methods.RequestEntity#getContentLength()
*/
public long getContentLength() {
try {
return Part.getLengthOfParts(parts, getMultipartBoundary());
} catch (Exception e) {
log.error("An exception occurred while getting the length of the parts", e);
return 0;
}
}
/* (non-Javadoc)
* @see org.apache.commons.httpclient.methods.RequestEntity#getContentType()
*/
public String getContentType() {
StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
buffer.append("; boundary=");
buffer.append(MultipartEncodingUtil.getAsciiString(getMultipartBoundary()));
return buffer.toString();
}
}

View File

@ -0,0 +1,453 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import org.elasticsearch.util.logging.ESLogger;
import org.elasticsearch.util.logging.Loggers;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public abstract class Part {
/**
* Log object for this class.
*/
private final static ESLogger LOG = Loggers.getLogger(Part.class);
/**
* The boundary
*/
protected static final String BOUNDARY = "----------------314159265358979323846";
/**
* The boundary as a byte array.
*
* @deprecated
*/
protected static final byte[] BOUNDARY_BYTES = MultipartEncodingUtil.getAsciiBytes(BOUNDARY);
/**
* The default boundary to be used if etBoundaryBytes(byte[]) has not
* been called.
*/
private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;
/**
* Carriage return/linefeed
*/
protected static final String CRLF = "\r\n";
/**
* Carriage return/linefeed as a byte array
*/
protected static final byte[] CRLF_BYTES = MultipartEncodingUtil.getAsciiBytes(CRLF);
/**
* Content dispostion characters
*/
protected static final String QUOTE = "\"";
/**
* Content dispostion as a byte array
*/
protected static final byte[] QUOTE_BYTES =
MultipartEncodingUtil.getAsciiBytes(QUOTE);
/**
* Extra characters
*/
protected static final String EXTRA = "--";
/**
* Extra characters as a byte array
*/
protected static final byte[] EXTRA_BYTES =
MultipartEncodingUtil.getAsciiBytes(EXTRA);
/**
* Content dispostion characters
*/
protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";
/**
* Content dispostion as a byte array
*/
protected static final byte[] CONTENT_DISPOSITION_BYTES =
MultipartEncodingUtil.getAsciiBytes(CONTENT_DISPOSITION);
/**
* Content type header
*/
protected static final String CONTENT_TYPE = "Content-Type: ";
/**
* Content type header as a byte array
*/
protected static final byte[] CONTENT_TYPE_BYTES =
MultipartEncodingUtil.getAsciiBytes(CONTENT_TYPE);
/**
* Content charset
*/
protected static final String CHARSET = "; charset=";
/**
* Content charset as a byte array
*/
protected static final byte[] CHARSET_BYTES =
MultipartEncodingUtil.getAsciiBytes(CHARSET);
/**
* Content type header
*/
protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";
/**
* Content type header as a byte array
*/
protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES =
MultipartEncodingUtil.getAsciiBytes(CONTENT_TRANSFER_ENCODING);
/**
* Return the boundary string.
*
* @return the boundary string
* @deprecated uses a constant string. Rather use {@link #getPartBoundary}
*/
public static String getBoundary() {
return BOUNDARY;
}
/**
* The ASCII bytes to use as the multipart boundary.
*/
private byte[] boundaryBytes;
/**
* Return the name of this part.
*
* @return The name.
*/
public abstract String getName();
/**
* Returns the content type of this part.
*
* @return the content type, or <code>null</code> to exclude the content type header
*/
public abstract String getContentType();
/**
* Return the character encoding of this part.
*
* @return the character encoding, or <code>null</code> to exclude the character
* encoding header
*/
public abstract String getCharSet();
/**
* Return the transfer encoding of this part.
*
* @return the transfer encoding, or <code>null</code> to exclude the transfer encoding header
*/
public abstract String getTransferEncoding();
/**
* Gets the part boundary to be used.
*
* @return the part boundary as an array of bytes.
* @since 3.0
*/
protected byte[] getPartBoundary() {
if (boundaryBytes == null) {
// custom boundary bytes have not been set, use the default.
return DEFAULT_BOUNDARY_BYTES;
} else {
return boundaryBytes;
}
}
/**
* Sets the part boundary. Only meant to be used by
* {@link Part#sendParts(java.io.OutputStream , Part[], byte[])}
* and {@link Part#getLengthOfParts(Part[], byte[])}
*
* @param boundaryBytes An array of ASCII bytes.
* @since 3.0
*/
void setPartBoundary(byte[] boundaryBytes) {
this.boundaryBytes = boundaryBytes;
}
/**
* Tests if this part can be sent more than once.
*
* @return <code>true</code> if {@link #sendData(java.io.OutputStream)} can be successfully called
* more than once.
* @since 3.0
*/
public boolean isRepeatable() {
return true;
}
/**
* Write the start to the specified output stream
*
* @param out The output stream
* @throws java.io.IOException If an IO problem occurs.
*/
protected void sendStart(OutputStream out) throws IOException {
LOG.trace("enter sendStart(OutputStream out)");
out.write(EXTRA_BYTES);
out.write(getPartBoundary());
out.write(CRLF_BYTES);
}
/**
* Write the content disposition header to the specified output stream
*
* @param out The output stream
* @throws IOException If an IO problem occurs.
*/
protected void sendDispositionHeader(OutputStream out) throws IOException {
LOG.trace("enter sendDispositionHeader(OutputStream out)");
out.write(CONTENT_DISPOSITION_BYTES);
out.write(QUOTE_BYTES);
out.write(MultipartEncodingUtil.getAsciiBytes(getName()));
out.write(QUOTE_BYTES);
}
/**
* Write the content type header to the specified output stream
*
* @param out The output stream
* @throws IOException If an IO problem occurs.
*/
protected void sendContentTypeHeader(OutputStream out) throws IOException {
LOG.trace("enter sendContentTypeHeader(OutputStream out)");
String contentType = getContentType();
if (contentType != null) {
out.write(CRLF_BYTES);
out.write(CONTENT_TYPE_BYTES);
out.write(MultipartEncodingUtil.getAsciiBytes(contentType));
String charSet = getCharSet();
if (charSet != null) {
out.write(CHARSET_BYTES);
out.write(MultipartEncodingUtil.getAsciiBytes(charSet));
}
}
}
/**
* Write the content transfer encoding header to the specified
* output stream
*
* @param out The output stream
* @throws IOException If an IO problem occurs.
*/
protected void sendTransferEncodingHeader(OutputStream out) throws IOException {
LOG.trace("enter sendTransferEncodingHeader(OutputStream out)");
String transferEncoding = getTransferEncoding();
if (transferEncoding != null) {
out.write(CRLF_BYTES);
out.write(CONTENT_TRANSFER_ENCODING_BYTES);
out.write(MultipartEncodingUtil.getAsciiBytes(transferEncoding));
}
}
/**
* Write the end of the header to the output stream
*
* @param out The output stream
* @throws IOException If an IO problem occurs.
*/
protected void sendEndOfHeader(OutputStream out) throws IOException {
LOG.trace("enter sendEndOfHeader(OutputStream out)");
out.write(CRLF_BYTES);
out.write(CRLF_BYTES);
}
/**
* Write the data to the specified output stream
*
* @param out The output stream
* @throws IOException If an IO problem occurs.
*/
protected abstract void sendData(OutputStream out) throws IOException;
/**
* Return the length of the main content
*
* @return long The length.
* @throws IOException If an IO problem occurs
*/
protected abstract long lengthOfData() throws IOException;
/**
* Write the end data to the output stream.
*
* @param out The output stream
* @throws IOException If an IO problem occurs.
*/
protected void sendEnd(OutputStream out) throws IOException {
LOG.trace("enter sendEnd(OutputStream out)");
out.write(CRLF_BYTES);
}
/**
* Write all the data to the output stream.
* If you override this method make sure to override
* #length() as well
*
* @param out The output stream
* @throws IOException If an IO problem occurs.
*/
public void send(OutputStream out) throws IOException {
LOG.trace("enter send(OutputStream out)");
sendStart(out);
sendDispositionHeader(out);
sendContentTypeHeader(out);
sendTransferEncodingHeader(out);
sendEndOfHeader(out);
sendData(out);
sendEnd(out);
}
/**
* Return the full length of all the data.
* If you override this method make sure to override
* #send(OutputStream) as well
*
* @return long The length.
* @throws IOException If an IO problem occurs
*/
public long length() throws IOException {
LOG.trace("enter length()");
if (lengthOfData() < 0) {
return -1;
}
ByteArrayOutputStream overhead = new ByteArrayOutputStream();
sendStart(overhead);
sendDispositionHeader(overhead);
sendContentTypeHeader(overhead);
sendTransferEncodingHeader(overhead);
sendEndOfHeader(overhead);
sendEnd(overhead);
return overhead.size() + lengthOfData();
}
/**
* Return a string representation of this object.
*
* @return A string representation of this object.
* @see java.lang.Object#toString()
*/
public String toString() {
return this.getName();
}
/**
* Write all parts and the last boundary to the specified output stream.
*
* @param out The stream to write to.
* @param parts The parts to write.
* @throws IOException If an I/O error occurs while writing the parts.
*/
public static void sendParts(OutputStream out, final Part[] parts)
throws IOException {
sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
}
/**
* Write all parts and the last boundary to the specified output stream.
*
* @param out The stream to write to.
* @param parts The parts to write.
* @param partBoundary The ASCII bytes to use as the part boundary.
* @throws IOException If an I/O error occurs while writing the parts.
* @since 3.0
*/
public static void sendParts(OutputStream out, Part[] parts, byte[] partBoundary)
throws IOException {
if (parts == null) {
throw new IllegalArgumentException("Parts may not be null");
}
if (partBoundary == null || partBoundary.length == 0) {
throw new IllegalArgumentException("partBoundary may not be empty");
}
for (int i = 0; i < parts.length; i++) {
// set the part boundary before the part is sent
parts[i].setPartBoundary(partBoundary);
parts[i].send(out);
}
out.write(EXTRA_BYTES);
out.write(partBoundary);
out.write(EXTRA_BYTES);
out.write(CRLF_BYTES);
}
/**
* Return the total sum of all parts and that of the last boundary
*
* @param parts The parts.
* @return The total length
* @throws IOException If an I/O error occurs while writing the parts.
*/
public static long getLengthOfParts(Part[] parts)
throws IOException {
return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
}
/**
* Gets the length of the multipart message including the given parts.
*
* @param parts The parts.
* @param partBoundary The ASCII bytes to use as the part boundary.
* @return The total length
* @throws IOException If an I/O error occurs while writing the parts.
* @since 3.0
*/
public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws IOException {
LOG.trace("getLengthOfParts(Parts[])");
if (parts == null) {
throw new IllegalArgumentException("Parts may not be null");
}
long total = 0;
for (int i = 0; i < parts.length; i++) {
// set the part boundary before we calculate the part's length
parts[i].setPartBoundary(partBoundary);
long l = parts[i].length();
if (l < 0) {
return -1;
}
total += l;
}
total += EXTRA_BYTES.length;
total += partBoundary.length;
total += EXTRA_BYTES.length;
total += CRLF_BYTES.length;
return total;
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public abstract class PartBase extends Part {
/**
* Name of the file part.
*/
private String name;
/**
* Content type of the file part.
*/
private String contentType;
/**
* Content encoding of the file part.
*/
private String charSet;
/**
* The transfer encoding.
*/
private String transferEncoding;
/**
* Constructor.
*
* @param name The name of the part
* @param contentType The content type, or <code>null</code>
* @param charSet The character encoding, or <code>null</code>
* @param transferEncoding The transfer encoding, or <code>null</code>
*/
public PartBase(String name, String contentType, String charSet, String transferEncoding) {
if (name == null) {
throw new IllegalArgumentException("Name must not be null");
}
this.name = name;
this.contentType = contentType;
this.charSet = charSet;
this.transferEncoding = transferEncoding;
}
/**
* Returns the name.
*
* @return The name.
*/
public String getName() {
return this.name;
}
/**
* Returns the content type of this part.
*
* @return String The name.
*/
public String getContentType() {
return this.contentType;
}
/**
* Return the character encoding of this part.
*
* @return String The name.
*/
public String getCharSet() {
return this.charSet;
}
/**
* Returns the transfer encoding of this part.
*
* @return String The name.
*/
public String getTransferEncoding() {
return transferEncoding;
}
/**
* Sets the character encoding.
*
* @param charSet the character encoding, or <code>null</code> to exclude the character
* encoding header
*/
public void setCharSet(String charSet) {
this.charSet = charSet;
}
/**
* Sets the content type.
*
* @param contentType the content type, or <code>null</code> to exclude the content type header
*/
public void setContentType(String contentType) {
this.contentType = contentType;
}
/**
* Sets the part name.
*
* @param name
*/
public void setName(String name) {
if (name == null) {
throw new IllegalArgumentException("Name must not be null");
}
this.name = name;
}
/**
* Sets the transfer encoding.
*
* @param transferEncoding the transfer encoding, or <code>null</code> to exclude the
* transfer encoding header
*/
public void setTransferEncoding(String transferEncoding) {
this.transferEncoding = transferEncoding;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import java.io.IOException;
import java.io.InputStream;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public interface PartSource {
/**
* Gets the number of bytes contained in this source.
*
* @return a value >= 0
*/
long getLength();
/**
* Gets the name of the file this source represents.
*
* @return the fileName used for posting a MultiPart file part
*/
String getFileName();
/**
* Gets a new InputStream for reading this source. This method can be
* called more than once and should therefore return a new stream every
* time.
*
* @return a new InputStream
* @throws java.io.IOException if an error occurs when creating the InputStream
*/
InputStream createInputStream() throws IOException;
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import java.io.IOException;
import java.io.OutputStream;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public interface RequestEntity {
/**
* Tests if {@link #writeRequest(java.io.OutputStream)} can be called more than once.
*
* @return <tt>true</tt> if the entity can be written to {@link java.io.OutputStream} more than once,
* <tt>false</tt> otherwise.
*/
boolean isRepeatable();
/**
* Writes the request entity to the given stream.
*
* @param out
* @throws java.io.IOException
*/
void writeRequest(OutputStream out) throws IOException;
/**
* Gets the request entity's length. This method should return a non-negative value if the content
* length is known or a negative value if it is not. In the latter case the
* EntityEnclosingMethod will use chunk encoding to
* transmit the request entity.
*
* @return a non-negative value when content length is known or a negative value when content length
* is not known
*/
long getContentLength();
/**
* Gets the entity's content type. This content type will be used as the value for the
* "Content-Type" header.
*
* @return the entity's content type
*/
String getContentType();
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.multipart;
import org.elasticsearch.util.logging.ESLogger;
import org.elasticsearch.util.logging.Loggers;
import java.io.IOException;
import java.io.OutputStream;
/**
* This class is an adaptation of the Apache HttpClient implementation
*
* @link http://hc.apache.org/httpclient-3.x/
*/
public class StringPart extends PartBase {
/**
* Log object for this class.
*/
private final static ESLogger LOG = Loggers.getLogger(StringPart.class);
/**
* Default content encoding of string parameters.
*/
public static final String DEFAULT_CONTENT_TYPE = "text/plain";
/**
* Default charset of string parameters
*/
public static final String DEFAULT_CHARSET = "US-ASCII";
/**
* Default transfer encoding of string parameters
*/
public static final String DEFAULT_TRANSFER_ENCODING = "8bit";
/**
* Contents of this StringPart.
*/
private byte[] content;
/**
* The String value of this part.
*/
private String value;
/**
* Constructor.
*
* @param name The name of the part
* @param value the string to post
* @param charset the charset to be used to encode the string, if <code>null</code>
* the {@link #DEFAULT_CHARSET default} is used
*/
public StringPart(String name, String value, String charset) {
super(
name,
DEFAULT_CONTENT_TYPE,
charset == null ? DEFAULT_CHARSET : charset,
DEFAULT_TRANSFER_ENCODING
);
if (value == null) {
throw new IllegalArgumentException("Value may not be null");
}
if (value.indexOf(0) != -1) {
// See RFC 2048, 2.8. "8bit Data"
throw new IllegalArgumentException("NULs may not be present in string parts");
}
this.value = value;
}
/**
* Constructor.
*
* @param name The name of the part
* @param value the string to post
*/
public StringPart(String name, String value) {
this(name, value, null);
}
/**
* Gets the content in bytes. Bytes are lazily created to allow the charset to be changed
* after the part is created.
*
* @return the content in bytes
*/
private byte[] getContent() {
if (content == null) {
content = MultipartEncodingUtil.getBytes(value, getCharSet());
}
return content;
}
/**
* Writes the data to the given OutputStream.
*
* @param out the OutputStream to write to
* @throws java.io.IOException if there is a write error
*/
protected void sendData(OutputStream out) throws IOException {
LOG.trace("enter sendData(OutputStream)");
out.write(getContent());
}
/**
* Return the length of the data.
*
* @return The length of the data.
* @throws IOException If an IO problem occurs
*/
protected long lengthOfData() throws IOException {
LOG.trace("enter lengthOfData()");
return getContent().length;
}
/* (non-Javadoc)
* @see org.apache.commons.httpclient.methods.multipart.BasePart#setCharSet(java.lang.String)
*/
public void setCharSet(String charSet) {
super.setCharSet(charSet);
this.content = null;
}
}

View File

@ -0,0 +1,11 @@
/**
* Ning Async-Client, revision 1fbadf5, date: 2010-05-24.
*
* Changes:
*
* 1. Use elasticsearch google collect.
* 2. Change to use ESLogger instead of log4j.
* 3. AsyncHttpClient: * Change string based provider name
* 4. AsyncHttpClientConfig: Change userAgent to ES
*/
package org.elasticsearch.util.http;

View File

@ -0,0 +1,436 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
package org.elasticsearch.util.http.url;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Url implements Cloneable, Serializable {
private static final long serialVersionUID = 2187287725357847401L;
public enum Protocol {
HTTP(80),
HTTPS(443);
private int port;
Protocol(int port) {
this.port = port;
}
public int getPort() {
return port;
}
}
private String scheme;
private String host;
private int port;
private String path;
private Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
/**
* Constructs a Url object from the given url string. The string is expected to be compliant with the URL RFC
* and all relevant parts need to be properly url-encoded
*
* @param value the string
* @return a url object
* @throws MalformedURLException
*/
public static Url valueOf(String value)
throws MalformedURLException {
URL url = new URL(value);
try {
return new Url(url.getProtocol(),
url.getHost(),
// url.getPort() return -1 rather than default for the protocol/scheme if no port is specified.
url.getPort() > 0 ? url.getPort() : Protocol.valueOf(url.getProtocol().toUpperCase()).getPort(),
URLDecoder.decode(url.getPath(), "UTF-8"),
url.getQuery());
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
/**
* Constructs a Url object from the given url string parts. The parts are expected to be compliant with the URL RFC
* and need to be properly url-encoded
*/
public static Url valueOf(String scheme, String host, String uri) {
try {
return Url.valueOf(scheme + "://" + host + uri);
}
catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
/**
* Resolves 'path' with respect to a given base url
*/
public static Url valueOf(Url base, String uri)
throws MalformedURLException {
final Url result;
if (!uri.matches("[\\p{Alpha}][\\p{Alnum}-.+_]+://.+")) { // relative url?
result = base.clone();
if (uri.startsWith("/")) { // relative to root?
result.setUri(uri);
} else { // relative to current dir
String path = base.getPath();
int index = path.lastIndexOf('/');
if (index != -1) {
path = path.substring(0, index + 1) + uri;
} else {
path = "/" + uri;
}
result.setUri(path);
}
} else { // uri is absolute
result = Url.valueOf(uri);
}
return result;
}
private Url(String scheme, String host, int port, String path, Map<String, List<String>> params) {
setScheme(scheme);
setHost(host);
this.port = port;
this.path = path;
this.params = new LinkedHashMap<String, List<String>>(params);
}
public Url(String scheme, String host, int port, String path, String query) {
setScheme(scheme);
setHost(host);
this.port = port;
this.path = path;
setQueryString(query);
}
public String getScheme() {
return scheme;
}
public void setScheme(String s) {
if (Protocol.valueOf(s.toUpperCase()) == null) {
throw new IllegalArgumentException("Illegal scheme used [" + s + "]");
}
this.scheme = s;
}
public Protocol getProtocol() {
return Protocol.valueOf(scheme.toUpperCase());
}
/**
* Gets the path part of this url. The value is url-decoded.
*
* @return he path part of this url. The value is url-decoded.
*/
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host.toLowerCase();
}
public void setBaseUrl(String url) {
Pattern pattern = Pattern.compile("([^:]+)://([^:]+)(:([0-9]+))?");
Matcher matcher = pattern.matcher(url);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid url: " + url);
}
setScheme(matcher.group(1));
setHost(matcher.group(2));
String port = matcher.group(4);
if (port != null) {
this.port = Integer.valueOf(port);
} else {
Protocol protocol = getProtocol();
this.port = protocol.getPort();
}
}
public String getBaseUrl() {
StringBuilder builder = new StringBuilder();
builder.append(scheme);
builder.append("://");
builder.append(getLocation());
return builder.toString();
}
public String getLocation() {
StringBuilder builder = new StringBuilder(host);
int defaultPort;
try {
Protocol protocol = getProtocol();
defaultPort = protocol.getPort();
}
catch (Exception e) {
throw new IllegalStateException("Unable to fetch protocol", e);
}
if (port != defaultPort) {
builder.append(':');
builder.append(port);
}
return builder.toString();
}
public void setUri(String uri) {
int index = uri.indexOf('?');
if (index != -1) {
path = uri.substring(0, index);
String query = uri.substring(index + 1);
setQueryString(query);
} else {
path = uri;
params.clear();
}
}
public String getUri() {
StringBuilder builder = new StringBuilder();
builder.append(path);
if (!params.isEmpty()) {
builder.append('?');
builder.append(getQueryString());
}
return builder.toString();
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(getBaseUrl());
builder.append(getUri());
return builder.toString();
}
/**
* Return the URL string, but without any parameters
*
* @return a string
*/
public String toStringWithoutParams() {
return new Url(scheme, host, port, path, "").toString();
}
private void parseParameters(String query) {
params.clear();
if (query != null) {
StringTokenizer tokenizer = new StringTokenizer(query, "&");
while (tokenizer.hasMoreElements()) {
String token = tokenizer.nextToken();
String[] param = token.split("=", 2);
if (param.length > 0) {
String name = param[0];
String value = param.length > 1
? urlDecode(param[1])
: null; // null case distinguishes between ?name= and ?name
if (name.length() > 0) {
addParameter(name, value);
}
}
}
}
}
private String urlDecode(String value) {
try {
return URLDecoder.decode(value, "UTF-8");
}
catch (UnsupportedEncodingException e) {
// this should not be possible
throw new IllegalStateException("Should never happen: UTF-8 encoding is unsupported.", e);
}
}
public void setParameter(String name, String value) {
removeParameter(name);
addParameter(name, value);
}
/**
* reset the parameters list.
*/
public void clearParameters() {
params.clear();
}
public void setParameter(String name, Object value) {
setParameter(name, value.toString());
}
public void addParameter(String name, String value) {
List<String> list = params.get(name);
if (list == null) {
list = new ArrayList<String>();
params.put(name, list);
}
list.add(value);
//params.add(new Pair<String, String>(name, value));
}
public String getParameter(String name) {
String result = null;
List<String> list = params.get(name);
if (list != null && list.size() > 0) {
result = list.get(0);
}
return result;
}
public List<String> getParameters(String name) {
List<String> result = new ArrayList<String>();
List<String> list = params.get(name);
if (list != null && list.size() > 0) {
result.addAll(list);
}
return result;
}
public Map<String, List<String>> getParameters() {
return params;
}
public void removeParameter(String name) {
params.remove(name);
}
public void setQueryString(String query) {
// TODO: don't parse until addParameter/setParameter/removeParameter is called
parseParameters(query);
}
public String getQueryString() {
// TODO: cache query string
String result = null;
if (!params.isEmpty()) {
StringBuilder builder = new StringBuilder();
for (Iterator<Map.Entry<String, List<String>>> i = params.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, List<String>> param = i.next();
String name = param.getKey();
for (Iterator<String> j = param.getValue().iterator(); j.hasNext();) {
String value = j.next();
builder.append(name);
if (value != null) {
builder.append('=');
try {
builder.append(URLEncoder.encode(value, "UTF-8"));
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
if (j.hasNext()) {
builder.append('&');
}
}
if (i.hasNext()) {
builder.append('&');
}
}
result = builder.toString();
}
return result;
}
public Url clone() {
return new Url(scheme, host, port, path, params);
}
public int hashCode() {
return toString().hashCode();
}
public boolean equals(Object obj) {
// TODO: we should compare piece by piece. Argument ordering shouldn't affect equality
return obj instanceof Url && toString().equals(obj.toString());
}
/**
* Is the given Url string in a valid format?
*
* @param url
* @return true if valid
*/
public static boolean isValidUrl(String url) {
try {
Url.valueOf(url);
return true;
}
catch (MalformedURLException e) {
return false;
}
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright 2010 Ning, Inc.
*
* Ning licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.util.http.util;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
/**
* This class is a copy of http://github.com/sonatype/wagon-ning/raw/master/src/main/java/org/apache/maven/wagon/providers/http/SslUtils.java
*/
public class SslUtils {
public static SSLEngine getSSLEngine()
throws GeneralSecurityException, IOException {
SSLEngine engine = null;
SSLContext context = getSSLContext();
if (context != null) {
engine = context.createSSLEngine();
engine.setUseClientMode(true);
}
return engine;
}
static SSLContext getSSLContext()
throws GeneralSecurityException, IOException {
SSLConfig config = new SSLConfig();
if (config.keyStoreLocation == null) {
return getLooseSSLContext();
} else {
return getStrictSSLContext(config);
}
}
static SSLContext getStrictSSLContext(SSLConfig config)
throws GeneralSecurityException, IOException {
KeyStore keyStore = KeyStore.getInstance(config.keyStoreType);
InputStream keystoreInputStream = new FileInputStream(config.keyStoreLocation);
try {
keyStore.load(keystoreInputStream, (config.keyStorePassword == null) ? null
: config.keyStorePassword.toCharArray());
}
finally {
keystoreInputStream.close();
}
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(config.keyManagerAlgorithm);
keyManagerFactory.init(keyStore, (config.keyManagerPassword == null) ? null
: config.keyManagerPassword.toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
KeyStore trustStore = KeyStore.getInstance(config.trustStoreType);
InputStream truststoreInputStream = new FileInputStream(config.trustStoreLocation);
try {
trustStore.load(truststoreInputStream, (config.trustStorePassword == null) ? null
: config.trustStorePassword.toCharArray());
}
finally {
truststoreInputStream.close();
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(config.trustManagerAlgorithm);
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagers, trustManagers, null);
return context;
}
static SSLContext getLooseSSLContext()
throws GeneralSecurityException {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{LooseTrustManager.INSTANCE}, new SecureRandom());
return sslContext;
}
static class LooseTrustManager
implements X509TrustManager {
public static final LooseTrustManager INSTANCE = new LooseTrustManager();
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
}
private final static class SSLConfig {
public String keyStoreLocation;
public String keyStoreType = "JKS";
public String keyStorePassword = "changeit";
public String keyManagerAlgorithm = "SunX509";
public String keyManagerPassword = "changeit";
public String trustStoreLocation;
public String trustStoreType = "JKS";
public String trustStorePassword = "changeit";
public String trustManagerAlgorithm = "SunX509";
public SSLConfig() {
keyStoreLocation = System.getProperty("javax.net.ssl.keyStore");
keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "changeit");
keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
keyManagerAlgorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (keyManagerAlgorithm == null) {
keyManagerAlgorithm = "SunX509";
}
keyManagerPassword = System.getProperty("javax.net.ssl.keyStorePassword", "changeit");
trustStoreLocation = System.getProperty("javax.net.ssl.trustStore");
if (trustStoreLocation == null) {
trustStoreLocation = keyStoreLocation;
trustStorePassword = keyStorePassword;
trustStoreType = keyStoreType;
} else {
trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword", "changeit");
trustStoreType = System.getProperty("javax.net.ssl.trustStoreType", KeyStore.getDefaultType());
}
trustManagerAlgorithm = Security.getProperty("ssl.TrustManagerFactory.algorithm");
if (trustManagerAlgorithm == null) {
trustManagerAlgorithm = "SunX509";
}
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search licenses this
* file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.test.integration.rest.http;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus;
import org.elasticsearch.client.Client;
import org.elasticsearch.node.internal.InternalNode;
import org.elasticsearch.test.integration.AbstractNodesTests;
import org.elasticsearch.util.http.HttpClientService;
import org.elasticsearch.util.http.client.AsyncHttpClient;
import org.elasticsearch.util.http.client.Response;
import org.elasticsearch.util.xcontent.XContentFactory;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static org.elasticsearch.client.Requests.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
/**
* @author kimchy (shay.banon)
*/
public class RestHttpDocumentActions extends AbstractNodesTests {
protected Client client1;
protected Client client2;
protected AsyncHttpClient httpClient;
@BeforeMethod public void startNodes() {
startNode("server1");
startNode("server2");
client1 = getClient1();
client2 = getClient2();
httpClient = ((InternalNode) node("server1")).injector().getInstance(HttpClientService.class).asyncHttpClient();
createIndex();
}
protected void createIndex() {
logger.info("Creating index test");
client1.admin().indices().create(createIndexRequest("test")).actionGet();
}
protected String getConcreteIndexName() {
return "test";
}
@AfterMethod public void closeNodes() {
client1.close();
client2.close();
closeAllNodes();
}
protected Client getClient1() {
return client("server1");
}
protected Client getClient2() {
return client("server2");
}
@Test public void testSimpleActions() throws IOException, ExecutionException, InterruptedException {
logger.info("Running Cluster Health");
ClusterHealthResponse clusterHealth = client1.admin().cluster().health(clusterHealth().waitForGreenStatus()).actionGet();
logger.info("Done Cluster Health, status " + clusterHealth.status());
assertThat(clusterHealth.timedOut(), equalTo(false));
assertThat(clusterHealth.status(), equalTo(ClusterHealthStatus.GREEN));
Future<Response> response = httpClient.preparePut("http://localhost:9200/test/type1/1").setBody(source("1", "test")).execute();
Map<String, Object> respMap = XContentFactory.xContent(response.get().getResponseBody()).createParser(response.get().getResponseBody()).map();
assertThat((Boolean) respMap.get("ok"), equalTo(Boolean.TRUE));
assertThat((String) respMap.get("_id"), equalTo("1"));
assertThat((String) respMap.get("_type"), equalTo("type1"));
assertThat((String) respMap.get("_index"), equalTo("test"));
}
private String source(String id, String nameValue) {
return "{ type1 : { \"id\" : \"" + id + "\", \"name\" : \"" + nameValue + "\" } }";
}
}