
io.jsync.http.impl.DefaultHttpClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jsync.io Show documentation
Show all versions of jsync.io Show documentation
jsync.io is a non-blocking, event-driven networking framework for Java
/*
* Copyright (c) 2011-2013 The original author or authors
* ------------------------------------------------------
* 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 io.jsync.http.impl;
import io.jsync.AsyncResult;
import io.jsync.Handler;
import io.jsync.MultiMap;
import io.jsync.VoidHandler;
import io.jsync.buffer.Buffer;
import io.jsync.http.*;
import io.jsync.http.impl.ws.DefaultWebSocketFrame;
import io.jsync.http.impl.ws.WebSocketFrameInternal;
import io.jsync.impl.AsyncInternal;
import io.jsync.impl.Closeable;
import io.jsync.impl.DefaultContext;
import io.jsync.impl.DefaultFutureResult;
import io.jsync.net.NetSocket;
import io.jsync.net.impl.AsyncEventLoopGroup;
import io.jsync.net.impl.TCPSSLHelper;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultHttpClient implements HttpClient {
final AsyncInternal async;
final Map connectionMap = new ConcurrentHashMap<>();
final TCPSSLHelper tcpHelper = new TCPSSLHelper();
private final DefaultContext actualCtx;
private Bootstrap bootstrap;
private Handler exceptionHandler;
private int port = 80;
private String host = "localhost";
private boolean tryUseCompression;
private int maxWebSocketFrameSize = 65536;
private boolean keepAlive = true;
private boolean pipelining = true;
private int connectionMaxOutstandingRequest = -1;
private final HttpPool pool = new PriorityHttpConnectionPool() {
protected void connect(Handler connectHandler, Handler connectErrorHandler, DefaultContext context) {
internalConnect(connectHandler, connectErrorHandler);
}
};
private boolean configurable = true;
private boolean closed;
private final Closeable closeHook = new Closeable() {
@Override
public void close(Handler> doneHandler) {
DefaultHttpClient.this.close();
doneHandler.handle(new DefaultFutureResult<>((Void) null));
}
};
public DefaultHttpClient(AsyncInternal async) {
this.async = async;
actualCtx = async.getOrCreateContext();
actualCtx.addCloseHook(closeHook);
// Default value of verifyHost for an HTTP client is true
tcpHelper.setVerifyHost(true);
}
// @Override
public DefaultHttpClient exceptionHandler(Handler handler) {
checkClosed();
this.exceptionHandler = handler;
return this;
}
@Override
public int getMaxPoolSize() {
checkClosed();
return pool.getMaxPoolSize();
}
@Override
public DefaultHttpClient setMaxPoolSize(int maxConnections) {
checkClosed();
checkConfigurable();
pool.setMaxPoolSize(maxConnections);
return this;
}
@Override
public boolean isKeepAlive() {
checkClosed();
return keepAlive;
}
@Override
public DefaultHttpClient setKeepAlive(boolean keepAlive) {
checkClosed();
checkConfigurable();
this.keepAlive = keepAlive;
return this;
}
@Override
public boolean isPipelining() {
checkClosed();
return pipelining;
}
@Override
public DefaultHttpClient setPipelining(boolean pipelining) {
checkClosed();
checkConfigurable();
this.pipelining = pipelining;
return this;
}
@Override
public int getPort() {
checkClosed();
return port;
}
@Override
public DefaultHttpClient setPort(int port) {
checkClosed();
checkConfigurable();
this.port = port;
return this;
}
@Override
public String getHost() {
checkClosed();
return host;
}
@Override
public DefaultHttpClient setHost(String host) {
checkClosed();
checkConfigurable();
this.host = host;
return this;
}
@Override
public HttpClient connectWebsocket(final String uri, final Handler wsConnect) {
checkClosed();
connectWebsocket(uri, WebSocketVersion.RFC6455, wsConnect);
return this;
}
@Override
public HttpClient connectWebsocket(final String uri, final WebSocketVersion wsVersion, final Handler wsConnect) {
checkClosed();
connectWebsocket(uri, wsVersion, null, wsConnect);
return this;
}
@Override
public HttpClient connectWebsocket(final String uri, final WebSocketVersion wsVersion, final MultiMap headers, final Handler wsConnect) {
connectWebsocket(uri, wsVersion, headers, null, wsConnect);
return this;
}
@Override
public HttpClient connectWebsocket(final String uri, final WebSocketVersion wsVersion, final MultiMap headers, final Set subprotocols, final Handler wsConnect) {
checkClosed();
configurable = false;
getConnection(new Handler() {
public void handle(final ClientConnection conn) {
if (!conn.isClosed()) {
conn.toWebSocket(uri, wsVersion, headers, maxWebSocketFrameSize, subprotocols, wsConnect);
} else {
connectWebsocket(uri, wsVersion, headers, subprotocols, wsConnect);
}
}
}, exceptionHandler, actualCtx);
return this;
}
@Override
public HttpClient getNow(String uri, Handler responseHandler) {
checkClosed();
getNow(uri, null, responseHandler);
return this;
}
@Override
public HttpClient getNow(String uri, MultiMap headers, Handler responseHandler) {
checkClosed();
HttpClientRequest req = get(uri, responseHandler);
if (headers != null) {
req.headers().set(headers);
}
req.end();
return this;
}
@Override
public HttpClientRequest options(String uri, Handler responseHandler) {
checkClosed();
return doRequest("OPTIONS", uri, responseHandler);
}
@Override
public HttpClientRequest get(String uri, Handler responseHandler) {
checkClosed();
return doRequest("GET", uri, responseHandler);
}
@Override
public HttpClientRequest head(String uri, Handler responseHandler) {
checkClosed();
return doRequest("HEAD", uri, responseHandler);
}
@Override
public HttpClientRequest post(String uri, Handler responseHandler) {
checkClosed();
return doRequest("POST", uri, responseHandler);
}
@Override
public HttpClientRequest put(String uri, Handler responseHandler) {
checkClosed();
return doRequest("PUT", uri, responseHandler);
}
@Override
public HttpClientRequest delete(String uri, Handler responseHandler) {
checkClosed();
return doRequest("DELETE", uri, responseHandler);
}
@Override
public HttpClientRequest trace(String uri, Handler responseHandler) {
checkClosed();
return doRequest("TRACE", uri, responseHandler);
}
@Override
public HttpClientRequest connect(String uri, final Handler responseHandler) {
checkClosed();
return doRequest("CONNECT", uri, connectHandler(responseHandler));
}
private Handler connectHandler(final Handler responseHandler) {
return new Handler() {
@Override
public void handle(final HttpClientResponse event) {
HttpClientResponse response;
if (event.statusCode() == 200) {
// connect successful force the modification of the ChannelPipeline
// beside this also pause the socket for now so the user has a chance to register its dataHandler
// after received the NetSocket
final NetSocket socket = event.netSocket();
socket.pause();
response = new HttpClientResponse() {
private boolean resumed;
@Override
public int statusCode() {
return event.statusCode();
}
@Override
public String statusMessage() {
return event.statusMessage();
}
@Override
public MultiMap headers() {
return event.headers();
}
@Override
public MultiMap trailers() {
return event.trailers();
}
@Override
public List cookies() {
return event.cookies();
}
@Override
public HttpClientResponse bodyHandler(Handler bodyHandler) {
event.bodyHandler(bodyHandler);
return this;
}
@Override
public NetSocket netSocket() {
if (!resumed) {
resumed = true;
async.getContext().execute(new Runnable() {
@Override
public void run() {
// resume the socket now as the user had the chance to register a dataHandler
socket.resume();
}
});
}
return socket;
}
@Override
public HttpClientResponse endHandler(Handler endHandler) {
event.endHandler(endHandler);
return this;
}
@Override
public HttpClientResponse dataHandler(Handler handler) {
event.dataHandler(handler);
return this;
}
@Override
public HttpClientResponse pause() {
event.pause();
return this;
}
@Override
public HttpClientResponse resume() {
event.resume();
return this;
}
@Override
public HttpClientResponse exceptionHandler(Handler handler) {
event.exceptionHandler(handler);
return this;
}
};
} else {
response = event;
}
responseHandler.handle(response);
}
};
}
@Override
public HttpClientRequest patch(String uri, Handler responseHandler) {
checkClosed();
return doRequest("PATCH", uri, responseHandler);
}
@Override
public HttpClientRequest request(String method, String uri, Handler responseHandler) {
checkClosed();
if (method.equalsIgnoreCase("CONNECT")) {
// special handling for CONNECT
responseHandler = connectHandler(responseHandler);
}
return doRequest(method, uri, responseHandler);
}
@Override
public void close() {
checkClosed();
pool.close();
for (ClientConnection conn : connectionMap.values()) {
conn.close();
}
actualCtx.removeCloseHook(closeHook);
closed = true;
}
@Override
public HttpClient setSSLContext(SSLContext sslContext) {
checkClosed();
checkConfigurable();
tcpHelper.setExternalSSLContext(sslContext);
return this;
}
@Override
public boolean isTCPNoDelay() {
checkClosed();
return tcpHelper.isTCPNoDelay();
}
@Override
public DefaultHttpClient setTCPNoDelay(boolean tcpNoDelay) {
checkClosed();
checkConfigurable();
tcpHelper.setTCPNoDelay(tcpNoDelay);
return this;
}
@Override
public int getSendBufferSize() {
checkClosed();
return tcpHelper.getSendBufferSize();
}
@Override
public DefaultHttpClient setSendBufferSize(int size) {
checkClosed();
checkConfigurable();
tcpHelper.setSendBufferSize(size);
return this;
}
@Override
public int getReceiveBufferSize() {
checkClosed();
return tcpHelper.getReceiveBufferSize();
}
@Override
public DefaultHttpClient setReceiveBufferSize(int size) {
checkClosed();
checkConfigurable();
tcpHelper.setReceiveBufferSize(size);
return this;
}
@Override
public boolean isTCPKeepAlive() {
checkClosed();
return tcpHelper.isTCPKeepAlive();
}
@Override
public DefaultHttpClient setTCPKeepAlive(boolean keepAlive) {
checkClosed();
checkConfigurable();
tcpHelper.setTCPKeepAlive(keepAlive);
return this;
}
@Override
public boolean isReuseAddress() {
checkClosed();
return tcpHelper.isReuseAddress();
}
@Override
public DefaultHttpClient setReuseAddress(boolean reuse) {
checkClosed();
checkConfigurable();
tcpHelper.setReuseAddress(reuse);
return this;
}
@Override
public int getSoLinger() {
checkClosed();
return tcpHelper.getSoLinger();
}
@Override
public DefaultHttpClient setSoLinger(int linger) {
checkClosed();
checkConfigurable();
tcpHelper.setSoLinger(linger);
return this;
}
@Override
public int getTrafficClass() {
checkClosed();
return tcpHelper.getTrafficClass();
}
@Override
public DefaultHttpClient setTrafficClass(int trafficClass) {
checkClosed();
checkConfigurable();
tcpHelper.setTrafficClass(trafficClass);
return this;
}
@Override
public int getConnectTimeout() {
checkClosed();
return tcpHelper.getConnectTimeout();
}
@Override
public DefaultHttpClient setConnectTimeout(int timeout) {
checkClosed();
checkConfigurable();
tcpHelper.setConnectTimeout(timeout);
return this;
}
@Override
public boolean isSSL() {
checkClosed();
return tcpHelper.isSSL();
}
@Override
public DefaultHttpClient setSSL(boolean ssl) {
checkClosed();
checkConfigurable();
tcpHelper.setSSL(ssl);
return this;
}
@Override
public boolean isVerifyHost() {
checkClosed();
return tcpHelper.isVerifyHost();
}
@Override
public DefaultHttpClient setVerifyHost(boolean verifyHost) {
checkClosed();
checkConfigurable();
tcpHelper.setVerifyHost(verifyHost);
return this;
}
@Override
public boolean isTrustAll() {
checkClosed();
return tcpHelper.isTrustAll();
}
@Override
public DefaultHttpClient setTrustAll(boolean trustAll) {
checkClosed();
checkConfigurable();
tcpHelper.setTrustAll(trustAll);
return this;
}
@Override
public String getKeyStorePath() {
checkClosed();
return tcpHelper.getKeyStorePath();
}
@Override
public DefaultHttpClient setKeyStorePath(String path) {
checkClosed();
checkConfigurable();
tcpHelper.setKeyStorePath(path);
return this;
}
@Override
public String getKeyStorePassword() {
checkClosed();
return tcpHelper.getKeyStorePassword();
}
@Override
public DefaultHttpClient setKeyStorePassword(String pwd) {
checkClosed();
checkConfigurable();
tcpHelper.setKeyStorePassword(pwd);
return this;
}
@Override
public String getTrustStorePath() {
checkClosed();
return tcpHelper.getTrustStorePath();
}
@Override
public DefaultHttpClient setTrustStorePath(String path) {
checkClosed();
checkConfigurable();
tcpHelper.setTrustStorePath(path);
return this;
}
@Override
public String getTrustStorePassword() {
checkClosed();
return tcpHelper.getTrustStorePassword();
}
@Override
public DefaultHttpClient setTrustStorePassword(String pwd) {
checkClosed();
checkConfigurable();
tcpHelper.setTrustStorePassword(pwd);
return this;
}
@Override
public HttpClient setUsePooledBuffers(boolean pooledBuffers) {
checkClosed();
checkConfigurable();
tcpHelper.setUsePooledBuffers(pooledBuffers);
return this;
}
@Override
public boolean isUsePooledBuffers() {
checkClosed();
return tcpHelper.isUsePooledBuffers();
}
@Override
public HttpClient setTryUseCompression(boolean tryUseCompression) {
checkClosed();
this.tryUseCompression = tryUseCompression;
return this;
}
@Override
public boolean getTryUseCompression() {
return tryUseCompression;
}
@Override
public HttpClient setMaxWebSocketFrameSize(int maxSize) {
maxWebSocketFrameSize = maxSize;
return this;
}
@Override
public int getMaxWebSocketFrameSize() {
return maxWebSocketFrameSize;
}
@Override
public HttpClient setMaxWaiterQueueSize(int maxWaiterQueueSize) {
checkClosed();
pool.setMaxWaiterQueueSize(maxWaiterQueueSize);
return this;
}
@Override
public int getMaxWaiterQueueSize() {
checkClosed();
return pool.getMaxWaiterQueueSize();
}
@Override
public HttpClient setConnectionMaxOutstandingRequestCount(int connectionMaxOutstandingRequestCount) {
checkClosed();
connectionMaxOutstandingRequest = connectionMaxOutstandingRequestCount;
return this;
}
@Override
public int getConnectionMaxOutstandingRequestCount() {
checkClosed();
return connectionMaxOutstandingRequest;
}
void getConnection(Handler handler, Handler connectionExceptionHandler, DefaultContext context) {
pool.getConnection(handler, connectionExceptionHandler, context);
}
void returnConnection(final ClientConnection conn) {
// prevent connection from taking more requests if it's fully occupied.
if (!conn.isFullyOccupied()) {
pool.returnConnection(conn);
}
}
void handleException(Exception e) {
if (exceptionHandler != null) {
exceptionHandler.handle(e);
} else {
async.reportException(e);
}
}
/**
* @return the async, for use in package related classes only.
*/
AsyncInternal getAsync() {
return async;
}
void internalConnect(final Handler connectHandler, final Handler connectErrorHandler) {
if (bootstrap == null) {
// Share the event loop thread to also serve the HttpClient's network traffic.
AsyncEventLoopGroup pool = new AsyncEventLoopGroup();
pool.addWorker(actualCtx.getEventLoop());
bootstrap = new Bootstrap();
bootstrap.group(pool);
bootstrap.channel(NioSocketChannel.class);
tcpHelper.checkSSL(async);
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (tcpHelper.isSSL()) {
pipeline.addLast("ssl", tcpHelper.createSslHandler(async, true, host, port));
}
pipeline.addLast("codec", new HttpClientCodec(4096, 8192, 8192, false, false));
if (tryUseCompression) {
pipeline.addLast("inflater", new HttpContentDecompressor(true));
}
pipeline.addLast("handler", new ClientHandler());
}
});
}
tcpHelper.applyConnectionOptions(bootstrap);
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture channelFuture) throws Exception {
final Channel ch = channelFuture.channel();
if (channelFuture.isSuccess()) {
if (tcpHelper.isSSL()) {
// TCP connected, so now we must do the SSL handshake
SslHandler sslHandler = ch.pipeline().get(SslHandler.class);
Future fut = sslHandler.handshakeFuture();
fut.addListener(new GenericFutureListener>() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.isSuccess()) {
connected(ch, connectHandler);
} else {
connectionFailed(ch, connectErrorHandler, new SSLHandshakeException("Failed to create SSL connection"));
}
}
});
} else {
connected(ch, connectHandler);
}
} else {
connectionFailed(ch, connectErrorHandler, channelFuture.cause());
}
}
});
}
private HttpClientRequest doRequest(String method, String uri, Handler responseHandler) {
configurable = false;
return new DefaultHttpClientRequest(this, method, uri, responseHandler, actualCtx);
}
private final void checkClosed() {
if (closed) {
throw new IllegalStateException("Client is closed");
}
}
private final void checkConfigurable() {
if (!configurable) {
throw new IllegalStateException("Can't set property after connect has been called");
}
}
private void connected(final Channel ch, final Handler connectHandler) {
actualCtx.execute(ch.eventLoop(), new Runnable() {
public void run() {
createConn(ch, connectHandler);
}
});
}
private void createConn(Channel ch, Handler connectHandler) {
final ClientConnection conn = new ClientConnection(async, DefaultHttpClient.this, ch,
tcpHelper.isSSL(), host, port, keepAlive, pipelining, actualCtx);
conn.closeHandler(new VoidHandler() {
public void handle() {
// The connection has been closed - tell the pool about it, this allows the pool to create more
// connections. Note the pool doesn't actually remove the connection, when the next person to get a connection
// gets the closed on, they will check if it's closed and if so get another one.
pool.connectionClosed(conn);
}
});
conn.setMaxOutstandingRequestCount(connectionMaxOutstandingRequest);
connectionMap.put(ch, conn);
connectHandler.handle(conn);
}
private void connectionFailed(final Channel ch, final Handler connectionExceptionHandler,
final Throwable t) {
// If no specific exception handler is provided, fall back to the HttpClient's exception handler.
final Handler exHandler = connectionExceptionHandler == null ? exceptionHandler : connectionExceptionHandler;
actualCtx.execute(ch.eventLoop(), new Runnable() {
public void run() {
pool.connectionClosed(null);
try {
ch.close();
} catch (Exception ignore) {
}
if (exHandler != null) {
exHandler.handle(t);
} else {
actualCtx.reportException(t);
}
}
});
}
@Override
protected void finalize() throws Throwable {
// Make sure this gets cleaned up if there are no more references to it
// so as not to leave connections and resources dangling until the system is shutdown
// which could make the JVM run out of file handles.
close();
super.finalize();
}
private class ClientHandler extends AsyncHttpHandler {
private boolean closeFrameSent;
public ClientHandler() {
super(async, DefaultHttpClient.this.connectionMap);
}
@Override
protected DefaultContext getContext(ClientConnection connection) {
return actualCtx;
}
@Override
protected void doMessageReceived(ClientConnection conn, ChannelHandlerContext ctx, Object msg) {
if (conn == null) {
return;
}
boolean valid = false;
if (msg instanceof HttpResponse) {
HttpResponse response = (HttpResponse) msg;
conn.handleResponse(response);
valid = true;
}
if (msg instanceof HttpContent) {
HttpContent chunk = (HttpContent) msg;
if (chunk.content().isReadable()) {
Buffer buff = new Buffer(chunk.content().slice());
conn.handleResponseChunk(buff);
}
if (chunk instanceof LastHttpContent) {
conn.handleResponseEnd((LastHttpContent) chunk);
}
valid = true;
} else if (msg instanceof WebSocketFrameInternal) {
WebSocketFrameInternal frame = (WebSocketFrameInternal) msg;
switch (frame.type()) {
case BINARY:
case CONTINUATION:
case TEXT:
conn.handleWsFrame(frame);
break;
case PING:
// Echo back the content of the PING frame as PONG frame as specified in RFC 6455 Section 5.5.2
ctx.writeAndFlush(new DefaultWebSocketFrame(WebSocketFrame.FrameType.PONG, frame.getBinaryData()));
break;
case CLOSE:
if (!closeFrameSent) {
// Echo back close frame and close the connection once it was written.
// This is specified in the WebSockets RFC 6455 Section 5.4.1
ctx.writeAndFlush(frame).addListener(ChannelFutureListener.CLOSE);
closeFrameSent = true;
}
break;
}
valid = true;
}
if (!valid) {
throw new IllegalStateException("Invalid object " + msg);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy