add async http client
This commit is contained in:
parent
b7d11f1303
commit
32e4c405de
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>() {
|
||||
*
|
||||
* @Override
|
||||
* public Response onCompleted(Response response) throws IOException {
|
||||
* // Do something
|
||||
* return response;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void onThrowable(Throwable t) {
|
||||
* }
|
||||
* });
|
||||
* 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>() {
|
||||
*
|
||||
* @Override
|
||||
* public Integer onCompleted(Response response) throws IOException {
|
||||
* // Do something
|
||||
* return response.getStatusCode();
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void onThrowable(Throwable t) {
|
||||
* }
|
||||
* });
|
||||
* 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>() {
|
||||
* private StringBuilder builder = new StringBuilder();
|
||||
*
|
||||
* @Override
|
||||
* public void onStatusReceived(HttpResponseStatus s) throws Exception {
|
||||
* // The Status have been read
|
||||
* // If you don't want to read the headers,body, or stop processing the response
|
||||
* throw new ResponseComplete();
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void onHeadersReceived(HttpResponseHeaders bodyPart) throws Exception {
|
||||
* // The headers have been read
|
||||
* // If you don't want to read the body, or stop processing the response
|
||||
* throw new ResponseComplete();
|
||||
* }
|
||||
* @Override
|
||||
*
|
||||
* public void onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
|
||||
* builder.append(new String(bodyPart));
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public String onCompleted() throws Exception {
|
||||
* // Will be invoked once the response has been fully read or a ResponseComplete exception
|
||||
* // has been thrown.
|
||||
* return builder.toString();
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public void onThrowable(Throwable t) {
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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[]{});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 + "\" } }";
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue