Merge branch 'master' of github.com:netty/netty
This commit is contained in:
commit
7242771688
10
NOTICE.txt
10
NOTICE.txt
@ -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
|
||||
|
||||
|
37
license/LICENSE.webbit.txt
Normal file
37
license/LICENSE.webbit.txt
Normal 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.
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
48
src/main/java/org/jboss/netty/channel/iostream/IOStreamAddress.java
Executable file
48
src/main/java/org/jboss/netty/channel/iostream/IOStreamAddress.java
Executable 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;
|
||||
}
|
||||
}
|
73
src/main/java/org/jboss/netty/channel/iostream/IOStreamChannel.java
Executable file
73
src/main/java/org/jboss/netty/channel/iostream/IOStreamChannel.java
Executable 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();
|
||||
}
|
||||
}
|
60
src/main/java/org/jboss/netty/channel/iostream/IOStreamChannelFactory.java
Executable file
60
src/main/java/org/jboss/netty/channel/iostream/IOStreamChannelFactory.java
Executable 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);
|
||||
}
|
||||
}
|
178
src/main/java/org/jboss/netty/channel/iostream/IOStreamChannelSink.java
Executable file
178
src/main/java/org/jboss/netty/channel/iostream/IOStreamChannelSink.java
Executable 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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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/");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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ö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);
|
||||
}
|
@ -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ö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);
|
||||
}
|
@ -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ö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;
|
||||
}
|
||||
}
|
@ -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ö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;
|
||||
}
|
||||
}
|
@ -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ö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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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/");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
94
src/main/java/org/jboss/netty/example/iostream/IOStream.java
Executable file
94
src/main/java/org/jboss/netty/example/iostream/IOStream.java
Executable 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();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
|
@ -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"}
|
||||
*/
|
||||
|
@ -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() + ')';
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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() + ')';
|
||||
}
|
||||
|
||||
}
|
@ -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() + ')';
|
||||
}
|
||||
|
||||
}
|
@ -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() + ')';
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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.");
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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;
|
||||
|
@ -71,7 +71,7 @@ public class CIDR4 extends CIDR
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CIDR arg)
|
||||
public int compareTo(CIDR arg)
|
||||
{
|
||||
if (arg instanceof CIDR6)
|
||||
{
|
||||
|
@ -78,7 +78,7 @@ public class CIDR6 extends CIDR
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CIDR arg)
|
||||
public int compareTo(CIDR arg)
|
||||
{
|
||||
if (arg instanceof CIDR4)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user