Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.xlightweb.WebSocketConnection Maven / Gradle / Ivy
/*
* Copyright (c) xlightweb.org, 2008 - 2010. 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;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xlightweb.AbstractHttpConnection.IMultimodeExecutor;
import org.xlightweb.WebSocketHandlerAdapter.IPostConnectInterceptor;
import org.xlightweb.client.HttpClientConnection;
import org.xlightweb.server.HttpServerConnection;
import org.xsocket.Execution;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.IConnectHandler;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.IDisconnectHandler;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnectionPool;
/**
* NonBlockingWebSocketConnection
*
* This is an experimental implementation of the Web Socket protocol draft and subject to change
*
*
* @author grro
*/
public final class WebSocketConnection implements IWebSocketConnection {
private static final Logger LOG = Logger.getLogger(WebSocketConnection.class.getName());
private INonBlockingConnection tcpConnection;
// receive timeout
public static final int DEFAULT_RECEIVE_TIMEOUT = Integer.MAX_VALUE;
private int receiveTimeoutSec = DEFAULT_RECEIVE_TIMEOUT;
// protocol handling
private final WebSocketProtocolHandler protocolHandler = new WebSocketProtocolHandler();
private final IMultimodeExecutor executor;
private final List inQueue = new ArrayList();
private int inQueueVersion = 0;
// upgrade headers
private final IHttpRequestHeader upgradeRequestHeader;
private final IHttpResponseHeader upgradeResponseHeader;
// interceptor support
private final IPostWriteInterceptor interceptor;
// web socket handler
private final Object webSocketHandlerGuard = new Object();
private WebSocketHandlerAdapter webSocketHandlerAdapter = null;
// attachment management
private AtomicReference attachmentRef = new AtomicReference(null);
public WebSocketConnection(String uriString) throws IOException {
this(uriString, (String) null);
}
public WebSocketConnection(String uriString, String protocol) throws IOException {
this(uriString, protocol, null);
}
public WebSocketConnection(String uriString, IWebSocketHandler webSocketHandler) throws IOException {
this(uriString, null, webSocketHandler);
}
public WebSocketConnection(String uriString, String protocol, IWebSocketHandler webSocketHandler) throws IOException {
this(URI.create(uriString), protocol, webSocketHandler);
}
private WebSocketConnection(URI uri, String protocol, IWebSocketHandler webSocketHandler) throws IOException {
this(connect(uri), uri, protocol, webSocketHandler);
}
private static HttpClientConnection connect(URI uri) throws IOException {
int port = uri.getPort();
if (port == -1) {
if (uri.getScheme().toLowerCase().equals("wss")) {
port = 443;
} else {
port = 80;
}
}
return new HttpClientConnection(uri.getHost(), port);
}
public WebSocketConnection(HttpClientConnection httpConnection, URI uri, String protocol, IWebSocketHandler webSocketHandler) throws IOException {
this(performHandshake(httpConnection, uri, protocol), protocol, webSocketHandler);
}
private WebSocketConnection(HandeshakeResult handeshakeResult, String protocol, IWebSocketHandler webSocketHandler) throws IOException {
this(handeshakeResult.con, webSocketHandler, handeshakeResult.upgradeRequestHeader, handeshakeResult.upgradeResponseHeader);
}
private WebSocketConnection(HttpClientConnection httpConnection, IWebSocketHandler webSocketHandler, IHttpRequestHeader upgradeRequestHeader, IHttpResponseHeader upgradeResponseHeader) throws IOException {
this(convertToTcpConnection(httpConnection), null, webSocketHandler, null, upgradeRequestHeader, upgradeResponseHeader);
}
private static HandeshakeResult performHandshake(HttpClientConnection httpCon, URI uri, String protocol) throws IOException {
GetRequest request = new GetRequest(uri.toString());
request.setHeader("Upgrade", "WebSocket");
request.setHeader("Connection", "Upgrade");
request.setHeader("Origin", request.getRequestUrl().toString());
if (protocol != null) {
request.setHeader("WebSocket-Protocol", protocol);
}
IHttpResponse response = httpCon.call(request);
if (response.getStatus() != 101) {
if (response.getStatus() == 501) {
if (response.hasBody()) {
throw new UnsupportedProtocolException(response.getBody().toString());
} else {
throw new UnsupportedProtocolException();
}
} else {
throw new IOException(response.getStatus() + " " + response.getReason());
}
}
return new HandeshakeResult(httpCon, request.getRequestHeader(), response.getResponseHeader());
}
private static final class HandeshakeResult {
private HttpClientConnection con;
private IHttpRequestHeader upgradeRequestHeader;
private IHttpResponseHeader upgradeResponseHeader;
public HandeshakeResult(HttpClientConnection con, IHttpRequestHeader upgradeRequestHeader, IHttpResponseHeader upgradeResponseHeader) {
this.con = con;
this.upgradeRequestHeader = upgradeRequestHeader;
this.upgradeResponseHeader = upgradeResponseHeader;
}
}
public WebSocketConnection(HttpServerConnection httpConnection, IWebSocketHandler webSocketHandler, IHttpExchange exchange) throws IOException {
this(httpConnection, webSocketHandler, new UpgradeResponseSender(exchange));
}
private WebSocketConnection(HttpServerConnection httpConnection, IWebSocketHandler webSocketHandler, UpgradeResponseSender upgradeHandler) throws IOException {
this(httpConnection, webSocketHandler, upgradeHandler.getRequestHeader(), upgradeHandler.getResponseHeader(), upgradeHandler);
}
private static INonBlockingConnection convertToTcpConnection(AbstractHttpConnection httpConnection) throws IOException {
INonBlockingConnection tcpCon = httpConnection.getUnderlyingTcpConnection();
tcpCon.setHandler(null);
return tcpCon;
}
private WebSocketConnection(HttpServerConnection httpConnection, IWebSocketHandler webSocketHandler, IHttpRequestHeader upgradeRequestHeader, IHttpResponseHeader upgradeResponseHeader, UpgradeResponseSender upgradeResponseSender) throws IOException {
this(convertToTcpConnection(httpConnection), upgradeResponseSender, webSocketHandler, upgradeResponseSender, upgradeRequestHeader, upgradeResponseHeader);
}
private static final class UpgradeResponseSender implements IPostConnectInterceptor, IPostWriteInterceptor {
private final IHttpExchange exchange;
private final AtomicBoolean isUpgradeSent = new AtomicBoolean(false);
private final IHttpResponseHeader responseHeader;
public UpgradeResponseSender(IHttpExchange exchange) {
this.exchange = exchange;
String protocol = exchange.getRequest().getHeader("WebSocket-Protocol");
String webSocketOrigin = exchange.getRequest().getHeader("Origin");
String webSocketLocation;
if (exchange.getRequest().isSecure()) {
webSocketLocation = "wss://" + exchange.getRequest().getHost() + exchange.getRequest().getRequestURI();
} else {
webSocketLocation = "ws://" + exchange.getRequest().getHost() + exchange.getRequest().getRequestURI();
}
responseHeader = new HttpResponseHeader(101);
responseHeader.setReason("Web Socket Protocol Handshake");
// do not change the setting order! Chrome would not thank you
responseHeader.setHeader("Upgrade", "WebSocket");
responseHeader.setHeader("Connection", "Upgrade");
responseHeader.setHeader("WebSocket-Origin", webSocketOrigin);
responseHeader.setHeader("WebSocket-Location", webSocketLocation);
if (protocol != null) {
responseHeader.setHeader("WebSocket-Protocol", protocol);
}
}
public void onConnectException(IOException ioe) {
if (ioe instanceof UnsupportedProtocolException) {
exchange.sendError(501, ioe.getMessage());
} else {
exchange.sendError(501);
}
}
public void onPostConnect() throws IOException {
sentUpgradeIfNecessary();
}
public void onPreWrite() throws IOException {
sentUpgradeIfNecessary() ;
}
private void sentUpgradeIfNecessary() throws IOException {
if (!isUpgradeSent.getAndSet(true)) {
exchange.send(new HttpResponse(responseHeader));
}
}
IHttpRequestHeader getRequestHeader() {
return exchange.getRequest().getRequestHeader();
}
IHttpResponseHeader getResponseHeader() {
return responseHeader;
}
}
private WebSocketConnection(INonBlockingConnection tcpConnection, IPostWriteInterceptor connectInterceptor, IWebSocketHandler webSocketHandler, IPostConnectInterceptor postConnectInterceptor, IHttpRequestHeader upgradeRequestHeader, IHttpResponseHeader upgradeResponseHeader) throws IOException {
this.interceptor = connectInterceptor;
this.tcpConnection = tcpConnection;
this.upgradeRequestHeader = upgradeRequestHeader;
this.upgradeResponseHeader = upgradeResponseHeader;
executor = HttpUtils.newMultimodeExecutor(tcpConnection.getWorkerpool());
setMessageHandler(webSocketHandler, postConnectInterceptor);
protocolHandler.onConnect(tcpConnection);
tcpConnection.setHandler(protocolHandler);
}
/**
* {@inheritDoc}
*/
public String getProtocol() {
return getUpgradeResponseHeader().getHeader("WebSocket-Protocol");
}
/**
* {@inheritDoc}
*/
public String getWebSocketLocation() {
return getUpgradeResponseHeader().getHeader("WebSocket-Location");
}
/**
* {@inheritDoc}
*/
public String getWebSocketOrigin() {
return getUpgradeResponseHeader().getHeader("WebSocket-Origin");
}
public IHttpRequestHeader getUpgradeRequestHeader() {
return upgradeRequestHeader;
}
public IHttpResponseHeader getUpgradeResponseHeader() {
return upgradeResponseHeader;
}
public void destroy() {
// destroy (underlying tcp) connection by using connection pool. The underlying connection could be a pooled one)
// The connection pool detects automatically if the connection is pooled or not. The connection will be
// closed (logically) anyway
try {
NonBlockingConnectionPool.destroy(tcpConnection);
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by destroying htttp connection " + getId() + " " + ioe.toString());
}
}
}
public INonBlockingConnection getUnderlyingTcpConnection() {
return tcpConnection;
}
public int availableMessages() {
synchronized (inQueue) {
return inQueue.size();
}
}
int getInQueueVersion() {
synchronized (inQueue) {
return inQueueVersion;
}
}
@Override
public String toString() {
return tcpConnection.toString();
}
public WebSocketMessage readMessage() throws BufferUnderflowException, SocketTimeoutException {
long start = System.currentTimeMillis();
long remainingTime = receiveTimeoutSec;
do {
synchronized (inQueue) {
if (inQueue.isEmpty()) {
try {
inQueue.wait(remainingTime);
} catch (InterruptedException ie) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
} else {
inQueueVersion++;
return inQueue.remove(0);
}
}
remainingTime = computeRemainingTime(start, receiveTimeoutSec);
} while (remainingTime > 0);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("receive timeout " + receiveTimeoutSec + " sec reached. throwing timeout exception");
}
throw new SocketTimeoutException("timeout " + receiveTimeoutSec + " sec reached");
}
private long computeRemainingTime(long start, int receiveTimeoutSec) {
return (start + ((long) receiveTimeoutSec * 1000)) - System.currentTimeMillis();
}
public int writeMessage(WebSocketMessage msg) throws IOException {
if (interceptor != null) {
interceptor.onPreWrite();
}
return msg.writeTo(this, null);
}
public void writeMessage(WebSocketMessage msg, IWebSocketWriteCompleteHandler completeHandler) throws IOException {
if (interceptor != null) {
interceptor.onPreWrite();
}
msg.writeTo(this, completeHandler);
}
public void closeQuitly() {
try {
close();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by closing connection " + getId() + " " + ioe.toString());
}
try {
NonBlockingConnectionPool.destroy(tcpConnection);
} catch (IOException e) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by closing connection " + getId() + " " + e.toString());
}
}
}
}
public void close() throws IOException {
destroy();
}
void processNonthreaded(Runnable task) {
executor.processNonthreaded(task);
}
void processMultithreaded(Runnable task) {
executor.processMultithreaded(task);
}
public void setMessageHandler(IWebSocketHandler webSocketHandler) throws IOException {
setMessageHandler(webSocketHandler, null);
}
void setMessageHandler(IWebSocketHandler webSocketHandler, IPostConnectInterceptor postConnectInterceptor) throws IOException {
synchronized (webSocketHandlerGuard) {
if (webSocketHandlerAdapter != null) {
webSocketHandlerAdapter.onDisconnect(this);
}
webSocketHandlerAdapter = new WebSocketHandlerAdapter(webSocketHandler, postConnectInterceptor);
webSocketHandlerAdapter.onConnect(this);
}
}
public boolean isOpen() {
return tcpConnection.isOpen();
}
public boolean isServerSide() {
return tcpConnection.isServerSide();
}
public INonBlockingConnection getTcpConnection() {
return tcpConnection;
}
public void setAttachment(Object obj) {
attachmentRef.set(obj);
}
public Object getAttachment() {
return attachmentRef.get();
}
public long getConnectionTimeoutMillis() {
return tcpConnection.getConnectionTimeoutMillis();
}
public void setConnectionTimeoutMillis(long timeoutMillis) {
tcpConnection.setConnectionTimeoutMillis(timeoutMillis);
}
public long getRemainingMillisToConnectionTimeout() {
return tcpConnection.getRemainingMillisToConnectionTimeout();
}
public long getIdleTimeoutMillis() {
return tcpConnection.getIdleTimeoutMillis();
}
public void setIdleTimeoutMillis(long timeoutInMillis) {
tcpConnection.setIdleTimeoutMillis(timeoutInMillis);
}
public long getRemainingMillisToIdleTimeout() {
return tcpConnection.getRemainingMillisToIdleTimeout();
}
public String getId() {
return tcpConnection.getId();
}
public int getLocalPort() {
return tcpConnection.getLocalPort();
}
public InetAddress getLocalAddress() {
return tcpConnection.getLocalAddress();
}
public InetAddress getRemoteAddress() {
return tcpConnection.getRemoteAddress();
}
public int getRemotePort() {
return tcpConnection.getRemotePort();
}
public Object getOption(String name) throws IOException {
return tcpConnection.getOption(name);
}
@SuppressWarnings("unchecked")
public Map getOptions() {
return tcpConnection.getOptions();
}
public void setOption(String name, Object value) throws IOException {
tcpConnection.setOption(name, value);
}
@Execution(Execution.NONTHREADED)
private final class WebSocketProtocolHandler implements IConnectHandler, IDataHandler, IDisconnectHandler {
// network data
private ByteBuffer rawBuffer = null;
public boolean onConnect(INonBlockingConnection connection) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
synchronized (webSocketHandlerGuard) {
if (webSocketHandlerAdapter != null) {
webSocketHandlerAdapter.onConnect(WebSocketConnection.this);
}
}
return true;
}
public boolean onData(INonBlockingConnection connection) throws IOException, BufferUnderflowException, ClosedChannelException, MaxReadSizeExceededException {
if (connection.isOpen()) {
// copying available network data into raw data buffer
int available = connection.available();
if (available > 0) {
ByteBuffer[] data = connection.readByteBufferByLength(available);
onData(data);
}
}
return true;
}
void onData(ByteBuffer[] data) throws IOException {
if (rawBuffer == null) {
rawBuffer = HttpUtils.merge(data);
} else {
rawBuffer = HttpUtils.merge(rawBuffer, data);
}
parse(rawBuffer);
if (!rawBuffer.hasRemaining()) {
rawBuffer = null;
}
}
void parse(ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining()) {
WebSocketMessage msg = WebSocketMessage.parse(buffer);
if (msg == null) {
return;
} else {
synchronized (inQueue) {
inQueueVersion++;
inQueue.add(msg);
inQueue.notifyAll();
}
synchronized (webSocketHandlerGuard) {
if (webSocketHandlerAdapter != null) {
webSocketHandlerAdapter.onMessage(WebSocketConnection.this);
}
}
}
}
}
public boolean onDisconnect(INonBlockingConnection connection) throws IOException {
synchronized (webSocketHandlerGuard) {
if (webSocketHandlerAdapter != null) {
webSocketHandlerAdapter.onDisconnect(WebSocketConnection.this);
}
}
return true;
}
}
private static interface IPostWriteInterceptor {
void onPreWrite() throws IOException ;
}
}