org.socketio.netty.pipeline.HandshakeHandler Maven / Gradle / Ivy
Show all versions of socketIo4Netty Show documentation
/**
* Copyright 2012 Ronen Hamias, Anton Kharenko
*
* 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 org.socketio.netty.pipeline;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.io.IOException;
import java.util.UUID;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.socketio.netty.serialization.JsonObjectMapperProvider;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
/**
* This class implements Socket.IO handshake procedure described below:
*
* Handshake
*
*
* The client will perform an initial HTTP POST request like the following
*
*
*
* {@code http://example.com/socket.io/1/}
*
*
*
* The absence of the transport id and session id segments will signal the
* server this is a new, non-handshaken connection.
*
*
*
* The server can respond in three different ways:
*
*
*
* {@code 401 Unauthorized}
*
*
*
* If the server refuses to authorize the client to connect, based on the
* supplied information (eg: Cookie header or custom query components).
*
*
*
* No response body is required.
*
*
*
* {@code 503 Service Unavailable}
*
*
*
* If the server refuses the connection for any reason (eg: overload).
*
*
*
* No response body is required.
*
*
*
* {@code 200 OK}
*
*
*
* The handshake was successful.
*
*
*
* The body of the response should contain the session id (sid) given to the
* client, followed by the heartbeat timeout, the connection closing timeout,
* and the list of supported transports separated by {@code :}
*
*
*
* The absence of a heartbeat timeout ('') is interpreted as the server and
* client not expecting heartbeats.
*
*
*
* For example {@code 4d4f185e96a7b:15:10:websocket,xhr-polling}
*
*
* @author Anton Kharenko, Ronen Hamias
*
*/
@ChannelHandler.Sharable
public class HandshakeHandler extends ChannelInboundHandlerAdapter {
private final Logger log = LoggerFactory.getLogger(getClass());
private final String handshakePath;
private final String commonHandshakeParameters;
public HandshakeHandler(final String handshakePath, final int heartbeatTimeout, final int closeTimeout, final String transports) {
this.handshakePath = handshakePath;
commonHandshakeParameters = ":" + heartbeatTimeout + ":" + closeTimeout + ":" + transports;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
final HttpRequest req = (HttpRequest) msg;
final HttpMethod requestMethod = req.getMethod();
final QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri());
final String requestPath = queryDecoder.path();
if (!requestPath.startsWith(handshakePath)) {
log.warn("Received HTTP bad request: {} {} from channel: {}", requestMethod, requestPath, ctx.channel());
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
ChannelFuture f = ctx.channel().writeAndFlush(res);
f.addListener(ChannelFutureListener.CLOSE);
ReferenceCountUtil.release(req);
return;
}
if (HttpMethod.GET.equals(requestMethod) && requestPath.equals(handshakePath)) {
log.debug("Received HTTP handshake request: {} {} from channel: {}", requestMethod, requestPath, ctx.channel());
handshake(ctx, req, queryDecoder);
ReferenceCountUtil.release(req);
return;
}
}
super.channelRead(ctx, msg);
}
private void handshake(final ChannelHandlerContext ctx, final HttpRequest req, final QueryStringDecoder queryDecoder)
throws IOException {
// Generate session ID
final String sessionId = UUID.randomUUID().toString();
log.debug("New sessionId: {} generated", sessionId);
// Send handshake response
final String handshakeMessage = getHandshakeMessage(sessionId, queryDecoder);
ByteBuf content = PipelineUtils.copiedBuffer(ctx.alloc(), handshakeMessage);
HttpResponse res = PipelineUtils.createHttpResponse(PipelineUtils.getOrigin(req), content, false);
ChannelFuture f = ctx.writeAndFlush(res);
f.addListener(ChannelFutureListener.CLOSE);
log.debug("Sent handshake response: {} to channel: {}", handshakeMessage, ctx.channel());
}
private String getHandshakeMessage(final String sessionId, final QueryStringDecoder queryDecoder) throws IOException {
String jsonpParam = PipelineUtils.extractParameter(queryDecoder, "jsonp");
String handshakeParameters = sessionId + commonHandshakeParameters;
if (jsonpParam != null) {
return "io.j[" + jsonpParam + "](" + JsonObjectMapperProvider.getObjectMapper().writeValueAsString(handshakeParameters) + ");";
} else {
return handshakeParameters;
}
}
}