demo websocket implementation

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@1087 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2009-11-23 08:44:37 +00:00
parent 153bd88fc6
commit f9455c7712
30 changed files with 541 additions and 395 deletions

View File

@ -38,5 +38,10 @@
<artifactId>jetty-jmx</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -377,6 +377,15 @@
<outputDirectory>${assembly.directory}</outputDirectory>
<destFileName>start.jar</destFileName>
</artifactItem>
<artifactItem>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>${project.version}</version>
<type>jar</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly.directory}/lib</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
@ -500,5 +509,10 @@
<artifactId>jetty-policy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -14,6 +14,8 @@
#
#===========================================================
OPTIONS=Server,jmx,resources,websocket
#===========================================================
# The following is an example of the ini args to run the
# server with a heap size, JMX remote enabled, JMX mbeans

View File

@ -485,6 +485,7 @@ public class HttpGenerator extends AbstractGenerator
HttpFields.Field field = fields.getField(f);
if (field==null)
continue;
switch (field.getNameOrdinal())
{
case HttpHeaders.CONTENT_LENGTH_ORDINAL:
@ -506,7 +507,8 @@ public class HttpGenerator extends AbstractGenerator
break;
case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
if (_version == HttpVersions.HTTP_1_1_ORDINAL) transfer_encoding = field;
if (_version == HttpVersions.HTTP_1_1_ORDINAL)
transfer_encoding = field;
// Do NOT add yet!
break;
@ -566,6 +568,15 @@ public class HttpGenerator extends AbstractGenerator
break;
}
case HttpHeaderValues.UPGRADE_ORDINAL:
{
// special case for websocket connection ordering
if (_method==null)
{
field.put(_header);
continue;
}
}
case HttpHeaderValues.CLOSE_ORDINAL:
{
close=true;

View File

@ -42,7 +42,8 @@ public class HttpHeaderValues extends BufferCache
PROCESSING="102-processing",
TE="TE",
BYTES="bytes",
NO_CACHE="no-cache";
NO_CACHE="no-cache",
UPGRADE="Upgrade";
public final static int
CLOSE_ORDINAL=1,
@ -54,7 +55,8 @@ public class HttpHeaderValues extends BufferCache
PROCESSING_ORDINAL=7,
TE_ORDINAL=8,
BYTES_ORDINAL=9,
NO_CACHE_ORDINAL=10;
NO_CACHE_ORDINAL=10,
UPGRADE_ORDINAL=11;
public final static HttpHeaderValues CACHE= new HttpHeaderValues();
@ -68,7 +70,8 @@ public class HttpHeaderValues extends BufferCache
PROCESSING_BUFFER=CACHE.add(PROCESSING, PROCESSING_ORDINAL),
TE_BUFFER=CACHE.add(TE,TE_ORDINAL),
BYTES_BUFFER=CACHE.add(BYTES,BYTES_ORDINAL),
NO_CACHE_BUFFER=CACHE.add(NO_CACHE,NO_CACHE_ORDINAL);
NO_CACHE_BUFFER=CACHE.add(NO_CACHE,NO_CACHE_ORDINAL),
UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL);
static
{

View File

@ -32,9 +32,6 @@ import org.eclipse.jetty.util.thread.Timeout;
/* ------------------------------------------------------------ */
/**
* An Endpoint that can be scheduled by {@link SelectorManager}.
*
*
*
*/
public class SelectChannelEndPoint extends ChannelEndPoint implements Runnable, AsyncEndPoint, ConnectedEndPoint
{

View File

@ -648,7 +648,7 @@ public class ObjectMBean implements DynamicMBean
}
catch (Exception e)
{
Log.warn(Log.EXCEPTION, e);
Log.warn(name+": "+metaData, e);
throw new IllegalArgumentException(e.toString());
}
}

View File

@ -61,7 +61,7 @@ public class Server extends HandlerWrapper implements Attributes
if (Server.class.getPackage()!=null && Server.class.getPackage().getImplementationVersion()!=null)
_version=Server.class.getPackage().getImplementationVersion();
else
_version=System.getProperty("jetty.version","7.0.y.z-SNAPSHOT");
_version=System.getProperty("jetty.version","7.0.2-SNAPSHOT");
}
private final Container _container=new Container();
private final AttributesMap _attributes = new AttributesMap();

View File

@ -63,8 +63,8 @@ import org.eclipse.jetty.util.thread.Timeout.Task;
public class SelectChannelConnector extends AbstractNIOConnector
{
protected ServerSocketChannel _acceptChannel;
private long _lowResourcesConnections;
private long _lowResourcesMaxIdleTime;
private int _lowResourcesConnections;
private int _lowResourcesMaxIdleTime;
private final SelectorManager _manager = new SelectorManager()
{
@ -226,7 +226,7 @@ public class SelectChannelConnector extends AbstractNIOConnector
/**
* @return the lowResourcesConnections
*/
public long getLowResourcesConnections()
public int getLowResourcesConnections()
{
return _lowResourcesConnections;
}
@ -236,9 +236,9 @@ public class SelectChannelConnector extends AbstractNIOConnector
* Set the number of connections, which if exceeded places this manager in low resources state.
* This is not an exact measure as the connection count is averaged over the select sets.
* @param lowResourcesConnections the number of connections
* @see {@link #setLowResourcesMaxIdleTime(long)}
* @see {@link #setLowResourcesMaxIdleTime(int)}
*/
public void setLowResourcesConnections(long lowResourcesConnections)
public void setLowResourcesConnections(int lowResourcesConnections)
{
_lowResourcesConnections=lowResourcesConnections;
}
@ -247,27 +247,11 @@ public class SelectChannelConnector extends AbstractNIOConnector
/**
* @return the lowResourcesMaxIdleTime
*/
public long getLowResourcesMaxIdleTime()
public int getLowResourcesMaxIdleTime()
{
return _lowResourcesMaxIdleTime;
}
/* ------------------------------------------------------------ */
/**
* Set the period in ms that a connection is allowed to be idle when this there are more
* than {@link #getLowResourcesConnections()} connections. This allows the server to rapidly close idle connections
* in order to gracefully handle high load situations.
* @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low.
* @see {@link #setMaxIdleTime(long)}
* @deprecated use {@link #setLowResourceMaxIdleTime(int)}
*/
@Deprecated
public void setLowResourcesMaxIdleTime(long lowResourcesMaxIdleTime)
{
_lowResourcesMaxIdleTime=lowResourcesMaxIdleTime;
super.setLowResourceMaxIdleTime((int)lowResourcesMaxIdleTime); // TODO fix the name duplications
}
/* ------------------------------------------------------------ */
/**
* Set the period in ms that a connection is allowed to be idle when this there are more

View File

@ -143,6 +143,9 @@ $(jetty.home)/lib/policy/jetty.policy
$(jetty.home)/lib/jetty-http-$(version).jar ! available org.eclipse.jetty.http.HttpParser
$(jetty.home)/lib/jetty-client-$(version).jar ! available org.eclipse.jetty.client.HttpClient
[All,websocket]
$(jetty.home)/lib/jetty-websocket-$(version).jar ! available org.eclipse.jetty.websocket.WebSocket
# Add ext if it exists
[All,default,=$(jetty.home)/lib/ext]

View File

@ -46,8 +46,8 @@ public class StringMap extends AbstractMap implements Externalizable
protected boolean _ignoreCase=false;
protected NullEntry _nullEntry=null;
protected Object _nullValue=null;
protected HashSet _entrySet=new HashSet(3);
protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet);
protected HashSet _entrySet=new HashSet(3);
protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet);
/* ------------------------------------------------------------ */
/** Constructor.
@ -135,6 +135,9 @@ public class StringMap extends AbstractMap implements Externalizable
return oldValue;
}
if (_ignoreCase)
key=key.toUpperCase();
Node node = _root;
int ni=-1;
Node prev = null;

View File

@ -16,7 +16,7 @@ public interface WebSocket
void sendMessage(byte frame,String data) throws IOException;
void sendMessage(byte frame,byte[] data) throws IOException;
void sendMessage(byte frame,byte[] data, int offset, int length) throws IOException;
void disconnect() throws IOException;
void disconnect();
boolean isOpen();
}
}

View File

@ -4,6 +4,7 @@ import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.io.ThreadLocalBuffers;
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
/* ------------------------------------------------------------ */
@ -31,7 +32,7 @@ public class WebSocketBuffers
@Override
protected Buffer newBuffer(int size)
{
return new ByteArrayBuffer(bufferSize);
return new IndirectNIOBuffer(bufferSize);
}
@Override

View File

@ -5,10 +5,12 @@ import java.io.IOException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.util.log.Log;
public class WebSocketConnection implements Connection, WebSocket.Outbound
{
final Connector _connector;
final EndPoint _endp;
final WebSocketParser _parser;
final WebSocketGenerator _generator;
@ -16,8 +18,9 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
final WebSocket _websocket;
final int _maxIdleTimeMs=30000;
public WebSocketConnection(WebSocketBuffers buffers, EndPoint endpoint, long timestamp, WebSocket websocket)
public WebSocketConnection(Connector connector, WebSocketBuffers buffers, EndPoint endpoint, long timestamp, WebSocket websocket)
{
_connector=connector;
_endp = endpoint;
_timestamp = timestamp;
_websocket = websocket;
@ -72,21 +75,28 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
int filled=_parser.parseNext();
more = flushed>0 || filled>0 || !_parser.isBufferEmpty() || !_generator.isBufferEmpty();
if (filled<0 || flushed<0)
_endp.close();
// System.err.println("flushed="+flushed+" filled="+filled+" more="+more+" endp="+_endp.isOpen());
// System.err.println("flushed="+flushed+" filled="+filled+" more="+more+" p.e="+_parser.isBufferEmpty()+" g.e="+_generator.isBufferEmpty());
if (filled<0 || flushed<0)
{
_endp.close();
break;
}
}
}
catch(IOException e)
{
System.err.println(e);
e.printStackTrace();
throw e;
}
finally
{
// TODO - not really the best way
if (!_endp.isOpen())
if (_endp.isOpen())
_connector.persist(_endp);
else
// TODO - not really the best way
_websocket.onDisconnect();
}
}
@ -115,24 +125,34 @@ public class WebSocketConnection implements Connection, WebSocket.Outbound
{
_generator.addFrame(frame,content,_maxIdleTimeMs);
_generator.flush();
_connector.persist(_endp);
}
public void sendMessage(byte frame, byte[] content) throws IOException
{
_generator.addFrame(frame,content,_maxIdleTimeMs);
_generator.flush();
_connector.persist(_endp);
}
public void sendMessage(byte frame, byte[] content, int offset, int length) throws IOException
{
_generator.addFrame(frame,content,offset,length,_maxIdleTimeMs);
_generator.flush();
_connector.persist(_endp);
}
public void disconnect() throws IOException
public void disconnect()
{
_generator.flush(_maxIdleTimeMs);
_endp.close();
try
{
_generator.flush(_maxIdleTimeMs);
_endp.close();
}
catch(IOException e)
{
Log.ignore(e);
}
}
public void fill(Buffer buffer)

View File

@ -73,12 +73,17 @@ public abstract class WebSocketHandler extends HandlerWrapper
{
HttpConnection http = HttpConnection.getCurrentConnection();
ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
WebSocketConnection connection = new WebSocketConnection(_buffers,endp,http.getTimeStamp(),websocket);
WebSocketConnection connection = new WebSocketConnection(http.getConnector(),_buffers,endp,http.getTimeStamp(),websocket);
String uri=request.getRequestURI();
String host=request.getHeader("Host");
String origin=request.getHeader("Origin");
origin=checkOrigin(request,host,origin);
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("WebSocket-Origin",request.getScheme()+"://"+request.getServerName());
response.addHeader("WebSocket-Location","ws://"+request.getHeader("Host")+request.getRequestURI());
response.addHeader("WebSocket-Origin",origin);
response.addHeader("WebSocket-Location","ws://"+host+uri);
if (protocol!=null)
response.addHeader("WebSocket-Protocol",protocol);
response.sendError(101,"Web Socket Protocol Handshake");
@ -101,6 +106,13 @@ public abstract class WebSocketHandler extends HandlerWrapper
}
}
protected String checkOrigin(HttpServletRequest request, String host, String origin)
{
if (origin==null)
origin=host;
return origin;
}
abstract protected WebSocket doWebSocketConnect(HttpServletRequest request,String protocol);
}

View File

@ -73,6 +73,7 @@ public class WebSocketParser
int total_filled=0;
// Loop until an datagram call back or can't fill anymore
boolean progress=true;
while(true)
{
int length=_buffer.length();
@ -92,7 +93,7 @@ public class WebSocketParser
{
int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
if (filled<=0)
return total_filled>0?total_filled:-1;
return total_filled;
total_filled+=filled;
length=_buffer.length();
}

View File

@ -15,19 +15,19 @@ import org.eclipse.jetty.server.HttpConnection;
/* ------------------------------------------------------------ */
/**
* Servlet to ugrade connections to WebSocket
* Servlet to upgrade connections to WebSocket
* <p>
* The request must have the correct upgrade headers, else it is
* handled as a normal servlet request.
* <p>
* The initParameter "bufferSize" can be used to set the buffer size,
* which is also the max frame byte size (default 8192).
*
*/
public abstract class WebSocketServlet extends HttpServlet
{
WebSocketBuffers _buffers;
/* ------------------------------------------------------------ */
/**
* @see javax.servlet.GenericServlet#init()
@ -56,12 +56,17 @@ public abstract class WebSocketServlet extends HttpServlet
{
HttpConnection http = HttpConnection.getCurrentConnection();
ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
WebSocketConnection connection = new WebSocketConnection(_buffers,endp,http.getTimeStamp(),websocket);
WebSocketConnection connection = new WebSocketConnection(http.getConnector(),_buffers,endp,http.getTimeStamp(),websocket);
String uri=request.getRequestURI();
String host=request.getHeader("Host");
String origin=request.getHeader("Origin");
origin=checkOrigin(request,host,origin);
response.setHeader("Upgrade","WebSocket");
response.addHeader("Connection","Upgrade");
response.addHeader("WebSocket-Origin",request.getScheme()+"://"+request.getServerName());
response.addHeader("WebSocket-Location","ws://"+request.getHeader("Host")+request.getRequestURI());
response.addHeader("WebSocket-Origin",origin);
response.addHeader("WebSocket-Location","ws://"+host+uri);
if (protocol!=null)
response.addHeader("WebSocket-Protocol",protocol);
response.sendError(101,"Web Socket Protocol Handshake");
@ -81,6 +86,13 @@ public abstract class WebSocketServlet extends HttpServlet
else
super.service(request,response);
}
protected String checkOrigin(HttpServletRequest request, String host, String origin)
{
if (origin==null)
origin=host;
return origin;
}
abstract protected WebSocket doWebSocketConnect(HttpServletRequest request,String protocol);

View File

@ -1,110 +0,0 @@
package org.eclipse.jetty.websocket;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocketTest.TestWebSocket;
public class SimpleWebSocketServer extends Server
{
TestWebSocket _websocket;
SelectChannelConnector _connector;
WebSocketHandler _handler;
public SimpleWebSocketServer()
{
_connector = new SelectChannelConnector();
_connector.setPort(8080);
addConnector(_connector);
_handler= new WebSocketHandler()
{
@Override
protected WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
{
_websocket = new TestWebSocket();
return _websocket;
}
};
ResourceHandler rh=new ResourceHandler();
_handler.setHandler(rh);
rh.setDirectoriesListed(true);
rh.setResourceBase("./src/test/resources");
setHandler(_handler);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
class TestWebSocket implements WebSocket
{
Outbound _outbound;
public void onConnect(Outbound outbound)
{
System.err.println("onConnect");
_outbound=outbound;
new Thread()
{
public void run()
{
for (int i=0;_outbound.isOpen()&& i<10;i++)
{
try
{
Thread.sleep(1000);
System.err.println("send "+i);
_outbound.sendMessage(SENTINEL_FRAME,"Roger That "+i);
}
catch (Exception e)
{
Log.warn(e);
}
}
}
}.start();
}
public void onMessage(byte frame, byte[] data,int offset, int length)
{
System.err.println("onMessage: "+TypeUtil.toHexString(data,offset,length));
}
public void onMessage(byte frame, String data)
{
System.err.println("onMessage: "+data);
}
public void onDisconnect()
{
System.err.println("onDisconnect");
}
}
public static void main(String[] args)
{
try
{
SimpleWebSocketServer server = new SimpleWebSocketServer();
server.start();
server.join();
}
catch(Exception e)
{
Log.warn(e);
}
}
}

View File

@ -1,17 +0,0 @@
<h1>WebSocket Test</h1>
<script lang="javascript">
alert("testing");
var ws = new WebSocket("ws://localhost:8080/");
ws.onopen = function(evt)
{
alert("Conn opened");
ws.send("Hello World");
}
ws.onmessage = function(evt) { alert("onmessage: " + evt.data); }
ws.onclose = function(evt) { alert("Conn closed"); }
</script>

View File

@ -117,6 +117,12 @@
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>

View File

@ -28,7 +28,6 @@ import org.eclipse.jetty.continuation.Continuation;
import org.eclipse.jetty.continuation.ContinuationSupport;
// Simple asynchronous Chat room.
// This does not handle duplicate usernames or multiple frames/tabs from the same browser
// Some code is duplicated for clarity.
@ -90,7 +89,7 @@ public class ChatServlet extends HttpServlet
return;
}
Member member = room.get(username);
if (room==null)
if (member==null)
{
response.sendError(503);
return;
@ -147,19 +146,22 @@ public class ChatServlet extends HttpServlet
throws IOException
{
Map<String,Member> room=_rooms.get(request.getPathInfo());
// Post chat to all members
for (Member m:room.values())
if (room!=null)
{
synchronized (m)
// Post chat to all members
for (Member m:room.values())
{
m._queue.add(username); // from
m._queue.add(message); // chat
// wakeup member if polling
if (m._continuation!=null)
synchronized (m)
{
m._continuation.resume();
m._continuation=null;
m._queue.add(username); // from
m._queue.add(message); // chat
// wakeup member if polling
if (m._continuation!=null)
{
m._continuation.resume();
m._continuation=null;
}
}
}
}
@ -168,111 +170,16 @@ public class ChatServlet extends HttpServlet
PrintWriter out=response.getWriter();
out.print("{action:\"chat\"}");
}
// Serve the HTML with embedded CSS and Javascript.
// This should be static content and should use real JS libraries.
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
if (!request.getRequestURI().endsWith("/"))
{
response.sendRedirect(request.getRequestURI()+"/");
return;
}
if (request.getParameter("action")!=null)
{
doPost(request,response);
return;
}
response.setContentType("text/html");
PrintWriter out=response.getWriter();
out.println("<html><head>");
out.println(" <title>async chat</title>");
out.println(" <script type='text/javascript'>");
out.println(" function $() { return document.getElementById(arguments[0]); }");
out.println(" function $F() { return document.getElementById(arguments[0]).value; }");
out.println(" function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; } ");
out.println(" function xhr(method,uri,body,handler) {");
out.println(" var req=(window.XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');");
out.println(" req.onreadystatechange=function() { if (req.readyState==4 && handler) { eval('var o='+req.responseText);handler(o);} }");
out.println(" req.open(method,uri,true);");
out.println(" req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');");
out.println(" req.send(body);");
out.println(" };");
out.println(" function send(action,user,message,handler){");
out.println(" if (message) message=message.replace('%','%25').replace('&','%26').replace('=','%3D');");
out.println(" if (user) user=user.replace('%','%25').replace('&','%26').replace('=','%3D');");
out.println(" xhr('POST','chat','action='+action+'&user='+user+'&message='+message,handler);");
out.println(" };");
out.println(" ");
out.println(" var room = {");
out.println(" join: function(name) {");
out.println(" this._username=name;");
out.println(" $('join').className='hidden';");
out.println(" $('joined').className='';");
out.println(" $('phrase').focus();");
out.println(" send('join', room._username,null);");
out.println(" send('chat', room._username,'has joined!');");
out.println(" send('poll', room._username,null, room._poll);");
out.println(" },");
out.println(" chat: function(text) {");
out.println(" if (text != null && text.length>0 )");
out.println(" send('chat',room._username,text);");
out.println(" },");
out.println(" _poll: function(m) {");
out.println(" //console.debug(m);");
out.println(" if (m.chat){");
out.println(" var chat=document.getElementById('chat');");
out.println(" var spanFrom = document.createElement('span');");
out.println(" spanFrom.className='from';");
out.println(" spanFrom.innerHTML=m.from+':&nbsp;';");
out.println(" var spanText = document.createElement('span');");
out.println(" spanText.className='text';");
out.println(" spanText.innerHTML=m.chat;");
out.println(" var lineBreak = document.createElement('br');");
out.println(" chat.appendChild(spanFrom);");
out.println(" chat.appendChild(spanText);");
out.println(" chat.appendChild(lineBreak);");
out.println(" chat.scrollTop = chat.scrollHeight - chat.clientHeight; ");
out.println(" }");
out.println(" if (m.action=='poll')");
out.println(" send('poll', room._username,null, room._poll);");
out.println(" },");
out.println(" _end:''");
out.println(" };");
out.println(" </script>");
out.println(" <style type='text/css'>");
out.println(" div { border: 0px solid black; }");
out.println(" div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }");
out.println(" div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }");
out.println(" input#phrase { width:30em; background-color: #e0f0f0; }");
out.println(" input#username { width:14em; background-color: #e0f0f0; }");
out.println(" div.hidden { display: none; }");
out.println(" span.from { font-weight: bold; }");
out.println(" span.alert { font-style: italic; }");
out.println(" </style>");
out.println("</head><body>");
out.println("<div id='chat'></div>");
out.println("<div id='input'>");
out.println(" <div id='join' >");
out.println(" Username:&nbsp;<input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>");
out.println(" </div>");
out.println(" <div id='joined' class='hidden'>");
out.println(" Chat:&nbsp;<input id='phrase' type='text'></input>");
out.println(" <input id='sendB' class='button' type='submit' name='join' value='Send'/>");
out.println(" </div>");
out.println("</div>");
out.println("<script type='text/javascript'>");
out.println("$('username').setAttribute('autocomplete','OFF');");
out.println("$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ; ");
out.println("$('joinB').onclick = function(event) { room.join($F('username')); return false; };");
out.println("$('phrase').setAttribute('autocomplete','OFF');");
out.println("$('phrase').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };");
out.println("$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };");
out.println("</script>");
out.println("</body></html>");
else
getServletContext().getNamedDispatcher("default").forward(request,response);
}
}

View File

@ -0,0 +1,73 @@
package com.acme;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
public class WebSocketChatServlet extends WebSocketServlet
{
private final Set<ChatWebSocket> _members = new CopyOnWriteArraySet<ChatWebSocket>();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws javax.servlet.ServletException ,IOException
{
getServletContext().getNamedDispatcher("default").forward(request,response);
};
@Override
protected WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
{
return new ChatWebSocket();
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
class ChatWebSocket implements WebSocket
{
Outbound _outbound;
public void onConnect(Outbound outbound)
{
// Log.info(this+" onConnect");
_outbound=outbound;
_members.add(this);
}
public void onMessage(byte frame, byte[] data,int offset, int length)
{
// Log.info(this+" onMessage: "+TypeUtil.toHexString(data,offset,length));
}
public void onMessage(byte frame, String data)
{
// Log.info(this+" onMessage: "+data);
for (ChatWebSocket member : _members)
{
try
{
member._outbound.sendMessage(frame,data);
}
catch(IOException e)
{
Log.warn(e);
}
}
}
public void onDisconnect()
{
// Log.info(this+" onDisconnect");
_members.remove(this);
}
}
}

View File

@ -176,6 +176,17 @@
<url-pattern>/chat/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>WSChat</servlet-name>
<servlet-class>com.acme.WebSocketChatServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>WSChat</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Rewrite</servlet-name>

View File

@ -0,0 +1,85 @@
<html><head>
<title>Async Chat</title>
<script type='text/javascript'>
function $() { return document.getElementById(arguments[0]); }
function $F() { return document.getElementById(arguments[0]).value; }
function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; }
function xhr(method,uri,body,handler) {
var req=(window.XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP');
req.onreadystatechange=function() { if (req.readyState==4 && handler) { eval('var o='+req.responseText);handler(o);} }
req.open(method,uri,true);
req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
req.send(body);
};
function send(action,user,message,handler){
if (message) message=message.replace('%','%25').replace('&','%26').replace('=','%3D');
if (user) user=user.replace('%','%25').replace('&','%26').replace('=','%3D');
xhr('POST','chat','action='+action+'&user='+user+'&message='+message,handler);
};
var room = {
join: function(name) {
this._username=name;
$('join').className='hidden';
$('joined').className='';
$('phrase').focus();
send('join', room._username,null);
send('chat', room._username,'has joined!');
send('poll', room._username,null, room._poll);
},
chat: function(text) {
if (text != null && text.length>0 )
send('chat',room._username,text);
},
_poll: function(m) {
//console.debug(m);
if (m.chat){
var chat=document.getElementById('chat');
var spanFrom = document.createElement('span');
spanFrom.className='from';
spanFrom.innerHTML=m.from+':&nbsp;';
var spanText = document.createElement('span');
spanText.className='text';
spanText.innerHTML=m.chat;
var lineBreak = document.createElement('br');
chat.appendChild(spanFrom);
chat.appendChild(spanText);
chat.appendChild(lineBreak);
chat.scrollTop = chat.scrollHeight - chat.clientHeight;
}
if (m.action=='poll')
send('poll', room._username,null, room._poll);
},
_end:''
};
</script>
<style type='text/css'>
div { border: 0px solid black; }
div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }
div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }
input#phrase { width:30em; background-color: #e0f0f0; }
input#username { width:14em; background-color: #e0f0f0; }
div.hidden { display: none; }
span.from { font-weight: bold; }
span.alert { font-style: italic; }
</style>
</head><body>
<div id='chat'></div>
<div id='input'>
<div id='join' >
Username:&nbsp;<input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>
</div>
<div id='joined' class='hidden'>
Chat:&nbsp;<input id='phrase' type='text'/>
<input id='sendB' class='button' type='submit' name='join' value='Send'/>
</div>
</div>
<script type='text/javascript'>
$('username').setAttribute('autocomplete','OFF');
$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ;
$('joinB').onclick = function(event) { room.join($F('username')); return false; };
$('phrase').setAttribute('autocomplete','OFF');
$('phrase').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };
$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };
</script>
</body></html>

View File

@ -18,21 +18,19 @@ Commercial support for Jetty is available via <a href="http://www.webtide.com">w
This is a test context that serves:
</p>
<ul>
<li>static content (
<li>static content:
<a href="d.txt">tiny</a>,
<a href="da.txt">small</a>,
<a href="dat.txt">medium</a>,
<a href="data.txt">large</a>,
<a href="data.txt.gz">large gziped</a>)</li>
<li>a <a href="hello/">Hello World Servlet</a></li>
<li>a <a href="dump/info">Request Dump Servlet</a></li>
<li>a <a href="session/">Session Dump Servlet</a></li>
<li>a <a href="cookie/">Cookie Dump Servlet</a></li>
<li>a <a href="dispatch">Dispatcher Servlet</a></li>
<li>a <a href="rewrite/">Request Rewrite Servlet</a></li>
<li>a <a href="google/">Transparent Proxy</a> (to www.google.com)</li>
<li>a <a href="chat/">Comet Chat</a></li>
<li>a <a href="auth.html">Authentication</a></li>
<a href="data.txt.gz">large gziped</a></li>
<li><a href="hello/">Hello World Servlet</a></li>
<li>Dump: <a href="dump/info">Request</a>, <a href="session/">Session</a>, <a href="cookie/">Cookie</a></li>
<li><a href="dispatch">Dispatcher Servlet</a></li>
<li><a href="rewrite/">Request Rewrite Servlet</a></li>
<li><a href="google/">Transparent Proxy</a> (to www.google.com)</li>
<li>Comet Chat: <a href="chat/">Long Polling</a>, <a href="ws">WebSocket</a></li>
<li><a href="auth.html">Authentication</a></li>
</ul>
<p/>

View File

@ -0,0 +1,110 @@
<html><head>
<title>WebSocket Chat</title>
<script type='text/javascript'>
if (!window.WebSocket)
alert("WebSocket not supported by this browser");
function $() { return document.getElementById(arguments[0]); }
function $F() { return document.getElementById(arguments[0]).value; }
function getKeyCode(ev) { if (window.event) return window.event.keyCode; return ev.keyCode; }
var room = {
join: function(name) {
this._username=name;
var location = document.location.toString().replace('http:','ws:');
this._ws=new WebSocket(location);
this._ws.onopen=this._onopen;
this._ws.onmessage=this._onmessage;
this._ws.onclose=this._onclose;
},
_onopen: function(){
$('join').className='hidden';
$('joined').className='';
$('phrase').focus();
room._send(room._username,'has joined!');
},
_send: function(user,message){
user=user.replace(':','_');
if (this._ws)
this._ws.send(user+':'+message);
},
chat: function(text) {
if (text != null && text.length>0 )
room._send(room._username,text);
},
_onmessage: function(m) {
if (m.data){
var c=m.data.indexOf(':');
var from=m.data.substring(0,c);
var text=m.data.substring(c+1);
var chat=$('chat');
var spanFrom = document.createElement('span');
spanFrom.className='from';
spanFrom.innerHTML=from+':&nbsp;';
var spanText = document.createElement('span');
spanText.className='text';
spanText.innerHTML=text;
var lineBreak = document.createElement('br');
chat.appendChild(spanFrom);
chat.appendChild(spanText);
chat.appendChild(lineBreak);
chat.scrollTop = chat.scrollHeight - chat.clientHeight;
}
},
_onclose: function(m) {
this._ws=null;
$('join').className='';
$('joined').className='hidden';
$('username').focus();
$('chat').innerHTML='';
}
};
</script>
<style type='text/css'>
div { border: 0px solid black; }
div#chat { clear: both; width: 40em; height: 20ex; overflow: auto; background-color: #f0f0f0; padding: 4px; border: 1px solid black; }
div#input { clear: both; width: 40em; padding: 4px; background-color: #e0e0e0; border: 1px solid black; border-top: 0px }
input#phrase { width:30em; background-color: #e0f0f0; }
input#username { width:14em; background-color: #e0f0f0; }
div.hidden { display: none; }
span.from { font-weight: bold; }
span.alert { font-style: italic; }
</style>
</head><body>
<div id='chat'></div>
<div id='input'>
<div id='join' >
Username:&nbsp;<input id='username' type='text'/><input id='joinB' class='button' type='submit' name='join' value='Join'/>
</div>
<div id='joined' class='hidden'>
Chat:&nbsp;<input id='phrase' type='text'/>
<input id='sendB' class='button' type='submit' name='join' value='Send'/>
</div>
</div>
<script type='text/javascript'>
$('username').setAttribute('autocomplete','OFF');
$('username').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.join($F('username')); return false; } return true; } ;
$('joinB').onclick = function(event) { room.join($F('username')); return false; };
$('phrase').setAttribute('autocomplete','OFF');
$('phrase').onkeyup = function(ev) { var keyc=getKeyCode(ev); if (keyc==13 || keyc==10) { room.chat($F('phrase')); $('phrase').value=''; return false; } return true; };
$('sendB').onclick = function(event) { room.chat($F('phrase')); $('phrase').value=''; return false; };
</script>
<p>
This is a demonstration of the Jetty websocket server.
</p>
</body></html>

View File

@ -1,61 +0,0 @@
package org.eclipse.jetty;
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import org.eclipse.jetty.http.security.Password;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.webapp.WebAppContext;
public class DemoServer
{
public static void main(String[] args)
throws Exception
{
if (args.length!=1)
{
System.err.println("Usage - java "+DemoServer.class+" webappdir|war");
System.exit(1);
}
Server server = new Server();
// setup JMX
MBeanServer mbeanS = ManagementFactory.getPlatformMBeanServer();
MBeanContainer mbeanC = new MBeanContainer(mbeanS);
server.getContainer().addEventListener(mbeanC);
server.addBean(mbeanC);
// setup connector
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(8080);
server.addConnector(connector);
// setup Login service
HashLoginService login = new HashLoginService();
login.putUser("jetty",new Password("password"),new String[]{"user"});
login.putUser("admin",new Password("password"),new String[]{"user","admin"});
server.addBean(login);
// ContextHandlerCollection
ContextHandlerCollection contexts = new ContextHandlerCollection();
server.setHandler(contexts);
// setup webapp
WebAppContext context = new WebAppContext();
context.setWar(args[0]);
context.setDefaultsDescriptor("../jetty-webapp/src/main/config/etc/webdefault.xml");
contexts.addHandler(context);
// start the server
server.start();
System.err.println(server.dump());
server.join();
}
}

View File

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package com.acme;
package org.eclipse.jetty;
import junit.framework.TestCase;
@ -19,6 +19,8 @@ import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.testing.ServletTester;
import com.acme.DispatchServlet;
/**
* Simple tests against DispatchServlet.
*/

View File

@ -1,32 +0,0 @@
package org.eclipse.jetty;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DebugHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.IncludableGzipFilter;
import com.acme.Dump;
public class DumpServer
{
public static void main(String[] args)
throws Exception
{
Server server = new Server(8080);
DebugHandler debug = new DebugHandler();
debug.setOutputStream(System.err);
server.setHandler(debug);
ServletContextHandler context = new ServletContextHandler(debug,"/",ServletContextHandler.SESSIONS);
FilterHolder gzip=context.addFilter(IncludableGzipFilter.class,"/*",0);
gzip.setInitParameter("uncheckedPrintWriter","true");
context.addServlet(new ServletHolder(new Dump()), "/*");
server.start();
server.join();
}
}

View File

@ -0,0 +1,106 @@
// ========================================================================
// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty;
import java.lang.management.ManagementFactory;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
public class TestServer
{
public static void main(String[] args) throws Exception
{
String jetty_home = System.getProperty("jetty.home","../jetty-distribution/target/distribution");
System.setProperty("jetty.home",jetty_home);
Server server = new Server();
// Setup JMX
MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.getContainer().addEventListener(mbContainer);
server.addBean(mbContainer);
mbContainer.addBean(Log.getLog());
// Setup Threadpool
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setMaxThreads(100);
server.setThreadPool(threadPool);
// Setup Connectors
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(8080);
connector.setMaxIdleTime(30000);
connector.setConfidentialPort(8443);
server.setConnectors(new Connector[]
{ connector });
SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector();
ssl_connector.setPort(8443);
ssl_connector.setKeystore(jetty_home + "/etc/keystore");
ssl_connector.setPassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
ssl_connector.setKeyPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
ssl_connector.setTruststore(jetty_home + "/etc/keystore");
ssl_connector.setTrustPassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
server.addConnector(ssl_connector);
HandlerCollection handlers = new HandlerCollection();
ContextHandlerCollection contexts = new ContextHandlerCollection();
RequestLogHandler requestLogHandler = new RequestLogHandler();
handlers.setHandlers(new Handler[]
{ contexts, new DefaultHandler(), requestLogHandler });
server.setHandler(handlers);
// Setup deployers
HashLoginService login = new HashLoginService();
login.setName("Test Realm");
login.setConfig(jetty_home + "/etc/realm.properties");
server.addBean(login);
NCSARequestLog requestLog = new NCSARequestLog(jetty_home + "/logs/jetty-yyyy_mm_dd.log");
requestLog.setExtended(false);
requestLogHandler.setRequestLog(requestLog);
server.setStopAtShutdown(true);
server.setSendServerVersion(true);
WebAppContext webapp = new WebAppContext();
webapp.setParentLoaderPriority(true);
webapp.setResourceBase("./src/main/webapp");
contexts.addHandler(webapp);
server.start();
server.join();
}
}