12 changed files with 259 additions and 11 deletions
-
16src/main/java/com/corundumstudio/socketio/SocketIOPipelineFactory.java
-
5src/main/java/com/corundumstudio/socketio/handler/AuthorizeHandler.java
-
5src/main/java/com/corundumstudio/socketio/handler/PacketHandler.java
-
227src/main/java/com/corundumstudio/socketio/handler/ResourceHandler.java
-
4src/main/java/com/corundumstudio/socketio/scheduler/SchedulerKey.java
-
3src/main/java/com/corundumstudio/socketio/transport/FlashPolicyHandler.java
-
2src/main/java/com/corundumstudio/socketio/transport/FlashSocketTransport.java
-
3src/main/java/com/corundumstudio/socketio/transport/WebSocketTransport.java
-
4src/main/java/com/corundumstudio/socketio/transport/XHRPollingTransport.java
-
BINsrc/main/resources/static/flashsocket/WebSocketMain.swf
-
BINsrc/main/resources/static/flashsocket/WebSocketMainInsecure.swf
-
1src/test/java/com/corundumstudio/socketio/PacketHandlerTest.java
@ -0,0 +1,227 @@ |
|||
/** |
|||
* Copyright 2012 Nikita Koksharov |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package com.corundumstudio.socketio.handler; |
|||
|
|||
import static org.jboss.netty.handler.codec.http.HttpHeaders.setContentLength; |
|||
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; |
|||
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; |
|||
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileNotFoundException; |
|||
import java.io.IOException; |
|||
import java.io.RandomAccessFile; |
|||
import java.net.URL; |
|||
import java.text.ParseException; |
|||
import java.text.SimpleDateFormat; |
|||
import java.util.Calendar; |
|||
import java.util.Date; |
|||
import java.util.GregorianCalendar; |
|||
import java.util.HashMap; |
|||
import java.util.Locale; |
|||
import java.util.Map; |
|||
import java.util.TimeZone; |
|||
|
|||
import javax.activation.MimetypesFileTypeMap; |
|||
|
|||
import org.jboss.netty.buffer.ChannelBuffers; |
|||
import org.jboss.netty.channel.Channel; |
|||
import org.jboss.netty.channel.ChannelFuture; |
|||
import org.jboss.netty.channel.ChannelFutureListener; |
|||
import org.jboss.netty.channel.ChannelFutureProgressListener; |
|||
import org.jboss.netty.channel.ChannelHandlerContext; |
|||
import org.jboss.netty.channel.DefaultFileRegion; |
|||
import org.jboss.netty.channel.FileRegion; |
|||
import org.jboss.netty.channel.MessageEvent; |
|||
import org.jboss.netty.channel.SimpleChannelUpstreamHandler; |
|||
import org.jboss.netty.channel.ChannelHandler.Sharable; |
|||
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.HttpResponse; |
|||
import org.jboss.netty.handler.codec.http.HttpResponseStatus; |
|||
import org.jboss.netty.handler.codec.http.QueryStringDecoder; |
|||
import org.jboss.netty.handler.ssl.SslHandler; |
|||
import org.jboss.netty.handler.stream.ChunkedFile; |
|||
import org.jboss.netty.util.CharsetUtil; |
|||
|
|||
@Sharable |
|||
public class ResourceHandler 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; |
|||
|
|||
private final Map<String, File> resources = new HashMap<String, File>(); |
|||
|
|||
public ResourceHandler(String context) { |
|||
addResource(context + "/static/flashsocket/WebSocketMain.swf", |
|||
"/static/flashsocket/WebSocketMain.swf"); |
|||
addResource(context + "/static/flashsocket/WebSocketMainInsecure.swf", |
|||
"/static/flashsocket/WebSocketMainInsecure.swf"); |
|||
} |
|||
|
|||
private void addResource(String pathPart, String resourcePath) { |
|||
URL resource = getClass().getResource(resourcePath); |
|||
File file = new File(resource.getFile()); |
|||
resources.put(pathPart, file); |
|||
} |
|||
|
|||
@Override |
|||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { |
|||
Object msg = e.getMessage(); |
|||
if (msg instanceof HttpRequest) { |
|||
HttpRequest req = (HttpRequest) msg; |
|||
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri()); |
|||
File resource = resources.get(queryDecoder.getPath()); |
|||
if (resource != null) { |
|||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK); |
|||
|
|||
if (isNotModified(req, resource)) { |
|||
sendNotModified(ctx); |
|||
return; |
|||
} |
|||
|
|||
RandomAccessFile raf; |
|||
try { |
|||
raf = new RandomAccessFile(resource, "r"); |
|||
} catch (FileNotFoundException fnfe) { |
|||
sendError(ctx, NOT_FOUND); |
|||
return; |
|||
} |
|||
long fileLength = raf.length(); |
|||
|
|||
setContentLength(res, fileLength); |
|||
setContentTypeHeader(res, resource); |
|||
setDateAndCacheHeaders(res, resource); |
|||
writeContent(raf, fileLength, e.getChannel()); |
|||
return; |
|||
} |
|||
} |
|||
ctx.sendUpstream(e); |
|||
} |
|||
|
|||
private boolean isNotModified(HttpRequest request, File file) throws ParseException { |
|||
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; |
|||
return ifModifiedSinceDateSeconds == fileLastModifiedSeconds; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
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())); |
|||
} |
|||
|
|||
private void writeContent(RandomAccessFile raf, long fileLength, Channel ch) throws IOException { |
|||
ChannelFuture writeFuture; |
|||
if (ch.getPipeline().get(SslHandler.class) != null) { |
|||
// Cannot use zero-copy with HTTPS. |
|||
writeFuture = ch.write(new ChunkedFile(raf, 0, fileLength, 8192)); |
|||
} else { |
|||
// No encryption - use zero-copy. |
|||
final FileRegion region = |
|||
new DefaultFileRegion(raf.getChannel(), 0, fileLength); |
|||
writeFuture = ch.write(region); |
|||
writeFuture.addListener(new ChannelFutureProgressListener() { |
|||
public void operationComplete(ChannelFuture future) { |
|||
region.releaseExternalResources(); |
|||
} |
|||
|
|||
@Override |
|||
public void operationProgressed(ChannelFuture future, long amount, long current, long total) |
|||
throws Exception { |
|||
} |
|||
|
|||
}); |
|||
} |
|||
|
|||
writeFuture.addListener(ChannelFutureListener.CLOSE); |
|||
} |
|||
|
|||
private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) { |
|||
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status); |
|||
response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8"); |
|||
response.setContent(ChannelBuffers.copiedBuffer( |
|||
"Failure: " + status.toString() + "\r\n", |
|||
CharsetUtil.UTF_8)); |
|||
|
|||
// Close the connection as soon as the error message is sent. |
|||
ctx.getChannel().write(response).addListener(ChannelFutureListener.CLOSE); |
|||
} |
|||
|
|||
/** |
|||
* 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())); |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue