Merge pull request #243 from veebs/3
Apache benchmark (ab) hangs on keep alive connections.
This commit is contained in:
commit
182d544a61
@ -26,6 +26,14 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import javax.activation.MimetypesFileTypeMap;
|
||||||
|
|
||||||
import org.jboss.netty.buffer.ChannelBuffers;
|
import org.jboss.netty.buffer.ChannelBuffers;
|
||||||
import org.jboss.netty.channel.Channel;
|
import org.jboss.netty.channel.Channel;
|
||||||
@ -40,6 +48,7 @@ import org.jboss.netty.channel.MessageEvent;
|
|||||||
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||||
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
|
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
|
||||||
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
|
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpHeaders;
|
||||||
import org.jboss.netty.handler.codec.http.HttpRequest;
|
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||||
import org.jboss.netty.handler.codec.http.HttpResponse;
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
|
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
|
||||||
@ -95,6 +104,10 @@ import org.jboss.netty.util.CharsetUtil;
|
|||||||
*/
|
*/
|
||||||
public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler {
|
public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler {
|
||||||
|
|
||||||
|
public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
|
||||||
|
public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
|
||||||
|
public static final int HTTP_CACHE_SECONDS = 60;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
||||||
HttpRequest request = (HttpRequest) e.getMessage();
|
HttpRequest request = (HttpRequest) e.getMessage();
|
||||||
@ -119,6 +132,21 @@ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache Validation
|
||||||
|
String ifModifiedSince = request.getHeader(HttpHeaders.Names.IF_MODIFIED_SINCE);
|
||||||
|
if (ifModifiedSince != null && !ifModifiedSince.equals("")) {
|
||||||
|
SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
|
||||||
|
Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);
|
||||||
|
|
||||||
|
// Only compare up to the second because the datetime format we send to the client does not have milliseconds
|
||||||
|
long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
|
||||||
|
long fileLastModifiedSeconds = file.lastModified() / 1000;
|
||||||
|
if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
|
||||||
|
sendNotModified(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RandomAccessFile raf;
|
RandomAccessFile raf;
|
||||||
try {
|
try {
|
||||||
raf = new RandomAccessFile(file, "r");
|
raf = new RandomAccessFile(file, "r");
|
||||||
@ -130,7 +158,9 @@ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler {
|
|||||||
|
|
||||||
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
|
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
|
||||||
setContentLength(response, fileLength);
|
setContentLength(response, fileLength);
|
||||||
|
setContentTypeHeader(response, file);
|
||||||
|
setDateAndCacheHeaders(response, file);
|
||||||
|
|
||||||
Channel ch = e.getChannel();
|
Channel ch = e.getChannel();
|
||||||
|
|
||||||
// Write the initial line and the header.
|
// Write the initial line and the header.
|
||||||
@ -147,10 +177,12 @@ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler {
|
|||||||
new DefaultFileRegion(raf.getChannel(), 0, fileLength);
|
new DefaultFileRegion(raf.getChannel(), 0, fileLength);
|
||||||
writeFuture = ch.write(region);
|
writeFuture = ch.write(region);
|
||||||
writeFuture.addListener(new ChannelFutureProgressListener() {
|
writeFuture.addListener(new ChannelFutureProgressListener() {
|
||||||
|
@Override
|
||||||
public void operationComplete(ChannelFuture future) {
|
public void operationComplete(ChannelFuture future) {
|
||||||
region.releaseExternalResources();
|
region.releaseExternalResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void operationProgressed(
|
public void operationProgressed(
|
||||||
ChannelFuture future, long amount, long current, long total) {
|
ChannelFuture future, long amount, long current, long total) {
|
||||||
System.out.printf("%s: %d / %d (+%d)%n", path, current, total, amount);
|
System.out.printf("%s: %d / %d (+%d)%n", path, current, total, amount);
|
||||||
@ -218,4 +250,69 @@ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler {
|
|||||||
// Close the connection as soon as the error message is sent.
|
// Close the connection as soon as the error message is sent.
|
||||||
ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);
|
ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When file timestamp is the same as what the browser is sending up, send a "304 Not Modified"
|
||||||
|
*
|
||||||
|
* @param ctx
|
||||||
|
* Context
|
||||||
|
*/
|
||||||
|
private void sendNotModified(ChannelHandlerContext ctx) {
|
||||||
|
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_MODIFIED);
|
||||||
|
setDateHeader(response);
|
||||||
|
|
||||||
|
// Close the connection as soon as the error message is sent.
|
||||||
|
ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Date header for the HTTP response
|
||||||
|
*
|
||||||
|
* @param response
|
||||||
|
* HTTP response
|
||||||
|
*/
|
||||||
|
private void setDateHeader(HttpResponse response) {
|
||||||
|
SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
|
||||||
|
dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));
|
||||||
|
|
||||||
|
Calendar time = new GregorianCalendar();
|
||||||
|
response.setHeader(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Date and Cache headers for the HTTP Response
|
||||||
|
*
|
||||||
|
* @param response
|
||||||
|
* HTTP response
|
||||||
|
* @param fileToCache
|
||||||
|
* file to extract content type
|
||||||
|
*/
|
||||||
|
private void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {
|
||||||
|
SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
|
||||||
|
dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));
|
||||||
|
|
||||||
|
// Date header
|
||||||
|
Calendar time = new GregorianCalendar();
|
||||||
|
response.setHeader(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime()));
|
||||||
|
|
||||||
|
// Add cache headers
|
||||||
|
time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
|
||||||
|
response.setHeader(HttpHeaders.Names.EXPIRES, dateFormatter.format(time.getTime()));
|
||||||
|
response.setHeader(HttpHeaders.Names.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
|
||||||
|
response.setHeader(HttpHeaders.Names.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the content type header for the HTTP Response
|
||||||
|
*
|
||||||
|
* @param response
|
||||||
|
* HTTP response
|
||||||
|
* @param file
|
||||||
|
* file to extract content type
|
||||||
|
*/
|
||||||
|
private void setContentTypeHeader(HttpResponse response, File file) {
|
||||||
|
MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
|
||||||
|
response.setHeader(HttpHeaders.Names.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ import org.jboss.netty.handler.codec.http.CookieEncoder;
|
|||||||
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
|
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
|
||||||
import org.jboss.netty.handler.codec.http.HttpChunk;
|
import org.jboss.netty.handler.codec.http.HttpChunk;
|
||||||
import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
|
import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
|
||||||
|
import org.jboss.netty.handler.codec.http.HttpHeaders;
|
||||||
import org.jboss.netty.handler.codec.http.HttpRequest;
|
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||||
import org.jboss.netty.handler.codec.http.HttpResponse;
|
import org.jboss.netty.handler.codec.http.HttpResponse;
|
||||||
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
|
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
|
||||||
@ -131,6 +132,8 @@ public class HttpSnoopServerHandler extends SimpleChannelUpstreamHandler {
|
|||||||
if (keepAlive) {
|
if (keepAlive) {
|
||||||
// Add 'Content-Length' header only for a keep-alive connection.
|
// Add 'Content-Length' header only for a keep-alive connection.
|
||||||
response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes());
|
response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes());
|
||||||
|
// Add keep alive header as per http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection
|
||||||
|
response.setHeader(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the cookie.
|
// Encode the cookie.
|
||||||
|
Loading…
Reference in New Issue
Block a user