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

org.eclipse.jetty.client.HttpClient Maven / Gradle / Ivy

The newest version!
//
//  ========================================================================
//  Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.client;

import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import javax.net.ssl.SSLEngine;

import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.CookieStore;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.ProxyConfiguration;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.TimerScheduler;

/**
 * 

{@link HttpClient} provides an efficient, asynchronous, non-blocking implementation * to perform HTTP requests to a server through a simple API that offers also blocking semantic.

*

{@link HttpClient} provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP * requests in a one-liner, but also gives the ability to fine tune the configuration of requests via * {@link HttpClient#newRequest(URI)}.

*

{@link HttpClient} acts as a central configuration point for network parameters (such as idle timeouts) * and HTTP parameters (such as whether to follow redirects).

*

{@link HttpClient} transparently pools connections to servers, but allows direct control of connections * for cases where this is needed.

*

{@link HttpClient} also acts as a central configuration point for cookies, via {@link #getCookieStore()}.

*

Typical usage:

*
 * // One liner:
 * new HttpClient().GET("http://localhost:8080/").get().status();
 *
 * // Building a request with a timeout
 * HttpClient client = new HttpClient();
 * Response response = client.newRequest("http://localhost:8080").send().get(5, TimeUnit.SECONDS);
 * int status = response.status();
 *
 * // Asynchronously
 * HttpClient client = new HttpClient();
 * client.newRequest("http://localhost:8080").send(new Response.Listener.Empty()
 * {
 *     @Override
 *     public void onSuccess(Response response)
 *     {
 *         ...
 *     }
 * });
 * 
*/ public class HttpClient extends ContainerLifeCycle { private static final Logger LOG = Log.getLogger(HttpClient.class); private final ConcurrentMap destinations = new ConcurrentHashMap<>(); private final ConcurrentMap conversations = new ConcurrentHashMap<>(); private final List handlers = new CopyOnWriteArrayList<>(); private final List requestListeners = new CopyOnWriteArrayList<>(); private final CookieStore cookieStore = new HttpCookieStore(); private final AuthenticationStore authenticationStore = new HttpAuthenticationStore(); private final Set decoderFactories = Collections.newSetFromMap(new ConcurrentHashMap()); private final SslContextFactory sslContextFactory; private volatile Executor executor; private volatile ByteBufferPool byteBufferPool; private volatile Scheduler scheduler; private volatile SelectorManager selectorManager; private volatile String agent = "Jetty/" + Jetty.VERSION; private volatile boolean followRedirects = true; private volatile int maxConnectionsPerAddress = 8; private volatile int maxQueueSizePerAddress = 1024; private volatile int requestBufferSize = 4096; private volatile int responseBufferSize = 4096; private volatile int maxRedirects = 8; private volatile SocketAddress bindAddress; private volatile long connectTimeout = 15000; private volatile long idleTimeout; private volatile boolean tcpNoDelay = true; private volatile boolean dispatchIO = true; private volatile ProxyConfiguration proxyConfig; public HttpClient() { this(null); } public HttpClient(SslContextFactory sslContextFactory) { this.sslContextFactory = sslContextFactory; } public SslContextFactory getSslContextFactory() { return sslContextFactory; } @Override protected void doStart() throws Exception { if (sslContextFactory != null) { addBean(sslContextFactory); // Avoid to double dispatch when using SSL setDispatchIO(false); } String name = HttpClient.class.getSimpleName() + "@" + hashCode(); if (executor == null) { QueuedThreadPool threadPool = new QueuedThreadPool(); threadPool.setName(name); executor = threadPool; } addBean(executor); if (byteBufferPool == null) byteBufferPool = new MappedByteBufferPool(); addBean(byteBufferPool); if (scheduler == null) scheduler = new TimerScheduler(name + "-scheduler"); addBean(scheduler); selectorManager = newSelectorManager(); selectorManager.setConnectTimeout(getConnectTimeout()); addBean(selectorManager); handlers.add(new ContinueProtocolHandler(this)); handlers.add(new RedirectProtocolHandler(this)); handlers.add(new AuthenticationProtocolHandler(this)); decoderFactories.add(new GZIPContentDecoder.Factory()); super.doStart(); LOG.info("Started {}", this); } protected SelectorManager newSelectorManager() { return new ClientSelectorManager(getExecutor(), getScheduler()); } @Override protected void doStop() throws Exception { LOG.debug("Stopping {}", this); for (HttpDestination destination : destinations.values()) destination.close(); destinations.clear(); conversations.clear(); handlers.clear(); requestListeners.clear(); cookieStore.clear(); authenticationStore.clearAuthentications(); authenticationStore.clearAuthenticationResults(); decoderFactories.clear(); super.doStop(); LOG.info("Stopped {}", this); } public List getRequestListeners() { return requestListeners; } public CookieStore getCookieStore() { return cookieStore; } public AuthenticationStore getAuthenticationStore() { return authenticationStore; } public Set getContentDecoderFactories() { return decoderFactories; } public Future GET(String uri) { return GET(URI.create(uri)); } public Future GET(URI uri) { return newRequest(uri).send(); } public Request POST(String uri) { return POST(URI.create(uri)); } public Request POST(URI uri) { return newRequest(uri).method(HttpMethod.POST); } public Request newRequest(String host, int port) { return newRequest(URI.create(address("http", host, port))); } public Request newRequest(String uri) { return newRequest(URI.create(uri)); } public Request newRequest(URI uri) { return new HttpRequest(this, uri); } protected Request copyRequest(Request oldRequest, String newURI) { Request newRequest = new HttpRequest(this, oldRequest.getConversationID(), URI.create(newURI)); newRequest.method(oldRequest.getMethod()) .version(oldRequest.getVersion()) .content(oldRequest.getContent()); for (HttpFields.Field header : oldRequest.getHeaders()) { // We have a new URI, so skip the host header if present if (HttpHeader.HOST == header.getHeader()) continue; newRequest.header(header.getName(), header.getValue()); } return newRequest; } private String address(String scheme, String host, int port) { return scheme + "://" + host + ":" + port; } public Destination getDestination(String scheme, String host, int port) { return provideDestination(scheme, host, port); } protected HttpDestination provideDestination(String scheme, String host, int port) { if (port <= 0) port = "https".equalsIgnoreCase(scheme) ? 443 : 80; String address = address(scheme, host, port); HttpDestination destination = destinations.get(address); if (destination == null) { destination = new HttpDestination(this, scheme, host, port); if (isRunning()) { HttpDestination existing = destinations.putIfAbsent(address, destination); if (existing != null) destination = existing; else LOG.debug("Created {}", destination); if (!isRunning()) destinations.remove(address); } } return destination; } public List getDestinations() { return new ArrayList(destinations.values()); } protected void send(final Request request, List listeners) { String scheme = request.getScheme().toLowerCase(Locale.ENGLISH); if (!Arrays.asList("http", "https").contains(scheme)) throw new IllegalArgumentException("Invalid protocol " + scheme); for (Response.ResponseListener listener : listeners) if (listener instanceof Schedulable) ((Schedulable)listener).schedule(scheduler); HttpDestination destination = provideDestination(scheme, request.getHost(), request.getPort()); destination.send(request, listeners); } protected void newConnection(HttpDestination destination, Callback callback) { SocketChannel channel = null; try { channel = SocketChannel.open(); SocketAddress bindAddress = getBindAddress(); if (bindAddress != null) channel.bind(bindAddress); configure(channel); channel.configureBlocking(false); channel.connect(destination.getConnectAddress()); Future result = new ConnectionCallback(destination, callback); selectorManager.connect(channel, result); } catch (IOException x) { if (channel != null) close(channel); callback.failed(null, x); } } protected void configure(SocketChannel channel) throws SocketException { channel.socket().setTcpNoDelay(isTCPNoDelay()); } private void close(SocketChannel channel) { try { channel.close(); } catch (IOException x) { LOG.ignore(x); } } protected HttpConversation getConversation(long id, boolean create) { HttpConversation conversation = conversations.get(id); if (conversation == null && create) { conversation = new HttpConversation(this, id); HttpConversation existing = conversations.putIfAbsent(id, conversation); if (existing != null) conversation = existing; else LOG.debug("{} created", conversation); } return conversation; } protected void removeConversation(HttpConversation conversation) { conversations.remove(conversation.id()); LOG.debug("{} removed", conversation); } protected List getProtocolHandlers() { return handlers; } protected ProtocolHandler findProtocolHandler(Request request, Response response) { for (ProtocolHandler handler : getProtocolHandlers()) { if (handler.accept(request, response)) return handler; } return null; } public ByteBufferPool getByteBufferPool() { return byteBufferPool; } public void setByteBufferPool(ByteBufferPool byteBufferPool) { this.byteBufferPool = byteBufferPool; } public long getConnectTimeout() { return connectTimeout; } public void setConnectTimeout(long connectTimeout) { this.connectTimeout = connectTimeout; } public long getIdleTimeout() { return idleTimeout; } public void setIdleTimeout(long idleTimeout) { this.idleTimeout = idleTimeout; } /** * @return the address to bind socket channels to * @see #setBindAddress(SocketAddress) */ public SocketAddress getBindAddress() { return bindAddress; } /** * @param bindAddress the address to bind socket channels to * @see #getBindAddress() */ public void setBindAddress(SocketAddress bindAddress) { this.bindAddress = bindAddress; } public String getUserAgent() { return agent; } public void setUserAgent(String agent) { this.agent = agent; } public boolean isFollowRedirects() { return followRedirects; } public void setFollowRedirects(boolean follow) { this.followRedirects = follow; } public Executor getExecutor() { return executor; } public void setExecutor(Executor executor) { this.executor = executor; } public Scheduler getScheduler() { return scheduler; } public void setScheduler(Scheduler scheduler) { this.scheduler = scheduler; } public SelectorManager getSelectorManager() { return selectorManager; } public int getMaxConnectionsPerAddress() { return maxConnectionsPerAddress; } public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress) { this.maxConnectionsPerAddress = maxConnectionsPerAddress; } public int getMaxQueueSizePerAddress() { return maxQueueSizePerAddress; } public void setMaxQueueSizePerAddress(int maxQueueSizePerAddress) { this.maxQueueSizePerAddress = maxQueueSizePerAddress; } public int getRequestBufferSize() { return requestBufferSize; } public void setRequestBufferSize(int requestBufferSize) { this.requestBufferSize = requestBufferSize; } public int getResponseBufferSize() { return responseBufferSize; } public void setResponseBufferSize(int responseBufferSize) { this.responseBufferSize = responseBufferSize; } public int getMaxRedirects() { return maxRedirects; } public void setMaxRedirects(int maxRedirects) { this.maxRedirects = maxRedirects; } public boolean isTCPNoDelay() { return tcpNoDelay; } public void setTCPNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; } /** * @return true to dispatch I/O operations in a different thread, false to execute them in the selector thread * @see #setDispatchIO(boolean) */ public boolean isDispatchIO() { return dispatchIO; } /** * Whether to dispatch I/O operations from the selector thread to a different thread. *

* This implementation never blocks on I/O operation, but invokes application callbacks that may * take time to execute or block on other I/O. * If application callbacks are known to take time or block on I/O, then parameter {@code dispatchIO} * must be set to true. * If application callbacks are known to be quick and never block on I/O, then parameter {@code dispatchIO} * may be set to false. * * @param dispatchIO true to dispatch I/O operations in a different thread, false to execute them in the selector thread */ public void setDispatchIO(boolean dispatchIO) { this.dispatchIO = dispatchIO; } public ProxyConfiguration getProxyConfiguration() { return proxyConfig; } public void setProxyConfiguration(ProxyConfiguration proxyConfig) { this.proxyConfig = proxyConfig; } @Override public void dump(Appendable out, String indent) throws IOException { dumpThis(out); dump(out, indent, getBeans(), destinations.values()); } protected class ClientSelectorManager extends SelectorManager { public ClientSelectorManager(Executor executor, Scheduler scheduler) { this(executor, scheduler, 1); } public ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors) { super(executor, scheduler, selectors); } @Override protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key) { return new SelectChannelEndPoint(channel, selector, key, getScheduler(), getIdleTimeout()); } @Override public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException { ConnectionCallback callback = (ConnectionCallback)attachment; HttpDestination destination = callback.destination; SslContextFactory sslContextFactory = getSslContextFactory(); if ("https".equals(destination.getScheme())) { if (sslContextFactory == null) { IOException failure = new ConnectException("Missing " + SslContextFactory.class.getSimpleName() + " for " + destination.getScheme() + " requests"); callback.failed(null, failure); throw failure; } else { SSLEngine engine = sslContextFactory.newSSLEngine(endPoint.getRemoteAddress()); engine.setUseClientMode(true); SslConnection sslConnection = new SslConnection(getByteBufferPool(), getExecutor(), endPoint, engine); // TODO: configureConnection => implies we should use SslConnectionFactory to do it EndPoint appEndPoint = sslConnection.getDecryptedEndPoint(); HttpConnection connection = new HttpConnection(HttpClient.this, appEndPoint, destination); // TODO: configureConnection, see above appEndPoint.setConnection(connection); callback.callback.completed(connection); return sslConnection; } } else { HttpConnection connection = new HttpConnection(HttpClient.this, endPoint, destination); // TODO: configureConnection, see above callback.callback.completed(connection); return connection; } } @Override protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) { ConnectionCallback callback = (ConnectionCallback)attachment; callback.callback.failed(null, ex); } } private class ConnectionCallback extends FutureCallback { private final HttpDestination destination; private final Callback callback; private ConnectionCallback(HttpDestination destination, Callback callback) { this.destination = destination; this.callback = callback; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy