/*
* Copyright (c) xlightweb.org, 2008 - 2009. 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.server;
import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.ConnectException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xlightweb.AbstractHttpConnection;
import org.xlightweb.BadMessageException;
import org.xlightweb.BodyDataSink;
import org.xlightweb.BodyForwarder;
import org.xlightweb.HttpResponse;
import org.xlightweb.HttpResponseHeader;
import org.xlightweb.IBodyCompleteListener;
import org.xlightweb.IHttpHeader;
import org.xlightweb.IHttpSession;
import org.xlightweb.HttpUtils;
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.NonBlockingBodyDataSource;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.connection.INonBlockingConnection;
/**
* Represents the server side endpoint implementation of a http connection. Typically, a
* HttpServerConnection will be created by the {@link HttpProtocolAdapter} which is used
* internally by the {@link HttpServer}.
*
* @author grro@xlightweb.org
*/
public final class HttpServerConnection extends AbstractHttpConnection {
private static final Logger LOG = Logger.getLogger(HttpServerConnection.class.getName());
public static final boolean DEFAULT_REMOVE_REQUEST_CONNECTION_HEADER = false;
public static final Integer DEFAULT_RECEIVE_TIMEOUT_MILLIS = Integer.MAX_VALUE;
public static final boolean DEFAULT_AUTOCONFIRM_EXPECT_100CONTINUE_HEADER = true;
public static final boolean DEFAULT_AUTOHANDLE_CONNECTION_UPGRADE_HEADER = true;
public static final boolean DEFAULT_SESSION_MANAGEMENT = true;
static final int MIN_REQUEST_TIMEOUT_MILLIS = 1000;
// close management
private final ConnectionCloser connectionCloser = new ConnectionCloser();
private boolean isCloseOnSendingError = true;
// timeout support
private static final long MIN_WATCHDOG_PERIOD_MILLIS = 10 * 1000;
private TimeoutWatchDogTask watchDogTask;
private Long requestTimeoutMillis;
// flags
boolean isAutconfirmExpect100ContinueHeader = DEFAULT_AUTOCONFIRM_EXPECT_100CONTINUE_HEADER;
boolean isAutohandleUpgadeHeader = DEFAULT_AUTOHANDLE_CONNECTION_UPGRADE_HEADER;
// session support
private static final boolean IS_SESSION_COOKIE_HTTPONLY = Boolean.parseBoolean(System.getProperty("org.xlightweb.server.sessionCookieHttpOnly", "true"));
private final ISessionManager sessionManager;
private final int sessionMaxInactiveIntervalSec;
private boolean useCookies = true;
// max transaction support
private Integer maxTransactions;
// transaction monitor support
private final TransactionMonitor transactionMonitor;
// message handling
private final IMessageHeaderHandler messageHandler = new MessageHeaderHandler();
// handler support
private final RequestHandlerAdapter requestHandlerAdapter;
/**
* constructor
*
* @param sessionManager the session manager
* @param transactionMonitor the transaction monitor or <code>null</code>
* @param tcpConnection the tcp connection
* @param requestHandler the request handler
* @param requestHandlerInfo the request handle info
* @param isCloseOnSendingError true, if connection should be closed by sending an error
* @param connectionHandlers the connection handler
* @param useCookies true, if cookies is used for session state management
* @throws IOException if an exception occurs
*/
HttpServerConnection(ISessionManager sessionManager, TransactionMonitor transactionMonitor, int defaultMaxInactiveIntervalSec, INonBlockingConnection tcpConnection, Object requestHandlerAdapter, boolean isCloseOnSendingError, List<IHttpConnectionHandler> connectionHandlers, boolean useCookies) throws IOException {
super(tcpConnection, false);
this.sessionManager = sessionManager;
this.transactionMonitor = transactionMonitor;
this.sessionMaxInactiveIntervalSec = defaultMaxInactiveIntervalSec;
this.isCloseOnSendingError = isCloseOnSendingError;
this.useCookies = useCookies;
this.requestHandlerAdapter = (RequestHandlerAdapter) requestHandlerAdapter;
if (connectionHandlers != null) {
for (IHttpConnectionHandler connectionHandler : connectionHandlers) {
addConnectionHandler(connectionHandler);
}
}
init();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] http server connection established");
}
}
/**
* {@inheritDoc}
*/
public boolean isServerSide() {
return true;
}
/**
* set the max transactions per connection. Setting this filed causes that
* a keep-alive response header will be added
*
* @param maxTransactions the max transactions
*/
public void setMaxTransactions(int maxTransactions) {
this.maxTransactions = maxTransactions;
}
/**
* schedules the task
*
* @param task the task to schedule
* @param delay the delay
* @param period the period
*/
protected static void schedule(TimerTask task, long delay, long period) {
AbstractHttpConnection.schedule(task, delay, period);
}
protected static int getSizeDataReceived(NonBlockingBodyDataSource body) {
return AbstractHttpConnection.getSizeDataReceived(body);
}
protected static int getSizeWritten(BodyDataSink body) {
return AbstractHttpConnection.getSizeWritten(body);
}
static Object wrap(IHttpRequestHandler requestHandler) {
return new RequestHandlerAdapter(requestHandler);
}
/**
* set the max receive timeout which is accepted for the connection. Setting this
* field cases, that a keep-alive response header will be added
*
* @param requestTimout the timeout
*/
public void setRequestTimeoutMillis(long requestTimeoutMillis) {
if (requestTimeoutMillis < MIN_REQUEST_TIMEOUT_MILLIS) {
LOG.warning("[" + getId() + "] try to set request timeout with " + requestTimeoutMillis + " millis. This value will be ignored because it is smaller that " + MIN_REQUEST_TIMEOUT_MILLIS + " millis");
}
if (requestTimeoutMillis <= 0) {
requestHandlerAdapter.onRequestTimeout(this);
return;
}
setLastTimeDataReceivedMillis(System.currentTimeMillis());
if ((this.requestTimeoutMillis == null) || (this.requestTimeoutMillis != requestTimeoutMillis)) {
this.requestTimeoutMillis = requestTimeoutMillis;
if (requestTimeoutMillis == Long.MAX_VALUE) {
terminateWatchDogTask();
} else {
long watchdogPeriod = 100;
if (requestTimeoutMillis > 1000) {
watchdogPeriod = requestTimeoutMillis / 10;
}
if (watchdogPeriod > MIN_WATCHDOG_PERIOD_MILLIS) {
watchdogPeriod = MIN_WATCHDOG_PERIOD_MILLIS;
}
updateWatchDog(watchdogPeriod);
}
}
}
private void checkRequestTimeout(long currentTimeMillis) {
if (requestTimeoutMillis != null) {
long timeoutReceived = getLastTimeDataReceivedMillis() + requestTimeoutMillis;
long timeoutSend = getLastTimeWritten() + requestTimeoutMillis;
if ((currentTimeMillis > timeoutReceived) && (currentTimeMillis > timeoutSend)) {
onRequestTimeout();
}
}
}
private void onRequestTimeout() {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] request timeout " + DataConverter.toFormatedDuration(requestTimeoutMillis) + " reached");
}
terminateWatchDogTask();
requestHandlerAdapter.onRequestTimeout(this);
}
private synchronized void updateWatchDog(long watchDogPeriod) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] update timeout watchdog task period to " + DataConverter.toFormatedDuration(watchDogPeriod));
}
terminateWatchDogTask();
watchDogTask = new TimeoutWatchDogTask(this);
AbstractHttpConnection.schedule(watchDogTask, watchDogPeriod, watchDogPeriod);
}
private synchronized void terminateWatchDogTask() {
if (watchDogTask != null) {
watchDogTask.close();
watchDogTask = null;
}
}
private static final class TimeoutWatchDogTask extends TimerTask implements Closeable {
private WeakReference<HttpServerConnection> connectionRef = null;
public TimeoutWatchDogTask(HttpServerConnection connection) {
connectionRef = new WeakReference<HttpServerConnection>(connection);
}
@Override
public void run() {
WeakReference<HttpServerConnection> ref = connectionRef;
if (ref != null) {
HttpServerConnection connection = ref.get();
if (connection == null) {
this.close();
} else {
connection.checkRequestTimeout(System.currentTimeMillis());
}
}
}
public void close() {
this.cancel();
connectionRef = null;
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onProtocolException(Exception ex) {
if (ex instanceof BadMessageException) {
setPersistent(false);
try {
sendResponseMessage(new HttpResponse(400, "text/html", generateErrorMessageHtml(400, ex.getMessage(), getId())));
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] could not send error message " + 400 + " reason " + ioe.toString());
}
destroy();
}
} else {
super.onProtocolException(ex);
}
}
@Override
protected void onPostMessageWrite() {
if (!isPersistent()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] connection is not persistent. destroying it");
}
destroy();
}
}
/**
* {@inheritDoc}
*/
@Override
protected IMessageHeaderHandler getMessageHeaderHandler() {
return messageHandler;
}
@Override
protected void onDisconnect() {
super.onDisconnect();
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] http server connection destroyed");
}
}
/*
private void suspendMessageReceiving() {
try {
super.suspendReceiving();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by suspending read " + ioe.toString());
}
}
}
private void resumeMessageReceiving() {
try {
super.resumeReceiving();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by resuming read " + ioe.toString());
}
}
}
*/
private void handleLifeCycleHeaders(IHttpRequest request) {
IHttpRequestHeader header = request.getRequestHeader();
String keepAliveHeader = header.getKeepAlive();
if (keepAliveHeader != null) {
String[] tokens = keepAliveHeader.split(",");
for (String token : tokens) {
handleKeepAlive(token);
}
}
String connectionHeader = header.getConnection();
if (connectionHeader != null) {
String[] values = connectionHeader.split(",");
for (String value : values) {
value = value.trim();
if (value.equalsIgnoreCase("close")) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] received connection: closed header. destroying connection");
}
setPersistent(false);
}
if (value.equalsIgnoreCase("Keep-Alive")) {
setPersistent(true);
}
}
}
}
private void handleKeepAlive(String option) {
Integer timeoutSec = null;
if (option.toUpperCase().startsWith("TIMEOUT=")) {
timeoutSec = Integer.parseInt(option.substring("TIMEOUT=".length(), option.length()));
} else {
timeoutSec = Integer.parseInt(option);
}
if ((timeoutSec != null) && (getBodyDataReceiveTimeoutMillis() == Long.MAX_VALUE)) {
setBodyDataReceiveTimeoutMillis(timeoutSec * 1000L);
}
}
private BodyDataSink sendResponseHeader(IHttpResponseHeader header) throws IOException {
try{
enhanceResponseHeader(header);
BodyDataSink bodyDataSink = writeMessage(header);
if (!isPersistent()) {
setBodyCloseListener(bodyDataSink, connectionCloser);
}
return bodyDataSink;
} catch (IOException ioe) {
destroy();
throw ioe;
}
}
private BodyDataSink sendResponseHeader(IHttpResponseHeader header, int contentLength) throws IOException {
try{
enhanceResponseHeader(header);
BodyDataSink bodyDataSink = writeMessage(header, contentLength);
if(!isPersistent()) {
setBodyCloseListener(bodyDataSink, connectionCloser);
}
return bodyDataSink;
} catch (IOException ioe) {
destroy();
throw ioe;
}
}
/**
* @deprecated
*/
public void send(IHttpResponse response) throws IOException {
sendResponseMessage(response);
}
private BodyDataSink sendResponseMessage(IHttpResponse response) throws IOException {
try{
IHttpResponseHeader responseHeader = response.getResponseHeader();
enhanceResponseHeader(responseHeader);
// does the message contain a body?
if (response.hasBody()) {
if (response.getNonBlockingBody().getDataHandler() != null) {
LOG.warning("[" + getId() + "] a body handler is already assigned to the message body. current body handler will be removed");
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] sending: " + response.getResponseHeader());
}
return writeMessage(response);
// ... no its bodyless
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] sending (bodyless): " + response.getResponseHeader());
}
if (response.getContentLength() == -1) {
response.setContentLength(0);
}
BodyDataSink bodyDataSink = writeMessage(responseHeader, 0);
bodyDataSink.setFlushmode(FlushMode.ASYNC);
bodyDataSink.close();
if(!isPersistent()) {
closeQuitly();
}
return null;
}
} catch (IOException ioe) {
destroy();
throw ioe;
}
}
private void enhanceResponseHeader(IHttpResponseHeader header) {
String server = header.getServer();
if (server == null) {
header.setServer(ServerUtils.getComponentInfo());
}
int remainingTransactions = 0;
if (maxTransactions != null) {
remainingTransactions = maxTransactions - getCountMessagesReceived();
if (remainingTransactions <= 0) {
setPersistent(false);
}
}
if ((isPersistent() && (header.getConnection() == null)) &&
((maxTransactions != null) || (requestTimeoutMillis != null))) {
header.setConnection("Keep-Alive");
String keepAliveValue = null;
if (maxTransactions != null) {
keepAliveValue = "max=" + remainingTransactions;
}
if (requestTimeoutMillis != null) {
if (keepAliveValue == null) {
keepAliveValue = "timeout=" + requestTimeoutMillis / 1000;
} else {
keepAliveValue += ", timeout=" + requestTimeoutMillis / 1000;
}
}
header.setHeader("Keep-Alive", keepAliveValue);
}
String connectionHeader = header.getConnection();
if ((connectionHeader != null) && connectionHeader.equalsIgnoreCase("close")) {
setPersistent(false);
} else {
if (!isPersistent()) {
header.setConnection("close");
}
}
}
private HttpSession getSession(IHttpRequest request) throws IOException {
HttpSession session = null;
if (useCookies) {
List<String> cookieHeaders = request.getHeaderList("Cookie");
for (String cookieHeader : cookieHeaders) {
String[] cookies = cookieHeader.split(";");
for (String cookie : cookies) {
cookie = cookie.trim();
int idx = cookie.indexOf("=");
if (idx != -1) {
String name = cookie.substring(0, idx);
if (name.equals("JSESSIONID")) {
String sessionId = cookie.substring(idx + 1, cookie.length()).trim();
session = sessionManager.getSession(sessionId);
if (session != null) {
return session;
}
}
}
}
}
} else {
String sessionToken = request.getMatrixParameter("jsessionid");
if (sessionToken != null) {
request.removeMatrixParameter("jsessionid");
session = sessionManager.getSession(sessionToken);
if (session != null) {
return session;
}
}
}
return null;
}
private boolean isLargerOrEquals(String protocolVersion, String otherProtocolVersion) {
try {
int idx = protocolVersion.indexOf(".");
int major = Integer.parseInt(protocolVersion.substring(0, idx));
int minor = Integer.parseInt(protocolVersion.substring(idx + 1, protocolVersion.length()));
int idxOther = otherProtocolVersion.indexOf(".");
int majorOther = Integer.parseInt(otherProtocolVersion.substring(0, idxOther));
int minorOther = Integer.parseInt(otherProtocolVersion.substring(idxOther + 1, otherProtocolVersion.length()));
if (major > majorOther) {
return true;
} else if (major < majorOther) {
return false;
}
if (minor > minorOther) {
return true;
} else if (minor < minorOther) {
return false;
}
return true;
} catch (Exception e) {
throw new RuntimeException("[" + getId() + "] error occured by comparing protocol version " + protocolVersion + " and " + otherProtocolVersion);
}
}
private boolean isContentTypeFormUrlencoded(IHttpMessage message) {
if (!message.hasBody()) {
return false;
}
String contentType = message.getContentType();
if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencoded"))) {
return true;
}
return false;
}
private final class ConnectionCloser implements Runnable {
public void run() {
closeQuitly();
}
}
private final class MessageHeaderHandler implements IMessageHeaderHandler, IMessageHandler {
public IHttpHeader getAssociatedHeader() {
return null;
}
public IMessageHandler onMessageHeaderReceived(IHttpMessage message) throws IOException {
final IHttpRequest request = (IHttpRequest) message;
if (transactionMonitor != null) {
transactionMonitor.registerMessageHeaderReceived(HttpServerConnection.this, request.getRequestHeader());
}
// handle Connection header
handleLifeCycleHeaders(request);
if (LOG.isLoggable(Level.FINE)) {
if (request.hasBody()) {
String body = "";
String contentType = request.getContentType();
if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencode"))) {
body = request.getNonBlockingBody().toString() + "\n";
}
LOG.fine("[" + getId() + "] request received from " + getRemoteAddress() +
":" + getRemotePort() +
" (" + getCountMessagesReceived() + ". request) " + request.getRequestHeader().toString() + body);
} else {
LOG.fine("[" + getId() + "] bodyless request received from " + getRemoteAddress() +
":" + getRemotePort() +
" (" + getCountMessagesReceived() + ". request) " + request.getRequestHeader().toString());
}
}
// handle upgrade header
if (isAutohandleUpgadeHeader) {
String upgrade = request.getRequestHeader().getUpgrade();
if ((upgrade != null) && upgrade.equalsIgnoreCase("TLS/1.0")) {
if (getUnderlyingTcpConnection().isSecuredModeActivateable()) {
suspendReceiving();
HttpResponse response = new HttpResponse(101);
response.setHeader("Connection", "Upgrade");
response.setHeader("Upgrade", "TLS/1.0, HTTP/1.1");
writeMessage(response);
getUnderlyingTcpConnection().activateSecuredMode();
resumeReceiving();
} else {
sendResponseMessage(new HttpResponse(400, "text/html", generateErrorMessageHtml(400, "upgrade TLS is not supported", getId())));
return this;
}
return this;
}
}
// handle 100 continue header
if (isAutconfirmExpect100ContinueHeader) {
String expectHeader = request.getHeader("Expect");
if ((expectHeader != null) && expectHeader.equalsIgnoreCase("100-Continue")) {
writeMessageSilence(new HttpResponse(100));
}
}
if (message.hasBody()) {
// will set body receive timeout, if exits
if (getBodyDataReceiveTimeoutMillis() != Long.MAX_VALUE) {
message.getNonBlockingBody().setBodyDataReceiveTimeoutMillis(getBodyDataReceiveTimeoutMillis());
}
// is FORM encoded request?
if (isContentTypeFormUrlencoded(request)) {
ServerExchange exchange = new ServerExchange(HttpServerConnection.this, newFormEncodedRequestWrapper(request));
return new InvokeOnMessageReceivedMessageHandler(this, exchange);
// is invokeOnMessage?
} else if (requestHandlerAdapter.isInvokeOnMessageReceived()) {
ServerExchange exchange = new ServerExchange(HttpServerConnection.this, request);
return new InvokeOnMessageReceivedMessageHandler(this, exchange);
}
}
ServerExchange exchange = new ServerExchange(HttpServerConnection.this, request);
exchange.perform();
if (transactionMonitor == null) {
return this;
} else {
return new IMessageHandler() {
public void onBodyException(IOException ioe, ByteBuffer[] rawData) {
transactionMonitor.registerMessageReceivedException(HttpServerConnection.this, request, ioe);
}
public void onMessageReceived() throws IOException {
transactionMonitor.registerMessageReceived(HttpServerConnection.this, request);
}
};
}
}
/**
* {@inheritDoc}
*/
public void onHeaderException(IOException ioe, ByteBuffer[] rawData) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by receiving request header " + ioe.toString());
}
destroy();
}
/**
* {@inheritDoc}
*/
public void onMessageReceived() throws IOException {
}
/**
* {@inheritDoc}
*/
public void onBodyException(IOException ioe, ByteBuffer[] rawData) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by receiving request body " + ioe.toString());
}
destroy();
}
}
private final class InvokeOnMessageReceivedMessageHandler implements IMessageHandler {
private final MessageHeaderHandler headerHandler;
private final ServerExchange exchange;
public InvokeOnMessageReceivedMessageHandler(MessageHeaderHandler headerHandler, ServerExchange exchange) {
this.headerHandler = headerHandler;
this.exchange = exchange;
}
/**
* {@inheritDoc}
*/
public void onMessageReceived() throws IOException {
if (transactionMonitor != null) {
transactionMonitor.registerMessageReceived(HttpServerConnection.this, exchange.getRequest());
}
exchange.perform();
}
/**
* {@inheritDoc}
*/
public void onBodyException(IOException ioe, ByteBuffer[] rawData) {
if (transactionMonitor != null) {
transactionMonitor.registerMessageReceivedException(HttpServerConnection.this, exchange.getRequest(), ioe);
}
headerHandler.onHeaderException(ioe, rawData);
}
}
private class ServerExchange extends AbstractExchange {
private final IHttpRequest request;
private HttpSession session = null;
private boolean isSessionCreated = false;
protected ServerExchange(HttpServerConnection httpCon, IHttpRequest request) throws IOException {
super(httpCon);
this.request = request;
if (!sessionManager.isEmtpy()) {
resolveSession();
}
}
void perform() throws BadMessageException, IOException {
requestHandlerAdapter.onRequest(this);
}
public IHttpSession getSession(boolean create) {
resolveSession();
if (session != null) {
return session;
}
if (create) {
isSessionCreated = true;
try {
int prefix = request.getContextPath().hashCode();
session = sessionManager.getSession(sessionManager.newSession(Integer.toHexString(prefix)));
session.setMaxInactiveInterval(sessionMaxInactiveIntervalSec);
return session;
} catch (IOException ioe) {
throw new RuntimeException(ioe.toString());
}
}
return null;
}
public String encodeURL(String url) {
if (useCookies) {
return url;
} else {
// add path parameter (for URL path paramter see http://doriantaylor.com/policy/http-url-path-parameter-syntax)
int pos = url.indexOf('?');
if (pos == -1) {
pos = url.indexOf('#');
}
if (pos == -1) {
return url + ";jsessionid=" + session.getId();
} else {
return url.substring(0, pos) + ";jsessionid=" + session.getId() + url.substring(pos);
}
}
}
private void resolveSession() {
if (session != null) {
return;
}
try {
session = HttpServerConnection.this.getSession(request);
if (session != null) {
String idPrefix = Integer.toHexString(request.getContextPath().hashCode());
if (session.getId().startsWith(idPrefix)) {
session.setLastAccessTime(System.currentTimeMillis());
} else {
session = null;
}
}
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by resolving session " + ioe.toString());
}
}
}
public final IHttpRequest getRequest() {
return request;
}
/**
* {@inheritDoc}
*/
public BodyDataSink send(IHttpResponseHeader header) throws IOException {
assert (header.getContentLength() == -1);
ensureResponseHasNotBeenCommitted();
handleCookieOnSend(header);
// does caller support chunked response? if not convert it to length-based response
if (!AbstractHttpConnection.isAcceptingChunkedResponseBody(getRequest())) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] the requestor does not support chunked response messages (request protocol: " + getRequest().getProtocol() + "). Converting chunked response into simple response.");
}
HttpResponseHeader newHeader = new HttpResponseHeader(header.getStatus(), header.getReason());
newHeader.copyHeaderFrom(header);
newHeader.setProtocol(getRequest().getProtocol());
newHeader.setHeader("Connection", "close");
header = newHeader;
} else {
header.setTransferEncoding("chunked");
}
setResponseCommited(true);
final BodyDataSink bodyDataSink = HttpServerConnection.this.sendResponseHeader(header);
if (transactionMonitor != null) {
transactionMonitor.registerMessageHeaderSent(request, header, bodyDataSink);
Runnable task = new Runnable() {
public void run() {
transactionMonitor.registerMessageSent(request);
}
};
setBodyCloseListener(bodyDataSink, task);
}
return bodyDataSink;
}
/**
* send the response in a plain body non-blocking mode
*
* @param header the header
* @param contentLength the body content length
* @return the body handle to write
* @throws IOException if an exception occurs
*/
public BodyDataSink send(IHttpResponseHeader header, int contentLength) throws IOException {
ensureResponseHasNotBeenCommitted();
handleCookieOnSend(header);
header.setContentLength(contentLength);
try {
// http protocol version downgrade necessary?
if ((!getRequest().getProtocolVersion().equals(header.getProtocolVersion())) &&
(isLargerOrEquals(header.getProtocolVersion(), getRequest().getRequestHeader().getProtocolVersion()))) {
header.setHeader("Connection", "close");
header.setProtocol(getRequest().getProtocol());
}
} catch (Exception e) {
HttpResponse errorResponse = null;
if (HttpUtils.isShowDetailedError()) {
errorResponse = new HttpResponse(400, "text/html", generateErrorMessageHtml(400, DataConverter.toString(e), getId()));
} else {
errorResponse = new HttpResponse(400, "text/html", generateErrorMessageHtml(400, HttpUtils.getReason(400), getId()));
}
setResponseCommited(true);
HttpServerConnection.this.sendResponseMessage(errorResponse);
throw new IOException(e.toString());
}
setResponseCommited(true);
final BodyDataSink bodyDataSink = HttpServerConnection.this.sendResponseHeader(header, contentLength);
if (transactionMonitor != null) {
transactionMonitor.registerMessageHeaderSent(request, header, bodyDataSink);
Runnable task = new Runnable() {
public void run() {
transactionMonitor.registerMessageSent(request);
}
};
setBodyCloseListener(bodyDataSink, task);
}
return bodyDataSink;
}
/**
* send the response
*
* @param response the response
* @throws IOException if an exception occurs
*/
public void send(IHttpResponse response) throws IOException {
ensureResponseHasNotBeenCommitted();
handleCookieOnSend(response.getResponseHeader());
try {
// request protocol version not equals response protocol version?
if (!response.getProtocolVersion().equals(getRequest().getProtocolVersion())) {
// simple (HTTP/0.9) response?
if (response.getProtocolVersion().equals("0.9") && (response.getContentLength() == -1)) {
HttpResponseHeader header = new HttpResponseHeader(200);
header.copyHeaderFrom(response.getResponseHeader());
header.setProtocol(getRequest().getProtocol());
header.setHeader("Connection", "close");
BodyDataSink bodyDataSink = HttpServerConnection.this.sendResponseHeader(header);
NonBlockingBodyDataSource bodyDataSource = response.getNonBlockingBody();
BodyForwarder forwarder = new BodyForwarder(bodyDataSource, bodyDataSink);
bodyDataSource.setDataHandler(forwarder);
return;
}
// http protocol version downgrade necessary?
if (isLargerOrEquals(response.getResponseHeader().getProtocolVersion(), getRequest().getRequestHeader().getProtocolVersion())) {
response.getResponseHeader().setProtocol(getRequest().getProtocol());
response.getResponseHeader().setHeader("Connection", "close");
}
}
} catch (Exception e) {
HttpResponse errorResponse = null;
if (HttpUtils.isShowDetailedError()) {
errorResponse = new HttpResponse(400, "text/html", generateErrorMessageHtml(400, DataConverter.toString(e), getId()));
} else {
errorResponse = new HttpResponse(400, "text/html", generateErrorMessageHtml(400, HttpUtils.getReason(400), getId()));
}
setResponseCommited(true);
HttpServerConnection.this.sendResponseMessage(errorResponse);
throw new IOException(e.toString());
}
setResponseCommited(true);
BodyDataSink dataSink = HttpServerConnection.this.sendResponseMessage(response);
if (transactionMonitor != null) {
transactionMonitor.registerMessageHeaderSent(request, response.getResponseHeader(), dataSink);
if (response.hasBody()) {
IBodyCompleteListener cl = new IBodyCompleteListener() {
@Execution(Execution.NONTHREADED)
public void onComplete() throws IOException {
transactionMonitor.registerMessageSent(request);
}
};
response.getNonBlockingBody().addCompleteListener(cl);
} else {
transactionMonitor.registerMessageSent(request);
}
}
}
private void handleCookieOnSend(IHttpResponseHeader header) {
if (session != null) {
try {
sessionManager.saveSession(session.getId());
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by saving session " + session.getId());
}
}
}
if (isSessionCreated && useCookies) {
StringBuilder sb = new StringBuilder("JSESSIONID=" + session.getId() + ";path=/");
if (IS_SESSION_COOKIE_HTTPONLY) {
sb.append(";HttpOnly");
}
if (isSecure()) {
sb.append(";secure");
}
header.addHeader("Set-Cookie", sb.toString());
}
}
public BodyDataSink forward(IHttpRequestHeader requestHeader) throws IOException, ConnectException, IllegalStateException {
return forward(requestHeader, new ForwardingResponseHandler(this));
}
public BodyDataSink forward(IHttpRequestHeader requestHeader, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (responseHandler == null) {
responseHandler = new ForwardingResponseHandler(this);
}
BodyDataSink bodyDataSink = newEmtpyBodyDataSink();
// send not handled error after the data sink is closed
setBodyCloseListener(bodyDataSink, newCloseListener(responseHandler));
return bodyDataSink;
}
public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength) throws IOException, ConnectException, IllegalStateException {
return forward(requestHeader, contentLength, new ForwardingResponseHandler(this));
}
public BodyDataSink forward(IHttpRequestHeader requestHeader, int contentLength, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (responseHandler == null) {
responseHandler = new ForwardingResponseHandler(this);
}
BodyDataSink bodyDataSink = newEmtpyBodyDataSink();
// send not handled error after the data sink is closed
setBodyCloseListener(bodyDataSink, newCloseListener(responseHandler));
return bodyDataSink;
}
private Runnable newCloseListener(final IHttpResponseHandler responseHandler) {
return new Runnable() {
public void run() {
sendNotHandledError(responseHandler);
}
};
}
public void forward(IHttpRequest request, IHttpResponseHandler responseHandler) throws IOException, ConnectException {
if (responseHandler == null) {
responseHandler = new ForwardingResponseHandler(this);
}
sendNotHandledError(responseHandler);
}
public void forward(IHttpRequest request) throws IOException, ConnectException, IllegalStateException {
forward(request, new ForwardingResponseHandler(this));
}
private void sendNotHandledError(final IHttpResponseHandler responseHandler) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] no handler found (for requested resource). returning error message");
}
try {
IHttpResponse response = new HttpResponse(404, "text/html", generateErrorMessageHtml(404, null, getId()));
callResponseHandler(responseHandler, response);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] could not send not handle response. " + ioe.toString());
}
}
}
/**
* {@inheritDoc}
*/
public void sendError(Exception e) {
int code = 500;
if (e instanceof BadMessageException) {
code = 400;
}
if (HttpUtils.isShowDetailedError()) {
sendError(code, DataConverter.toString(e));
} else {
sendError(code);
}
}
/**
* send an error response
*
* @param errorCode the error code
* @param msg the error message
*/
public void sendError(int errorCode, String msg) {
if (isCloseOnSendingError) {
setPersistent(false);
}
if (isResponseCommitted()) {
throw new IllegalStateException("response is already committed");
}
super.sendError(errorCode, msg);
}
}
@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);
}
}
}