Merge remote branch 'upstream/master'

This commit is contained in:
Jestan Nirojan 2011-10-30 13:50:02 +05:30
commit 6766513157
75 changed files with 5227 additions and 239 deletions

View File

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

View File

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

View File

@ -41,20 +41,20 @@
<title>Rich Buffer Data Structure</title>
<para>
Netty uses its own buffer API instead of NIO <classname>ByteBuffer</classname>
to represent a sequence of bytes. This approach has significant advantage
to represent a sequence of bytes. This approach has significant advantages
over using <classname>ByteBuffer</classname>. Netty's new buffer type,
&ChannelBuffer; has been designed from ground up to address the problems
&ChannelBuffer; has been designed from the ground up to address the problems
of <classname>ByteBuffer</classname> and to meet the daily needs of
network application developers. To list a few cool features:
<itemizedlist>
<listitem>
<para>
You can define your buffer type if necessary.
You can define your own buffer type if necessary.
</para>
</listitem>
<listitem>
<para>
Transparent zero copy is achieved by built-in composite buffer type.
Transparent zero copy is achieved by a built-in composite buffer type.
</para>
</listitem>
<listitem>
@ -84,7 +84,7 @@
<section>
<title>Universal Asynchronous I/O API</title>
<para>
Traditional I/O APIs in Java provided different types and methods for
Traditional I/O APIs in Java provide different types and methods for
different transport types. For example,
<classname>java.net.Socket</classname> and
<classname>java.net.DatagramSocket</classname> do not have any common
@ -93,32 +93,33 @@
</para>
<para>
This mismatch makes porting a network application from one transport to
the other tedious and difficult. The lack of portability between
transports becomes a problem when you need to support more transports not
rewriting the network layer of the application. Logically, many protocols
can run on more than one transport such as TCP/IP, UDP/IP, SCTP, and
serial port communication.
another tedious and difficult. The lack of portability between
transports becomes a problem when you need to support additional
transports, as this often entails rewriting the network layer of the
application. Logically, many protocols can run on more than one
transport such as TCP/IP, UDP/IP, SCTP, and serial port communication.
</para>
<para>
To make the matter worse, Java New I/O (NIO) API introduced the
incompatibility with the old blocking I/O (OIO) API, and so will NIO.2
(AIO). Because all these APIs are different from each other in design
and performance characteristics, you are often forced to determine which
API your application will depend on before you even begin the
implementation phase.
To make matters worse, Java's New I/O (NIO) API introduced
incompatibilities with the old blocking I/O (OIO) API and will continue
to do so in the next release, NIO.2 (AIO). Because all these APIs are
different from each other in design and performance characteristics, you
are often forced to determine which API your application will depend on
before you even begin the implementation phase.
</para>
<para>
For instance, you might want to start with OIO because the number of
clients you are going to serve will be very small and writing a socket
server using OIO is much easier than using NIO. However, you are going
to be in trouble when your business grows up exponentially and your server
starts to serve tens of thousand clients simultaneously. You could
start with NIO, but it might take much longer time to implement due to
the complexity of the NIO Selector API, hindering rapid development.
to be in trouble when your business grows exponentially and your server
needs to serve tens of thousands of clients simultaneously. You could
start with NIO, but doing so may hinder rapid development by greatly
increasing development time due to the complexity of the NIO Selector
API.
</para>
<para>
Netty has a universal asynchronous I/O interface called &Channel;, which
abstracts away all operations required to point-to-point communication.
Netty has a universal asynchronous I/O interface called a &Channel;, which
abstracts away all operations required for point-to-point communication.
That is, once you wrote your application on one Netty transport, your
application can run on other Netty transports. Netty provides a number
of essential transports via one universal API:
@ -144,30 +145,29 @@
</para>
</listitem>
</itemizedlist>
Switching from one transport to the other usually takes just a couple
Switching from one transport to another usually takes just a couple
lines of changes such as choosing a different &ChannelFactory;
implementation.
</para>
<para>
Also, you are even able to take advantage of a new transport which is
not written yet, serial port communication transport for instance, again
Also, you are even able to take advantage of new transports which aren't
yet written (such as serial port communication transport), again
by replacing just a couple lines of constructor calls. Moreover, you can
write your own transport by extending the core API because it is highly
extensible.
write your own transport by extending the core API.
</para>
</section>
<section>
<title>Event Model based on the Interceptor Chain Pattern</title>
<para>
Well-defined and extensible event model is a must for an event-driven
application. Netty does have a well-defined event model focused on I/O.
It also allows you to implement your own event type without breaking the
existing code at all because each event type is distinguished from
each other by strict type hierarchy. This is another differentiator
against other frameworks. Many NIO frameworks have no or very limited
notion of event model; they often break the existing code when you try
to add a new custom event type, or just do not allow extension.
A well-defined and extensible event model is a must for an event-driven
application. Netty has a well-defined event model focused on I/O. It
also allows you to implement your own event type without breaking the
existing code because each event type is distinguished from another by
a strict type hierarchy. This is another differentiator against other
frameworks. Many NIO frameworks have no or a very limited notion of an
event model. If they offer extension at all, they often break the
existing code when you try to add custom event types
</para>
<para>
A &ChannelEvent; is handled by a list of &ChannelHandler;s in a
@ -175,7 +175,7 @@
<ulink url="http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html">Intercepting Filter</ulink>
pattern to give a user full control over how an event is handled and how
the handlers in the pipeline interact with each other. For example,
you can define what to do when a data is read from a socket:
you can define what to do when data is read from a socket:
</para>
<programlisting>public class MyReadHandler implements &SimpleChannelHandler; {
public void messageReceived(&ChannelHandlerContext; ctx, &MessageEvent; evt) {
@ -188,8 +188,7 @@
}
}</programlisting>
<para>
You can also define what to do when other handler requested a write
operation:
You can also define what to do when a handler receives a write request:
</para>
<programlisting>public class MyWriteHandler implements &SimpleChannelHandler; {
public void writeRequested(&ChannelHandlerContext; ctx, &MessageEvent; evt) {
@ -202,7 +201,7 @@
}
}</programlisting>
<para>
For more information about the event model, please refer to the
For more information on the event model, please refer to the
API documentation of &ChannelEvent; and &ChannelPipeline;.
</para>
</section>
@ -212,29 +211,28 @@
<para>
On top of the core components mentioned above, that already enable the
implementation of all types of network applications, Netty provides a set
of advanced features to accelerate the development pace even more.
of advanced features to accelerate the page of development even more.
</para>
<section>
<title>Codec framework</title>
<para>
As demonstrated in <xref linkend="start.pojo"/>, it is always a good
idea to separate a protocol codec from a business logic. However, there
idea to separate a protocol codec from business logic. However, there
are some complications when implementing this idea from scratch. You
have to deal with the fragmentation of messages. Some protocols are
multi-layered (i.e. built on top of other lower level protocol). Some
multi-layered (i.e. built on top of other lower level protocols). Some
are too complicated to be implemented in a single state machine.
</para>
<para>
Consequently, a good network application framework should provide an
extensible, reusable, unit-testable, and multi-layered codec framework
that generates maintainable user codec.
that generates maintainable user codecs.
</para>
<para>
Netty provides a number of basic and advanced codecs built on top of
its core to address most issues you will encounter when you write a
protocol codec regardless if it is simple or not, binary or text -
simply whatever.
Netty provides a number of basic and advanced codecs to address most
issues you will encounter when you write a protocol codec regardless
if it is simple or not, binary or text - simply whatever.
</para>
</section>
@ -245,16 +243,16 @@
You can't simply wrap a stream to encrypt or decrypt data but you have
to use <classname>javax.net.ssl.SSLEngine</classname>.
<classname>SSLEngine</classname> is a state machine which is as complex
as SSL is. You have to manage all possible states such as cipher suite
and encryption key negotiation (or re-negotiation), certificate
exchange and validation. Moreover, <classname>SSLEngine</classname> is
not even completely thread-safe unlike usual expectation.
as SSL itself. You have to manage all possible states such as cipher
suite and encryption key negotiation (or re-negotiation), certificate
exchange, and validation. Moreover, <classname>SSLEngine</classname> is
not even completely thread-safe, as one would expect.
</para>
<para>
In Netty, &SslHandler; takes care of all the gory details and pitfalls
of <classname>SSLEngine</classname>. All you need to do is to configure
and insert the &SslHandler; to your &ChannelPipeline;. It also allows
you to implement advanced features like
the &SslHandler; and insert it into your &ChannelPipeline;. It also
allows you to implement advanced features like
<ulink url="http://en.wikipedia.org/wiki/Starttls">StartTLS</ulink>
very easily.
</para>
@ -269,13 +267,13 @@
</para>
<para>
Netty's HTTP support is very different from the existing HTTP libraries.
It gives you complete control over how HTTP messages are exchanged in a
low level. Because it is basically the combination of HTTP codec and
HTTP message classes, there is no restriction such as enforced thread
It gives you complete control over how HTTP messages are exchanged at a
low level. Because it is basically the combination of an HTTP codec and
HTTP message classes, there is no restriction such as an enforced thread
model. That is, you can write your own HTTP client or server that works
exactly the way you want. You have full control over thread model,
connection life cycle, chunked encoding, and as much as what HTTP
specification allows you to do.
exactly the way you want. You have full control over everything that's
in the HTTP specification, including the thread model, connection life
cycle, and chunked encoding.
</para>
<para>
Thanks to its highly customizable nature, you can write a very efficient
@ -291,18 +289,18 @@
<listitem>
<para>
Media streaming server that needs to keep the connection open
until the whole media is streamed (e.g. 2 hours of movie)
until the whole media is streamed (e.g. 2 hours of video)
</para>
</listitem>
<listitem>
<para>
File server that allows the upload of large files without memory
pressure (e.g. uploading 1GB per request)
File server that allows the uploading of large files without
memory pressure (e.g. uploading 1GB per request)
</para>
</listitem>
<listitem>
<para>
Scalable mash-up client that connects to tens of thousand 3rd
Scalable mash-up client that connects to tens of thousands of 3rd
party web services asynchronously
</para>
</listitem>
@ -315,10 +313,11 @@
<para>
<ulink url="http://code.google.com/apis/protocolbuffers/docs/overview.html">Google Protocol Buffers</ulink>
are an ideal solution for the rapid implementation of a highly efficient
binary protocol that evolves over time. With &ProtobufEncoder; and
&ProtobufDecoder;, you can turn the message classes generated by Google
Protocol Buffers Compiler (protoc) into Netty codec. Please take a look
into the <ulink url="&XRef;example/localtime/package-summary.html">'LocalTime' example</ulink>
binary protocols that evolve over time. With &ProtobufEncoder; and
&ProtobufDecoder;, you can turn the message classes generated by the
Google Protocol Buffers Compiler (protoc) into Netty codec. Please take
a look into the
<ulink url="&XRef;example/localtime/package-summary.html">'LocalTime' example</ulink>
that shows how easily you can create a high-performing binary protocol
client and server from the
<ulink url="http://anonsvn.jboss.org/repos/netty/trunk/src/main/java/org/jboss/netty/example/localtime/LocalTimeProtocol.proto">sample protocol definition</ulink>.
@ -330,18 +329,18 @@
<title>Summary</title>
<para>
In this chapter, we reviewed the overall architecture of Netty from the
feature-wise standpoint. Netty has simple yet powerful architecture.
feature standpoint. Netty has a simple, yet powerful architecture.
It is composed of three components - buffer, channel, and event model -
and all advanced features are built on top of the three core components.
Once you understood how these three work together, it should not be
difficult to understand more advanced features which were covered briefly
in this chapter.
difficult to understand the more advanced features which were covered
briefly in this chapter.
</para>
<para>
You might still have an unanswered question about what the overall
architecture looks exactly like and how each feature work together.
If so, it is a good idea to <ulink url="&Community;">talk to us</ulink>
to improve this guide.
You might still have unanswered questions about what the overall
architecture looks like exactly and how each of the features work
together. If so, it is a good idea to
<ulink url="&Community;">talk to us</ulink> to improve this guide.
</para>
</section>
</chapter>

View File

@ -71,6 +71,7 @@ import org.jboss.netty.channel.socket.ServerSocketChannel;
* <td>{@code "channelOpen"}</td>
* <td>{@link ChannelStateEvent}<br/>(state = {@link ChannelState#OPEN OPEN}, value = {@code true})</td>
* <td>a {@link Channel} is open, but not bound nor connected</td>
* <td><strong>Be aware that this event is fired from within the Boss-Thread so you should not execute any heavy operation in there as it will block the dispatching to other workers!</strong></td>
* </tr>
* <tr>
* <td>{@code "channelClosed"}</td>
@ -81,6 +82,7 @@ import org.jboss.netty.channel.socket.ServerSocketChannel;
* <td>{@code "channelBound"}</td>
* <td>{@link ChannelStateEvent}<br/>(state = {@link ChannelState#BOUND BOUND}, value = {@link SocketAddress})</td>
* <td>a {@link Channel} is open and bound to a local address, but not connected</td>
* <td><strong>Be aware that this event is fired from within the Boss-Thread so you should not execute any heavy operation in there as it will block the dispatching to other workers!</strong></td>
* </tr>
* <tr>
* <td>{@code "channelUnbound"}</td>

View File

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

View File

@ -14,11 +14,21 @@ public class DefaultFileRegion implements FileRegion {
private final FileChannel file;
private final long position;
private final long count;
private final boolean releaseAfterTransfer;
/**
* Calls {@link #DefaultFileRegion(FileChannel, long, long, boolean)}
* with <code>true</code> as the last argument.
*/
public DefaultFileRegion(FileChannel file, long position, long count) {
this(file, position, count, true);
}
public DefaultFileRegion(FileChannel file, long position, long count, boolean releaseAfterTransfer) {
this.file = file;
this.position = position;
this.count = count;
this.releaseAfterTransfer = releaseAfterTransfer;
}
@Override
@ -31,6 +41,11 @@ public class DefaultFileRegion implements FileRegion {
return count;
}
@Override
public boolean releaseAfterTransfer() {
return releaseAfterTransfer;
}
@Override
public long transferTo(WritableByteChannel target, long position) throws IOException {
long count = this.count - position;

View File

@ -73,6 +73,12 @@ public interface FileRegion extends ExternalResourceReleasable {
*/
long getCount();
/**
* Returns <code>true</code> if {@link #releaseExternalResources()} has to
* be called after the transfer of this {@link FileRegion} is complete.
*/
boolean releaseAfterTransfer();
/**
* Transfers the content of this file region to the specified channel.
*

View File

@ -151,6 +151,8 @@ public class SimpleChannelUpstreamHandler implements ChannelUpstreamHandler {
/**
* Invoked when a {@link Channel} is open, but not bound nor connected.
* <br/>
* <strong>Be aware that this event is fired from within the Boss-Thread so you should not execute any heavy operation in there as it will block the dispatching to other workers!</strong>
*/
public void channelOpen(
ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
@ -160,6 +162,8 @@ public class SimpleChannelUpstreamHandler implements ChannelUpstreamHandler {
/**
* Invoked when a {@link Channel} is open and bound to a local address,
* but not connected.
* <br/>
* <strong>Be aware that this event is fired from within the Boss-Thread so you should not execute any heavy operation in there as it will block the dispatching to other workers!</strong>
*/
public void channelBound(
ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {

View File

@ -0,0 +1,48 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.channel.iostream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketAddress;
/**
* A {@link java.net.SocketAddress} implementation holding an {@link java.io.InputStream} and an {@link java.io.OutputStream} instance used as
* "remote" address to connect to with a {@link IOStreamChannel}.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStreamAddress extends SocketAddress {
private final InputStream inputStream;
private final OutputStream outputStream;
public IOStreamAddress(final InputStream inputStream, final OutputStream outputStream) {
this.inputStream = inputStream;
this.outputStream = outputStream;
}
public InputStream getInputStream() {
return inputStream;
}
public OutputStream getOutputStream() {
return outputStream;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.channel.iostream;
import org.jboss.netty.channel.*;
import java.net.SocketAddress;
/**
* A channel to an {@link java.io.InputStream} and an {@link java.io.OutputStream}.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStreamChannel extends AbstractChannel {
IOStreamChannel(final ChannelFactory factory, final ChannelPipeline pipeline, final ChannelSink sink) {
super(null, factory, pipeline, sink);
}
@Override
public ChannelConfig getConfig() {
return ((IOStreamChannelSink) getPipeline().getSink()).getConfig();
}
@Override
public boolean isBound() {
return ((IOStreamChannelSink) getPipeline().getSink()).isBound();
}
@Override
public boolean isConnected() {
return ((IOStreamChannelSink) getPipeline().getSink()).isConnected();
}
@Override
public SocketAddress getLocalAddress() {
return null;
}
@Override
public SocketAddress getRemoteAddress() {
return ((IOStreamChannelSink) getPipeline().getSink()).getRemoteAddress();
}
@Override
public ChannelFuture bind(final SocketAddress localAddress) {
throw new UnsupportedOperationException();
}
@Override
public ChannelFuture unbind() {
throw new UnsupportedOperationException();
}
void doSetClosed() {
setClosed();
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.channel.iostream;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.util.internal.ExecutorUtil;
import java.util.concurrent.ExecutorService;
/**
* A {@link org.jboss.netty.channel.ChannelFactory} for creating {@link IOStreamChannel} instances.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStreamChannelFactory implements ChannelFactory {
private final ChannelGroup channels = new DefaultChannelGroup("IOStreamChannelFactory-ChannelGroup");
private final ExecutorService executorService;
public IOStreamChannelFactory(ExecutorService executorService) {
this.executorService = executorService;
}
@Override
public Channel newChannel(final ChannelPipeline pipeline) {
IOStreamChannelSink sink = new IOStreamChannelSink(executorService);
IOStreamChannel channel = new IOStreamChannel(this, pipeline, sink);
sink.setChannel(channel);
channels.add(channel);
return channel;
}
@Override
public void releaseExternalResources() {
ChannelGroupFuture close = channels.close();
close.awaitUninterruptibly();
ExecutorUtil.terminate(executorService);
}
}

View File

@ -0,0 +1,178 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.channel.iostream;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.*;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.util.concurrent.ExecutorService;
import static org.jboss.netty.channel.Channels.*;
/**
* A {@link org.jboss.netty.channel.ChannelSink} implementation which reads from an {@link java.io.InputStream} and
* writes to an {@link java.io.OutputStream}.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStreamChannelSink extends AbstractChannelSink {
private static class ReadRunnable implements Runnable {
private final IOStreamChannelSink channelSink;
public ReadRunnable(final IOStreamChannelSink channelSink) {
this.channelSink = channelSink;
}
@Override
public void run() {
PushbackInputStream in = channelSink.inputStream;
while (channelSink.channel.isOpen()) {
byte[] buf;
int readBytes;
try {
int bytesToRead = in.available();
if (bytesToRead > 0) {
buf = new byte[bytesToRead];
readBytes = in.read(buf);
} else {
// peek into the stream if it was closed (value=-1)
int b = in.read();
if (b < 0) {
break;
}
// push back the byte which was read too much
in.unread(b);
continue;
}
} catch (Throwable t) {
if (!channelSink.channel.getCloseFuture().isDone()) {
fireExceptionCaught(channelSink.channel, t);
}
break;
}
fireMessageReceived(channelSink.channel, ChannelBuffers.wrappedBuffer(buf, 0, readBytes));
}
// Clean up.
close(channelSink.channel);
}
}
private final ExecutorService executorService;
private IOStreamChannel channel;
public IOStreamChannelSink(final ExecutorService executorService) {
this.executorService = executorService;
}
public boolean isConnected() {
return inputStream != null && outputStream != null;
}
public IOStreamAddress getRemoteAddress() {
return remoteAddress;
}
public boolean isBound() {
return false;
}
public ChannelConfig getConfig() {
return config;
}
public void setChannel(final IOStreamChannel channel) {
this.channel = channel;
}
private IOStreamAddress remoteAddress;
private OutputStream outputStream;
private PushbackInputStream inputStream;
private ChannelConfig config = new DefaultChannelConfig();
@Override
public void eventSunk(final ChannelPipeline pipeline, final ChannelEvent e) throws Exception {
final ChannelFuture future = e.getFuture();
if (e instanceof ChannelStateEvent) {
final ChannelStateEvent stateEvent = (ChannelStateEvent) e;
final ChannelState state = stateEvent.getState();
final Object value = stateEvent.getValue();
switch (state) {
case OPEN:
if (Boolean.FALSE.equals(value)) {
outputStream = null;
inputStream = null;
((IOStreamChannel) e.getChannel()).doSetClosed();
}
break;
case BOUND:
throw new UnsupportedOperationException();
case CONNECTED:
if (value != null) {
remoteAddress = (IOStreamAddress) value;
outputStream = remoteAddress.getOutputStream();
inputStream = new PushbackInputStream(remoteAddress.getInputStream());
executorService.execute(new ReadRunnable(this));
future.setSuccess();
}
break;
case INTEREST_OPS:
// TODO implement
throw new UnsupportedOperationException();
}
} else if (e instanceof MessageEvent) {
final MessageEvent event = (MessageEvent) e;
if (event.getMessage() instanceof ChannelBuffer) {
final ChannelBuffer buffer = (ChannelBuffer) event.getMessage();
buffer.readBytes(outputStream, buffer.readableBytes());
outputStream.flush();
future.setSuccess();
} else {
throw new IllegalArgumentException(
"Only ChannelBuffer objects are supported to be written onto the IOStreamChannelSink! "
+ "Please check if the encoder pipeline is configured correctly."
);
}
}
}
}

View File

@ -47,7 +47,5 @@ final class NioAcceptedSocketChannel extends NioSocketChannel {
setConnected();
fireChannelOpen(this);
fireChannelBound(this, getLocalAddress());
fireChannelConnected(this, getRemoteAddress());
}
}

View File

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

View File

@ -776,12 +776,10 @@ class NioWorker implements Runnable {
}
}
if (!server) {
if (!((NioClientSocketChannel) channel).boundManually) {
if (server || !((NioClientSocketChannel) channel).boundManually) {
fireChannelBound(channel, localAddress);
}
fireChannelConnected(channel, remoteAddress);
}
}
}
}

View File

@ -306,7 +306,10 @@ final class SocketSendBufferPool {
@Override
public void release() {
// Unpooled.
if (file.releaseAfterTransfer()) {
// Make sure the FileRegion resource are released otherwise it may cause a FD leak or something similar
file.releaseExternalResources();
}
}
}

View File

@ -63,7 +63,6 @@ class OioAcceptedSocketChannel extends OioSocketChannel {
fireChannelOpen(this);
fireChannelBound(this, getLocalAddress());
fireChannelConnected(this, getRemoteAddress());
}
@Override

View File

@ -20,12 +20,15 @@ import static org.jboss.netty.channel.Channels.*;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.net.SocketException;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.WritableByteChannel;
import java.util.regex.Pattern;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.FileRegion;
/**
*
@ -50,8 +53,13 @@ class OioWorker implements Runnable {
public void run() {
channel.workerThread = Thread.currentThread();
final PushbackInputStream in = channel.getInputStream();
boolean fireOpen = channel instanceof OioAcceptedSocketChannel;
while (channel.isOpen()) {
if (fireOpen) {
fireOpen = false;
fireChannelConnected(channel, channel.getRemoteAddress());
}
synchronized (channel.interestOpsLock) {
while (!channel.isReadable()) {
try {
@ -114,13 +122,41 @@ class OioWorker implements Runnable {
}
try {
int length = 0;
// Add support to write a FileRegion. This in fact will not give any performance gain but at least it not fail and
// we did the best to emulate it
if (message instanceof FileRegion) {
FileRegion fr = (FileRegion) message;
try {
synchronized (out) {
WritableByteChannel bchannel = Channels.newChannel(out);
long i = 0;
while ((i = fr.transferTo(bchannel, length)) > 0) {
length += i;
if (length >= fr.getCount()) {
break;
}
}
}
} finally {
if (fr.releaseAfterTransfer()) {
fr.releaseExternalResources();
}
}
} else {
ChannelBuffer a = (ChannelBuffer) message;
int length = a.readableBytes();
length = a.readableBytes();
synchronized (out) {
a.getBytes(a.readerIndex(), out, length);
}
}
fireWriteComplete(channel, length);
future.setSuccess();
} catch (Throwable t) {
// Convert 'SocketException: Socket closed' to
// ClosedChannelException.

View File

@ -142,8 +142,11 @@ public class HttpStaticFileServerHandler extends SimpleChannelUpstreamHandler {
{
SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);
if (ifModifiedSinceDate.getTime() == file.lastModified())
{
// Only compare up to the second because the datetime format we send to the client does not have milliseconds
long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
long fileLastModifiedSeconds = file.lastModified() / 1000;
if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
sendNotModified(ctx);
return;
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.http.websocketx.autobahn;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
/**
* A Web Socket echo server for running the <a href="http://www.tavendo.de/autobahn/testsuite.html">autobahn</a>
* test suite
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServer {
public static void main(String[] args) {
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE);
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
//bootstrap.setOption("child.tcpNoDelay", true);
// Set up the event pipeline factory.
bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory());
// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(9000));
System.out.println("Web Socket Server started on 9000. Open your browser and navigate to http://localhost:9000/");
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.http.websocketx.autobahn;
import static org.jboss.netty.handler.codec.http.HttpHeaders.*;
import static org.jboss.netty.handler.codec.http.HttpMethod.*;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*;
import static org.jboss.netty.handler.codec.http.HttpVersion.*;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.CharsetUtil;
/**
* Handles handshakes and messages
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandler.class);
private WebSocketServerHandshaker handshaker = null;
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object msg = e.getMessage();
if (msg instanceof HttpRequest) {
handleHttpRequest(ctx, (HttpRequest) msg);
} else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
// Allow only GET methods.
if (req.getMethod() != GET) {
sendHttpResponse(ctx, req, new DefaultHttpResponse(HTTP_1_1, FORBIDDEN));
return;
}
// Handshake
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
this.getWebSocketLocation(req), null, false);
this.handshaker = wsFactory.newHandshaker(ctx, req);
if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx);
} else {
this.handshaker.executeOpeningHandshake(ctx, req);
}
return;
}
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
logger.debug(String
.format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName()));
if (frame instanceof CloseWebSocketFrame) {
this.handshaker.executeClosingHandshake(ctx, (CloseWebSocketFrame) frame);
} else if (frame instanceof PingWebSocketFrame) {
ctx.getChannel().write(
new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof TextWebSocketFrame) {
//String text = ((TextWebSocketFrame) frame).getText();
ctx.getChannel().write(new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof BinaryWebSocketFrame) {
ctx.getChannel().write(
new BinaryWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof ContinuationWebSocketFrame) {
ctx.getChannel().write(
new ContinuationWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
} else if (frame instanceof PongWebSocketFrame) {
// Ignore
} else {
throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
.getName()));
}
}
private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
// Generate an error page if response status code is not OK (200).
if (res.getStatus().getCode() != 200) {
res.setContent(ChannelBuffers.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8));
setContentLength(res, res.getContent().readableBytes());
}
// Send the response and close the connection if necessary.
ChannelFuture f = ctx.getChannel().write(res);
if (!isKeepAlive(req) || res.getStatus().getCode() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
e.getCause().printStackTrace();
e.getChannel().close();
}
private String getWebSocketLocation(HttpRequest req) {
return "ws://" + req.getHeader(HttpHeaders.Names.HOST);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.http.websocketx.autobahn;
import static org.jboss.netty.channel.Channels.*;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
/**
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
// Create a default pipeline implementation.
ChannelPipeline pipeline = pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketServerHandler());
return pipeline;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* This package is intended for use with testing against the Python
* <a href="http://www.tavendo.de/autobahn/testsuite.html">AutoBahn test suite</a>.
*
* <h3>How to run the tests on Ubuntu</h3>
*
* <p>01. Add <tt>ppa:twisted-dev/ppa</tt> to your system's Software Sources
*
* <p>02. Install Twisted V11: <tt>sudo apt-get install python-twisted</tt>
*
* <p>03. Intall Python Setup Tools: <tt>sudo apt-get install python-setuptools</tt>
*
* <p>04. Install AutoBahn: <tt>sudo easy_install Autobahn</tt>
*
* <p>05. Get AutoBahn testsuite source code: <tt>git clone git@github.com:oberstet/Autobahn.git</tt>
*
* <p>06. Go to AutoBahn directory: <tt>cd Autobahn</tt>
*
* <p>07. Checkout stable version: <tt>git checkout v0.4.2</tt>
*
* <p>08. Go to test suite directory: <tt>cd testsuite/websockets</tt>
*
* <p>09. Edit <tt>fuzzing_clinet_spec.json</tt> and set the version to 10.
* <code>
* {
* "servers": [{"agent": "Netty", "hostname": "localhost", "port": 9000, "version": 10}],
* "cases": ["*"]
* }
* </code>
*
* <p>10. Run the test <tt>python fuzzing_client.py</tt>. Note that the actual test case python code is
* located in <tt>/usr/local/lib/python2.6/dist-packages/autobahn-0.4.2-py2.6.egg/autobahn/cases</tt>
* and not in the checked out git repository.
*
* <p>11. See the results in <tt>reports/servers/index.html</tt>
*/
package org.jboss.netty.example.http.websocketx.autobahn;

View File

@ -0,0 +1,136 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.http.websocketx.client;
import java.net.URI;
import java.util.ArrayList;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.jboss.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
/**
* A HTTP client demo app
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class App {
public static void main(String[] args) throws Exception {
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE);
runClient();
System.exit(0);
}
/**
* Send and receive some messages using a web socket client
*
* @throws Exception
*/
public static void runClient() throws Exception {
MyCallbackHandler callbackHandler = new MyCallbackHandler();
WebSocketClientFactory factory = new WebSocketClientFactory();
// Connect with spec version 10 (try changing it to V00 and it will
// still work ... fingers crossed ;-)
WebSocketClient client = factory.newClient(new URI("ws://localhost:8080/websocket"),
WebSocketSpecificationVersion.V10, callbackHandler);
// Connect
System.out.println("WebSocket Client connecting");
client.connect().awaitUninterruptibly();
Thread.sleep(200);
// Send 10 messages and wait for responses
System.out.println("WebSocket Client sending message");
for (int i = 0; i < 10; i++) {
client.send(new TextWebSocketFrame("Message #" + i));
}
Thread.sleep(1000);
// Ping
System.out.println("WebSocket Client sending ping");
client.send(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 })));
Thread.sleep(1000);
// Close
System.out.println("WebSocket Client sending close");
client.send(new CloseWebSocketFrame());
Thread.sleep(1000);
// Disconnect
client.disconnect();
}
/**
* Our web socket callback handler for this app
*/
public static class MyCallbackHandler implements WebSocketCallback {
public boolean connected = false;
public ArrayList<String> messagesReceived = new ArrayList<String>();
public MyCallbackHandler() {
return;
}
@Override
public void onConnect(WebSocketClient client) {
System.out.println("WebSocket Client connected!");
connected = true;
}
@Override
public void onDisconnect(WebSocketClient client) {
System.out.println("WebSocket Client disconnected!");
connected = false;
}
@Override
public void onMessage(WebSocketClient client, WebSocketFrame frame) {
if (frame instanceof TextWebSocketFrame) {
TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
System.out.println("WebSocket Client received message:" + textFrame.getText());
messagesReceived.add(textFrame.getText());
} else if (frame instanceof PongWebSocketFrame) {
System.out.println("WebSocket Client received pong");
} else if (frame instanceof CloseWebSocketFrame) {
System.out.println("WebSocket Client received closing");
}
}
@Override
public void onError(Throwable t) {
System.out.println("WebSocket Client error " + t.toString());
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,53 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.http.websocketx.client;
import org.jboss.netty.handler.codec.http.HttpMessage;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
/**
* Fix bug in standard HttpResponseDecoder for web socket clients. When status 101 is received for Hybi00, there are 16
* bytes of contents expected
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class WebSocketHttpResponseDecoder extends HttpResponseDecoder {
@Override
protected boolean isContentAlwaysEmpty(HttpMessage msg) {
if (msg instanceof HttpResponse) {
HttpResponse res = (HttpResponse) msg;
int code = res.getStatus().getCode();
// FIX force reading of protocol upgrade challenge data into the content buffer
if (code == 101) {
return false;
}
if (code < 200) {
return true;
}
switch (code) {
case 204:
case 205:
case 304:
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* <p>This is an example web service client.
* <p>To run this example, you must first start
* <tt>org.jboss.netty.example.http.websocketx.server.WebSocketServer</tt>
* <p>Next, run <tt>org.jboss.netty.example.http.websocketx.client.App</tt>.
*/
package org.jboss.netty.example.http.websocketx.client;

View File

@ -0,0 +1,74 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.http.websocketx.server;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
/**
* A HTTP server which serves Web Socket requests at:
*
* http://localhost:8080/websocket
*
* Open your browser at http://localhost:8080/, then the demo page will be
* loaded and a Web Socket connection will be made automatically.
*
* This server illustrates support for the different web socket specification
* versions and will work with:
*
* <ul>
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
* <li>
* <li>Chrome 6-13 (draft-ietf-hybi-thewebsocketprotocol-00)
* <li>
* <li>Chrome 14+ (draft-ietf-hybi-thewebsocketprotocol-10)
* <li>
* <li>Firefox 7+ (draft-ietf-hybi-thewebsocketprotocol-10)
* <li>
* </ul>
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServer {
public static void main(String[] args) {
ConsoleHandler ch = new ConsoleHandler();
ch.setLevel(Level.FINE);
Logger.getLogger("").addHandler(ch);
Logger.getLogger("").setLevel(Level.FINE);
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
// Set up the event pipeline factory.
bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory());
// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(8080));
System.out.println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/");
}
}

View File

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

View File

@ -0,0 +1,73 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.http.websocketx.server;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil;
/**
* Generates the demo HTML page which is served at http://localhost:8080/
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerIndexPage {
private static final String NEWLINE = "\r\n";
public static ChannelBuffer getContent(String webSocketLocation) {
return ChannelBuffers.copiedBuffer(
"<html><head><title>Web Socket Test</title></head>" + NEWLINE +
"<body>" + NEWLINE +
"<script type=\"text/javascript\">" + NEWLINE +
"var socket;" + NEWLINE +
"if (!window.WebSocket) {" + NEWLINE +
" window.WebSocket = window.MozWebSocket;" + NEWLINE +
"}" + NEWLINE +
"if (window.WebSocket) {" + NEWLINE +
" socket = new WebSocket(\"" + webSocketLocation + "\");" + NEWLINE +
" socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };" + NEWLINE +
" socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };" + NEWLINE +
" socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };" + NEWLINE +
"} else {" + NEWLINE +
" alert(\"Your browser does not support Web Socket.\");" + NEWLINE +
"}" + NEWLINE +
"" + NEWLINE +
"function send(message) {" + NEWLINE +
" if (!window.WebSocket) { return; }" + NEWLINE +
" if (socket.readyState == WebSocket.OPEN) {" + NEWLINE +
" socket.send(message);" + NEWLINE +
" } else {" + NEWLINE +
" alert(\"The socket is not open.\");" + NEWLINE +
" }" + NEWLINE +
"}" + NEWLINE +
"</script>" + NEWLINE +
"<form onsubmit=\"return false;\">" + NEWLINE +
"<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>" +
"<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />" + NEWLINE +
"<h3>Output</h3>" + NEWLINE +
"<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>" + NEWLINE +
"</form>" + NEWLINE +
"</body>" + NEWLINE +
"</html>" + NEWLINE,
CharsetUtil.US_ASCII);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.http.websocketx.server;
import static org.jboss.netty.channel.Channels.*;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
/**
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
* @version $Rev$, $Date$
*/
public class WebSocketServerPipelineFactory implements ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
// Create a default pipeline implementation.
ChannelPipeline pipeline = pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("handler", new WebSocketServerHandler());
return pipeline;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2009 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* <p>This package contains an example web socket web server.
* <p>The web server only handles text, ping and closing frames. For text frames,
* it echoes the received text in upper case.
* <p>Once started, you can test the web server against your browser by navigating
* to http://localhost:8080/
* <p>You can also test it with a web socket client. Send web socket traffic to
* ws://localhost:8080/websocket.
*/
package org.jboss.netty.example.http.websocketx.server;

View File

@ -0,0 +1,94 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.example.iostream;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.iostream.IOStreamAddress;
import org.jboss.netty.channel.iostream.IOStreamChannelFactory;
import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.Delimiters;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* An example demonstrating the use of the {@link org.jboss.netty.channel.iostream.IOStreamChannel}.
*
* @author Daniel Bimschas
* @author Dennis Pfisterer
*/
public class IOStream {
private static volatile boolean running = true;
public static void main(String[] args) {
final ExecutorService executorService = Executors.newCachedThreadPool();
final ClientBootstrap bootstrap = new ClientBootstrap(new IOStreamChannelFactory(executorService));
// Configure the event pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
DefaultChannelPipeline pipeline = new DefaultChannelPipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("loggingHandler", new SimpleChannelHandler() {
@Override
public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e)
throws Exception {
final String message = (String) e.getMessage();
synchronized (System.out) {
e.getChannel().write("Message received: " + message);
}
if ("exit".equals(message)) {
IOStream.running = false;
}
super.messageReceived(ctx, e);
}
}
);
return pipeline;
}
});
// Make a new connection.
ChannelFuture connectFuture = bootstrap.connect(new IOStreamAddress(System.in, System.out));
// Wait until the connection is made successfully.
Channel channel = connectFuture.awaitUninterruptibly().getChannel();
while (running) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Close the connection.
channel.close().awaitUninterruptibly();
// Shut down all thread pools to exit.
bootstrap.releaseExternalResources();
}
}

View File

@ -60,30 +60,34 @@ public class HttpContentCompressor extends HttpContentEncoder {
}
@Override
protected EncoderEmbedder<ChannelBuffer> newContentEncoder(String acceptEncoding) throws Exception {
ZlibWrapper wrapper = determineWrapper(acceptEncoding);
if (wrapper == null) {
return null;
}
return new EncoderEmbedder<ChannelBuffer>(new ZlibEncoder(wrapper, compressionLevel));
}
@Override
protected String getTargetContentEncoding(String acceptEncoding) throws Exception {
protected Result beginEncode(HttpMessage msg, String acceptEncoding) throws Exception {
String contentEncoding = msg.getHeader(HttpHeaders.Names.CONTENT_ENCODING);
if (contentEncoding != null &&
!HttpHeaders.Values.IDENTITY.equalsIgnoreCase(contentEncoding)) {
return null;
}
ZlibWrapper wrapper = determineWrapper(acceptEncoding);
if (wrapper == null) {
return null;
}
String targetContentEncoding;
switch (wrapper) {
case GZIP:
return "gzip";
targetContentEncoding = "gzip";
break;
case ZLIB:
return "deflate";
targetContentEncoding = "deflate";
break;
default:
throw new Error();
}
return new Result(
targetContentEncoding,
new EncoderEmbedder<ChannelBuffer>(
new ZlibEncoder(wrapper, compressionLevel)));
}
private ZlibWrapper determineWrapper(String acceptEncoding) {

View File

@ -29,19 +29,20 @@ import org.jboss.netty.util.internal.LinkedTransferQueue;
/**
* Encodes the content of the outbound {@link HttpResponse} and {@link HttpChunk}.
* The original content is replaced with the new content encoded by the
* {@link EncoderEmbedder}, which is created by {@link #newContentEncoder(String)}.
* {@link EncoderEmbedder}, which is created by {@link #beginEncode(HttpMessage, String)}.
* Once encoding is finished, the value of the <tt>'Content-Encoding'</tt> header
* is set to the target content encoding, as returned by {@link #getTargetContentEncoding(String)}.
* is set to the target content encoding, as returned by
* {@link #beginEncode(HttpMessage, String)}.
* Also, the <tt>'Content-Length'</tt> header is updated to the length of the
* encoded content. If there is no supported encoding in the
* encoded content. If there is no supported or allowed encoding in the
* corresponding {@link HttpRequest}'s {@code "Accept-Encoding"} header,
* {@link #newContentEncoder(String)} should return {@code null} so that no
* encoding occurs (i.e. pass-through).
* {@link #beginEncode(HttpMessage, String)} should return {@code null} so that
* no encoding occurs (i.e. pass-through).
* <p>
* Please note that this is an abstract class. You have to extend this class
* and implement {@link #newContentEncoder(String)} and {@link #getTargetContentEncoding(String)}
* properly to make this class functional. For example, refer to the source
* code of {@link HttpContentCompressor}.
* and implement {@link #beginEncode(HttpMessage, String)} properly to make
* this class functional. For example, refer to the source code of
* {@link HttpContentCompressor}.
* <p>
* This handler must be placed after {@link HttpMessageEncoder} in the pipeline
* so that this handler can intercept HTTP responses before {@link HttpMessageEncoder}
@ -103,12 +104,24 @@ public abstract class HttpContentEncoder extends SimpleChannelHandler {
}
boolean hasContent = m.isChunked() || m.getContent().readable();
if (hasContent && (encoder = newContentEncoder(acceptEncoding)) != null) {
if (!hasContent) {
ctx.sendDownstream(e);
return;
}
Result result = beginEncode(m, acceptEncoding);
if (result == null) {
ctx.sendDownstream(e);
return;
}
encoder = result.getContentEncoder();
// Encode the content and remove or replace the existing headers
// so that the message looks like a decoded message.
m.setHeader(
HttpHeaders.Names.CONTENT_ENCODING,
getTargetContentEncoding(acceptEncoding));
result.getTargetContentEncoding());
if (!m.isChunked()) {
ChannelBuffer content = m.getContent();
@ -124,7 +137,6 @@ public abstract class HttpContentEncoder extends SimpleChannelHandler {
Integer.toString(content.readableBytes()));
}
}
}
// Because HttpMessage is a mutable object, we can simply forward the write request.
ctx.sendDownstream(e);
@ -162,24 +174,20 @@ public abstract class HttpContentEncoder extends SimpleChannelHandler {
}
/**
* Returns a new {@link EncoderEmbedder} that encodes the HTTP message
* content.
* Prepare to encode the HTTP message content.
*
* @param msg
* the HTTP message whose content should be encoded
* @param acceptEncoding
* the value of the {@code "Accept-Encoding"} header
*
* @return a new {@link EncoderEmbedder} if there is a supported encoding
* in {@code acceptEncoding}. {@code null} otherwise.
* @return the result of preparation, which is composed of the determined
* target content encoding and a new {@link EncoderEmbedder} that
* encodes the content into the target content encoding.
* {@code null} if {@code acceptEncoding} is unsupported or rejected
* and thus the content should be handled as-is (i.e. no encoding).
*/
protected abstract EncoderEmbedder<ChannelBuffer> newContentEncoder(String acceptEncoding) throws Exception;
/**
* Returns the expected content encoding of the encoded content.
*
* @param acceptEncoding the value of the {@code "Accept-Encoding"} header
* @return the expected content encoding of the new content
*/
protected abstract String getTargetContentEncoding(String acceptEncoding) throws Exception;
protected abstract Result beginEncode(HttpMessage msg, String acceptEncoding) throws Exception;
private ChannelBuffer encode(ChannelBuffer buf) {
encoder.offer(buf);
@ -196,4 +204,29 @@ public abstract class HttpContentEncoder extends SimpleChannelHandler {
encoder = null;
return result;
}
public static final class Result {
private final String targetContentEncoding;
private final EncoderEmbedder<ChannelBuffer> contentEncoder;
public Result(String targetContentEncoding, EncoderEmbedder<ChannelBuffer> contentEncoder) {
if (targetContentEncoding == null) {
throw new NullPointerException("targetContentEncoding");
}
if (contentEncoder == null) {
throw new NullPointerException("contentEncoder");
}
this.targetContentEncoding = targetContentEncoding;
this.contentEncoder = contentEncoder;
}
public String getTargetContentEncoding() {
return targetContentEncoding;
}
public EncoderEmbedder<ChannelBuffer> getContentEncoder() {
return contentEncoder;
}
}
}

View File

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

View File

@ -0,0 +1,68 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
/**
* Web Socket frame containing binary data
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class BinaryWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty binary frame.
*/
public BinaryWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new binary frame with the specified binary data. The final
* fragment flag is set to true.
*
* @param binaryData
* the content of the frame.
*/
public BinaryWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new binary frame with the specified binary data and the final
* fragment flag.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
*/
public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffers;
/**
* Web Socket Frame for closing the connection
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class CloseWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty close frame.
*/
public CloseWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new close frame
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
*/
public CloseWebSocketFrame(boolean finalFragment, int rsv) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil;
/**
* Web Socket continuation frame containing continuation text or binary data.
* This is used for fragmented messages where the contents of a messages is
* contained more than 1 frame.
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class ContinuationWebSocketFrame extends WebSocketFrame {
private String aggregatedText = null;
/**
* Creates a new empty continuation frame.
*/
public ContinuationWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new continuation frame with the specified binary data. The
* final fragment flag is set to true.
*
* @param binaryData
* the content of the frame.
*/
public ContinuationWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new continuation frame with the specified binary data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
/**
* Creates a new continuation frame with the specified binary data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
* @param aggregatedText
* Aggregated text set by decoder on the final continuation frame
* of a fragmented text message
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
this.aggregatedText = aggregatedText;
}
/**
* Creates a new continuation frame with the specified text data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param text
* text content of the frame.
*/
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setText(text);
}
/**
* Returns the text data in this frame
*/
public String getText() {
if (this.getBinaryData() == null) {
return null;
}
return this.getBinaryData().toString(CharsetUtil.UTF_8);
}
/**
* Sets the string for this frame
*
* @param text
* text to store
*/
public void setText(String text) {
if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
/**
* Aggregated text returned by decoder on the final continuation frame of a
* fragmented text message
*/
public String getAggregatedText() {
return aggregatedText;
}
public void setAggregatedText(String aggregatedText) {
this.aggregatedText = aggregatedText;
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
/**
* Web Socket frame containing binary data
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class PingWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty ping frame.
*/
public PingWebSocketFrame() {
this.setFinalFragment(true);
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new ping frame with the specified binary data.
*
* @param binaryData
* the content of the frame.
*/
public PingWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new ping frame with the specified binary data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
*/
public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
/**
* Web Socket frame containing binary data
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*/
public class PongWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty pong frame.
*/
public PongWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new pong frame with the specified binary data.
*
* @param binaryData
* the content of the frame.
*/
public PongWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new pong frame with the specified binary data
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame.
*/
public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
@Override
public String toString() {
return getClass().getSimpleName() + "(data: " + getBinaryData() + ')';
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.util.CharsetUtil;
/**
* Web Socket text frame with assumed UTF-8 encoding
*
* @author <a href="http://www.veebsbraindump.com/">Vibul Imtarnasan</a>
*
*/
public class TextWebSocketFrame extends WebSocketFrame {
/**
* Creates a new empty text frame.
*/
public TextWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
}
/**
* Creates a new text frame with the specified text string. The final
* fragment flag is set to true.
*
* @param text
* String to put in the frame
*/
public TextWebSocketFrame(String text) {
if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
/**
* Creates a new text frame with the specified binary data. The final
* fragment flag is set to true.
*
* @param binaryData
* the content of the frame. Must be UTF-8 encoded
*/
public TextWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData);
}
/**
* Creates a new text frame with the specified text string. The final
* fragment flag is set to true.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param text
* String to put in the frame
*/
public TextWebSocketFrame(boolean finalFragment, int rsv, String text) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
}
/**
* Creates a new text frame with the specified binary data. The final
* fragment flag is set to true.
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame. Must be UTF-8 encoded
*/
public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment);
this.setRsv(rsv);
this.setBinaryData(binaryData);
}
/**
* Returns the text data in this frame
*/
public String getText() {
if (this.getBinaryData() == null) {
return null;
}
return this.getBinaryData().toString(CharsetUtil.UTF_8);
}
/**
* Sets the string for this frame
*
* @param text
* text to store
*/
public void setText(String text) {
if (text == null) {
throw new NullPointerException("text");
}
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
}
@Override
public String toString() {
return getClass().getSimpleName() + "(text: " + getText() + ')';
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,103 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelHandler.Sharable;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
/**
* Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}.
* <p>
* For the detailed instruction on adding add Web Socket support to your HTTP
* server, take a look into the <tt>WebSocketServer</tt> example located in the
* {@code org.jboss.netty.example.http.websocket} package.
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
* @author Mike Heath (mheath@apache.org)
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
* @version $Rev: 2362 $, $Date: 2010-09-09 19:59:22 +0900 (Thu, 09 Sep 2010) $
*
* @apiviz.landmark
* @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame
*/
@Sharable
public class WebSocket00FrameEncoder extends OneToOneEncoder {
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame) {
// Text frame
ChannelBuffer data = frame.getBinaryData();
ChannelBuffer encoded = channel.getConfig().getBufferFactory()
.getBuffer(data.order(), data.readableBytes() + 2);
encoded.writeByte((byte) 0x00);
encoded.writeBytes(data, data.readerIndex(), data.readableBytes());
encoded.writeByte((byte) 0xFF);
return encoded;
} else if (frame instanceof CloseWebSocketFrame) {
// Close frame
ChannelBuffer data = frame.getBinaryData();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), 2);
encoded.writeByte((byte) 0xFF);
encoded.writeByte((byte) 0x00);
return encoded;
} else {
// Binary frame
ChannelBuffer data = frame.getBinaryData();
int dataLen = data.readableBytes();
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), dataLen + 5);
// Encode type.
encoded.writeByte((byte) 0x80);
// Encode length.
int b1 = dataLen >>> 28 & 0x7F;
int b2 = dataLen >>> 14 & 0x7F;
int b3 = dataLen >>> 7 & 0x7F;
int b4 = dataLen & 0x7F;
if (b1 == 0) {
if (b2 == 0) {
if (b3 == 0) {
encoded.writeByte(b4);
} else {
encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4);
}
} else {
encoded.writeByte(b2 | 0x80);
encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4);
}
} else {
encoded.writeByte(b1 | 0x80);
encoded.writeByte(b2 | 0x80);
encoded.writeByte(b3 | 0x80);
encoded.writeByte(b4);
}
// Encode binary data.
encoded.writeBytes(data, data.readerIndex(), dataLen);
return encoded;
}
}
return msg;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,56 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import java.net.URI;
/**
* Instances the appropriate handshake class to use for clients
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public class WebSocketClientHandshakerFactory {
/**
* Instances a new handshaker
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the
* server
* @param subProtocol
* Sub protocol request sent to the server. Null if no
* sub-protocol support is required.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame
* @throws WebSocketHandshakeException
*/
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketSpecificationVersion version,
String subProtocol, boolean allowExtensions) throws WebSocketHandshakeException {
if (version == WebSocketSpecificationVersion.V10) {
return new WebSocketClientHandshaker10(webSocketURL, version, subProtocol, allowExtensions);
}
if (version == WebSocketSpecificationVersion.V00) {
return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol);
}
throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported.");
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* Base class for web socket frames
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public abstract class WebSocketFrame {
/**
* Flag to indicate if this frame is the final fragment in a message. The
* first fragment (frame) may also be the final fragment.
*/
private boolean finalFragment = true;
/**
* RSV1, RSV2, RSV3 used for extensions
*/
private int rsv = 0;
/**
* Contents of this frame
*/
private ChannelBuffer binaryData;
/**
* Returns binary data
*/
public ChannelBuffer getBinaryData() {
return binaryData;
}
/**
* Sets the binary data for this frame
*/
public void setBinaryData(ChannelBuffer binaryData) {
this.binaryData = binaryData;
}
/**
* Flag to indicate if this frame is the final fragment in a message. The
* first fragment (frame) may also be the final fragment.
*/
public boolean isFinalFragment() {
return finalFragment;
}
public void setFinalFragment(boolean finalFragment) {
this.finalFragment = finalFragment;
}
/**
* Bits used for extensions to the standard.
*/
public int getRsv() {
return rsv;
}
public void setRsv(int rsv) {
this.rsv = rsv;
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
/**
* Type of web socket frames
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public enum WebSocketFrameType {
TEXT, BINARY, PING, PONG, CLOSE, CONTINUATION
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
/**
* Exception during handshaking process
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public class WebSocketHandshakeException extends Exception {
private static final long serialVersionUID = 1L;
public WebSocketHandshakeException(String s) {
super(s);
}
public WebSocketHandshakeException(String s, Throwable throwable) {
super(s, throwable);
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,108 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
/**
* Instances the appropriate handshake class to use for clients
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public class WebSocketServerHandshakerFactory {
private String webSocketURL;
private String subProtocols;
private boolean allowExtensions = false;
/**
* Constructor specifying the destination web socket location
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param subProtocols
* CSV of supported protocols. Null if sub protocols not
* supported.
* @param allowExtensions
* Allow extensions to be used in the reserved bits of the web
* socket frame
*/
public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) {
this.webSocketURL = webSocketURL;
this.subProtocols = subProtocols;
this.allowExtensions = allowExtensions;
return;
}
/**
* Instances a new handshaker
*
* @param webSocketURL
* URL for web socket communications. e.g
* "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the
* server
* @param subProtocol
* Sub protocol request sent to the server. Null if no
* sub-protocol support is required.
* @return A new WebSocketServerHandshaker for the requested web socket
* version. Null if web socket version is not supported.
*/
public WebSocketServerHandshaker newHandshaker(ChannelHandlerContext ctx, HttpRequest req) {
String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
if (version != null) {
if (version.equals("8")) {
// Version 8 of the wire protocol - assume version 10 of the
// specification.
return new WebSocketServerHandshaker10(webSocketURL, subProtocols, this.allowExtensions);
} else {
return null;
}
} else {
// Assume version 00 where version header was not specified
return new WebSocketServerHandshaker00(webSocketURL, subProtocols);
}
}
/**
* Return that we need cannot not support the web socket version
*
* @param ctx
* Context
*/
public void sendUnsupportedWebSocketVersionResponse(ChannelHandlerContext ctx) {
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101,
"Switching Protocols"));
res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);
res.setHeader(Names.SEC_WEBSOCKET_VERSION, "8");
ctx.getChannel().write(res);
return;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.jboss.netty.handler.codec.http.websocketx;
/**
* <p>
* Versions of the web socket specification.
* </p>
* <p>
* A specification is tied to one wire protocol version but a protocol version
* may have use by more than 1 version of the specification.
* </p>
*
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
*/
public enum WebSocketSpecificationVersion {
UNKNOWN,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>.
*/
V00,
/**
* <a href=
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
*/
V10
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2010 Red Hat, Inc.
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Encoder, decoder, handshakers and their related message types for
* <a href="http://en.wikipedia.org/wiki/Web_Sockets">Web Socket</a> data frames.
* <p>
* This package supports different web socket specification versions (hence the X suffix).
* The specification current supported are:
* <ul>
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">draft-ietf-hybi-thewebsocketprotocol-00</a></li>
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10">draft-ietf-hybi-thewebsocketprotocol-10</a></li>
* </ul>
* </p>
* <p>
* In the future, as the specification develops, more versions will be supported.
* This contrasts the <tt>org.jboss.netty.handler.codec.http.websocket</tt> package which only
* supports draft-ietf-hybi-thewebsocketprotocol-00.
* </p>
* <p>
* For the detailed instruction on adding add Web Socket support to your HTTP
* server, take a look into the <tt>WebSocketServerX</tt> example located in the
* {@code org.jboss.netty.example.http.websocket} package.
* </p>
*
* @apiviz.exclude OneToOne(Encoder|Decoder)$
* @apiviz.exclude \.codec\.replay\.
* @apiviz.exclude \.Default
*/
package org.jboss.netty.handler.codec.http.websocketx;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,17 +69,22 @@ import org.jboss.netty.util.internal.NonReentrantLock;
*
* <h3>Renegotiation</h3>
* <p>
* TLS renegotiation has been disabled by default due to a known security issue,
* <a href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3555">CVE-2009-3555</a>.
* You can re-enable renegotiation by calling {@link #setEnableRenegotiation(boolean)}
* with {@code true} at your own risk.
* <p>
* If {@link #isEnableRenegotiation() enableRenegotiation} is {@code true} and
* the initial handshake has been done successfully, you can call
* If {@link #isEnableRenegotiation() enableRenegotiation} is {@code true}
* (default) and the initial handshake has been done successfully, you can call
* {@link #handshake()} to trigger the renegotiation.
* <p>
* If {@link #isEnableRenegotiation() enableRenegotiation} is {@code false},
* an attempt to trigger renegotiation will result in the connection closure.
* <p>
* Please note that TLS renegotiation had a security issue before. If your
* runtime environment did not fix it, please make sure to disable TLS
* renegotiation by calling {@link #setEnableRenegotiation(boolean)} with
* {@code false}. For more information, please refer to the following documents:
* <ul>
* <li><a href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-3555">CVE-2009-3555</a></li>
* <li><a href="http://www.ietf.org/rfc/rfc5746.txt">RFC5746</a></li>
* <li><a href="http://www.oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html">Phased Approach to Fixing the TLS Renegotiation Issue</a></li>
* </ul>
*
* <h3>Closing the session</h3>
* <p>
@ -173,7 +178,7 @@ public class SslHandler extends FrameDecoder
private final Executor delegatedTaskExecutor;
private final boolean startTls;
private volatile boolean enableRenegotiation;
private volatile boolean enableRenegotiation = true;
final Object handshakeLock = new Object();
private boolean handshaking;
@ -866,17 +871,21 @@ public class SslHandler extends FrameDecoder
loop:
for (;;) {
SSLEngineResult result;
boolean needsHandshake = false;
synchronized (handshakeLock) {
if (!handshaken && !handshaking &&
!engine.getUseClientMode() &&
!engine.isInboundDone() && !engine.isOutboundDone()) {
needsHandshake = true;
}
}
if (needsHandshake) {
handshake();
}
try {
synchronized (handshakeLock) {
result = engine.unwrap(inNetBuf, outAppBuf);
} catch (SSLException e) {
throw e;
}
final HandshakeStatus handshakeStatus = result.getHandshakeStatus();
@ -905,7 +914,7 @@ public class SslHandler extends FrameDecoder
throw new IllegalStateException(
"Unknown handshake status: " + handshakeStatus);
}
}
}
if (needsWrap) {
@ -1037,30 +1046,60 @@ public class SslHandler extends FrameDecoder
if (handshakeFuture == null) {
handshakeFuture = future(channel);
}
// Release all resources such as internal buffers that SSLEngine
// is managing.
engine.closeOutbound();
try {
engine.closeInbound();
} catch (SSLException e) {
logger.debug(
"SSLEngine.closeInbound() raised an exception after " +
"a handshake failure.", e);
}
}
handshakeFuture.setFailure(cause);
}
private void closeOutboundAndChannel(
final ChannelHandlerContext context, final ChannelStateEvent e) throws SSLException {
final ChannelHandlerContext context, final ChannelStateEvent e) {
if (!e.getChannel().isConnected()) {
context.sendDownstream(e);
return;
}
boolean success = false;
try {
try {
unwrap(context, e.getChannel(), ChannelBuffers.EMPTY_BUFFER, 0, 0);
} catch (SSLException ex) {
logger.debug("Failed to unwrap before sending a close_notify message", ex);
}
if (!engine.isInboundDone()) {
if (sentCloseNotify.compareAndSet(false, true)) {
engine.closeOutbound();
try {
ChannelFuture closeNotifyFuture = wrapNonAppData(context, e.getChannel());
closeNotifyFuture.addListener(
new ClosingChannelFutureListener(context, e));
return;
success = true;
} catch (SSLException ex) {
logger.debug("Failed to encode a close_notify message", ex);
}
}
} else {
success = true;
}
} finally {
if (!success) {
context.sendDownstream(e);
}
}
}
private static final class PendingWrite {
final ChannelFuture future;

View File

@ -154,18 +154,20 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns
private void discard(ChannelHandlerContext ctx) {
ClosedChannelException cause = null;
boolean fireExceptionCaught = false;
synchronized (this) {
for (;;) {
if (currentEvent == null) {
MessageEvent currentEvent = this.currentEvent;
if (this.currentEvent == null) {
currentEvent = queue.poll();
} else {
this.currentEvent = null;
}
if (currentEvent == null) {
break;
}
MessageEvent currentEvent = this.currentEvent;
this.currentEvent = null;
Object m = currentEvent.getMessage();
if (m instanceof ChunkedInput) {
@ -181,7 +183,7 @@ public class ChunkedWriteHandler implements ChannelUpstreamHandler, ChannelDowns
currentEvent = null;
}
}
if (fireExceptionCaught) {
Channels.fireExceptionCaught(ctx.getChannel(), cause);

View File

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