11 changed files with 386 additions and 70 deletions
-
12README.md
-
18src/main/java/com/corundumstudio/socketio/Configuration.java
-
29src/main/java/com/corundumstudio/socketio/HeartbeatHandler.java
-
11src/main/java/com/corundumstudio/socketio/PacketListener.java
-
63src/main/java/com/corundumstudio/socketio/SocketIORouter.java
-
1src/main/java/com/corundumstudio/socketio/parser/Decoder.java
-
4src/main/java/com/corundumstudio/socketio/transport/SocketIOTransport.java
-
111src/main/java/com/corundumstudio/socketio/transport/WebSocketClient.java
-
163src/main/java/com/corundumstudio/socketio/transport/WebSocketTransport.java
-
25src/main/java/com/corundumstudio/socketio/transport/XHRPollingClient.java
-
19src/main/java/com/corundumstudio/socketio/transport/XHRPollingTransport.java
@ -0,0 +1,111 @@ |
|||
/** |
|||
* 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.transport; |
|||
|
|||
import java.io.IOException; |
|||
import java.net.SocketAddress; |
|||
import java.util.LinkedList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
import org.jboss.netty.channel.Channel; |
|||
import org.jboss.netty.channel.ChannelFuture; |
|||
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
|||
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import com.corundumstudio.socketio.NullChannelFuture; |
|||
import com.corundumstudio.socketio.SocketIOClient; |
|||
import com.corundumstudio.socketio.SocketIORouter; |
|||
import com.corundumstudio.socketio.parser.Encoder; |
|||
import com.corundumstudio.socketio.parser.Packet; |
|||
import com.corundumstudio.socketio.parser.PacketType; |
|||
|
|||
public class WebSocketClient implements SocketIOClient { |
|||
|
|||
private final Logger log = LoggerFactory.getLogger(getClass()); |
|||
|
|||
private final List<String> messages = new LinkedList<String>(); |
|||
private final UUID sessionId; |
|||
|
|||
private Channel channel; |
|||
|
|||
private final SocketIORouter socketIORouter; |
|||
private final Encoder encoder; |
|||
|
|||
public WebSocketClient(Channel channel, Encoder encoder, SocketIORouter socketIORouter, UUID sessionId) { |
|||
this.channel = channel; |
|||
this.encoder = encoder; |
|||
this.socketIORouter = socketIORouter; |
|||
this.sessionId = sessionId; |
|||
} |
|||
|
|||
public Channel getChannel() { |
|||
return channel; |
|||
} |
|||
|
|||
public UUID getSessionId() { |
|||
return sessionId; |
|||
} |
|||
|
|||
private ChannelFuture sendPayload() { |
|||
CharSequence data = encoder.encodePayload(messages); |
|||
messages.clear(); |
|||
return write(data); |
|||
} |
|||
|
|||
private ChannelFuture write(CharSequence message) { |
|||
WebSocketFrame res = new TextWebSocketFrame(message.toString()); |
|||
|
|||
if (channel.isConnected()) { |
|||
log.trace("Out message: {} sessionId: {}", new Object[] {message, sessionId}); |
|||
ChannelFuture f = channel.write(res); |
|||
return f; |
|||
} |
|||
return NullChannelFuture.INSTANCE; |
|||
} |
|||
|
|||
public ChannelFuture sendJsonObject(Object object) { |
|||
Packet packet = new Packet(PacketType.JSON); |
|||
packet.setData(object); |
|||
return send(packet); |
|||
} |
|||
|
|||
public ChannelFuture send(Packet packet) { |
|||
try { |
|||
String message = encoder.encodePacket(packet); |
|||
return sendUnencoded(message); |
|||
} catch (IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
public synchronized ChannelFuture sendUnencoded(String message) { |
|||
messages.add(message); |
|||
return sendPayload(); |
|||
} |
|||
|
|||
public void disconnect() { |
|||
socketIORouter.disconnect(sessionId); |
|||
|
|||
} |
|||
|
|||
public SocketAddress getRemoteAddress() { |
|||
return channel.getRemoteAddress(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,163 @@ |
|||
/** |
|||
* 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.transport; |
|||
|
|||
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; |
|||
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY; |
|||
import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
|
|||
import org.jboss.netty.channel.Channel; |
|||
import org.jboss.netty.channel.ChannelFuture; |
|||
import org.jboss.netty.channel.ChannelFutureListener; |
|||
import org.jboss.netty.channel.ChannelHandlerContext; |
|||
import org.jboss.netty.channel.MessageEvent; |
|||
import org.jboss.netty.handler.codec.http.HttpHeaders; |
|||
import org.jboss.netty.handler.codec.http.HttpHeaders.Names; |
|||
import org.jboss.netty.handler.codec.http.HttpHeaders.Values; |
|||
import org.jboss.netty.handler.codec.http.HttpRequest; |
|||
import org.jboss.netty.handler.codec.http.QueryStringDecoder; |
|||
import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame; |
|||
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame; |
|||
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; |
|||
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker00; |
|||
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker13; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
|
|||
import com.corundumstudio.socketio.PacketListener; |
|||
import com.corundumstudio.socketio.SocketIOClient; |
|||
import com.corundumstudio.socketio.SocketIORouter; |
|||
import com.corundumstudio.socketio.parser.Decoder; |
|||
import com.corundumstudio.socketio.parser.Encoder; |
|||
import com.corundumstudio.socketio.parser.Packet; |
|||
import com.corundumstudio.socketio.parser.PacketType; |
|||
|
|||
public class WebSocketTransport implements SocketIOTransport { |
|||
|
|||
private final Logger log = LoggerFactory.getLogger(getClass()); |
|||
|
|||
private final Map<UUID, WebSocketClient> sessionId2Client = new ConcurrentHashMap<UUID, WebSocketClient>(); |
|||
private final Map<Integer, WebSocketClient> channelId2Client = new ConcurrentHashMap<Integer, WebSocketClient>(); |
|||
|
|||
private final SocketIORouter socketIORouter; |
|||
private final PacketListener packetListener; |
|||
private final Decoder decoder; |
|||
private final Encoder encoder; |
|||
private final String path; |
|||
|
|||
public WebSocketTransport(String connectPath, Decoder decoder, Encoder encoder, |
|||
SocketIORouter socketIORouter, PacketListener packetListener) { |
|||
this.path = connectPath + "websocket"; |
|||
this.decoder = decoder; |
|||
this.encoder = encoder; |
|||
this.socketIORouter = socketIORouter; |
|||
this.packetListener = packetListener; |
|||
} |
|||
|
|||
@Override |
|||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { |
|||
Object msg = e.getMessage(); |
|||
if (msg instanceof CloseWebSocketFrame) { |
|||
ctx.getChannel().close(); |
|||
} else if (msg instanceof TextWebSocketFrame) { |
|||
TextWebSocketFrame frame = (TextWebSocketFrame) msg; |
|||
WebSocketClient client = channelId2Client.get(ctx.getChannel().getId()); |
|||
String content = frame.getText(); |
|||
|
|||
log.trace("In message: {} sessionId: {}", new Object[] {content, client.getSessionId()}); |
|||
|
|||
List<Packet> packets = decoder.decodePayload(content); |
|||
for (Packet packet : packets) { |
|||
packetListener.onPacket(packet, client); |
|||
} |
|||
} else if (msg instanceof HttpRequest) { |
|||
HttpRequest req = (HttpRequest) msg; |
|||
if (req.containsHeader(CONNECTION) && req.getHeader(CONNECTION).contains(Values.UPGRADE) |
|||
&& req.containsHeader(WEBSOCKET) && WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) { |
|||
WebSocketServerHandshaker handshaker = null; |
|||
if (req.containsHeader(SEC_WEBSOCKET_KEY)) { |
|||
handshaker = new WebSocketServerHandshaker13(getWebSocketLocation(req), null, false); |
|||
} else { |
|||
handshaker = new WebSocketServerHandshaker00(getWebSocketLocation(req), null); |
|||
} |
|||
handshaker.handshake(ctx.getChannel(), req); |
|||
|
|||
QueryStringDecoder queryDecoder = new QueryStringDecoder(req.getUri()); |
|||
connectClient(ctx.getChannel(), queryDecoder); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void connectClient(Channel channel, QueryStringDecoder queryDecoder) { |
|||
String path = queryDecoder.getPath(); |
|||
if (!path.startsWith(this.path)) { |
|||
return; |
|||
} |
|||
|
|||
String[] parts = path.split("/"); |
|||
if (parts.length > 3) { |
|||
UUID sessionId = UUID.fromString(parts[4]); |
|||
if (!socketIORouter.isSessionAuthorized(sessionId)) { |
|||
log.warn("Unauthorized client with sessionId: {}, from ip: {}. Channel closed!", |
|||
new Object[] {sessionId, channel.getRemoteAddress()}); |
|||
channel.close(); |
|||
return; |
|||
} |
|||
|
|||
WebSocketClient client = new WebSocketClient(channel, encoder, socketIORouter, sessionId); |
|||
channelId2Client.put(channel.getId(), client); |
|||
sessionId2Client.put(sessionId, client); |
|||
socketIORouter.connect(client); |
|||
} else { |
|||
log.warn("Wrong GET request path: {}, from ip: {}. Channel closed!", |
|||
new Object[] {path, channel.getRemoteAddress()}); |
|||
channel.close(); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void disconnect(UUID sessionId) { |
|||
WebSocketClient client = sessionId2Client.remove(sessionId); |
|||
if (client != null) { |
|||
ChannelFuture future = client.send(new Packet(PacketType.DISCONNECT)); |
|||
future.addListener(ChannelFutureListener.CLOSE); |
|||
socketIORouter.disconnect(client); |
|||
channelId2Client.remove(client.getChannel().getId()); |
|||
} |
|||
} |
|||
|
|||
private String getWebSocketLocation(HttpRequest req) { |
|||
return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + path; |
|||
} |
|||
|
|||
@Override |
|||
public void onDisconnect(SocketIOClient client) { |
|||
if (client instanceof WebSocketClient) { |
|||
WebSocketClient webClient = (WebSocketClient) client; |
|||
sessionId2Client.remove(webClient.getSessionId()); |
|||
channelId2Client.remove(webClient.getChannel().getId()); |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue