org.xlightweb.client.HttpClientConnection Maven / Gradle / Ivy
/*
* Copyright (c) xlightweb.org, 2008. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb.client;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xlightweb.AbstractHttpConnection;
import org.xlightweb.BodyDataSink;
import org.xlightweb.FutureResponseHandler;
import org.xlightweb.HttpRequest;
import org.xlightweb.HttpResponse;
import org.xlightweb.ResponseHandlerInfo;
import org.xlightweb.IHttpSession;
import org.xlightweb.HttpUtils;
import org.xlightweb.IBodyCompleteListener;
import org.xlightweb.IHttpConnection;
import org.xlightweb.IHttpConnectionHandler;
import org.xlightweb.IHttpExchange;
import org.xlightweb.IHttpMessage;
import org.xlightweb.IHttpRequest;
import org.xlightweb.IHttpRequestHandler;
import org.xlightweb.IHttpRequestHeader;
import org.xlightweb.IHttpResponse;
import org.xlightweb.IHttpResponseHandler;
import org.xlightweb.IHttpResponseHeader;
import org.xlightweb.IHttpSocketTimeoutHandler;
import org.xlightweb.RequestHandlerChain;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.SerializedTaskQueue;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;
/**
* Represents the client side endpoint implementation of a http connection. The HttpClientConnection supports
* constructors which accepts the remote address or a existing {@link INonBlockingConnection}. A INonBlockingConnection
* can become a HttpClientConnection at any time.
*
* @author [email protected]
*/
public final class HttpClientConnection extends AbstractHttpConnection implements IHttpClientEndpoint {
private static final Logger LOG = Logger.getLogger(HttpClientConnection.class.getName());
private static String implementationVersion;
// life cycle
private boolean isAutocloseAfterResponse = false;
private final AtomicBoolean isDisconnected = new AtomicBoolean(false);
// response timeout support
private static final Long DEFAULT_RESPONSE_TIMEOUT_SEC = Long.MAX_VALUE;
private static final long MIN_WATCHDOG_PERIOD_MILLIS = 30 * 1000;
private long responseTimeoutMillis = DEFAULT_RESPONSE_TIMEOUT_SEC;
private WatchDogTask watchDogTask;
// handler management
private final List bodyReceivingResponseHandlers = Collections.synchronizedList(new ArrayList());
private final ArrayList handlersWaitingForResponseHeader = new ArrayList();
// transaction monitor support
private ITransactionMonitor transactionMonitor;
/**
* constructor
*
* @param host the remote host
* @param port the remote port
* @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
* @throws IOException if an exception occurs
*/
public HttpClientConnection(String host, int port) throws IOException, ConnectException {
this(newNonBlockingConnection(new InetSocketAddress(host, port)), null);
}
/**
* constructor
*
* @param address the server address
* @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
* @throws IOException if an exception occurs
*/
public HttpClientConnection(InetSocketAddress address) throws IOException, ConnectException {
this(newNonBlockingConnection(address), null);
}
private static INonBlockingConnection newNonBlockingConnection(InetSocketAddress address) throws ConnectException {
try {
return new NonBlockingConnection(address);
} catch (IOException ioe) {
throw new ConnectException(ioe.toString());
}
}
/**
* constructor
*
* @param connection the underlying tcp connection
* @throws IOException if an exception occurs
*/
public HttpClientConnection(INonBlockingConnection connection) throws IOException {
this(connection, null);
init();
}
/**
* constructor
*
* @param host the remote host
* @param port the remote port
* @param connectionHandler the connection handler
* @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
* @throws IOException if an exception occurs
*/
public HttpClientConnection(String host, int port, IHttpConnectionHandler connectionHandler) throws IOException, ConnectException {
this(newNonBlockingConnection(new InetSocketAddress(host, port)), connectionHandler);
}
/**
* constructor
*
* @param connection the underlying tcp connection
* @param handler the handler
* @throws IOException if an exception occurs
*/
private HttpClientConnection(INonBlockingConnection connection, IHttpConnectionHandler connectionHandler) throws IOException {
super(connection, true);
if (connectionHandler != null) {
addConnectionHandler(connectionHandler);
}
init();
}
/**
* sets a transaction monitor
*
* @param transactionMonitor the transaction monitor
*/
void setTransactionMonitor(ITransactionMonitor transactionMonitor) {
this.transactionMonitor = transactionMonitor;
}
/**
* {@inheritDoc}
*/
@Override
protected void onIdleTimeout() throws IOException {
// notify waiting handler
for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
messageHandler.onException(new SocketTimeoutException("idle timeout " + DataConverter.toFormatedDuration(getIdleTimeoutMillis()) + " reached"));
}
handlersWaitingForResponseHeader.clear();
super.onIdleTimeout();
}
/**
* {@inheritDoc}
*/
@Override
protected void onConnectionTimeout() throws IOException {
// notify waiting handler
for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
messageHandler.onException(new SocketTimeoutException("connection timeout " + DataConverter.toFormatedDuration(getConnectionTimeoutMillis()) + " reached"));
}
handlersWaitingForResponseHeader.clear();
super.onConnectionTimeout();
}
@SuppressWarnings("unchecked")
private List getHandlersWaitingForResponseCopy() {
synchronized (handlersWaitingForResponseHeader) {
return (List) handlersWaitingForResponseHeader.clone();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onDisconnect() {
if (!isDisconnected.get()) {
isDisconnected.set(true);
// notify pending handlers
while (!bodyReceivingResponseHandlers.isEmpty()) {
bodyReceivingResponseHandlers.remove(0).onException(new ClosedChannelException());
}
// notify waiting handler
for (MessageHandler messageHandler : getHandlersWaitingForResponseCopy()) {
messageHandler.onException(new ClosedChannelException());
}
handlersWaitingForResponseHeader.clear();
super.onDisconnect();
}
}
/**
* generates a error page
*
*
* @param errorCode the error code
* @param msg the message
* @param id the connection id
* @return the error page
*/
protected static String generateErrorMessageHtml(int errorCode, String msg, String id) {
return AbstractHttpConnection.generateErrorMessageHtml(errorCode, msg, id);
}
/**
* schedules a timer task
*
* @param task the timer task
* @param delay the delay
* @param period the period
*/
protected static void schedule(TimerTask task, long delay, long period) {
AbstractHttpConnection.schedule(task, delay, period);
}
/**
* {@inheritDoc}
*/
public IHttpResponse call(IHttpRequest request) throws IOException, ConnectException, SocketTimeoutException {
// create response handler
FutureResponseHandler responseHandler = new FutureResponseHandler();
// send request
send(request, wrapResponsehandler(responseHandler));
return responseHandler.getResponse();
}
/**
* set if the connection should be closed after sending a response
*
* @param isAutocloseAfterResponse true, if the connection should be closed after sending a response
*/
void setAutocloseAfterResponse(boolean isAutocloseAfterResponse) {
this.isAutocloseAfterResponse = isAutocloseAfterResponse;
}
/**
* {@inheritDoc}
*/
public boolean isServerSide() {
return false;
}
/**
* {@inheritDoc}
*/
public void setResponseTimeoutMillis(long responseTimeoutMillis) {
if (this.responseTimeoutMillis != responseTimeoutMillis) {
this.responseTimeoutMillis = responseTimeoutMillis;
if (responseTimeoutMillis == Long.MAX_VALUE) {
terminateWatchDogTask();
} else {
long watchdogPeriod = 100;
if (responseTimeoutMillis > 1000) {
watchdogPeriod = responseTimeoutMillis / 10;
}
if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() +"] response timeout to " + DataConverter.toFormatedDuration(responseTimeoutMillis) + ". Updating wachdog tas to check period " + watchdogPeriod + " millis");
}
updateWatchDog(watchdogPeriod);
}
}
}
private synchronized void updateWatchDog(long watchDogPeriod) {
terminateWatchDogTask();
watchDogTask = new WatchDogTask(this);
schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
}
private synchronized void terminateWatchDogTask() {
if (watchDogTask != null) {
watchDogTask.cancel();
}
}
/**
* {@inheritDoc}
*/
public long getResponseTimeoutMillis() {
return responseTimeoutMillis;
}
private void checkTimeouts() {
try {
long currentMillis = System.currentTimeMillis();
for (Object hdl : handlersWaitingForResponseHeader.toArray()) {
boolean isTimeoutReached = ((MessageHandler) hdl).isResponseTimeoutReached(currentMillis);
if (isTimeoutReached) {
if (handlersWaitingForResponseHeader.remove(hdl)) {
onResponseTimeout((MessageHandler) hdl);
}
destroy();
}
}
} catch (Exception e) {
// eat and log exception
LOG.warning("exception occured by checking timouts. Reason: " + e.toString());
}
}
private void onResponseTimeout(MessageHandler internalResponseHandler) {
SocketTimeoutException ste = new SocketTimeoutException("response timeout " + DataConverter.toFormatedDuration(internalResponseHandler.getTimeout()) + " reached");
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(ste.getMessage());
}
performResponseTimeoutHandler(internalResponseHandler.getAppHandler(), ste);
}
private void performResponseTimeoutHandler(final IHttpResponseHandler handler, final SocketTimeoutException ste) {
final ResponseHandlerInfo responseHandlerInfo = AbstractHttpConnection.getResponseHandlerInfo(handler);
Runnable responseTimeoutHandlerCaller = new Runnable() {
public void run() {
try {
if ( responseHandlerInfo.isSocketTimeoutHandler()) {
IHttpSocketTimeoutHandler hdl = (IHttpSocketTimeoutHandler) handler;
hdl.onException(ste);
} else {
handler.onException(ste);
}
} catch (Exception e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" +getId() + "] error occured by calling on request " + handler + " " + e.toString());
}
throw new RuntimeException(e);
} finally {
destroy();
}
}
};
// ... and perform the handler
if (responseHandlerInfo.isSocketTimeoutHandlerMultithreaded()) {
getExecutor().processMultithreaded(responseTimeoutHandlerCaller);
} else {
getExecutor().processNonthreaded(responseTimeoutHandlerCaller);
}
}
/**
* {@inheritDoc}
*/
public BodyDataSink send(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (isOpen()) {
if (responseHandler == null) {
responseHandler = new DoNothingResponseHandler();
}
if (requestHeader.getContentLength() != -1) {
requestHeader.removeHeader("Content-Length");
}
if ((requestHeader.getTransferEncoding() == null)) {
requestHeader.setHeader("Transfer-Encoding", "chunked");
}
enhanceHeader(requestHeader);
try{
return sendInternal(requestHeader, responseHandler);
} catch (IOException ioe) {
String msg = "can not send request \r\n " + requestHeader.toString() +
"\r\n\r\nhttpConnection:\r\n" + this.toString() +
"\r\n\r\nreason:\r\n" + ioe.toString();
destroy();
throw new IOException(msg);
}
} else {
throw new ClosedChannelException();
}
}
private BodyDataSink sendInternal(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
incCountMessageSent();
synchronized (handlersWaitingForResponseHeader) {
handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, requestHeader, responseTimeoutMillis));
}
BodyDataSink bodyDataSink = writeMessage(requestHeader);
return bodyDataSink;
}
/**
* {@inheritDoc}
*/
public BodyDataSink send(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (isOpen()) {
if (responseHandler == null) {
responseHandler = new DoNothingResponseHandler();
}
if (requestHeader.getContentLength() != -1) {
requestHeader.removeHeader("Content-Length");
}
if ((requestHeader.getTransferEncoding() == null)) {
requestHeader.setHeader("Transfer-Encoding", "chunked");
}
enhanceHeader(requestHeader);
try{
if ((requestHeader.getTransferEncoding() != null) && (requestHeader.getTransferEncoding().equalsIgnoreCase("chunked"))) {
requestHeader.removeHeader("Transfer-Encoding");
}
if (requestHeader.getContentLength() == -1) {
requestHeader.setContentLength(contentLength);
}
return sendInternal(requestHeader, contentLength, responseHandler);
} catch (IOException ioe) {
String msg = "can not send request \r\n " + requestHeader.toString() +
"\r\n\r\nhttpConnection:\r\n" + this.toString() +
"\r\n\r\nreason:\r\n" + ioe.toString();
destroy();
throw new IOException(msg);
}
} else {
throw new ClosedChannelException();
}
}
private BodyDataSink sendInternal(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
incCountMessageSent();
synchronized (handlersWaitingForResponseHeader) {
handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, requestHeader, responseTimeoutMillis));
}
BodyDataSink bodyDataSink = writeMessage(requestHeader, contentLength);
return bodyDataSink;
}
/**
* {@inheritDoc}
*/
public void send(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (isOpen()) {
if (responseHandler == null) {
responseHandler = new DoNothingResponseHandler();
}
IHttpRequestHeader requestHeader = request.getRequestHeader();
enhanceHeader(requestHeader);
try {
sendInternal(request, responseHandler);
} catch (IOException ioe) {
String msg = "can not send request \r\n " + request.toString() +
"\r\n\r\nhttpConnection:\r\n" + this.toString() +
"\r\n\r\nreason:\r\n" + ioe.toString();
destroy();
throw new IOException(msg);
}
} else {
throw new ClosedChannelException();
}
}
private void sendInternal(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
incCountMessageSent();
synchronized (handlersWaitingForResponseHeader) {
handlersWaitingForResponseHeader.add(new MessageHandler(responseHandler, request.getRequestHeader(), responseTimeoutMillis));
}
// body less request?
if (request.getNonBlockingBody() == null) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] sending (bodyless): " + request.getRequestHeader());
}
BodyDataSink bodyDataSink = writeMessage(request.getRequestHeader(), 0);
bodyDataSink.setFlushmode(FlushMode.ASYNC);
bodyDataSink.close();
// no, request has body
} else {
if (request.getNonBlockingBody().getDataHandler() != null) {
throw new IOException("a body handler is already assigned to the message body. sending such messages is not supported (remove data handler)");
}
writeMessage(request);
}
}
private IHttpResponseHandler wrapResponsehandler(IHttpResponseHandler responseHandler) {
if (AbstractHttpConnection.getResponseHandlerInfo(responseHandler).isResponseHandlerInvokeOnMessageReceived()) {
return new InvokeOnMessageWrapper(responseHandler, this);
} else {
return responseHandler;
}
}
private void enhanceHeader(IHttpRequestHeader header) throws IOException {
String host = header.getHost();
if (host == null) {
header.setHost(getRemoteHostInfo());
}
String userAgent = header.getUserAgent();
if (userAgent == null) {
header.setUserAgent(getImplementationVersion());
}
}
private static String getImplementationVersion() {
if (implementationVersion == null) {
implementationVersion = "xLightweb/" + HttpUtils.getImplementationVersion();
}
return implementationVersion;
}
private String getRemoteHostInfo() throws IOException {
InetAddress remoteAddress = getRemoteAddress();
if (remoteAddress == null) {
return "";
}
int remotePort = getRemotePort();
return remoteAddress.getHostName() + ":" + remotePort;
}
/**
* {@inheritDoc}
*/
@Override
protected IMessageHandler getMessageHandler() {
synchronized (handlersWaitingForResponseHeader) {
if (handlersWaitingForResponseHeader.isEmpty()) {
return null;
} else {
return handlersWaitingForResponseHeader.remove(0);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getId() + " " + getUnderlyingTcpConnection().getLocalAddress() + ":" + getUnderlyingTcpConnection().getLocalPort() + " -> " + getUnderlyingTcpConnection().getRemoteAddress() + ":" + getUnderlyingTcpConnection().getRemotePort());
if (!getUnderlyingTcpConnection().isOpen()) {
sb.append(" (closed)");
}
return sb.toString();
}
/**
* message handler
*
* @author grro
*/
final class MessageHandler implements IMessageHandler, Runnable {
private final AtomicBoolean isCommitted = new AtomicBoolean(false);
private IHttpResponseHandler delegee = null;
private ResponseHandlerInfo delegeeInfo = null;
private IHttpRequestHeader requestHeader = null;
private IHttpResponse response = null;
private long timeout = Long.MAX_VALUE;
private long timeoutDate = Long.MAX_VALUE;
/**
* constructor
*
* @param delegee the response handler
* @param requestHeader the request header
* @param responseTimeout hte response timeout
*/
public MessageHandler(IHttpResponseHandler delegee, IHttpRequestHeader requestHeader, long responseTimeout) {
this.delegee = delegee;
this.delegeeInfo = AbstractHttpConnection.getResponseHandlerInfo(delegee);
this.requestHeader = requestHeader;
this.timeout = responseTimeout;
if ((responseTimeout != Long.MAX_VALUE) && (responseTimeout < Long.MAX_VALUE * 0.9)) {
timeoutDate = timeout + System.currentTimeMillis();
}
}
/**
* returns the response handler
*
* @return the response handler
*/
IHttpResponseHandler getAppHandler() {
return delegee;
}
/**
* returns if the response timeout is reached
*
* @param currentMillis the current time
* @return true, if the respnose timeout is reached
*/
boolean isResponseTimeoutReached(long currentMillis) {
if (isCommitted.get()) {
return false;
}
return (currentMillis > timeoutDate);
}
/**
* returns the timeout
*
* @return the timeout
*/
long getTimeout() {
return timeout;
}
/**
* {@inheritDoc}
*/
public boolean isBodylessMessageExpected() {
if (requestHeader.getMethod().equals(IHttpMessage.HEAD_METHOD) ||
requestHeader.getMethod().equals(IHttpMessage.OPTIONS_METHOD) ||
requestHeader.getMethod().equals(IHttpMessage.CONNECT_METHOD)) {
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public void onMessage(IHttpMessage message) throws IOException {
this.response = (IHttpResponse) message;
assert (Thread.currentThread().getName().startsWith("xDispatcher"));
incCountMessageReceived();
if (transactionMonitor != null) {
transactionMonitor.addTransaction(HttpClientConnection.this, requestHeader, response.getResponseHeader());
}
if (LOG.isLoggable(Level.FINE)) {
if (response.getNonBlockingBody() == null) {
LOG.fine("[" + getId() + "] bodyless response received from " + getRemoteAddress() +
":" + getRemotePort() +
" (" + getCountMessagesReceived() + ". request) " + response.getMessageHeader().toString());
} else {
String body = "";
String contentType = response.getContentType();
if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencode"))) {
body = response.getNonBlockingBody().toString() + "\n";
}
LOG.fine("[" + getId() + "]response received from " + getRemoteAddress() +
":" + getRemotePort() +
" (" + getCountMessagesReceived() + ". request) " + response.getMessageHeader().toString() + body);
}
}
// handle life cycle headers
handleLifeCycleHeaders(response);
// swallow 100 response
if (response.getStatus() == 100) {
synchronized (handlersWaitingForResponseHeader) {
ArrayList hdls = new ArrayList();
handlersWaitingForResponseHeader.removeAll(hdls);
handlersWaitingForResponseHeader.add(this);
handlersWaitingForResponseHeader.addAll(hdls);
}
setPersistent(true);
return;
}
// if response code 5xx -> set connection not reusable
if ((response.getStatus() >= 500) && (response.getStatus() < 600)) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("got return code 5xx. Set connection " + getId() + " to non persistent");
}
setPersistent(false);
}
if (isPersistent() == false) {
if (response.hasBody()) {
setDestroyConnectionAfterReceived(response.getNonBlockingBody(), true);
} else {
destroy(); // destroy required, because underlying tcp connection could be a pooled one
}
} else {
if (isAutocloseAfterResponse) {
if (response.hasBody()) {
setCloseConnectionAfterReceived(response.getNonBlockingBody(), true);
} else {
closeSilence();
}
}
}
if (response.hasBody() && delegeeInfo.isResponseHandlerInvokeOnMessageReceived()) {
bodyReceivingResponseHandlers.add(this);
IBodyCompleteListener cl = new IBodyCompleteListener() {
@Execution(Execution.NONTHREADED)
public void onComplete() throws IOException {
bodyReceivingResponseHandlers.remove(this);
isCommitted.set(true);
if (delegeeInfo.isResponseHandlerMultithreaded()) {
getExecutor().processMultithreaded(MessageHandler.this);
} else {
getExecutor().processNonthreaded(MessageHandler.this);
}
}
};
response.getNonBlockingBody().addCompleteListener(cl);
} else {
isCommitted.set(true);
if (delegeeInfo.isResponseHandlerMultithreaded()) {
getExecutor().processMultithreaded(this);
} else {
getExecutor().processNonthreaded(this);
}
}
}
/**
* {@inheritDoc}
*/
public void run() {
try {
delegee.onResponse(response);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by calling onResponse of " + delegee + " " + ioe.toString());
}
}
}
private void handleLifeCycleHeaders(IHttpResponse response) throws IOException {
// if HTTP 1.1 -> connection is persistent by default
if ((response.getProtocol() != null) && response.getProtocol().equals("HTTP/1.1")) {
setPersistent(true);
} else {
// response of connect is persistent, implicitly
if (requestHeader.getMethod().equals(IHttpMessage.CONNECT_METHOD)) {
setPersistent(true);
}
}
// handle connection header
handleConnectionHeaders(response.getResponseHeader());
}
private void handleConnectionHeaders(IHttpResponseHeader responseHeader) throws IOException {
String keepAliveHeader = responseHeader.getKeepAlive();
if (keepAliveHeader != null) {
String[] tokens = keepAliveHeader.split(",");
for (String token : tokens) {
handleKeepAlive(token);
}
}
//check if persistent connection
String connectionHeader = responseHeader.getConnection();
if (connectionHeader != null) {
String[] values = connectionHeader.split(",");
for (String value : values) {
value = value.trim();
if (value.equalsIgnoreCase("close")) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("[" + getId() + " http client connection received 'connection: close' header. set isPersistent=false");
}
setPersistent(false);
}
}
}
}
private void handleKeepAlive(String option) {
if (option.toUpperCase().startsWith("TIMEOUT=")) {
int timeoutSec = Integer.parseInt(option.substring("TIMEOUT=".length(), option.length()));
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("response keep alive defines timout. set timeout " + timeoutSec + " sec");
}
setResponseTimeoutMillis(timeoutSec * 1000L);
} else if (option.toUpperCase().startsWith("MAX=")) {
int maxTransactions = Integer.parseInt(option.substring("MAX=".length(), option.length()));
if (maxTransactions == 0) {
setPersistent(false);
}
} else {
Integer timeoutSec = Integer.parseInt(option);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("response keep alive defines timout. set timeout " + timeoutSec + " sec");
}
setResponseTimeoutMillis(timeoutSec * 1000L);
}
}
/**
* {@inheritDoc}
*/
public void onException(final IOException ioe) {
if (isCommitted.get()) {
return;
}
isCommitted.set(true);
if ((ioe instanceof SocketTimeoutException) && (delegeeInfo.isSocketTimeoutHandler())) {
Runnable task = new OnSocketTimeoutExceptionCaller((SocketTimeoutException) ioe, (IHttpSocketTimeoutHandler) delegee);
if (delegeeInfo.isSocketTimeoutHandlerMultithreaded()) {
getExecutor().processMultithreaded(task);
} else {
getExecutor().processNonthreaded(task);
}
} else {
Runnable task = new OnIOExceptionCaller(ioe, delegee);
if (delegeeInfo.isResponseExeptionHandlerMultithreaded()) {
getExecutor().processMultithreaded(task);
} else {
getExecutor().processNonthreaded(task);
}
}
}
}
private static final class OnSocketTimeoutExceptionCaller implements Runnable {
private SocketTimeoutException ste = null;
private IHttpSocketTimeoutHandler hdl = null;
public OnSocketTimeoutExceptionCaller(SocketTimeoutException ste, IHttpSocketTimeoutHandler hdl) {
this.ste = ste;
this.hdl = hdl;
}
public void run() {
hdl.onException(ste);
}
}
private static final class OnIOExceptionCaller implements Runnable {
private IOException ioe = null;
private IHttpResponseHandler hdl = null;
public OnIOExceptionCaller(IOException ioe, IHttpResponseHandler hdl) {
this.ioe = ioe;
this.hdl = hdl;
}
public void run() {
try {
hdl.onException(ioe);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by performing onException " + hdl + ". reason: " + ioe.toString());
}
}
}
}
private static final class WatchDogTask extends TimerTask {
private WeakReference httpClientConnectionRef = null;
public WatchDogTask(HttpClientConnection httpClientConnection) {
httpClientConnectionRef = new WeakReference(httpClientConnection);
}
@Override
public void run() {
HttpClientConnection httpClientConnection = httpClientConnectionRef.get();
if (httpClientConnection == null) {
this.cancel();
} else {
httpClientConnection.checkTimeouts();
}
}
}
/**
* wrapper
*
* @author [email protected]
*/
static final class InvokeOnMessageWrapper implements IHttpResponseHandler {
private static final Logger LOG = Logger.getLogger(InvokeOnMessageWrapper.class.getName());
private IHttpResponseHandler delegee = null;
private ResponseHandlerInfo delegeeInfo = null;
private HttpClientConnection connection = null;
public InvokeOnMessageWrapper(IHttpResponseHandler delegee, HttpClientConnection connection) {
this.delegee = delegee;
this.delegeeInfo = AbstractHttpConnection.getResponseHandlerInfo(delegee);
this.connection = connection;
}
@Execution(Execution.NONTHREADED)
public void onResponse(final IHttpResponse response) throws IOException {
final Runnable task = new Runnable() {
public void run() {
try {
delegee.onResponse(response);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by calling onResponse of " + delegee + " " + ioe.toString());
}
}
}
};
if (response.hasBody()) {
IBodyCompleteListener cl = new IBodyCompleteListener() {
public void onComplete() throws IOException {
if (delegeeInfo.isResponseHandlerMultithreaded()) {
connection.getExecutor().processMultithreaded(task);
} else {
connection.getExecutor().processNonthreaded(task);
}
}
};
response.getNonBlockingBody().addCompleteListener(cl);
} else {
if (delegeeInfo.isResponseHandlerMultithreaded()) {
connection.getExecutor().processMultithreaded(task);
} else {
connection.getExecutor().processNonthreaded(task);
}
}
}
@Execution(Execution.NONTHREADED)
public void onException(final IOException ioe) {
Runnable task = new Runnable() {
public void run() {
try {
delegee.onException(ioe);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by performing onException " + delegee + ". reason: " + ioe.toString());
}
}
}
};
if (delegeeInfo.isResponseExeptionHandlerMultithreaded()) {
connection.getExecutor().processMultithreaded(task);
} else {
connection.getExecutor().processNonthreaded(task);
}
}
}
static BodyDataSink handle(HttpClient httpClient, IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler, RequestHandlerChain requestHandlerChain, Executor workerpool) throws IOException {
ClientExchange exchange = new ClientExchange(httpClient, workerpool);
DataSourceSinkPair pair = newBodyDataSourceSinkPair(null, exchange.getExecutor(), requestHeader.getCharacterEncoding());
IHttpRequest request = new HttpRequest(requestHeader, pair.getBodyDataSource());
exchange.init(request, responseHandler);
requestHandlerChain.onRequest(exchange);
return pair.getBodyDataSink();
}
static final class ClientExchange implements IHttpExchange {
private boolean isResponseCommitted = false;
private HttpClientConnection con = null;
private IHttpRequest request = null;
private String targetURL = null;
private IHttpResponseHandler responseHandler = null;
private ResponseHandlerInfo responseHandlerInfo = null;
private HttpClient httpClient = null;
private IMultimodeExecutor executor = null;
public ClientExchange(HttpClient httpClient, Executor workerpool) {
this.httpClient = httpClient;
executor = new MultimodeExecutor(workerpool);
}
void init(IHttpRequest request, IHttpResponseHandler responseHandler) {
this.request = request;
this.responseHandler = responseHandler;
this.responseHandlerInfo = AbstractHttpConnection.getResponseHandlerInfo(responseHandler);
targetURL = request.getRequestUrl().toString();
if (targetURL.indexOf("?") != -1) {
targetURL = targetURL.substring(0, targetURL.indexOf("?"));
}
}
IMultimodeExecutor getExecutor() {
return executor;
}
/**
* {@inheritDoc}
*/
public IHttpRequest getRequest() {
return request;
}
/**
* {@inheritDoc}
*/
public IHttpSession getSession(boolean create) {
return httpClient.getSessionManager().getSession(httpClient, request.getRemoteHost(), request.getRequestURI(), create);
}
public String encodeURL(String url) {
return url;
}
/**
* {@inheritDoc}
*/
public void forward(IHttpRequest request) throws IOException, ConnectException {
forward(request, new ForwardingResponseHandler(this));
}
/**
* {@inheritDoc}
*/
public void forward(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (responseHandler == null) {
responseHandler = new DoNothingResponseHandler();
}
URL targetURL = request.getRequestUrl();
con = httpClient.getConnection(request.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), (IHttpRequestHandler) null);
con.send(request, responseHandler);
}
/**
* {@inheritDoc}
*/
public BodyDataSink forward(IHttpRequestHeader requestHeader) throws IOException, ConnectException, IllegalStateException {
return forward(requestHeader, new ForwardingResponseHandler(this));
}
/**
* {@inheritDoc}
*/
public BodyDataSink forward(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (responseHandler == null) {
responseHandler = new DoNothingResponseHandler();
}
if (requestHeader.getContentLength() != -1) {
requestHeader.removeHeader("Content-Length");
}
if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
throw new IOException(requestHeader.getMethod() + " is a bodyless request");
}
if ((requestHeader.getTransferEncoding() == null)) {
requestHeader.setHeader("Transfer-Encoding", "chunked");
}
URL targetURL = requestHeader.getRequestUrl();
con = httpClient.getConnection(requestHeader.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), null);
return con.send(requestHeader, responseHandler);
}
public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength) throws IOException, ConnectException, IllegalStateException {
return forward(requestHeader, contentLength, new ForwardingResponseHandler(this));
}
/**
* {@inheritDoc}
*/
public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (responseHandler == null) {
responseHandler = new DoNothingResponseHandler();
}
if ((requestHeader.getTransferEncoding() != null) && (requestHeader.getTransferEncoding().equalsIgnoreCase("chunked"))) {
requestHeader.removeHeader("Transfer-Encoding");
}
if (requestHeader.getMethod().equalsIgnoreCase("GET") || requestHeader.getMethod().equalsIgnoreCase("HEAD")) {
throw new IOException(requestHeader.getMethod() + " is a bodyless request");
}
if (requestHeader.getContentLength() == -1) {
requestHeader.setContentLength(contentLength);
}
URL targetURL = requestHeader.getRequestUrl();
con = httpClient.getConnection(requestHeader.isSecure(), targetURL.getHost(), targetURL.getPort(), targetURL.getProtocol(), null);
return con.send(requestHeader, contentLength, responseHandler);
}
public BodyDataSink send(IHttpResponseHeader header) throws IOException, IllegalStateException {
if (header.getContentLength() != -1) {
header.removeHeader("Content-Length");
}
if ((header.getTransferEncoding() == null)) {
header.setHeader("Transfer-Encoding", "chunked");
}
DataSourceSinkPair pair = newBodyDataSourceSinkPair((AbstractHttpConnection) getConnection(), executor, header.getCharacterEncoding());
send(new HttpResponse(header, pair.getBodyDataSource()));
return pair.getBodyDataSink();
}
public BodyDataSink send(IHttpResponseHeader header, int contentLength) throws IOException, IllegalStateException {
if ((header.getTransferEncoding() != null) && (header.getTransferEncoding().equalsIgnoreCase("chunked"))) {
header.removeHeader("Transfer-Encoding");
}
if (header.getContentLength() == -1) {
header.setContentLength(contentLength);
}
DataSourceSinkPair pair = newBodyDataSourceSinkPair((AbstractHttpConnection) getConnection(), executor, header.getCharacterEncoding());
send(new HttpResponse(header, pair.getBodyDataSource()));
return pair.getBodyDataSink();
}
/**
* {@inheritDoc}
*/
public void send(final IHttpResponse response) throws IOException, IllegalStateException {
if (isResponseCommitted) {
throw new IllegalStateException("response is already committed");
}
isResponseCommitted = true;
if (responseHandler == null) {
LOG.warning("response will not been send, because no response handler is assigned");
return;
}
if (responseHandlerInfo.isResponseHandlerInvokeOnMessageReceived()) {
IBodyCompleteListener completeListener = new IBodyCompleteListener() {
@Execution(Execution.NONTHREADED)
public void onComplete() throws IOException {
performOnResponse(response);
}
};
response.getNonBlockingBody().addCompleteListener(completeListener);
} else {
performOnResponse(response);
}
}
private void performOnResponse(final IHttpResponse response) throws IOException {
Runnable task = new Runnable() {
public void run() {
try {
responseHandler.onResponse(response);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured by performing on response on " + responseHandler);
}
}
}
};
if (responseHandlerInfo.isResponseHandlerMultithreaded()) {
executor.processMultithreaded(task);
} else {
executor.processNonthreaded(task);
}
}
public IHttpConnection getConnection() {
return con;
}
public void destroy() {
if (con != null) {
con.destroy();
}
}
public void sendError(int errorCode) throws IllegalStateException {
sendError(errorCode, HttpUtils.getReason(errorCode));
}
public void sendError(int errorCode, String msg) throws IllegalStateException {
try {
String id = "";
IHttpConnection connection = getConnection();
if (connection != null) {
id = connection.getId();
}
send(new HttpResponse(errorCode, "text/html", HttpClientConnection.generateErrorMessageHtml(errorCode, msg, id)));
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("could not send error message " + errorCode + " reason " + ioe.toString());
}
destroy();
}
}
public void sendError(final Exception e) {
if (isResponseCommitted) {
throw new IllegalStateException("response is already committed");
}
isResponseCommitted = true;
if (responseHandlerInfo.isSocketTimeoutHandler() && (e instanceof SocketTimeoutException)) {
Runnable task = new Runnable() {
public void run() {
((IHttpSocketTimeoutHandler) responseHandler).onException((SocketTimeoutException) e);
}
};
if (responseHandlerInfo.isSocketTimeoutHandlerMultithreaded()) {
executor.processMultithreaded(task);
} else {
executor.processNonthreaded(task);
}
} else if (e instanceof IOException) {
Runnable task = new Runnable() {
public void run() {
try {
responseHandler.onException((IOException) e);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Error occured by performing onException " + responseHandler + ". reason: " + ioe.toString());
}
}
}
};
if (responseHandlerInfo.isResponseExeptionHandlerMultithreaded()) {
executor.processMultithreaded(task);
} else {
executor.processNonthreaded(task);
}
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("error occured. sendig 500. Error: " + DataConverter.toString(e));
}
sendError(500);
}
}
}
private static final class MultimodeExecutor implements IMultimodeExecutor {
private final SerializedTaskQueue taskQueue = new SerializedTaskQueue();
private Executor workerpool = null;
public MultimodeExecutor(Executor workerpool) {
this.workerpool = workerpool;
}
public void processMultithreaded(Runnable task) {
taskQueue.performMultiThreaded(task, workerpool);
}
public void processNonthreaded(Runnable task) {
taskQueue.performNonThreaded(task);
}
}
@Execution(Execution.NONTHREADED)
private static final class ForwardingResponseHandler implements IHttpResponseHandler {
private IHttpExchange exchange = null;
public ForwardingResponseHandler(IHttpExchange exchange) {
this.exchange = exchange;
}
public void onResponse(IHttpResponse response) throws IOException {
exchange.send(response);
}
public void onException(IOException ioe) throws IOException {
exchange.sendError(ioe);
}
}
@Execution(Execution.NONTHREADED)
static final class DoNothingResponseHandler implements IHttpResponseHandler {
public void onResponse(IHttpResponse response) throws IOException { }
public void onException(IOException ioe) throws IOException { }
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy