Merge branch 'master' of github.com:netty/netty

This commit is contained in:
Trustin Lee 2011-10-31 15:22:47 -07:00
commit 7242771688
63 changed files with 4908 additions and 52 deletions

View File

@ -96,3 +96,13 @@ framework implementation, which can be obtained at:
* license/LICENSE.felix.txt (Apache License 2.0)
* HOMEPAGE:
* http://felix.apache.org/
This product optionally depends on 'Webbit', a Java event based
WebSocket and HTTP server:
* LICENSE:
* license/LICENSE.webbit.txt (BSD License)
* HOMEPAGE:
* https://github.com/joewalnes/webbit

View File

@ -0,0 +1,37 @@
(BSD License: http://www.opensource.org/licenses/bsd-license)
Copyright (c) 2011, Joe Walnes, Aslak Hellesøy and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
* Neither the name of the Webbit nor the names of
its contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -40,18 +40,38 @@ public class ChannelLocal<T> {
private final ConcurrentMap<Channel, T> map =
new ConcurrentIdentityWeakKeyHashMap<Channel, T>();
private final ChannelFutureListener remover = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
remove(future.getChannel());
}
};
private final boolean removeOnClose;
/**
* Creates a {@link Channel} local variable.
* Creates a {@link Channel} local variable by calling {@link #ChannelLocal(boolean)} with <code>true</code>
*/
public ChannelLocal() {
super();
this(true);
}
/**
* Creates a {@link Channel} local variable.
*
* @param removeOnClose if <code>true</code> the {@link ChannelLocal} will remove a {@link Channel} from it own once the {@link Channel} was closed.
*/
public ChannelLocal(boolean removeOnClose) {
this.removeOnClose = removeOnClose;
}
/**
* Returns the initial value of the variable. By default, it returns
* {@code null}. Override it to change the initial value.
*/
protected T initialValue(@SuppressWarnings("unused") Channel channel) {
protected T initialValue(Channel channel) {
return null;
}
@ -88,7 +108,11 @@ public class ChannelLocal<T> {
if (channel == null) {
throw new NullPointerException("channel");
}
return map.put(channel, value);
T old = map.put(channel, value);
if (removeOnClose) {
channel.getCloseFuture().addListener(remover);
}
return old;
}
}
@ -105,7 +129,12 @@ public class ChannelLocal<T> {
if (channel == null) {
throw new NullPointerException("channel");
}
return map.putIfAbsent(channel, value);
T mapping = map.putIfAbsent(channel, value);
if (removeOnClose && mapping == null) {
channel.getCloseFuture().addListener(remover);
}
return mapping;
}
}
@ -126,6 +155,9 @@ public class ChannelLocal<T> {
if (removed == null) {
return initialValue(channel);
} else {
if (removeOnClose) {
channel.getCloseFuture().removeListener(remover);
}
return removed;
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.channel.iostream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketAddress;
/**
* A {@link java.net.SocketAddress} implementation holding an {@link java.io.InputStream} and an {@link java.io.OutputStream} instance used as
* "remote" address to connect to with a {@link IOStreamChannel}.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStreamAddress extends SocketAddress {
private final InputStream inputStream;
private final OutputStream outputStream;
public IOStreamAddress(final InputStream inputStream, final OutputStream outputStream) {
this.inputStream = inputStream;
this.outputStream = outputStream;
}
public InputStream getInputStream() {
return inputStream;
}
public OutputStream getOutputStream() {
return outputStream;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.channel.iostream;
import org.jboss.netty.channel.*;
import java.net.SocketAddress;
/**
* A channel to an {@link java.io.InputStream} and an {@link java.io.OutputStream}.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStreamChannel extends AbstractChannel {
IOStreamChannel(final ChannelFactory factory, final ChannelPipeline pipeline, final ChannelSink sink) {
super(null, factory, pipeline, sink);
}
@Override
public ChannelConfig getConfig() {
return ((IOStreamChannelSink) getPipeline().getSink()).getConfig();
}
@Override
public boolean isBound() {
return ((IOStreamChannelSink) getPipeline().getSink()).isBound();
}
@Override
public boolean isConnected() {
return ((IOStreamChannelSink) getPipeline().getSink()).isConnected();
}
@Override
public SocketAddress getLocalAddress() {
return null;
}
@Override
public SocketAddress getRemoteAddress() {
return ((IOStreamChannelSink) getPipeline().getSink()).getRemoteAddress();
}
@Override
public ChannelFuture bind(final SocketAddress localAddress) {
throw new UnsupportedOperationException();
}
@Override
public ChannelFuture unbind() {
throw new UnsupportedOperationException();
}
void doSetClosed() {
setClosed();
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.channel.iostream;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.util.internal.ExecutorUtil;
import java.util.concurrent.ExecutorService;
/**
* A {@link org.jboss.netty.channel.ChannelFactory} for creating {@link IOStreamChannel} instances.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStreamChannelFactory implements ChannelFactory {
private final ChannelGroup channels = new DefaultChannelGroup("IOStreamChannelFactory-ChannelGroup");
private final ExecutorService executorService;
public IOStreamChannelFactory(ExecutorService executorService) {
this.executorService = executorService;
}
@Override
public Channel newChannel(final ChannelPipeline pipeline) {
IOStreamChannelSink sink = new IOStreamChannelSink(executorService);
IOStreamChannel channel = new IOStreamChannel(this, pipeline, sink);
sink.setChannel(channel);
channels.add(channel);
return channel;
}
@Override
public void releaseExternalResources() {
ChannelGroupFuture close = channels.close();
close.awaitUninterruptibly();
ExecutorUtil.terminate(executorService);
}
}

View File

@ -0,0 +1,178 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.channel.iostream;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.*;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.util.concurrent.ExecutorService;
import static org.jboss.netty.channel.Channels.*;
/**
* A {@link org.jboss.netty.channel.ChannelSink} implementation which reads from an {@link java.io.InputStream} and
* writes to an {@link java.io.OutputStream}.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStreamChannelSink extends AbstractChannelSink {
private static class ReadRunnable implements Runnable {
private final IOStreamChannelSink channelSink;
public ReadRunnable(final IOStreamChannelSink channelSink) {
this.channelSink = channelSink;
}
@Override
public void run() {
PushbackInputStream in = channelSink.inputStream;
while (channelSink.channel.isOpen()) {
byte[] buf;
int readBytes;
try {
int bytesToRead = in.available();
if (bytesToRead > 0) {
buf = new byte[bytesToRead];
readBytes = in.read(buf);
} else {
// peek into the stream if it was closed (value=-1)
int b = in.read();
if (b < 0) {
break;
}
// push back the byte which was read too much
in.unread(b);
continue;
}
} catch (Throwable t) {
if (!channelSink.channel.getCloseFuture().isDone()) {
fireExceptionCaught(channelSink.channel, t);
}
break;
}
fireMessageReceived(channelSink.channel, ChannelBuffers.wrappedBuffer(buf, 0, readBytes));
}
// Clean up.
close(channelSink.channel);
}
}
private final ExecutorService executorService;
private IOStreamChannel channel;
public IOStreamChannelSink(final ExecutorService executorService) {
this.executorService = executorService;
}
public boolean isConnected() {
return inputStream != null && outputStream != null;
}
public IOStreamAddress getRemoteAddress() {
return remoteAddress;
}
public boolean isBound() {
return false;
}
public ChannelConfig getConfig() {
return config;
}
public void setChannel(final IOStreamChannel channel) {
this.channel = channel;
}
private IOStreamAddress remoteAddress;
private OutputStream outputStream;
private PushbackInputStream inputStream;
private ChannelConfig config = new DefaultChannelConfig();
@Override
public void eventSunk(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception {
final ChannelFuture future = e.getFuture();
if (e instanceof ChannelStateEvent) {
final ChannelStateEvent stateEvent = (ChannelStateEvent) e;
final ChannelState state = stateEvent.getState();
final Object value = stateEvent.getValue();
switch (state) {
case OPEN:
if (Boolean.FALSE.equals(value)) {
outputStream = null;
inputStream = null;
((IOStreamChannel) e.getChannel()).doSetClosed();
}
break;
case BOUND:
throw new UnsupportedOperationException();
case CONNECTED:
if (value != null) {
remoteAddress = (IOStreamAddress) value;
outputStream = remoteAddress.getOutputStream();
inputStream = new PushbackInputStream(remoteAddress.getInputStream());
executorService.execute(new ReadRunnable(this));
future.setSuccess();
}
break;
case INTEREST_OPS:
// TODO implement
throw new UnsupportedOperationException();
}
} else if (e instanceof MessageEvent) {
final MessageEvent event = (MessageEvent) e;
if (event.getMessage() instanceof ChannelBuffer) {
final ChannelBuffer buffer = (ChannelBuffer) event.getMessage();
buffer.readBytes(outputStream, buffer.readableBytes());
outputStream.flush();
future.setSuccess();
} else {
throw new IllegalArgumentException(
"Only ChannelBuffer objects are supported to be written onto the IOStreamChannelSink! "
+ "Please check if the encoder pipeline is configured correctly."
);
}
}
}
}

View File

@ -29,7 +29,6 @@ import org.jboss.netty.channel.AbstractChannel;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelSink;
import org.jboss.netty.channel.MessageEvent;

View File

@ -142,8 +142,11 @@ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler {
{
SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);
if (ifModifiedSinceDate.getTime() == file.lastModified())
{
// 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;
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.autobahn;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
/**
* A Web Socket echo server for running the <a href="http://www.tavendo.de/autobahn/testsuite.html">autobahn</a>
* test suite
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServer {
public static void main(String[] args) {
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE);
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
//bootstrap.setOption("child.tcpNoDelay", true);
// Set up the event pipeline factory.
bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory());
// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(9000));
System.out.println("Web Socket Server started on 9000. Open your browser and navigate to http://localhost:9000/");
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.autobahn;
import static org.jboss.netty.handler.codec.http.HttpHeaders.*;
import static org.jboss.netty.handler.codec.http.HttpMethod.*;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*;
import static org.jboss.netty.handler.codec.http.HttpVersion.*;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
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.websocketx.BinaryWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.CharsetUtil;
/**
* Handles handshakes and messages
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class);
private WebSocketServerHandshaker handshaker = null;
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object msg = e.getMessage();
if (msg instanceof HttpRequest) {
handleHttpRequest(ctx, (HttpRequest) msg);
} else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
// Allow only GET methods.
if (req.getMethod() != GET) {
sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
return;
}
// Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
this.getWebSocketLocation(req), null, false);
this.handshaker = wsFactory.newHandshaker(ctx, req);
if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx);
} else {
this.handshaker.executeOpeningHandshake(ctx, req);
}
return;
}
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
logger.debug(String
.format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName()));
if (frame instanceof CloseWebSocketFrame) {
this.handshaker.executeClosingHandshake(ctx, (CloseWebSocketFrame) frame);
} else if (frame instanceof PingWebSocketFrame) {
ctx.getChannel().write(
new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof TextWebSocketFrame) {
//String text = ((TextWebSocketFrame) frame).getText();
ctx.getChannel().write(new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof BinaryWebSocketFrame) {
ctx.getChannel().write(
new BinaryWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof ContinuationWebSocketFrame) {
ctx.getChannel().write(
new ContinuationWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof PongWebSocketFrame) {
// Ignore
} else {
throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
.getName()));
}
}
private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
// Generate an error page if response status code is not OK (200).
if (res.getStatus().getCode() != 200) {
res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
setContentLength(res, res.getContent().readableBytes());
}
// Send the response and close the connection if necessary.
ChannelFuture f = ctx.getChannel().write(res);
if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace();
e.getChannel().close();
}
private String getWebSocketLocation(HttpRequest req) {
return "ws://" + req.getHeader(HttpHeaders.Names.HOST);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.autobahn;
import static org.jboss.netty.channel.Channels.*;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
/**
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
// Create a default pipeline implementation.
ChannelPipeline pipeline = pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketServerHandler());
return pipeline;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.
*/
/**
* This package is intended for use with testing against the Python
* <a href="http://www.tavendo.de/autobahn/testsuite.html">AutoBahn test suite</a>.
*
* <h3>How to run the tests on Ubuntu</h3>
*
* <p>01. Add <tt>ppa:twisted-dev/ppa</tt> to your system's Software Sources
*
* <p>02. Install Twisted V11: <tt>sudo apt-get install python-twisted</tt>
*
* <p>03. Intall Python Setup Tools: <tt>sudo apt-get install python-setuptools</tt>
*
* <p>04. Install AutoBahn: <tt>sudo easy_install Autobahn</tt>
*
* <p>05. Get AutoBahn testsuite source code: <tt>git clone git@github.com:oberstet/Autobahn.git</tt>
*
* <p>06. Go to AutoBahn directory: <tt>cd Autobahn</tt>
*
* <p>07. Checkout stable version: <tt>git checkout v0.4.2</tt>
*
* <p>08. Go to test suite directory: <tt>cd testsuite/websockets</tt>
*
* <p>09. Edit <tt>fuzzing_clinet_spec.json</tt> and set the version to 10.
* <code>
* {
* "servers": [{"agent": "Netty", "hostname": "localhost", "port": 9000, "version": 10}],
* "cases": ["*"]
* }
* </code>
*
* <p>10. Run the test <tt>python fuzzing_client.py</tt>. Note that the actual test case python code is
* located in <tt>/usr/local/lib/python2.6/dist-packages/autobahn-0.4.2-py2.6.egg/autobahn/cases</tt>
* and not in the checked out git repository.
*
* <p>11. See the results in <tt>reports/servers/index.html</tt>
*/
package org.jboss.netty.example.http.websocketx.autobahn;

View File

@ -0,0 +1,136 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.client;
import java.net.URI;
import java.util.ArrayList;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
/**
* A HTTP client demo app
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class App {
public static void main(String[] args) throws Exception {
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE);
runClient();
System.exit(0);
}
/**
* Send and receive some messages using a web socket client
*
* @throws Exception
*/
public static void runClient() throws Exception {
MyCallbackHandler callbackHandler = new MyCallbackHandler();
WebSocketClientFactory factory = new WebSocketClientFactory();
// Connect with spec version 10 (try changing it to V00 and it will
// still work ... fingers crossed ;-)
WebSocketClient client = factory.newClient(new URI("ws://localhost:8080/websocket"),
WebSocketSpecificationVersion.V10, callbackHandler);
// Connect
System.out.println("WebSocket Client connecting");
client.connect().awaitUninterruptibly();
Thread.sleep(200);
// Send 10 messages and wait for responses
System.out.println("WebSocket Client sending message");
for (int i = 0; i < 10; i++) {
client.send(new TextWebSocketFrame("Message #" + i));
}
Thread.sleep(1000);
// Ping
System.out.println("WebSocket Client sending ping");
client.send(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 })));
Thread.sleep(1000);
// Close
System.out.println("WebSocket Client sending close");
client.send(new CloseWebSocketFrame());
Thread.sleep(1000);
// Disconnect
client.disconnect();
}
/**
* Our web socket callback handler for this app
*/
public static class MyCallbackHandler implements WebSocketCallback {
public boolean connected = false;
public ArrayList<String> messagesReceived = new ArrayList<String>();
public MyCallbackHandler() {
return;
}
@Override
public void onConnect(WebSocketClient client) {
System.out.println("WebSocket Client connected!");
connected = true;
}
@Override
public void onDisconnect(WebSocketClient client) {
System.out.println("WebSocket Client disconnected!");
connected = false;
}
@Override
public void onMessage(WebSocketClient client, WebSocketFrame frame) {
if (frame instanceof TextWebSocketFrame) {
TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
System.out.println("WebSocket Client received message:" + textFrame.getText());
messagesReceived.add(textFrame.getText());
} else if (frame instanceof PongWebSocketFrame) {
System.out.println("WebSocket Client received pong");
} else if (frame instanceof CloseWebSocketFrame) {
System.out.println("WebSocket Client received closing");
}
}
@Override
public void onError(Throwable t) {
System.out.println("WebSocket Client error " + t.toString());
}
}
}

View File

@ -0,0 +1,71 @@
//The MIT License
//
//Copyright (c) 2009 Carl Bystršm
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package org.jboss.netty.example.http.websocketx.client;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
/**
* Copied from https://github.com/cgbystrom/netty-tools
*
* Callbacks for the {@link WebSocketClient}. Implement and get notified when events happen.
*
* @author <a href="http://www.pedantique.org/">Carl Bystr&ouml;m</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public interface WebSocketCallback {
/**
* Called when the client is connected to the server
*
* @param client
* Current client used to connect
*/
public void onConnect(WebSocketClient client);
/**
* Called when the client got disconnected from the server.
*
* @param client
* Current client that was disconnected
*/
public void onDisconnect(WebSocketClient client);
/**
* Called when a message arrives from the server.
*
* @param client
* Current client connected
* @param frame
* Data received from server
*/
public void onMessage(WebSocketClient client, WebSocketFrame frame);
/**
* Called when an unhandled errors occurs.
*
* @param t
* The causing error
*/
public void onError(Throwable t);
}

View File

@ -0,0 +1,57 @@
//The MIT License
//
//Copyright (c) 2009 Carl Bystršm
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package org.jboss.netty.example.http.websocketx.client;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
/**
* Copied from https://github.com/cgbystrom/netty-tools
*
* @author <a href="http://www.pedantique.org/">Carl Bystr&ouml;m</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public interface WebSocketClient {
/**
* Connect to server Host and port is setup by the factory.
*
* @return Connect future. Fires when connected.
*/
public ChannelFuture connect();
/**
* Disconnect from the server
*
* @return Disconnect future. Fires when disconnected.
*/
public ChannelFuture disconnect();
/**
* Send data to server
*
* @param frame
* Data for sending
* @return Write future. Will fire when the data is sent.
*/
public ChannelFuture send(WebSocketFrame frame);
}

View File

@ -0,0 +1,86 @@
//The MIT License
//
//Copyright (c) 2009 Carl Bystršm
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package org.jboss.netty.example.http.websocketx.client;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
import java.net.URI;
import java.util.concurrent.Executors;
/**
* Copied from https://github.com/cgbystrom/netty-tools
*
* A factory for creating WebSocket clients. The entry point for creating and connecting a client. Can and should be
* used to create multiple instances.
*
* @author <a href="http://www.pedantique.org/">Carl Bystr&ouml;m</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class WebSocketClientFactory {
private NioClientSocketChannelFactory socketChannelFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
/**
* Create a new WebSocket client
*
* @param url
* URL to connect to.
* @param version
* Web Socket version to support
* @param callback
* Callback interface to receive events
* @return A WebSocket client. Call {@link WebSocketClient#connect()} to connect.
*/
public WebSocketClient newClient(final URI url,
final WebSocketSpecificationVersion version,
final WebSocketCallback callback) {
ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory);
String protocol = url.getScheme();
if (!protocol.equals("ws") && !protocol.equals("wss")) {
throw new IllegalArgumentException("Unsupported protocol: " + protocol);
}
final WebSocketClientHandler clientHandler = new WebSocketClientHandler(bootstrap, url, version, callback);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new WebSocketHttpResponseDecoder());
pipeline.addLast("encoder", new HttpRequestEncoder());
pipeline.addLast("ws-handler", clientHandler);
return pipeline;
}
});
return clientHandler;
}
}

View File

@ -0,0 +1,124 @@
//The MIT License
//
//Copyright (c) 2009 Carl Bystršm
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package org.jboss.netty.example.http.websocketx.client;
import java.net.InetSocketAddress;
import java.net.URI;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
import org.jboss.netty.util.CharsetUtil;
/**
* Copied from https://github.com/cgbystrom/netty-tools
*
* Handles socket communication for a connected WebSocket client Not intended for end-users. Please use
* {@link WebSocketClient} or {@link WebSocketCallback} for controlling your client.
*
* @author <a href="http://www.pedantique.org/">Carl Bystr&ouml;m</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class WebSocketClientHandler extends SimpleChannelUpstreamHandler implements WebSocketClient {
private ClientBootstrap bootstrap;
private URI url;
private WebSocketCallback callback;
private Channel channel;
private WebSocketClientHandshaker handshaker = null;
private WebSocketSpecificationVersion version;
public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketSpecificationVersion version, WebSocketCallback callback) {
this.bootstrap = bootstrap;
this.url = url;
this.version = version;
this.callback = callback;
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
channel = e.getChannel();
this.handshaker = new WebSocketClientHandshakerFactory().newHandshaker(url, version, null, false);
handshaker.beginOpeningHandshake(ctx, channel);
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
callback.onDisconnect(this);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
if (!handshaker.isOpeningHandshakeCompleted()) {
handshaker.endOpeningHandshake(ctx, (HttpResponse) e.getMessage());
callback.onConnect(this);
return;
}
if (e.getMessage() instanceof HttpResponse) {
HttpResponse response = (HttpResponse) e.getMessage();
throw new WebSocketException("Unexpected HttpResponse (status=" + response.getStatus() + ", content="
+ response.getContent().toString(CharsetUtil.UTF_8) + ")");
}
WebSocketFrame frame = (WebSocketFrame) e.getMessage();
callback.onMessage(this, frame);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
final Throwable t = e.getCause();
callback.onError(t);
e.getChannel().close();
}
public ChannelFuture connect() {
return bootstrap.connect(new InetSocketAddress(url.getHost(), url.getPort()));
}
public ChannelFuture disconnect() {
return channel.close();
}
public ChannelFuture send(WebSocketFrame frame) {
return channel.write(frame);
}
public URI getUrl() {
return url;
}
public void setUrl(URI url) {
this.url = url;
}
}

View File

@ -0,0 +1,47 @@
//The MIT License
//
//Copyright (c) 2009 Carl Bystršm
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package org.jboss.netty.example.http.websocketx.client;
import java.io.IOException;
/**
* Copied from https://github.com/cgbystrom/netty-tools
*
* A WebSocket related exception
*
* @author <a href="http://www.pedantique.org/">Carl Bystr&ouml;m</a>
*/
public class WebSocketException extends IOException {
/**
*
*/
private static final long serialVersionUID = 1L;
public WebSocketException(String s) {
super(s);
}
public WebSocketException(String s, Throwable throwable) {
super(s, throwable);
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.client;
import org.jboss.netty.handler.codec.http.HttpMessage;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
/**
* Fix bug in standard HttpResponseDecoder for web socket clients. When status 101 is received for Hybi00, there are 16
* bytes of contents expected
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class WebSocketHttpResponseDecoder extends HttpResponseDecoder {
@Override
protected boolean isContentAlwaysEmpty(HttpMessage msg) {
if (msg instanceof HttpResponse) {
HttpResponse res = (HttpResponse) msg;
int code = res.getStatus().getCode();
// FIX force reading of protocol upgrade challenge data into the content buffer
if (code == 101) {
return false;
}
if (code < 200) {
return true;
}
switch (code) {
case 204:
case 205:
case 304:
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.
*/
/**
* <p>This is an example web service client.
* <p>To run this example, you must first start
* <tt>org.jboss.netty.example.http.websocketx.server.WebSocketServer</tt>
* <p>Next, run <tt>org.jboss.netty.example.http.websocketx.client.App</tt>.
*/
package org.jboss.netty.example.http.websocketx.client;

View File

@ -0,0 +1,74 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.server;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
/**
* A HTTP server which serves Web Socket requests at:
*
* http://localhost:8080/websocket
*
* Open your browser at http://localhost:8080/, then the demo page will be
* loaded and a Web Socket connection will be made automatically.
*
* This server illustrates support for the different web socket specification
* versions and will work with:
*
* <ul>
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
* <li>
* <li>Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00)
* <li>
* <li>Chrome 14+ (draft-ietf-hybi-thewebsocketprotocol-10)
* <li>
* <li>Firefox 7+ (draft-ietf-hybi-thewebsocketprotocol-10)
* <li>
* </ul>
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServer {
public static void main(String[] args) {
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE);
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
// Set up the event pipeline factory.
bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory());
// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(8080));
System.out.println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/");
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.server;
import static org.jboss.netty.handler.codec.http.HttpHeaders.*;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*;
import static org.jboss.netty.handler.codec.http.HttpMethod.*;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*;
import static org.jboss.netty.handler.codec.http.HttpVersion.*;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
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.websocketx.CloseWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.CharsetUtil;
/**
* Handles handshakes and messages
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class);
private static final String WEBSOCKET_PATH = "/websocket";
private WebSocketServerHandshaker handshaker = null;
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object msg = e.getMessage();
if (msg instanceof HttpRequest) {
handleHttpRequest(ctx, (HttpRequest) msg);
} else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
// Allow only GET methods.
if (req.getMethod() != GET) {
sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
return;
}
// Send the demo page and favicon.ico
if (req.getUri().equals("/")) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, OK);
ChannelBuffer content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req));
res.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8");
setContentLength(res, content.readableBytes());
res.setContent(content);
sendHttpResponse(ctx, req, res);
return;
} else if (req.getUri().equals("/favicon.ico")) {
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, NOT_FOUND);
sendHttpResponse(ctx, req, res);
return;
}
// Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
this.getWebSocketLocation(req), null, false);
this.handshaker = wsFactory.newHandshaker(ctx, req);
if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx);
} else {
this.handshaker.executeOpeningHandshake(ctx, req);
}
return;
}
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// Check for closing frame
if (frame instanceof CloseWebSocketFrame) {
this.handshaker.executeClosingHandshake(ctx, (CloseWebSocketFrame) frame);
return;
} else if (frame instanceof PingWebSocketFrame) {
ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));
return;
} else if (!(frame instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
.getName()));
}
// Send the uppercase string back.
String request = ((TextWebSocketFrame) frame).getText();
logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), request));
ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase()));
}
private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
// Generate an error page if response status code is not OK (200).
if (res.getStatus().getCode() != 200) {
res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
setContentLength(res, res.getContent().readableBytes());
}
// Send the response and close the connection if necessary.
ChannelFuture f = ctx.getChannel().write(res);
if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace();
e.getChannel().close();
}
private String getWebSocketLocation(HttpRequest req) {
return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.server;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil;
/**
* Generates the demo HTML page which is served at http://localhost:8080/
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerIndexPage {
private static final String NEWLINE = "\r\n";
public static ChannelBuffer getContent(String webSocketLocation) {
return ChannelBuffers.copiedBuffer(
"<html><head><title>Web Socket Test</title></head>" + NEWLINE +
"<body>" + NEWLINE +
"<script type=\"text/javascript\">" + NEWLINE +
"var socket;" + NEWLINE +
"if (!window.WebSocket) {" + NEWLINE +
" window.WebSocket = window.MozWebSocket;" + NEWLINE +
"}" + NEWLINE +
"if (window.WebSocket) {" + NEWLINE +
" socket = new WebSocket(\"" + webSocketLocation + "\");" + NEWLINE +
" socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };" + NEWLINE +
" socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };" + NEWLINE +
" socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };" + NEWLINE +
"} else {" + NEWLINE +
" alert(\"Your browser does not support Web Socket.\");" + NEWLINE +
"}" + NEWLINE +
"" + NEWLINE +
"function send(message) {" + NEWLINE +
" if (!window.WebSocket) { return; }" + NEWLINE +
" if (socket.readyState == WebSocket.OPEN) {" + NEWLINE +
" socket.send(message);" + NEWLINE +
" } else {" + NEWLINE +
" alert(\"The socket is not open.\");" + NEWLINE +
" }" + NEWLINE +
"}" + NEWLINE +
"</script>" + NEWLINE +
"<form onsubmit=\"return false;\">" + NEWLINE +
"<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>" +
"<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />" + NEWLINE +
"<h3>Output</h3>" + NEWLINE +
"<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>" + NEWLINE +
"</form>" + NEWLINE +
"</body>" + NEWLINE +
"</html>" + NEWLINE,
CharsetUtil.US_ASCII);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.http.websocketx.server;
import static org.jboss.netty.channel.Channels.*;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
/**
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
// Create a default pipeline implementation.
ChannelPipeline pipeline = pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketServerHandler());
return pipeline;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.
*/
/**
* <p>This package contains an example web socket web server.
* <p>The web server only handles text, ping and closing frames. For text frames,
* it echoes the received text in upper case.
* <p>Once started, you can test the web server against your browser by navigating
* to http://localhost:8080/
* <p>You can also test it with a web socket client. Send web socket traffic to
* ws://localhost:8080/websocket.
*/
package org.jboss.netty.example.http.websocketx.server;

View File

@ -0,0 +1,94 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.example.iostream;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.iostream.IOStreamAddress;
import org.jboss.netty.channel.iostream.IOStreamChannelFactory;
import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.Delimiters;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* An example demonstrating the use of the {@link org.jboss.netty.channel.iostream.IOStreamChannel}.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStream {
private static volatile boolean running = true;
public static void main(String[] args) {
final ExecutorService executorService = Executors.newCachedThreadPool();
final ClientBootstrap bootstrap = new ClientBootstrap(new IOStreamChannelFactory(executorService));
// Configure the event pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
DefaultChannelPipeline pipeline = new DefaultChannelPipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("loggingHandler", new SimpleChannelHandler() {
@Override
public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e)
throws Exception {
final String message = (String) e.getMessage();
synchronized (System.out) {
e.getChannel().write("Message received: " + message);
}
if ("exit".equals(message)) {
IOStream.running = false;
}
super.messageReceived(ctx, e);
}
}
);
return pipeline;
}
});
// Make a new connection.
ChannelFuture connectFuture = bootstrap.connect(new IOStreamAddress(System.in, System.out));
// Wait until the connection is made successfully.
Channel channel = connectFuture.awaitUninterruptibly().getChannel();
while (running) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Close the connection.
channel.close().awaitUninterruptibly();
// Shut down all thread pools to exit.
bootstrap.releaseExternalResources();
}
}

View File

@ -20,8 +20,6 @@ import org.jboss.netty.handler.codec.compression.ZlibEncoder;
import org.jboss.netty.handler.codec.compression.ZlibWrapper;
import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
import com.sun.net.httpserver.Headers;
/**
* Compresses an {@link HttpMessage} and an {@link HttpChunk} in {@code gzip} or
* {@code deflate} encoding while respecting the {@code "Accept-Encoding"} header.

View File

@ -236,6 +236,18 @@ public class HttpHeaders {
* {@code "Sec-WebSocket-Protocol"}
*/
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
/**
* {@code "Sec-WebSocket-Version"}
*/
public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
/**
* {@code "Sec-WebSocket-Key"}
*/
public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
/**
* {@code "Sec-WebSocket-Accept"}
*/
public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";
/**
* {@code "Server"}
*/

View File

@ -0,0 +1,68 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
/**
* Web Socket frame containing binary data
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class BinaryWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty binary frame.
*/
public BinaryWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new binary frame with the specified binary data. The final
* fragment flag is set to true.
*
* @param binaryData
* the content of the frame.
*/
public BinaryWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new binary frame with the specified binary data and the final
* fragment flag.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
*/
public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffers;
/**
* Web Socket Frame for closing the connection
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class CloseWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty close frame.
*/
public CloseWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new close frame
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
*/
public CloseWebSocketFrame(boolean finalFragment, int rsv) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil;
/**
* Web Socket continuation frame containing continuation text or binary data.
* This is used for fragmented messages where the contents of a messages is
* contained more than 1 frame.
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class ContinuationWebSocketFrame extends WebSocketFrame {
private String aggregatedText = null;
/**
* Creates a new empty continuation frame.
*/
public ContinuationWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new continuation frame with the specified binary data. The
* final fragment flag is set to true.
*
* @param binaryData
* the content of the frame.
*/
public ContinuationWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new continuation frame with the specified binary data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
/**
* Creates a new continuation frame with the specified binary data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
* @param aggregatedText
* Aggregated text set by decoder on the final continuation frame
* of a fragmented text message
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
this.aggregatedText = aggregatedText;
}
/**
* Creates a new continuation frame with the specified text data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param text
* text content of the frame.
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setText(text);
}
/**
* Returns the text data in this frame
*/
public String getText() {
if (this.getBinaryData() == null) {
return null;
}
return this.getBinaryData().toString(CharsetUtil.UTF_8);
}
/**
* Sets the string for this frame
*
* @param text
* text to store
*/
public void setText(String text) {
if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
/**
* Aggregated text returned by decoder on the final continuation frame of a
* fragmented text message
*/
public String getAggregatedText() {
return aggregatedText;
}
public void setAggregatedText(String aggregatedText) {
this.aggregatedText = aggregatedText;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
/**
* Web Socket frame containing binary data
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class PingWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty ping frame.
*/
public PingWebSocketFrame() {
this.setFinalFragment(true);
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new ping frame with the specified binary data.
*
* @param binaryData
* the content of the frame.
*/
public PingWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new ping frame with the specified binary data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
*/
public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
/**
* Web Socket frame containing binary data
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class PongWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty pong frame.
*/
public PongWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new pong frame with the specified binary data.
*
* @param binaryData
* the content of the frame.
*/
public PongWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new pong frame with the specified binary data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
*/
public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil;
/**
* Web Socket text frame with assumed UTF-8 encoding
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
*/
public class TextWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty text frame.
*/
public TextWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new text frame with the specified text string. The final
* fragment flag is set to true.
*
* @param text
* String to put in the frame
*/
public TextWebSocketFrame(String text) {
if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
/**
* Creates a new text frame with the specified binary data. The final
* fragment flag is set to true.
*
* @param binaryData
* the content of the frame. Must be UTF-8 encoded
*/
public TextWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new text frame with the specified text string. The final
* fragment flag is set to true.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param text
* String to put in the frame
*/
public TextWebSocketFrame(boolean finalFragment, int rsv, String text) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
/**
* Creates a new text frame with the specified binary data. The final
* fragment flag is set to true.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame. Must be UTF-8 encoded
*/
public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
/**
* Returns the text data in this frame
*/
public String getText() {
if (this.getBinaryData() == null) {
return null;
}
return this.getBinaryData().toString(CharsetUtil.UTF_8);
}
/**
* Sets the string for this frame
*
* @param text
* text to store
*/
public void setText(String text) {
if (text == null) {
throw new NullPointerException("text");
}
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
@Override
public String toString() {
return getClass().getSimpleName() + "(text: " + getText() + ')';
}
}

View File

@ -0,0 +1,35 @@
/*
* Adaptation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
*
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
package org.jboss.netty.handler.codec.http.websocketx;
/**
* Invalid UTF8 bytes encountered
*
* @author Bjoern Hoehrmann
* @author https://github.com/joewalnes/webbit
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class UTF8Exception extends RuntimeException {
private static final long serialVersionUID = 1L;
public UTF8Exception(String reason) {
super(reason);
}
}

View File

@ -0,0 +1,84 @@
/*
* Adaptation of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
*
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
package org.jboss.netty.handler.codec.http.websocketx;
/**
* Checks UTF8 bytes for validity before converting it into a string
*
* @author Bjoern Hoehrmann
* @author https://github.com/joewalnes/webbit
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class UTF8Output {
private static final int UTF8_ACCEPT = 0;
private static final int UTF8_REJECT = 12;
private static final byte[] TYPES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11,
6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
private static final byte[] STATES = { 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24,
12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12,
12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12 };
private int state = UTF8_ACCEPT;
private int codep = 0;
private final StringBuilder stringBuilder;
public UTF8Output(byte[] bytes) {
stringBuilder = new StringBuilder(bytes.length);
write(bytes);
}
public void write(byte[] bytes) {
for (byte b : bytes) {
write(b);
}
}
public void write(int b) {
byte type = TYPES[b & 0xFF];
codep = (state != UTF8_ACCEPT) ? (b & 0x3f) | (codep << 6) : (0xff >> type) & (b);
state = STATES[state + type];
if (state == UTF8_ACCEPT) {
stringBuilder.append((char) codep);
} else if (state == UTF8_REJECT) {
throw new UTF8Exception("bytes are not UTF-8");
}
}
public String toString() {
if (state != UTF8_ACCEPT) {
throw new UTF8Exception("bytes are not UTF-8");
}
return stringBuilder.toString();
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
import org.jboss.netty.handler.codec.replay.VoidEnum;
/**
* Decodes {@link ChannelBuffer}s into {@link WebSocketFrame}s.
* <p>
* For the detailed instruction on adding add Web Socket support to your HTTP
* server, take a look into the <tt>WebSocketServer</tt> example located in the
* {@code org.jboss.netty.example.http.websocket} package.
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author Mike Heath (mheath@apache.org)
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @version $Rev: 2342 $, $Date: 2010-07-07 14:07:39 +0900 (Wed, 07 Jul 2010) $
*
* @apiviz.landmark
* @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame
*/
public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
public static final int DEFAULT_MAX_FRAME_SIZE = 16384;
private final int maxFrameSize;
private boolean receivedClosingHandshake;
public WebSocket00FrameDecoder() {
this(DEFAULT_MAX_FRAME_SIZE);
}
/**
* Creates a new instance of {@code WebSocketFrameDecoder} with the
* specified {@code maxFrameSize}. If the client sends a frame size larger
* than {@code maxFrameSize}, the channel will be closed.
*
* @param maxFrameSize
* the maximum frame size to decode
*/
public WebSocket00FrameDecoder(int maxFrameSize) {
this.maxFrameSize = maxFrameSize;
}
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum state)
throws Exception {
// Discard all data received if closing handshake was received before.
if (receivedClosingHandshake) {
buffer.skipBytes(actualReadableBytes());
return null;
}
// Decode a frame otherwise.
byte type = buffer.readByte();
if ((type & 0x80) == 0x80) {
// If the MSB on type is set, decode the frame length
return decodeBinaryFrame(type, buffer);
} else {
// Decode a 0xff terminated UTF-8 string
return decodeTextFrame(type, buffer);
}
}
private WebSocketFrame decodeBinaryFrame(byte type, ChannelBuffer buffer) throws TooLongFrameException {
long frameSize = 0;
int lengthFieldSize = 0;
byte b;
do {
b = buffer.readByte();
frameSize <<= 7;
frameSize |= b & 0x7f;
if (frameSize > maxFrameSize) {
throw new TooLongFrameException();
}
lengthFieldSize++;
if (lengthFieldSize > 8) {
// Perhaps a malicious peer?
throw new TooLongFrameException();
}
} while ((b & 0x80) == 0x80);
if (type == ((byte) 0xFF) && frameSize == 0) {
receivedClosingHandshake = true;
return new CloseWebSocketFrame();
}
return new BinaryWebSocketFrame(buffer.readBytes((int) frameSize));
}
private WebSocketFrame decodeTextFrame(byte type, ChannelBuffer buffer) throws TooLongFrameException {
int ridx = buffer.readerIndex();
int rbytes = actualReadableBytes();
int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF);
if (delimPos == -1) {
// Frame delimiter (0xFF) not found
if (rbytes > maxFrameSize) {
// Frame length exceeded the maximum
throw new TooLongFrameException();
} else {
// Wait until more data is received
return null;
}
}
int frameSize = delimPos - ridx;
if (frameSize > maxFrameSize) {
throw new TooLongFrameException();
}
ChannelBuffer binaryData = buffer.readBytes(frameSize);
buffer.skipBytes(1);
int ffDelimPos = binaryData.indexOf(binaryData.readerIndex(), binaryData.writerIndex(), (byte) 0xFF);
if (ffDelimPos >= 0) {
throw new IllegalArgumentException("a text frame should not contain 0xFF.");
}
return new TextWebSocketFrame(binaryData);
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelHandler.Sharable;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
/**
* Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}.
* <p>
* For the detailed instruction on adding add Web Socket support to your HTTP
* server, take a look into the <tt>WebSocketServer</tt> example located in the
* {@code org.jboss.netty.example.http.websocket} package.
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author Mike Heath (mheath@apache.org)
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @version $Rev: 2362 $, $Date: 2010-09-09 19:59:22 +0900 (Thu, 09 Sep 2010) $
*
* @apiviz.landmark
* @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame
*/
@Sharable
public class WebSocket00FrameEncoder extends OneToOneEncoder {
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame) {
// Text frame
ChannelBuffer data = frame.getBinaryData();
ChannelBuffer encoded = channel.getConfig().getBufferFactory()
.getBuffer(data.order(), data.readableBytes() + 2);
encoded.writeByte((byte) 0x00);
encoded.writeBytes(data, data.readerIndex(), data.readableBytes());
encoded.writeByte((byte) 0xFF);
return encoded;
} else if (frame instanceof CloseWebSocketFrame) {
// Close frame
ChannelBuffer data = frame.getBinaryData();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), 2);
encoded.writeByte((byte) 0xFF);
encoded.writeByte((byte) 0x00);
return encoded;
} else {
// Binary frame
ChannelBuffer data = frame.getBinaryData();
int dataLen = data.readableBytes();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), dataLen + 5);
// Encode type.
encoded.writeByte((byte) 0x80);
// Encode length.
int b1 = dataLen >>> 28 & 0x7F;
int b2 = dataLen >>> 14 & 0x7F;
int b3 = dataLen >>> 7 & 0x7F;
int b4 = dataLen & 0x7F;
if (b1 == 0) {
if (b2 == 0) {
if (b3 == 0) {
encoded.writeByte(b4);
} else {
encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4);
}
} else {
encoded.writeByte(b2 | 0x80);
encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4);
}
} else {
encoded.writeByte(b1 | 0x80);
encoded.writeByte(b2 | 0x80);
encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4);
}
// Encode binary data.
encoded.writeBytes(data, data.readerIndex(), dataLen);
return encoded;
}
}
return msg;
}
}

View File

@ -0,0 +1,370 @@
// (BSD License: http://www.opensource.org/licenses/bsd-license)
//
// Copyright (c) 2011, Joe Walnes and contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the
// following conditions are met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the
// following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// * Neither the name of the Webbit nor the names of
// its contributors may be used to endorse or promote products
// derived from this software without specific prior written
// permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.CorruptedFrameException;
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
/**
* Decodes a web socket frame from wire protocol version 8 format. This code was
* forked from <a href="https://github.com/joewalnes/webbit">webbit</a> and modified.
*
* @author Aslak Hellesøy
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDecoder.State> {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameDecoder.class);
private static final byte OPCODE_CONT = 0x0;
private static final byte OPCODE_TEXT = 0x1;
private static final byte OPCODE_BINARY = 0x2;
private static final byte OPCODE_CLOSE = 0x8;
private static final byte OPCODE_PING = 0x9;
private static final byte OPCODE_PONG = 0xA;
private UTF8Output fragmentedFramesText = null;
private int fragmentedFramesCount = 0;
private boolean frameFinalFlag;
private int frameRsv;
private int frameOpcode;
private long framePayloadLength;
private ChannelBuffer framePayload = null;
private int framePayloadBytesRead = 0;
private ChannelBuffer maskingKey;
private boolean allowExtensions = false;
private boolean maskedPayload = false;
private boolean receivedClosingHandshake = false;
public static enum State {
FRAME_START, MASKING_KEY, PAYLOAD, CORRUPT
}
/**
* Constructor
*
* @param maskedPayload
* Web socket servers must set this to true processed incoming
* masked payload. Client implementations must set this to false.
* @param allowExtensions
* Flag to allow reserved extension bits to be used or not
*/
public WebSocket08FrameDecoder(boolean maskedPayload, boolean allowExtensions) {
super(State.FRAME_START);
this.maskedPayload = maskedPayload;
this.allowExtensions = allowExtensions;
}
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, State state)
throws Exception {
// Discard all data received if closing handshake was received before.
if (receivedClosingHandshake) {
buffer.skipBytes(actualReadableBytes());
return null;
}
switch (state) {
case FRAME_START:
framePayloadBytesRead = 0;
framePayloadLength = -1;
framePayload = null;
// FIN, RSV, OPCODE
byte b = buffer.readByte();
frameFinalFlag = (b & 0x80) != 0;
frameRsv = (b & 0x70) >> 4;
frameOpcode = (b & 0x0F);
logger.debug("Decoding WebSocket Frame opCode=" + frameOpcode);
// MASK, PAYLOAD LEN 1
b = buffer.readByte();
boolean frameMasked = (b & 0x80) != 0;
int framePayloadLen1 = (b & 0x7F);
if (frameRsv != 0 && !this.allowExtensions) {
protocolViolation(channel, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
return null;
}
if (this.maskedPayload && !frameMasked) {
protocolViolation(channel, "unmasked client to server frame");
return null;
}
if (frameOpcode > 7) { // control frame (have MSB in opcode set)
// control frames MUST NOT be fragmented
if (!frameFinalFlag) {
protocolViolation(channel, "fragmented control frame");
return null;
}
// control frames MUST have payload 125 octets or less
if (framePayloadLen1 > 125) {
protocolViolation(channel, "control frame with payload length > 125 octets");
return null;
}
// check for reserved control frame opcodes
if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING || frameOpcode == OPCODE_PONG)) {
protocolViolation(channel, "control frame using reserved opcode " + frameOpcode);
return null;
}
// close frame : if there is a body, the first two bytes of the
// body MUST be a 2-byte
// unsigned integer (in network byte order) representing a
// status code
if (frameOpcode == 8 && framePayloadLen1 == 1) {
protocolViolation(channel, "received close control frame with payload len 1");
return null;
}
} else { // data frame
// check for reserved data frame opcodes
if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT || frameOpcode == OPCODE_BINARY)) {
protocolViolation(channel, "data frame using reserved opcode " + frameOpcode);
return null;
}
// check opcode vs message fragmentation state 1/2
if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) {
protocolViolation(channel, "received continuation data frame outside fragmented message");
return null;
}
// check opcode vs message fragmentation state 2/2
if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) {
protocolViolation(channel, "received non-continuation data frame while inside fragmented message");
return null;
}
}
if (framePayloadLen1 == 126) {
framePayloadLength = buffer.readUnsignedShort();
if (framePayloadLength < 126) {
protocolViolation(channel, "invalid data frame length (not using minimal length encoding)");
return null;
}
} else if (framePayloadLen1 == 127) {
framePayloadLength = buffer.readLong();
// TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe
// just check if it's negative?
if (framePayloadLength < 65536) {
protocolViolation(channel, "invalid data frame length (not using minimal length encoding)");
return null;
}
} else {
framePayloadLength = framePayloadLen1;
}
//logger.debug("Frame length=" + framePayloadLength);
checkpoint(State.MASKING_KEY);
case MASKING_KEY:
if (this.maskedPayload){
maskingKey = buffer.readBytes(4);
}
checkpoint(State.PAYLOAD);
case PAYLOAD:
// Some times, the payload may not be delivered in 1 nice packet
// We need to accumulate the data until we have it all
int rbytes = actualReadableBytes();
ChannelBuffer payloadBuffer = null;
int willHaveReadByteCount = framePayloadBytesRead + rbytes;
//logger.debug("Frame rbytes=" + rbytes + " willHaveReadByteCount=" + willHaveReadByteCount + " framePayloadLength=" + framePayloadLength);
if (willHaveReadByteCount == framePayloadLength) {
// We have all our content so proceed to process
payloadBuffer = buffer.readBytes(rbytes);
} else if (willHaveReadByteCount < framePayloadLength) {
// We don't have all our content so accumulate payload.
// Returning null means we will get called back
payloadBuffer = buffer.readBytes(rbytes);
if (framePayload == null) {
framePayload = channel.getConfig().getBufferFactory().getBuffer(toFrameLength(framePayloadLength));
}
framePayload.writeBytes(payloadBuffer);
framePayloadBytesRead = framePayloadBytesRead + rbytes;
// Return null to wait for more bytes to arrive
return null;
} else if (willHaveReadByteCount > framePayloadLength) {
// We have more than what we need so read up to the end of frame
// Leave the remainder in the buffer for next frame
payloadBuffer = buffer.readBytes(toFrameLength(framePayloadLength - framePayloadBytesRead));
}
// Now we have all the data, the next checkpoint must be the next
// frame
checkpoint(State.FRAME_START);
// Take the data that we have in this packet
if (framePayload == null) {
framePayload = payloadBuffer;
} else {
framePayload.writeBytes(payloadBuffer);
}
// Unmask data if needed
if (this.maskedPayload) {
unmask(framePayload);
}
// Processing for fragmented messages
String aggregatedText = null;
if (frameFinalFlag) {
// Final frame of the sequence. Apparently ping frames are
// allowed in the middle of a fragmented message
if (frameOpcode != OPCODE_PING) {
fragmentedFramesCount = 0;
// Check text for UTF8 correctness
if (frameOpcode == OPCODE_TEXT || fragmentedFramesText != null) {
// Check UTF-8 correctness for this payload
checkUTF8String(channel, framePayload.array());
// This does a second check to make sure UTF-8
// correctness for entire text message
aggregatedText = fragmentedFramesText.toString();
fragmentedFramesText = null;
}
}
} else {
// Not final frame so we can expect more frames in the
// fragmented sequence
if (fragmentedFramesCount == 0) {
// First text or binary frame for a fragmented set
fragmentedFramesText = null;
if (frameOpcode == OPCODE_TEXT) {
checkUTF8String(channel, framePayload.array());
}
} else {
// Subsequent frames - only check if init frame is text
if (fragmentedFramesText != null) {
checkUTF8String(channel, framePayload.array());
}
}
// Increment counter
fragmentedFramesCount++;
}
// Return the frame
if (frameOpcode == OPCODE_TEXT) {
return new TextWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_BINARY) {
return new BinaryWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_PING) {
return new PingWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_PONG) {
return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_CONT) {
return new ContinuationWebSocketFrame(frameFinalFlag, frameRsv, framePayload, aggregatedText);
} else if (frameOpcode == OPCODE_CLOSE) {
this.receivedClosingHandshake = true;
return new CloseWebSocketFrame(frameFinalFlag, frameRsv);
} else {
throw new UnsupportedOperationException("Cannot decode web socket frame with opcode: " + frameOpcode);
}
case CORRUPT:
// If we don't keep reading Netty will throw an exception saying
// we can't return null if no bytes read and state not changed.
buffer.readByte();
return null;
default:
throw new Error("Shouldn't reach here.");
}
}
private void unmask(ChannelBuffer frame) {
byte[] bytes = frame.array();
for (int i = 0; i < bytes.length; i++) {
frame.setByte(i, frame.getByte(i) ^ maskingKey.getByte(i % 4));
}
}
private void protocolViolation(Channel channel, String reason) throws CorruptedFrameException {
checkpoint(State.CORRUPT);
if (channel.isConnected()) {
channel.write(ChannelBuffers.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
channel.close().awaitUninterruptibly();
}
throw new CorruptedFrameException(reason);
}
private int toFrameLength(long l) throws TooLongFrameException {
if (l > Integer.MAX_VALUE) {
throw new TooLongFrameException("Length:" + l);
} else {
return (int) l;
}
}
private void checkUTF8String(Channel channel, byte[] bytes) throws CorruptedFrameException {
try {
// StringBuilder sb = new StringBuilder("UTF8 " + bytes.length +
// " bytes: ");
// for (byte b : bytes) {
// sb.append(Integer.toHexString(b)).append(" ");
// }
// logger.debug(sb.toString());
if (fragmentedFramesText == null) {
fragmentedFramesText = new UTF8Output(bytes);
} else {
fragmentedFramesText.write(bytes);
}
} catch (UTF8Exception ex) {
protocolViolation(channel, "invalid UTF-8 bytes");
}
}
}

View File

@ -0,0 +1,174 @@
// (BSD License: http://www.opensource.org/licenses/bsd-license)
//
// Copyright (c) 2011, Joe Walnes and contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the
// following conditions are met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the
// following disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// * Neither the name of the Webbit nor the names of
// its contributors may be used to endorse or promote products
// derived from this software without specific prior written
// permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package org.jboss.netty.handler.codec.http.websocketx;
import java.nio.ByteBuffer;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
/**
* <p>
* Encodes a web socket frame into wire protocol version 8 format. This code was
* forked from <a href="https://github.com/joewalnes/webbit">webbit</a> and modified.
* </p>
*
* @author Aslak Hellesøy
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class WebSocket08FrameEncoder extends OneToOneEncoder {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class);
private static final byte OPCODE_CONT = 0x0;
private static final byte OPCODE_TEXT = 0x1;
private static final byte OPCODE_BINARY = 0x2;
private static final byte OPCODE_CLOSE = 0x8;
private static final byte OPCODE_PING = 0x9;
private static final byte OPCODE_PONG = 0xA;
private boolean maskPayload = false;
/**
* Constructor
*
* @param maskPayload
* Web socket clients must set this to true to mask payload.
* Server implementations must set this to false.
*/
public WebSocket08FrameEncoder(boolean maskPayload) {
this.maskPayload = maskPayload;
}
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
byte[] mask = null;
if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg;
ChannelBuffer data = frame.getBinaryData();
if (data == null) {
data = ChannelBuffers.EMPTY_BUFFER;
}
byte opcode;
if (frame instanceof TextWebSocketFrame) {
opcode = OPCODE_TEXT;
} else if (frame instanceof PingWebSocketFrame) {
opcode = OPCODE_PING;
} else if (frame instanceof PongWebSocketFrame) {
opcode = OPCODE_PONG;
} else if (frame instanceof CloseWebSocketFrame) {
opcode = OPCODE_CLOSE;
} else if (frame instanceof BinaryWebSocketFrame) {
opcode = OPCODE_BINARY;
} else if (frame instanceof ContinuationWebSocketFrame) {
opcode = OPCODE_CONT;
} else {
throw new UnsupportedOperationException("Cannot encode frame of type: " + frame.getClass().getName());
}
int length = data.readableBytes();
logger.debug("Encoding WebSocket Frame opCode=" + opcode + " length=" + length);
int b0 = 0;
if (frame.isFinalFragment()) {
b0 |= (1 << 7);
}
b0 |= (frame.getRsv() % 8) << 4;
b0 |= opcode % 128;
ChannelBuffer header;
ChannelBuffer body;
if (opcode == OPCODE_PING && length > 125) {
throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
+ length);
}
int maskLength = this.maskPayload ? 4 : 0;
if (length <= 125) {
header = ChannelBuffers.buffer(2 + maskLength);
header.writeByte(b0);
byte b = (byte) (this.maskPayload ? (0x80 | (byte) length) : (byte) length);
header.writeByte(b);
} else if (length <= 0xFFFF) {
header = ChannelBuffers.buffer(4 + maskLength);
header.writeByte(b0);
header.writeByte(this.maskPayload ? (0xFE) : 126);
header.writeByte((length >>> 8) & 0xFF);
header.writeByte((length) & 0xFF);
} else {
header = ChannelBuffers.buffer(10 + maskLength);
header.writeByte(b0);
header.writeByte(this.maskPayload ? (0xFF) : 127);
header.writeLong(length);
}
// Write payload
if (this.maskPayload) {
Integer random = (int) (Math.random() * Integer.MAX_VALUE);
mask = ByteBuffer.allocate(4).putInt(random).array();
header.writeBytes(mask);
body = ChannelBuffers.buffer(length);
int counter = 0;
while (data.readableBytes() > 0) {
byte byteData = data.readByte();
body.writeByte(byteData ^ mask[+counter++ % 4]);
}
} else {
body = data;
}
return ChannelBuffers.wrappedBuffer(header, body);
}
// If not websocket, then just return the message
return msg;
}
}

View File

@ -0,0 +1,211 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.base64.Base64;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.util.CharsetUtil;
/**
* Base class for web socket client handshake implementations
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public abstract class WebSocketClientHandshaker {
private URI webSocketURL;
private WebSocketSpecificationVersion version = WebSocketSpecificationVersion.UNKNOWN;
private boolean openingHandshakeCompleted = false;
private String subProtocolRequest = null;
private String subProtocolResponse = null;
/**
*
* @param webSocketURL
* @param version
* @param subProtocol
*/
public WebSocketClientHandshaker(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol) {
this.webSocketURL = webSocketURL;
this.version = version;
this.subProtocolRequest = subProtocol;
}
/**
* Returns the URI to the web socket. e.g. "ws://myhost.com/path"
*/
public URI getWebSocketURL() {
return webSocketURL;
}
protected void setWebSocketURL(URI webSocketURL) {
this.webSocketURL = webSocketURL;
}
/**
* Version of the web socket specification that is being used
*/
public WebSocketSpecificationVersion getVersion() {
return version;
}
protected void setVersion(WebSocketSpecificationVersion version) {
this.version = version;
}
/**
* Flag to indicate if the opening handshake is complete
*/
public boolean isOpeningHandshakeCompleted() {
return openingHandshakeCompleted;
}
protected void setOpenningHandshakeCompleted(boolean openningHandshakeCompleted) {
this.openingHandshakeCompleted = openningHandshakeCompleted;
}
/**
* Returns the sub protocol request sent to the server as specified in the
* constructor
*/
public String getSubProtocolRequest() {
return subProtocolRequest;
}
protected void setSubProtocolRequest(String subProtocolRequest) {
this.subProtocolRequest = subProtocolRequest;
}
/**
* Returns the sub protocol response and sent by the server. Only available
* after end of handshake.
*/
public String getSubProtocolResponse() {
return subProtocolResponse;
}
protected void setSubProtocolResponse(String subProtocolResponse) {
this.subProtocolResponse = subProtocolResponse;
}
/**
* Performs the opening handshake
*
* @param ctx
* Channel context
* @param channel
* Channel
*/
public abstract void beginOpeningHandshake(ChannelHandlerContext ctx, Channel channel);
/**
* Performs the closing handshake
*
* @param ctx
* Channel context
* @param response
* HTTP response containing the closing handshake details
*/
public abstract void endOpeningHandshake(ChannelHandlerContext ctx, HttpResponse response)
throws WebSocketHandshakeException;
/**
* Performs an MD5 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
protected byte[] md5(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 not supported on this platform");
}
}
/**
* Performs an SHA-1 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
protected byte[] sha1(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("SHA-1 not supported on this platform");
}
}
/**
* Base 64 encoding
*
* @param bytes
* Bytes to encode
* @return encoded string
*/
protected String base64Encode(byte[] bytes) {
ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
}
/**
* Creates some random bytes
*
* @param size
* Number of random bytes to create
* @return random bytes
*/
protected byte[] createRandomBytes(int size) {
byte[] bytes = new byte[size];
for (int i = 0; i < size; i++) {
bytes[i] = (byte) createRandomNumber(0, 255);
}
return bytes;
}
/**
* Generates a random number
*
* @param min
* Minimum value
* @param max
* Maximum value
* @return Random number
*/
protected int createRandomNumber(int min, int max) {
int rand = (int) (Math.random() * max + min);
return rand;
}
}

View File

@ -0,0 +1,249 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
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.HttpMethod;
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.HttpVersion;
/**
* <p>
* Performs client side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>
* </p>
* <p>
* A very large portion of this code was taken from the Netty 3.2 HTTP example.
* </p>
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
private byte[] expectedChallengeResponseBytes = null;
/**
* Constructor specifying the destination web socket location and version to
* initiate
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the
* server
* @param subProtocol
* Sub protocol request sent to the server.
*/
public WebSocketClientHandshaker00(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol) {
super(webSocketURL, version, subProtocol);
return;
}
/**
* <p>
* Sends the opening request to the server:
* </p>
*
* <pre>
* GET /demo HTTP/1.1
* Upgrade: WebSocket
* Connection: Upgrade
* Host: example.com
* Origin: http://example.com
* Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
* Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
*
* ^n:ds[4U
* </pre>
*
* @param ctx
* Channel context
* @param channel
* Channel into which we can write our request
*/
@Override
public void beginOpeningHandshake(ChannelHandlerContext ctx, Channel channel) {
// Make keys
int spaces1 = createRandomNumber(1, 12);
int spaces2 = createRandomNumber(1, 12);
int max1 = Integer.MAX_VALUE / spaces1;
int max2 = Integer.MAX_VALUE / spaces2;
int number1 = createRandomNumber(0, max1);
int number2 = createRandomNumber(0, max2);
int product1 = number1 * spaces1;
int product2 = number2 * spaces2;
String key1 = Integer.toString(product1);
String key2 = Integer.toString(product2);
key1 = insertRandomCharacters(key1);
key2 = insertRandomCharacters(key2);
key1 = insertSpaces(key1, spaces1);
key2 = insertSpaces(key2, spaces2);
byte[] key3 = createRandomBytes(8);
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt(number1);
byte[] number1Array = buffer.array();
buffer = ByteBuffer.allocate(4);
buffer.putInt(number2);
byte[] number2Array = buffer.array();
byte[] challenge = new byte[16];
System.arraycopy(number1Array, 0, challenge, 0, 4);
System.arraycopy(number2Array, 0, challenge, 4, 4);
System.arraycopy(key3, 0, challenge, 8, 8);
this.expectedChallengeResponseBytes = md5(challenge);
// Get path
URI wsURL = this.getWebSocketURL();
String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery();
}
// Format request
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
request.addHeader(Names.UPGRADE, Values.WEBSOCKET);
request.addHeader(Names.CONNECTION, Values.UPGRADE);
request.addHeader(Names.HOST, wsURL.getHost());
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1);
request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2);
if (this.getSubProtocolRequest() != null && !this.getSubProtocolRequest().equals("")) {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.getSubProtocolRequest());
}
request.setContent(ChannelBuffers.copiedBuffer(key3));
channel.write(request);
ctx.getPipeline().replace("encoder", "ws-encoder", new WebSocket00FrameEncoder());
}
/**
* <p>
* Process server response:
* </p>
*
* <pre>
* HTTP/1.1 101 WebSocket Protocol Handshake
* Upgrade: WebSocket
* Connection: Upgrade
* Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Location: ws://example.com/demo
* Sec-WebSocket-Protocol: sample
*
* 8jKS'y:G*Co,Wxa-
* </pre>
*
* @param ctx
* Channel context
* @param response
* HTTP response returned from the server for the request sent by
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException
*/
@Override
public void endOpeningHandshake(ChannelHandlerContext ctx, HttpResponse response)
throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101, "WebSocket Protocol Handshake");
if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
}
String upgrade = response.getHeader(Names.UPGRADE);
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET)) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ response.getHeader(Names.UPGRADE));
}
String connection = response.getHeader(Names.CONNECTION);
if (connection == null || !connection.equals(Values.UPGRADE)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ response.getHeader(Names.CONNECTION));
}
byte[] challenge = response.getContent().array();
if (!Arrays.equals(challenge, expectedChallengeResponseBytes)) {
throw new WebSocketHandshakeException("Invalid challenge");
}
String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
this.setSubProtocolResponse(protocol);
ctx.getPipeline().replace("decoder", "ws-decoder", new WebSocket00FrameDecoder());
this.setOpenningHandshakeCompleted(true);
return;
}
private String insertRandomCharacters(String key) {
int count = createRandomNumber(1, 12);
char[] randomChars = new char[count];
int randCount = 0;
while (randCount < count) {
int rand = (int) (Math.random() * 0x7e + 0x21);
if (((0x21 < rand) && (rand < 0x2f)) || ((0x3a < rand) && (rand < 0x7e))) {
randomChars[randCount] = (char) rand;
randCount += 1;
}
}
for (int i = 0; i < count; i++) {
int split = createRandomNumber(0, key.length());
String part1 = key.substring(0, split);
String part2 = key.substring(split);
key = part1 + randomChars[i] + part2;
}
return key;
}
private String insertSpaces(String key, int spaces) {
for (int i = 0; i < spaces; i++) {
int split = createRandomNumber(1, key.length() - 1);
String part1 = key.substring(0, split);
String part2 = key.substring(split);
key = part1 + " " + part2;
}
return key;
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
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.HttpMethod;
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.HttpVersion;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.CharsetUtil;
/**
* <p>
* Performs client side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
* </p>
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public class WebSocketClientHandshaker10 extends WebSocketClientHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker10.class);
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private String expectedChallengeResponseString = null;
private String protocol = null;
private boolean allowExtensions = false;
/**
* Constructor specifying the destination web socket location and version to
* initiate
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the
* server
* @param subProtocol
* Sub protocol request sent to the server.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame
*/
public WebSocketClientHandshaker10(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol,
boolean allowExtensions) {
super(webSocketURL, version, subProtocol);
this.allowExtensions = allowExtensions;
return;
}
/**
* /**
* <p>
* Sends the opening request to the server:
* </p>
*
* <pre>
* GET /chat HTTP/1.1
* Host: server.example.com
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
* Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 8
* </pre>
*
* @param ctx
* Channel context
* @param channel
* Channel into which we can write our request
*/
@Override
public void beginOpeningHandshake(ChannelHandlerContext ctx, Channel channel) {
// Get path
URI wsURL = this.getWebSocketURL();
String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery();
}
// Get 16 bit nonce and base 64 encode it
byte[] nonce = createRandomBytes(16);
String key = base64Encode(nonce);
String acceptSeed = key + MAGIC_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
this.expectedChallengeResponseString = base64Encode(sha1);
if (logger.isDebugEnabled()) {
logger.debug(String.format("HyBi10 Client Handshake key: %s. Expected response: %s.", key,
this.expectedChallengeResponseString));
}
// Format request
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
request.addHeader(Names.CONNECTION, Values.UPGRADE);
request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
request.addHeader(Names.HOST, wsURL.getHost());
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
if (protocol != null && !protocol.equals("")) {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
}
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8");
channel.write(request);
ctx.getPipeline().replace("encoder", "ws-encoder", new WebSocket08FrameEncoder(true));
return;
}
/**
* <p>
* Process server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param ctx
* Channel context
* @param response
* HTTP response returned from the server for the request sent by
* beginOpeningHandshake00().
* @throws WebSocketHandshakeException
*/
@Override
public void endOpeningHandshake(ChannelHandlerContext ctx, HttpResponse response)
throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
}
String upgrade = response.getHeader(Names.UPGRADE);
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
+ response.getHeader(Names.UPGRADE));
}
String connection = response.getHeader(Names.CONNECTION);
if (connection == null || !connection.equals(Values.UPGRADE)) {
throw new WebSocketHandshakeException("Invalid handshake response connection: "
+ response.getHeader(Names.CONNECTION));
}
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
this.expectedChallengeResponseString));
}
ctx.getPipeline().replace("decoder", "ws-decoder", new WebSocket08FrameDecoder(false, this.allowExtensions));
this.setOpenningHandshakeCompleted(true);
return;
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
/**
* Instances the appropriate handshake class to use for clients
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public class WebSocketClientHandshakerFactory {
/**
* Instances a new handshaker
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the
* server
* @param subProtocol
* Sub protocol request sent to the server. Null if no
* sub-protocol support is required.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame
* @throws WebSocketHandshakeException
*/
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketSpecificationVersion version,
String subProtocol, boolean allowExtensions) throws WebSocketHandshakeException {
if (version == WebSocketSpecificationVersion.V10) {
return new WebSocketClientHandshaker10(webSocketURL, version, subProtocol, allowExtensions);
}
if (version == WebSocketSpecificationVersion.V00) {
return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol);
}
throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported.");
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* Base class for web socket frames
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public abstract class WebSocketFrame {
/**
* Flag to indicate if this frame is the final fragment in a message. The
* first fragment (frame) may also be the final fragment.
*/
private boolean finalFragment = true;
/**
* RSV1, RSV2, RSV3 used for extensions
*/
private int rsv = 0;
/**
* Contents of this frame
*/
private ChannelBuffer binaryData;
/**
* Returns binary data
*/
public ChannelBuffer getBinaryData() {
return binaryData;
}
/**
* Sets the binary data for this frame
*/
public void setBinaryData(ChannelBuffer binaryData) {
this.binaryData = binaryData;
}
/**
* Flag to indicate if this frame is the final fragment in a message. The
* first fragment (frame) may also be the final fragment.
*/
public boolean isFinalFragment() {
return finalFragment;
}
public void setFinalFragment(boolean finalFragment) {
this.finalFragment = finalFragment;
}
/**
* Bits used for extensions to the standard.
*/
public int getRsv() {
return rsv;
}
public void setRsv(int rsv) {
this.rsv = rsv;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
/**
* Type of web socket frames
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public enum WebSocketFrameType {
TEXT, BINARY, PING, PONG, CLOSE, CONTINUATION
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
/**
* Exception during handshaking process
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public class WebSocketHandshakeException extends Exception {
private static final long serialVersionUID = 1L;
public WebSocketHandshakeException(String s) {
super(s);
}
public WebSocketHandshakeException(String s, Throwable throwable) {
super(s, throwable);
}
}

View File

@ -0,0 +1,191 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.base64.Base64;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.util.CharsetUtil;
/**
* Base class for server side web socket opening and closing handshakes
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public abstract class WebSocketServerHandshaker {
private String webSocketURL;
private String subProtocols;
private String[] subProtocolsArray = null;
private WebSocketSpecificationVersion version = WebSocketSpecificationVersion.UNKNOWN;
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param subProtocols
* CSV of supported protocols. Null if sub protocols not
* supported.
*/
public WebSocketServerHandshaker(String webSocketURL, String subProtocols) {
this.webSocketURL = webSocketURL;
this.subProtocols = subProtocols;
if (this.subProtocols != null) {
this.subProtocolsArray = subProtocols.split(",");
for (int i = 0; i < this.subProtocolsArray.length; i++) {
this.subProtocolsArray[i] = this.subProtocolsArray[i].trim();
}
}
return;
}
/**
* Returns the URL of the web socket
*/
public String getWebSocketURL() {
return webSocketURL;
}
public void setWebSocketURL(String webSocketURL) {
this.webSocketURL = webSocketURL;
}
/**
* Returns the CSV of supported sub protocols
*/
public String getSubProtocols() {
return subProtocols;
}
public void setSubProtocols(String subProtocols) {
this.subProtocols = subProtocols;
}
/**
* Returns the version of the specification being supported
*/
public WebSocketSpecificationVersion getVersion() {
return version;
}
public void setVersion(WebSocketSpecificationVersion version) {
this.version = version;
}
/**
* Performs the opening handshake
*
* @param ctx
* Context
* @param req
* HTTP Request
* @throws NoSuchAlgorithmException
*/
public abstract void executeOpeningHandshake(ChannelHandlerContext ctx, HttpRequest req);
/**
* Performs the closing handshake
*
* @param ctx
* Context
* @param frame
* Closing Frame that was received
*/
public abstract void executeClosingHandshake(ChannelHandlerContext ctx, CloseWebSocketFrame frame);
/**
* Performs an MD5 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
protected byte[] md5(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 not supported on this platform");
}
}
/**
* SHA-1 hashing. Instance this we think it is not thread safe
*
* @param bytes
* byte to hash
* @return hashed
*/
protected byte[] sha1(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("SHA-1 not supported on this platform");
}
}
/**
* Base 64 encoding
*
* @param bytes
* Bytes to encode
* @return encoded string
*/
protected String base64Encode(byte[] bytes) {
ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
}
/**
* Selects the first matching supported sub protocol
*
* @param requestedSubProtocol
* CSV of protocols to be supported. e.g. "chat, superchat"
* @return First matching supported sub protocol. Null if not found.
*/
protected String selectSubProtocol(String requestedSubProtocol) {
if (requestedSubProtocol == null || this.subProtocolsArray == null) {
return null;
}
String[] requesteSubProtocolsArray = requestedSubProtocol.split(",");
for (int i = 0; i < requesteSubProtocolsArray.length; i++) {
String requesteSubProtocol = requesteSubProtocolsArray[i].trim();
for (String supportedSubProtocol : this.subProtocolsArray) {
if (requesteSubProtocol.equals(supportedSubProtocol)) {
return requesteSubProtocol;
}
}
}
// No match found
return null;
}
}

View File

@ -0,0 +1,205 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ORIGIN;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY2;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_LOCATION;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_ORIGIN;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_PROTOCOL;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_LOCATION;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_ORIGIN;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_PROTOCOL;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.security.NoSuchAlgorithmException;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
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.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
/**
* <p>
* Performs server side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>
* </p>
* <p>
* A very large portion of this code was taken from the Netty 3.2 HTTP example.
* </p>
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker00.class);
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param subProtocols
* CSV of supported protocols
*/
public WebSocketServerHandshaker00(String webSocketURL, String subProtocols) {
super(webSocketURL, subProtocols);
return;
}
/**
* <p>
* Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi
* version 0</a> and lower. This standard is really a rehash of <a
* href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76"
* >hixie-76</a> and <a
* href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75"
* >hixie-75</a>.
* </p>
*
* <p>
* Browser request to the server:
* </p>
*
* <pre>
* GET /demo HTTP/1.1
* Upgrade: WebSocket
* Connection: Upgrade
* Host: example.com
* Origin: http://example.com
* Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
* Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
*
* ^n:ds[4U
* </pre>
*
* <p>
* Server response:
* </p>
*
* <pre>
* HTTP/1.1 101 WebSocket Protocol Handshake
* Upgrade: WebSocket
* Connection: Upgrade
* Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Location: ws://example.com/demo
* Sec-WebSocket-Protocol: sample
*
* 8jKS'y:G*Co,Wxa-
* </pre>
*
* @param ctx
* Channel context
* @param req
* HTTP request
* @throws NoSuchAlgorithmException
*/
@Override
public void executeOpeningHandshake(ChannelHandlerContext ctx, HttpRequest req) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s web socket spec version 00 handshake", ctx.getChannel().getId()));
}
this.setVersion(WebSocketSpecificationVersion.V00);
// Serve the WebSocket handshake request.
if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
|| !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
return;
}
// Hixie 75 does not contain these headers while Hixie 76 does
boolean isHixie76 = req.containsHeader(SEC_WEBSOCKET_KEY1) && req.containsHeader(SEC_WEBSOCKET_KEY2);
// Create the WebSocket handshake response.
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101,
isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
res.addHeader(Names.UPGRADE, WEBSOCKET);
res.addHeader(CONNECTION, Values.UPGRADE);
// Fill in the headers and contents depending on handshake method.
if (isHixie76) {
// New handshake method with a challenge:
res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
res.addHeader(SEC_WEBSOCKET_LOCATION, this.getWebSocketURL());
String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) {
res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubProtocol(protocol));
}
// Calculate the answer of the challenge.
String key1 = req.getHeader(SEC_WEBSOCKET_KEY1);
String key2 = req.getHeader(SEC_WEBSOCKET_KEY2);
int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1.replaceAll("[^ ]", "").length());
int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2.replaceAll("[^ ]", "").length());
long c = req.getContent().readLong();
ChannelBuffer input = ChannelBuffers.buffer(16);
input.writeInt(a);
input.writeInt(b);
input.writeLong(c);
ChannelBuffer output = ChannelBuffers.wrappedBuffer(this.md5(input.array()));
res.setContent(output);
} else {
// Old Hixie 75 handshake method with no challenge:
res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
res.addHeader(WEBSOCKET_LOCATION, this.getWebSocketURL());
String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
if (protocol != null) {
res.addHeader(WEBSOCKET_PROTOCOL, selectSubProtocol(protocol));
}
}
// Upgrade the connection and send the handshake response.
ChannelPipeline p = ctx.getChannel().getPipeline();
p.remove("aggregator");
p.replace("decoder", "wsdecoder", new WebSocket00FrameDecoder());
ctx.getChannel().write(res);
p.replace("encoder", "wsencoder", new WebSocket00FrameEncoder());
return;
}
/**
* Echo back the closing frame
*
* @param ctx
* Channel context
* @param frame
* Web Socket frame that was received
*/
@Override
public void executeClosingHandshake(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
ctx.getChannel().write(frame);
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.security.NoSuchAlgorithmException;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
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.HttpHeaders.Names;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.CharsetUtil;
/**
* <p>
* Performs server side opening and closing handshakes for web socket
* specification version <a
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
* </p>
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public class WebSocketServerHandshaker10 extends WebSocketServerHandshaker {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker10.class);
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private boolean allowExtensions = false;
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param subProtocols
* CSV of supported protocols
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketServerHandshaker10(String webSocketURL, String subProtocols, boolean allowExtensions) {
super(webSocketURL, subProtocols);
this.allowExtensions = allowExtensions;
return;
}
/**
* <p>
* Handle the web socket handshake for the web socket specification <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi
* version 8 to 10</a>. Version 8, 9 and 10 share the same wire protocol.
* </p>
*
* <p>
* Browser request to the server:
* </p>
*
* <pre>
* GET /chat HTTP/1.1
* Host: server.example.com
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
* Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 8
* </pre>
*
* <p>
* Server response:
* </p>
*
* <pre>
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
* </pre>
*
* @param ctx
* Channel context
* @param req
* HTTP request
* @throws NoSuchAlgorithmException
*/
@Override
public void executeOpeningHandshake(ChannelHandlerContext ctx, HttpRequest req) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s web socket spec version 10 handshake", ctx.getChannel().getId()));
}
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols"));
this.setVersion(WebSocketSpecificationVersion.V10);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
if (key == null) {
res.setStatus(HttpResponseStatus.BAD_REQUEST);
return;
}
String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
String accept = base64Encode(sha1);
if (logger.isDebugEnabled()) {
logger.debug(String.format("HyBi10 Server Handshake key: %s. Response: %s.", key, accept));
}
res.setStatus(new HttpResponseStatus(101, "Switching Protocols"));
res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
res.addHeader(Names.CONNECTION, Names.UPGRADE);
res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) {
res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol));
}
ctx.getChannel().write(res);
// Upgrade the connection and send the handshake response.
ChannelPipeline p = ctx.getChannel().getPipeline();
p.remove("aggregator");
p.replace("decoder", "wsdecoder", new WebSocket08FrameDecoder(true, this.allowExtensions));
p.replace("encoder", "wsencoder", new WebSocket08FrameEncoder(false));
return;
}
/**
* Echo back the closing frame
*
* @param ctx
* Channel context
* @param frame
* Web Socket frame that was received
*/
@Override
public void executeClosingHandshake(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
ctx.getChannel().write(frame);
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
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.HttpVersion;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
/**
* Instances the appropriate handshake class to use for clients
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public class WebSocketServerHandshakerFactory {
private String webSocketURL;
private String subProtocols;
private boolean allowExtensions = false;
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param subProtocols
* CSV of supported protocols. Null if sub protocols not
* supported.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) {
this.webSocketURL = webSocketURL;
this.subProtocols = subProtocols;
this.allowExtensions = allowExtensions;
return;
}
/**
* Instances a new handshaker
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the
* server
* @param subProtocol
* Sub protocol request sent to the server. Null if no
* sub-protocol support is required.
* @return A new WebSocketServerHandshaker for the requested web socket
* version. Null if web socket version is not supported.
*/
public WebSocketServerHandshaker newHandshaker(ChannelHandlerContext ctx, HttpRequest req) {
String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
if (version != null) {
if (version.equals("8")) {
// Version 8 of the wire protocol - assume version 10 of the
// specification.
return new WebSocketServerHandshaker10(webSocketURL, subProtocols, this.allowExtensions);
} else {
return null;
}
} else {
// Assume version 00 where version header was not specified
return new WebSocketServerHandshaker00(webSocketURL, subProtocols);
}
}
/**
* Return that we need cannot not support the web socket version
*
* @param ctx
* Context
*/
public void sendUnsupportedWebSocketVersionResponse(ChannelHandlerContext ctx) {
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101,
"Switching Protocols"));
res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);
res.setHeader(Names.SEC_WEBSOCKET_VERSION, "8");
ctx.getChannel().write(res);
return;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.jboss.netty.handler.codec.http.websocketx;
/**
* <p>
* Versions of the web socket specification.
* </p>
* <p>
* A specification is tied to one wire protocol version but a protocol version
* may have use by more than 1 version of the specification.
* </p>
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public enum WebSocketSpecificationVersion {
UNKNOWN,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>.
*/
V00,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
*/
V10
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you 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.
*/
/**
* Encoder, decoder, handshakers and their related message types for
* <a href="http://en.wikipedia.org/wiki/Web_Sockets">Web Socket</a> data frames.
* <p>
* This package supports different web socket specification versions (hence the X suffix).
* The specification current supported are:
* <ul>
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">draft-ietf-hybi-thewebsocketprotocol-00</a></li>
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10">draft-ietf-hybi-thewebsocketprotocol-10</a></li>
* </ul>
* </p>
* <p>
* In the future, as the specification develops, more versions will be supported.
* This contrasts the <tt>org.jboss.netty.handler.codec.http.websocket</tt> package which only
* supports draft-ietf-hybi-thewebsocketprotocol-00.
* </p>
* <p>
* For the detailed instruction on adding add Web Socket support to your HTTP
* server, take a look into the <tt>WebSocketServerX</tt> example located in the
* {@code org.jboss.netty.example.http.websocket} package.
* </p>
*
* @apiviz.exclude OneToOne(Encoder|Decoder)$
* @apiviz.exclude \.codec\.replay\.
* @apiviz.exclude \.Default
*/
package org.jboss.netty.handler.codec.http.websocketx;

View File

@ -71,7 +71,7 @@ public class CIDR4 extends CIDR
}
@Override
public int compareTo(CIDR arg)
public int compareTo(CIDR arg)
{
if (arg instanceof CIDR6)
{

View File

@ -78,7 +78,7 @@ public class CIDR6 extends CIDR
}
@Override
public int compareTo(CIDR arg)
public int compareTo(CIDR arg)
{
if (arg instanceof CIDR4)
{

View File

@ -113,7 +113,7 @@ public abstract class IpFilteringHandlerImpl implements ChannelUpstreamHandler,
* @see org.jboss.netty.channel.ChannelUpstreamHandler#handleUpstream(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelEvent)
*/
@Override
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception
{
if (e instanceof ChannelStateEvent)
{
@ -189,7 +189,7 @@ public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exc
* @see org.jboss.netty.handler.ipfilter.IpFilteringHandler#setIpFilterListener(org.jboss.netty.handler.ipfilter.IpFilterListener)
*/
@Override
public void setIpFilterListener(IpFilterListener listener)
public void setIpFilterListener(IpFilterListener listener)
{
this.listener = listener;
}
@ -198,7 +198,7 @@ public void setIpFilterListener(IpFilterListener listener)
* @see org.jboss.netty.handler.ipfilter.IpFilteringHandler#removeIpFilterListener()
*/
@Override
public void removeIpFilterListener()
public void removeIpFilterListener()
{
this.listener = null;

View File

@ -159,7 +159,7 @@ public boolean contains(InetAddress inetAddress)
* Compare two IpSubnet
*/
@Override
public int compareTo(IpSubnet o)
public int compareTo(IpSubnet o)
{
return cidr.toString().compareTo(o.cidr.toString());
}

View File

@ -79,13 +79,13 @@ public class IpSubnetFilterRule extends IpSubnet implements IpFilterRule
}
@Override
public boolean isAllowRule()
public boolean isAllowRule()
{
return isAllowRule;
}
@Override
public boolean isDenyRule()
public boolean isDenyRule()
{
return !isAllowRule;
}

View File

@ -278,7 +278,7 @@ public boolean contains(InetAddress inetAddress1)
* Compare two IpV4Subnet
*/
@Override
public int compareTo(IpV4Subnet o)
public int compareTo(IpV4Subnet o)
{
if (o.subnet == subnet && o.cidrMask == cidrMask)
{

View File

@ -74,13 +74,13 @@ public class IpV4SubnetFilterRule extends IpV4Subnet implements IpFilterRule
}
@Override
public boolean isAllowRule()
public boolean isAllowRule()
{
return isAllowRule;
}
@Override
public boolean isDenyRule()
public boolean isDenyRule()
{
return !isAllowRule;
}

View File

@ -871,42 +871,50 @@ public class SslHandler extends FrameDecoder
loop:
for (;;) {
SSLEngineResult result;
boolean needsHandshake = false;
synchronized (handshakeLock) {
if (!handshaken && !handshaking &&
!engine.getUseClientMode() &&
!engine.isInboundDone() && !engine.isOutboundDone()) {
handshake();
}
result = engine.unwrap(inNetBuf, outAppBuf);
final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
handleRenegotiation(handshakeStatus);
switch (handshakeStatus) {
case NEED_UNWRAP:
if (inNetBuf.hasRemaining() && !engine.isInboundDone()) {
break;
} else {
break loop;
}
case NEED_WRAP:
wrapNonAppData(ctx, channel);
break;
case NEED_TASK:
runDelegatedTasks();
break;
case FINISHED:
setHandshakeSuccess(channel);
needsWrap = true;
break loop;
case NOT_HANDSHAKING:
needsWrap = true;
break loop;
default:
throw new IllegalStateException(
"Unknown handshake status: " + handshakeStatus);
needsHandshake = true;
}
}
if (needsHandshake) {
handshake();
}
synchronized (handshakeLock) {
result = engine.unwrap(inNetBuf, outAppBuf);
}
final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
handleRenegotiation(handshakeStatus);
switch (handshakeStatus) {
case NEED_UNWRAP:
if (inNetBuf.hasRemaining() && !engine.isInboundDone()) {
break;
} else {
break loop;
}
case NEED_WRAP:
wrapNonAppData(ctx, channel);
break;
case NEED_TASK:
runDelegatedTasks();
break;
case FINISHED:
setHandshakeSuccess(channel);
needsWrap = true;
break loop;
case NOT_HANDSHAKING:
needsWrap = true;
break loop;
default:
throw new IllegalStateException(
"Unknown handshake status: " + handshakeStatus);
}
}
if (needsWrap) {

View File

@ -136,7 +136,7 @@ public class WriteTimeoutHandler extends SimpleChannelDownstreamHandler
timer.stop();
}
protected long getTimeoutMillis(@SuppressWarnings("unused") MessageEvent e) {
protected long getTimeoutMillis(MessageEvent e) {
return timeoutMillis;
}