Merge remote-tracking branch 'origin/master' into servlet-3.1-api

Conflicts:
	jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
This commit is contained in:
Jan Bartel 2013-05-02 09:07:24 +10:00
commit e7ecfd2b2b
113 changed files with 3102 additions and 1197 deletions

View File

@ -100,7 +100,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/webapps</outputDirectory>
<outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
<destFileName>test.war</destFileName>
</artifactItem>
<artifactItem>
@ -110,7 +110,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/webapps</outputDirectory>
<outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
<destFileName>xref-proxy.war</destFileName>
</artifactItem>
<artifactItem>
@ -120,7 +120,7 @@
<type>war</type>
<overWrite>true</overWrite>
<includes>**</includes>
<outputDirectory>${assembly-directory}/webapps</outputDirectory>
<outputDirectory>${assembly-directory}/webapps.demo</outputDirectory>
<destFileName>async-rest.war</destFileName>
</artifactItem>
<artifactItem>

View File

@ -0,0 +1,21 @@
#===========================================================
# Configure JVM arguments.
#-----------------------------------------------------------
--exec
-Xmx2000m
-Xmn512m
-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads=2
-XX:+CMSClassUnloadingEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:CMSInitiatingOccupancyFraction=80
# -verbose:gc
# -XX:+PrintGCDateStamps
# -XX:+PrintGCTimeStamps
# -XX:+PrintGCDetails
# -XX:+PrintTenuringDistribution
# -XX:+PrintCommandLineFlags
# -XX:+DisableExplicitGC
# -Dorg.apache.jasper.compiler.disablejsr199=true

View File

@ -0,0 +1,16 @@
#===========================================================
# Default Server Options
# Use the core server jars with websocket on the classpath
# Add the contents of the resources directory to the classpath
# Add jars discovered in lib/ext to the classpath
# Include the core jetty configuration file
#-----------------------------------------------------------
OPTIONS=Server,websocket,resources,ext
threads.min=10
threads.max=200
threads.timeout=60000
#jetty.host=myhost.com
jetty.dump.start=false
jetty.dump.stop=false
etc/jetty.xml

View File

@ -0,0 +1,10 @@
#===========================================================
# JMX Management
# To enable remote JMX access uncomment jmxremote and
# enable --exec
#-----------------------------------------------------------
OPTIONS=jmx
# jetty.jmxrmihost=localhost
# jetty.jmxrmiport=1099
# -Dcom.sun.management.jmxremote
etc/jetty-jmx.xml

View File

@ -0,0 +1,5 @@
#===========================================================
# Java Server Pages
#-----------------------------------------------------------
OPTIONS=jsp

View File

@ -0,0 +1,7 @@
#===========================================================
# Server logging.
# The following configuration will redirect stderr and stdout
# to file which is rolled over daily.
#-----------------------------------------------------------
jetty.log.retain=90
etc/jetty-logging.xml

View File

@ -0,0 +1,13 @@
#===========================================================
# Enable SetUID
# The default user and group is 'jetty' and if you are
# starting as root you must change the run privledged to true
#-----------------------------------------------------------
OPTIONS=setuid
jetty.startServerAsPrivileged=false
jetty.username=jetty
jetty.groupname=jetty
jetty.umask=002
etc/jetty-setuid.xml

View File

@ -0,0 +1,12 @@
#===========================================================
# NPN Next Protocol Negotiation
#
# The SPDY and HTTP/2.0 connectors require NPN. The jar for
# NPN cannot be downloaded from eclipse. So the --download
# option is used to install the NPN jar if it does not already
# exist
#
#-----------------------------------------------------------
--exec
--download=http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar:lib/npn/npn-boot-1.1.5.v20130313.jar
-Xbootclasspath/p:lib/npn/npn-boot-1.1.5.v20130313.jar

View File

@ -0,0 +1,12 @@
#===========================================================
# SSL Context
# For use by HTTPS and SPDY
#-----------------------------------------------------------
jetty.keystore=etc/keystore
jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
jetty.truststore=etc/keystore
jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
jetty.secure.port=8443
etc/jetty-ssl.xml

View File

@ -0,0 +1,6 @@
#===========================================================
# HTTP Connector
#-----------------------------------------------------------
jetty.port=8080
http.timeout=30000
etc/jetty-http.xml

View File

@ -0,0 +1,6 @@
#===========================================================
# HTTPS Connector
# Must be used with 200-ssl.ini
#-----------------------------------------------------------
jetty.https.port=8443
etc/jetty-https.xml

View File

@ -0,0 +1,9 @@
#===========================================================
# SPDY Connector
# Must be used with 200-ssl.ini and 200-npn.ini
#-----------------------------------------------------------
OPTIONS=spdy
jetty.spdy.port=8443
etc/jetty-spdy.xml

View File

@ -0,0 +1,8 @@
#===========================================================
# Annotations JNDI JAAS processing
#-----------------------------------------------------------
OPTIONS=plus
etc/jetty-plus.xml
OPTIONS=annotations
etc/jetty-annotations.xml

View File

@ -0,0 +1,9 @@
#===========================================================
# Request logger
# Will add a handler to log all HTTP requests to a standard
# request log format file.
#-----------------------------------------------------------
requestlog.retain=90
requestlog.append=true
requestlog.extended=true
etc/jetty-requestlog.xml

View File

@ -0,0 +1 @@
etc/jetty-debug.xml

View File

@ -0,0 +1 @@
etc/jetty-ipaccess.xml

View File

@ -0,0 +1,11 @@
#===========================================================
#-----------------------------------------------------------
lowresources.period=1050
lowresources.lowResourcesIdleTimeout=200
lowresources.monitorThreads=true
lowresources.maxConnections=0
lowresources.maxMemory=0
lowresources.maxLowResourcesTime=5000
etc/jetty-lowresources.xml

View File

@ -0,0 +1,4 @@
#===========================================================
#-----------------------------------------------------------
etc/jetty-stats.xml

View File

@ -0,0 +1,4 @@
#===========================================================
# Webapplication Deployer
#-----------------------------------------------------------
etc/jetty-deploy.xml

View File

@ -1,5 +1,6 @@
#===========================================================
# Jetty start.jar arguments
#
# The contents of this file, together with the start.ini
# fragments found in start.d directory are used to build
# the classpath and command line on a call to
@ -23,199 +24,8 @@
#
#===========================================================
#===========================================================
# The --exec option should be used if any of the JVM options
# in this file are uncommented (eg -D* or -X*). Because a
# JVM cannot change it's own options, the --exec flag causes
# start.jar to fork a new JVM with the requested arguments.
#
# Alternately, a command line may be generated by running
#
# java -jar start.jar --exec-print
#
# and the results executed to start the jetty server.
# For example --exec can be avoided if jetty is started on unix with
#
# eval $(java -jar start.jar --exec-print)
#
#-----------------------------------------------------------
# --exec
#===========================================================
#===========================================================
# Configure Properties.
# The properties defined here may be used by the
# <Property name="myproperty"/> element in the XML files
# passed to start.jar.
# Alternately a file ending with ".properties" can be
# added that will include multiple properties.
# Properties, unlike SystemProperties, do not need --exec
# to be specified.
#-----------------------------------------------------------
# jetty.home=.
# jetty.logs=./logs
# jetty.host=0.0.0.0
#===========================================================
#===========================================================
# Configure JVM arguments.
# Must be used with --exec or --exec-print
#-----------------------------------------------------------
# -Dorg.apache.jasper.compiler.disablejsr199=true
# -Xmx2000m
# -Xmn512m
# -verbose:gc
# -XX:+PrintGCDateStamps
# -XX:+PrintGCTimeStamps
# -XX:+PrintGCDetails
# -XX:+PrintTenuringDistribution
# -XX:+PrintCommandLineFlags
# -XX:+DisableExplicitGC
# -XX:+UseConcMarkSweepGC
# -XX:ParallelCMSThreads=2
# -XX:+CMSClassUnloadingEnabled
# -XX:+UseCMSCompactAtFullCollection
# -XX:CMSInitiatingOccupancyFraction=80
#===========================================================
#===========================================================
# Default Server Options
# Use the core server jars with websocket on the classpath
# Add the contents of the resources directory to the classpath
# Add jars discovered in lib/ext to the classpath
# Include the core jetty configuration file
#-----------------------------------------------------------
OPTIONS=Server,websocket,resources,ext
etc/jetty.xml
#===========================================================
#===========================================================
# Enable SetUID
# The default user and group is 'jetty' and if you are
# starting as root you must change the run privledged to true
#-----------------------------------------------------------
# OPTIONS=setuid
# etc/jetty-setuid.xml
# jetty.startServerAsPrivileged=false
# jetty.username=jetty
# jetty.groupname=jetty
# jetty.umask=002
#===========================================================
#===========================================================
# Server logging.
# The following configuration will redirect stderr and stdout
# to file which is rolled over daily.
#-----------------------------------------------------------
# etc/jetty-logging.xml
#===========================================================
#===========================================================
# JMX Management
# To enable remote JMX access uncomment jmxremote and
# enable --exec or use --exec-print (see above)
#-----------------------------------------------------------
OPTIONS=jmx
# jetty.jmxrmihost=localhost
# jetty.jmxrmiport=1099
# -Dcom.sun.management.jmxremote
etc/jetty-jmx.xml
#===========================================================
#===========================================================
# Java Server Pages
#-----------------------------------------------------------
OPTIONS=jsp
#===========================================================
#===========================================================
# Annotations JNDI JAAS processing
#-----------------------------------------------------------
# OPTIONS=plus
# etc/jetty-plus.xml
# OPTIONS=annotations
# etc/jetty-annotations.xml
#===========================================================
#===========================================================
# HTTP Connector
#-----------------------------------------------------------
# jetty.port=8080
etc/jetty-http.xml
#===========================================================
#===========================================================
# SSL Context
# For use by HTTPS and SPDY
#-----------------------------------------------------------
# jetty.keystore=etc/keystore
# jetty.keystore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
# jetty.keymanager.password=OBF:1u2u1wml1z7s1z7a1wnl1u2g
# jetty.truststore=etc/keystore
# jetty.truststore.password=OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4
# jetty.secure.port=8443
# etc/jetty-ssl.xml
#===========================================================
#===========================================================
# HTTPS Connector
#-----------------------------------------------------------
# jetty.https.port=8443
# etc/jetty-https.xml
#===========================================================
#===========================================================
# SPDY Connector
#
# SPDY requires the NPN jar which must be separately downloaded:
#
# http://repo1.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar
#
# Which should be saved in lib/npn-boot-1.1.5.v20130313.jar
#
# To include the NPN jar on the boot path, you must either:
#
# a) enable --exec above and uncomment the -Xbootclass line
# below
#
# b) Add -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar
# to the command line when running jetty.
#
#-----------------------------------------------------------
# OPTIONS=spdy
# -Xbootclasspath/p:lib/npn-boot-1.1.5.v20130313.jar
# jetty.spdy.port=8443
# etc/jetty-spdy.xml
#===========================================================
#===========================================================
# Webapplication Deployer
#-----------------------------------------------------------
etc/jetty-deploy.xml
#===========================================================
#===========================================================
# Request logger
# Will add a handler to log all HTTP requests to a standard
# request log format file.
#-----------------------------------------------------------
etc/jetty-requestlog.xml
#===========================================================
#===========================================================
# Additional configurations
# See headers of individual files for explanations
#-----------------------------------------------------------
# etc/jetty-stats.xml
# etc/jetty-debug.xml
# etc/jetty-ipaccess.xml
# etc/jetty-lowresources.xml
#===========================================================
#===========================================================
# Lookup additional ini files in start.d
#-----------------------------------------------------------
# The start.d directory contains the active start.ini fragments
start.d/
#===========================================================
# More start.ini fragments are in files named start.d/*.ini.disabled
# They may be enabled by moving, linking or copying to *.ini files.

View File

@ -0,0 +1,7 @@
This directory is scanned by the demo WebAppDeployer provider
created in the etc/jetty-demo.xml file and enabled by the
start.d/900-demo.ini file.
For normal deployment, use the webapps directory.

View File

@ -33,7 +33,7 @@ import org.eclipse.jetty.util.Trie;
*/
public class HttpField
{
public final static Trie<HttpField> CACHE = new ArrayTrie<>(1024);
public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
public final static Trie<HttpField> CONTENT_TYPE = new ArrayTrie<>(512);
static
@ -77,10 +77,7 @@ public class HttpField
}
// Add headers with null values so HttpParser can avoid looking up name again for unknown values
Set<HttpHeader> headers = new HashSet<>();
for (String key:CACHE.keySet())
headers.add(CACHE.get(key).getHeader());
for (HttpHeader h:headers)
for (HttpHeader h:HttpHeader.values())
if (!CACHE.put(new HttpField(h,(String)null)))
throw new IllegalStateException("CACHE FULL");
// Add some more common headers

View File

@ -110,7 +110,10 @@ public enum HttpMethod
{
if (buffer.hasArray())
return lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position(),buffer.arrayOffset()+buffer.limit());
return CACHE.getBest(buffer,0,buffer.remaining());
// TODO use cache and check for space
// return CACHE.getBest(buffer,0,buffer.remaining());
return null;
}
/* ------------------------------------------------------------ */

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.http;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
@ -26,6 +25,7 @@ import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -80,13 +80,13 @@ public class HttpParser
private String _methodString;
private HttpVersion _version;
private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
private byte _eol;
private EndOfContent _endOfContent;
private long _contentLength;
private long _contentPosition;
private int _chunkLength;
private int _chunkPosition;
private boolean _headResponse;
private boolean _cr;
private ByteBuffer _contentChunk;
private Trie<HttpField> _connectionFields;
@ -204,11 +204,70 @@ public class HttpParser
return _state == state;
}
/* ------------------------------------------------------------------------------- */
private byte next(ByteBuffer buffer)
{
byte ch=buffer.get();
// If not a special character
if (ch>=HttpTokens.SPACE || ch<0)
{
if (_cr)
{
badMessage(buffer,400,"Bad EOL");
return -1;
}
return ch;
}
// Only a LF acceptable after CR
if (_cr)
{
_cr=false;
if (ch==HttpTokens.LINE_FEED)
return ch;
badMessage(buffer,400,"Bad EOL");
return -1;
}
// If it is a CR
if (ch==HttpTokens.CARRIAGE_RETURN)
{
// Skip CR and look for a LF
if (buffer.hasRemaining())
{
if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
_headerBytes++;
ch=buffer.get();
if (ch==HttpTokens.LINE_FEED)
return ch;
badMessage(buffer,HttpStatus.BAD_REQUEST_400,null);
return -1;
}
// Defer lookup of LF
_cr=true;
return 0;
}
// Only LF or TAB acceptable special characters
if (ch!=HttpTokens.LINE_FEED && ch!=HttpTokens.TAB)
{
badMessage(buffer,HttpStatus.BAD_REQUEST_400,null);
return -1;
}
return ch;
}
/* ------------------------------------------------------------------------------- */
/* Quick lookahead for the start state looking for a request method or a HTTP version,
* otherwise skip white space until something else to parse.
*/
private void quickStart(ByteBuffer buffer)
private boolean quickStart(ByteBuffer buffer)
{
// Quick start look
while (_state==State.START && buffer.hasRemaining())
@ -221,7 +280,7 @@ public class HttpParser
_methodString = _method.asString();
buffer.position(buffer.position()+_methodString.length()+1);
setState(State.SPACE1);
return;
return false;
}
}
else if (_responseHandler!=null)
@ -231,27 +290,23 @@ public class HttpParser
{
buffer.position(buffer.position()+_version.asString().length()+1);
setState(State.SPACE1);
return;
return false;
}
}
byte ch=buffer.get();
byte ch=next(buffer);
if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
{
_eol=HttpTokens.LINE_FEED;
continue;
}
_eol=0;
if (ch > HttpTokens.SPACE || ch<0)
if (ch > HttpTokens.SPACE)
{
_string.setLength(0);
_string.append((char)ch);
setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
return;
return false;
}
if (ch==-1)
return true;
}
return false;
}
private String takeString()
@ -281,7 +336,11 @@ public class HttpParser
while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !return_from_parse)
{
// process each character
byte ch=buffer.get();
byte ch=next(buffer);
if (ch==-1)
return true;
if (ch==0)
continue;
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
@ -301,13 +360,6 @@ public class HttpParser
return true;
}
if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
{
_eol=HttpTokens.LINE_FEED;
continue;
}
_eol=0;
switch (_state)
{
case METHOD:
@ -413,7 +465,6 @@ public class HttpParser
else if (ch < HttpTokens.SPACE && ch>=0)
{
return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null);
_eol=ch;
setState(State.HEADER);
}
else
@ -464,28 +515,39 @@ public class HttpParser
{
setState(State.REQUEST_VERSION);
// try quick look ahead
// try quick look ahead for HTTP Version
if (buffer.position()>0 && buffer.hasArray())
{
_version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
if (_version!=null)
HttpVersion version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
if (version!=null)
{
int pos = buffer.position()+version.asString().length()-1;
if (pos<buffer.limit())
{
byte n=buffer.get(pos);
if (n==HttpTokens.CARRIAGE_RETURN)
{
_cr=true;
_version=version;
_string.setLength(0);
buffer.position(buffer.position()+_version.asString().length()-1);
_eol=buffer.get();
setState(State.HEADER);
_uri.flip();
return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri, _version);
buffer.position(pos+1);
}
else if (n==HttpTokens.LINE_FEED)
{
_version=version;
_string.setLength(0);
buffer.position(pos);
}
}
}
}
else if (ch < HttpTokens.SPACE)
}
}
else if (ch == HttpTokens.LINE_FEED)
{
if (_responseHandler!=null)
{
return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, null);
_eol=ch;
setState(State.HEADER);
}
else
@ -502,10 +564,10 @@ public class HttpParser
break;
case REQUEST_VERSION:
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
if (ch == HttpTokens.LINE_FEED)
{
String version = takeString();
_version=HttpVersion.CACHE.get(version);
if (_version==null)
_version=HttpVersion.CACHE.get(takeString());
if (_version==null)
{
badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Unknown Version");
@ -520,7 +582,6 @@ public class HttpParser
_connectionFields=new ArrayTernaryTrie<>(header_cache);
}
_eol=ch;
setState(State.HEADER);
_uri.flip();
return_from_parse|=_requestHandler.startRequest(_method,_methodString,_uri, _version);
@ -532,11 +593,10 @@ public class HttpParser
break;
case REASON:
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
if (ch == HttpTokens.LINE_FEED)
{
String reason=takeLengthString();
_eol=ch;
setState(State.HEADER);
return_from_parse|=_responseHandler.startResponse(_version, _responseStatus, reason);
continue;
@ -609,7 +669,8 @@ public class HttpParser
return true;
}
loop: for (int i = host.length(); i-- > 0;)
int len=host.length();
loop: for (int i = len; i-- > 0;)
{
char c2 = (char)(0xff & host.charAt(i));
switch (c2)
@ -620,6 +681,7 @@ public class HttpParser
case ':':
try
{
len=i;
port = StringUtil.toInt(host.substring(i+1));
}
catch (NumberFormatException e)
@ -628,10 +690,21 @@ public class HttpParser
badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad Host header");
return true;
}
host = host.substring(0,i);
break loop;
}
}
if (host.charAt(0)=='[')
{
if (host.charAt(len-1)!=']')
{
badMessage(buffer,HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
return true;
}
host = host.substring(1,len-1);
}
else if (len!=host.length())
host = host.substring(0,len);
if (_requestHandler!=null)
_requestHandler.parsedHostHeader(host,port);
@ -652,6 +725,9 @@ public class HttpParser
case CACHE_CONTROL:
case USER_AGENT:
add_to_connection_trie=_connectionFields!=null && _field==null;
break;
default: break;
}
if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
@ -676,7 +752,12 @@ public class HttpParser
while (_state.ordinal()<State.END.ordinal() && buffer.hasRemaining() && !return_from_parse)
{
// process each character
byte ch=buffer.get();
byte ch=next(buffer);
if (ch==-1)
return true;
if (ch==0)
continue;
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
LOG.warn("Header is too large >"+_maxHeaderBytes);
@ -684,13 +765,6 @@ public class HttpParser
return true;
}
if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
{
_eol=HttpTokens.LINE_FEED;
continue;
}
_eol=0;
switch (_state)
{
case HEADER:
@ -735,10 +809,8 @@ public class HttpParser
_field=null;
// now handle the ch
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
if (ch == HttpTokens.LINE_FEED)
{
consumeCRLF(ch,buffer);
_contentPosition=0;
// End of headers!
@ -799,43 +871,54 @@ public class HttpParser
if (buffer.remaining()>6)
{
// Try a look ahead for the known header name and value.
_field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
if (_field==null)
_field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining());
HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
if (field==null)
field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining());
if (_field!=null)
if (field!=null)
{
_header=_field.getHeader();
_headerString=_field.getName();
_valueString=_field.getValue();
if (_valueString==null)
String n=field.getName();
String v=field.getValue();
if (v==null)
{
// Header only
int pos=buffer.position()+n.length()+1;
byte b=buffer.get(pos);
_header=field.getHeader();
_headerString=n;
setState(State.HEADER_VALUE);
buffer.position(buffer.position()+_headerString.length()+1);
_string.setLength(0);
_length=0;
_field=null;
buffer.position(pos);
break;
}
else
{
setState(State.HEADER_IN_VALUE);
buffer.position(buffer.position()+_headerString.length()+_valueString.length()+1);
}
break;
}
// Header and value
int pos=buffer.position()+n.length()+v.length()+1;
byte b=buffer.get(pos);
// Try a look ahead for the known header name.
_header=HttpHeader.CACHE.getBest(buffer,-1,buffer.remaining());
//_header=HttpHeader.CACHE.getBest(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.remaining()+1);
if (_header!=null)
if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
{
_headerString=_header.asString();
_string.setLength(0);
setState(State.HEADER_IN_NAME);
buffer.position(buffer.position()+_headerString.length()-1);
_field=field;
_header=_field.getHeader();
_headerString=n;
_valueString=v;
setState(State.HEADER_IN_VALUE);
if (b==HttpTokens.CARRIAGE_RETURN)
{
_cr=true;
buffer.position(pos+1);
}
else
buffer.position(pos);
break;
}
}
}
}
// New header
setState(State.HEADER_NAME);
@ -851,9 +934,7 @@ public class HttpParser
case HEADER_NAME:
switch(ch)
{
case HttpTokens.CARRIAGE_RETURN:
case HttpTokens.LINE_FEED:
consumeCRLF(ch,buffer);
if (_headerString==null)
{
_headerString=takeLengthString();
@ -868,6 +949,8 @@ public class HttpParser
{
_headerString=takeLengthString();
_header=HttpHeader.CACHE.get(_headerString);
if (_header!=null)
System.err.println(_header);
}
setState(State.HEADER_VALUE);
break;
@ -888,9 +971,7 @@ public class HttpParser
case HEADER_IN_NAME:
switch(ch)
{
case HttpTokens.CARRIAGE_RETURN:
case HttpTokens.LINE_FEED:
consumeCRLF(ch,buffer);
_headerString=takeString();
_length=-1;
_header=HttpHeader.CACHE.get(_headerString);
@ -936,9 +1017,7 @@ public class HttpParser
case HEADER_VALUE:
switch(ch)
{
case HttpTokens.CARRIAGE_RETURN:
case HttpTokens.LINE_FEED:
consumeCRLF(ch,buffer);
if (_length > 0)
{
if (_valueString!=null)
@ -975,9 +1054,7 @@ public class HttpParser
case HEADER_IN_VALUE:
switch(ch)
{
case HttpTokens.CARRIAGE_RETURN:
case HttpTokens.LINE_FEED:
consumeCRLF(ch,buffer);
if (_length > 0)
{
if (HttpHeaderValue.hasKnownValues(_header))
@ -1030,17 +1107,6 @@ public class HttpParser
return return_from_parse;
}
/* ------------------------------------------------------------------------------- */
private void consumeCRLF(byte ch, ByteBuffer buffer)
{
_eol=ch;
if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
{
buffer.get();
_eol=0;
}
}
/* ------------------------------------------------------------------------------- */
/**
* Parse until next Event.
@ -1059,7 +1125,8 @@ public class HttpParser
_methodString=null;
_endOfContent=EndOfContent.UNKNOWN_CONTENT;
_header=null;
quickStart(buffer);
if(quickStart(buffer))
return true;
break;
case CONTENT:
@ -1089,6 +1156,8 @@ public class HttpParser
BufferUtil.clear(buffer);
}
return false;
default: break;
}
// Request/response line
@ -1113,13 +1182,6 @@ public class HttpParser
byte ch;
while (_state.ordinal() > State.END.ordinal() && buffer.hasRemaining())
{
if (_eol == HttpTokens.CARRIAGE_RETURN && buffer.get(buffer.position()) == HttpTokens.LINE_FEED)
{
_eol=buffer.get();
continue;
}
_eol=0;
switch (_state)
{
case EOF_CONTENT:
@ -1169,31 +1231,24 @@ public class HttpParser
case CHUNKED_CONTENT:
{
ch=buffer.get(buffer.position());
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
_eol=buffer.get();
else if (ch <= HttpTokens.SPACE)
buffer.get();
else
ch=next(buffer);
if (ch>HttpTokens.SPACE)
{
_chunkLength=0;
_chunkLength=TypeUtil.convertHexDigit(ch);
_chunkPosition=0;
setState(State.CHUNK_SIZE);
}
break;
}
case CHUNK_SIZE:
{
ch=buffer.get();
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
ch=next(buffer);
if (ch == HttpTokens.LINE_FEED)
{
_eol=ch;
if (_chunkLength == 0)
{
if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
_eol=buffer.get();
setState(State.END);
if (_handler.messageComplete())
return true;
@ -1203,27 +1258,18 @@ public class HttpParser
}
else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
setState(State.CHUNK_PARAMS);
else if (ch >= '0' && ch <= '9')
_chunkLength=_chunkLength * 16 + (ch - '0');
else if (ch >= 'a' && ch <= 'f')
_chunkLength=_chunkLength * 16 + (10 + ch - 'a');
else if (ch >= 'A' && ch <= 'F')
_chunkLength=_chunkLength * 16 + (10 + ch - 'A');
else
throw new IOException("bad chunk char: " + ch);
_chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
break;
}
case CHUNK_PARAMS:
{
ch=buffer.get();
if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
ch=next(buffer);
if (ch == HttpTokens.LINE_FEED)
{
_eol=ch;
if (_chunkLength == 0)
{
if (_eol==HttpTokens.CARRIAGE_RETURN && buffer.hasRemaining() && buffer.get(buffer.position())==HttpTokens.LINE_FEED)
_eol=buffer.get();
setState(State.END);
if (_handler.messageComplete())
return true;
@ -1262,6 +1308,9 @@ public class HttpParser
BufferUtil.clear(buffer);
return false;
}
default:
break;
}
}
@ -1269,6 +1318,7 @@ public class HttpParser
}
catch(Exception e)
{
e.printStackTrace();
BufferUtil.clear(buffer);
if (isClosed())
{
@ -1340,7 +1390,18 @@ public class HttpParser
case CLOSED:
case END:
break;
case EOF_CONTENT:
_handler.messageComplete();
break;
default:
if (_state.ordinal()>State.END.ordinal())
{
_handler.earlyEOF();
_handler.messageComplete();
}
else
LOG.warn("Closing {}",this);
}
setState(State.CLOSED);
@ -1369,6 +1430,7 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
private void setState(State state)
{
// LOG.debug("{} --> {}",_state,state);
_state=state;
}

View File

@ -25,13 +25,12 @@ public interface HttpTokens
{
// Terminal symbols.
static final byte COLON= (byte)':';
static final byte SPACE= 0x20;
static final byte CARRIAGE_RETURN= 0x0D;
static final byte TAB= 0x09;
static final byte LINE_FEED= 0x0A;
static final byte CARRIAGE_RETURN= 0x0D;
static final byte SPACE= 0x20;
static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
static final byte SEMI_COLON= (byte)';';
static final byte TAB= 0x09;
public enum EndOfContent { UNKNOWN_CONTENT,NO_CONTENT,EOF_CONTENT,CONTENT_LENGTH,CHUNKED_CONTENT,SELF_DEFINING_CONTENT }

View File

@ -546,6 +546,8 @@ public class HttpURI
{
if (_host==_port)
return null;
if (_raw[_host]=='[')
return new String(_raw,_host+1,_port-_host-2,_charset);
return new String(_raw,_host,_port-_host,_charset);
}

View File

@ -50,11 +50,13 @@ public class HttpParserTest
throw new IllegalStateException("!START");
// continue parsing
while (!parser.isState(State.END) && buffer.hasRemaining())
{
int remaining=buffer.remaining();
while (!parser.isState(State.END) && remaining>0)
{
int was_remaining=remaining;
parser.parseNext(buffer);
if (remaining==buffer.remaining())
remaining=buffer.remaining();
if (remaining==was_remaining)
break;
}
}
@ -64,8 +66,8 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /foo HTTP/1.0\015\012" + "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/foo", _uriOrStatus);
@ -79,8 +81,8 @@ public class HttpParserTest
ByteBuffer buffer= BufferUtil.toBuffer("GET /999\015\012");
_versionOrReason= null;
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/999", _uriOrStatus);
@ -94,8 +96,8 @@ public class HttpParserTest
ByteBuffer buffer= BufferUtil.toBuffer("POST /222 \015\012");
_versionOrReason= null;
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/222", _uriOrStatus);
@ -108,8 +110,8 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /fo\u0690 HTTP/1.0\015\012" + "\015\012",StringUtil.__UTF8_CHARSET);
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/fo\u0690", _uriOrStatus);
@ -122,8 +124,8 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /foo?param=\u0690 HTTP/1.0\015\012" + "\015\012",StringUtil.__UTF8_CHARSET);
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/foo?param=\u0690", _uriOrStatus);
@ -136,8 +138,8 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/ HTTP/1.0\015\012" + "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/", _uriOrStatus);
@ -149,10 +151,9 @@ public class HttpParserTest
public void testConnect() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer("CONNECT 192.168.1.2:80 HTTP/1.1\015\012" + "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertTrue(handler.request);
assertEquals("CONNECT", _methodOrVersion);
assertEquals("192.168.1.2:80", _uriOrStatus);
assertEquals("HTTP/1.1", _versionOrReason);
@ -182,8 +183,8 @@ public class HttpParserTest
BufferUtil.put(b0,buffer);
BufferUtil.flipToFlush(buffer,pos);
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
@ -230,8 +231,8 @@ public class HttpParserTest
"Accept-Encoding: gzip, deflated\015\012" +
"Accept: unknown\015\012" +
"\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
@ -278,8 +279,8 @@ public class HttpParserTest
"Accept-Encoding: gzip, deflated\n" +
"Accept: unknown\n" +
"\n");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
@ -328,9 +329,10 @@ public class HttpParserTest
for (int i=0;i<buffer.capacity()-4;i++)
{
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
System.err.println(BufferUtil.toDetailString(buffer));
buffer.position(2);
buffer.limit(2+i);
@ -377,8 +379,8 @@ public class HttpParserTest
+ "1a\015\012"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
+ "0\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
@ -421,8 +423,8 @@ public class HttpParserTest
+ "0123456789\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/mp", _uriOrStatus);
@ -466,8 +468,8 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("200", _uriOrStatus);
@ -485,8 +487,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("304", _uriOrStatus);
@ -509,8 +511,8 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("204", _uriOrStatus);
@ -542,8 +544,8 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("200", _uriOrStatus);
@ -563,8 +565,8 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("200", _uriOrStatus);
@ -582,8 +584,8 @@ public class HttpParserTest
+ "Content-Length: 10\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("304", _uriOrStatus);
@ -601,8 +603,8 @@ public class HttpParserTest
+ "Transfer-Encoding: chunked\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("101", _uriOrStatus);
@ -624,8 +626,8 @@ public class HttpParserTest
+ "HTTP/1.1 400 OK\015\012"); // extra data causes close
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@ -647,8 +649,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@ -667,8 +669,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@ -686,8 +688,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@ -705,8 +707,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@ -724,8 +726,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@ -743,8 +745,23 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.ResponseHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
assertEquals("Unknown Version",_bad);
assertFalse(buffer.hasRemaining());
assertEquals(HttpParser.State.CLOSED,parser.getState());
buffer= BufferUtil.toBuffer(
"GET / HTTP/1.01\015\012"
+ "Content-Length: 0\015\012"
+ "Connection: close\015\012"
+ "\015\012");
handler = new Handler();handler = new Handler();
parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals(null,_methodOrVersion);
@ -753,6 +770,42 @@ public class HttpParserTest
assertEquals(HttpParser.State.CLOSED,parser.getState());
}
@Test
public void testBadCR() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\r\n"
+ "Content-Length: 0\r"
+ "Connection: close\r"
+ "\r");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("Bad EOL",_bad);
assertFalse(buffer.hasRemaining());
assertEquals(HttpParser.State.CLOSED,parser.getState());
buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\r"
+ "Content-Length: 0\r"
+ "Connection: close\r"
+ "\r");
handler = new Handler();
parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("Bad EOL",_bad);
assertFalse(buffer.hasRemaining());
assertEquals(HttpParser.State.CLOSED,parser.getState());
}
@Test
public void testBadContentLength0() throws Exception
{
@ -762,8 +815,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET",_methodOrVersion);
@ -781,8 +834,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET",_methodOrVersion);
@ -800,8 +853,8 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
Handler handler = new Handler();
HttpParser parser= new HttpParser((HttpParser.RequestHandler)handler);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET",_methodOrVersion);
@ -810,6 +863,131 @@ public class HttpParserTest
assertEquals(HttpParser.State.CLOSED,parser.getState());
}
@Test
public void testHost() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: host\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("host",_host);
assertEquals(0,_port);
}
@Test
public void testIPHost() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: 192.168.0.1\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("192.168.0.1",_host);
assertEquals(0,_port);
}
@Test
public void testIPv6Host() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: [::1]\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("::1",_host);
assertEquals(0,_port);
}
@Test
public void testBadIPv6Host() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: [::1\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("Bad IPv6 Host header",_bad);
}
@Test
public void testHostPort() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: myhost:8888\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("myhost",_host);
assertEquals(8888,_port);
}
@Test
public void testHostBadPort() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: myhost:xxx\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("Bad Host header",_bad);
}
@Test
public void testIPHostPort() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: 192.168.0.1:8888\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("192.168.0.1",_host);
assertEquals(8888,_port);
}
@Test
public void testIPv6HostPort() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: [::1]:8888\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("::1",_host);
assertEquals(8888,_port);
}
@Before
public void init()
@ -826,6 +1004,8 @@ public class HttpParserTest
_messageCompleted=false;
}
private String _host;
private int _port;
private String _bad;
private String _content;
private String _methodOrVersion;
@ -884,7 +1064,8 @@ public class HttpParserTest
@Override
public boolean parsedHostHeader(String host,int port)
{
// TODO test this
_host=host;
_port=port;
return false;
}

View File

@ -29,39 +29,51 @@ import org.junit.Test;
/* ------------------------------------------------------------ */
public class HttpURITest
{
public static final String __input = "http://example.com:8080/path/to/context?parameter=%22value%22#fragment";
public static final String __scheme = "http";
public static final String __host = "example.com";
public static final int __port = 8080;
public static final String __path = "/path/to/context";
public static final String __query = "parameter=%22value%22";
public static final String __fragment = "fragment";
String[][] tests=
{
{"/path/to/context",null,null,"-1","/path/to/context",null,null,null},
{"http://example.com/path/to/context;param?query=%22value%22#fragment","http","example.com","-1","/path/to/context","param","query=%22value%22","fragment"},
{"http://[::1]/path/to/context;param?query=%22value%22#fragment","http","::1","-1","/path/to/context","param","query=%22value%22","fragment"},
{"http://example.com:8080/path/to/context;param?query=%22value%22#fragment","http","example.com","8080","/path/to/context","param","query=%22value%22","fragment"},
{"http://[::1]:8080/path/to/context;param?query=%22value%22#fragment","http","::1","8080","/path/to/context","param","query=%22value%22","fragment"},
};
public static int
INPUT=0,SCHEME=1,HOST=2,PORT=3,PATH=4,PARAM=5,QUERY=6,FRAGMENT=7;
/* ------------------------------------------------------------ */
@Test
public void testFromString() throws Exception
{
HttpURI uri = new HttpURI(__input);
for (String[] test:tests)
{
HttpURI uri = new HttpURI(test[INPUT]);
assertEquals(__scheme, uri.getScheme());
assertEquals(__host,uri.getHost());
assertEquals(__port,uri.getPort());
assertEquals(__path,uri.getPath());
assertEquals(__query,uri.getQuery());
assertEquals(__fragment,uri.getFragment());
assertEquals(test[SCHEME], uri.getScheme());
assertEquals(test[HOST], uri.getHost());
assertEquals(Integer.parseInt(test[PORT]), uri.getPort());
assertEquals(test[PATH], uri.getPath());
assertEquals(test[PARAM], uri.getParam());
assertEquals(test[QUERY], uri.getQuery());
assertEquals(test[FRAGMENT], uri.getFragment());
}
}
/* ------------------------------------------------------------ */
@Test
public void testFromURI() throws Exception
{
HttpURI uri = new HttpURI(new URI(__input));
for (String[] test:tests)
{
HttpURI uri = new HttpURI(new URI(test[INPUT]));
assertEquals(__scheme, uri.getScheme());
assertEquals(__host,uri.getHost());
assertEquals(__port,uri.getPort());
assertEquals(__path,uri.getPath());
assertEquals(__query,uri.getQuery());
assertEquals(__fragment,uri.getFragment());
assertEquals(test[SCHEME], uri.getScheme());
assertEquals(test[HOST], uri.getHost());
assertEquals(Integer.parseInt(test[PORT]), uri.getPort());
assertEquals(test[PATH], uri.getPath());
assertEquals(test[PARAM], uri.getParam());
assertEquals(test[QUERY], uri.getQuery());
assertEquals(test[FRAGMENT], uri.getFragment());
}
}
}

View File

@ -24,6 +24,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -113,6 +114,59 @@ public abstract class AbstractConnection implements Connection
break loop;
break;
case FILLING_BLOCKED:
if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING_BLOCKED_INTERESTED))
break loop;
break;
case BLOCKED:
if (_state.compareAndSet(State.BLOCKED,State.BLOCKED_INTERESTED))
break loop;
break;
case FILLING_BLOCKED_INTERESTED:
case FILLING_INTERESTED:
case BLOCKED_INTERESTED:
case INTERESTED:
break loop;
}
}
}
private void unblock()
{
LOG.debug("unblock {}",this);
loop:while(true)
{
switch(_state.get())
{
case FILLING_BLOCKED:
if (_state.compareAndSet(State.FILLING_BLOCKED,State.FILLING))
break loop;
break;
case FILLING_BLOCKED_INTERESTED:
if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.FILLING_INTERESTED))
break loop;
break;
case BLOCKED_INTERESTED:
if (_state.compareAndSet(State.BLOCKED_INTERESTED,State.INTERESTED))
{
getEndPoint().fillInterested(_readCallback);
break loop;
}
break;
case BLOCKED:
if (_state.compareAndSet(State.BLOCKED,State.IDLE))
break loop;
break;
case FILLING:
case IDLE:
case FILLING_INTERESTED:
case INTERESTED:
break loop;
@ -120,6 +174,70 @@ public abstract class AbstractConnection implements Connection
}
}
/**
*/
protected void block(final BlockingCallback callback)
{
LOG.debug("block {}",this);
final Callback blocked=new Callback()
{
@Override
public void succeeded()
{
unblock();
callback.succeeded();
}
@Override
public void failed(Throwable x)
{
unblock();
callback.failed(x);
}
};
loop:while(true)
{
switch(_state.get())
{
case IDLE:
if (_state.compareAndSet(State.IDLE,State.BLOCKED))
{
getEndPoint().fillInterested(blocked);
break loop;
}
break;
case FILLING:
if (_state.compareAndSet(State.FILLING,State.FILLING_BLOCKED))
{
getEndPoint().fillInterested(blocked);
break loop;
}
break;
case FILLING_INTERESTED:
if (_state.compareAndSet(State.FILLING_INTERESTED,State.FILLING_BLOCKED_INTERESTED))
{
getEndPoint().fillInterested(blocked);
break loop;
}
break;
case BLOCKED:
case BLOCKED_INTERESTED:
case FILLING_BLOCKED:
case FILLING_BLOCKED_INTERESTED:
throw new IllegalStateException("Already Blocked");
case INTERESTED:
throw new IllegalStateException();
}
}
}
/**
* <p>Callback method invoked when the endpoint is ready to be read.</p>
* @see #fillInterested()
@ -225,7 +343,7 @@ public abstract class AbstractConnection implements Connection
private enum State
{
IDLE, INTERESTED, FILLING, FILLING_INTERESTED
IDLE, INTERESTED, FILLING, FILLING_INTERESTED, FILLING_BLOCKED, BLOCKED, FILLING_BLOCKED_INTERESTED, BLOCKED_INTERESTED
}
private class ReadCallback implements Callback, Runnable
@ -247,13 +365,26 @@ public abstract class AbstractConnection implements Connection
{
case IDLE:
case INTERESTED:
throw new IllegalStateException();
case BLOCKED:
case BLOCKED_INTERESTED:
LOG.warn(new IllegalStateException());
return;
case FILLING:
if (_state.compareAndSet(State.FILLING,State.IDLE))
break loop;
break;
case FILLING_BLOCKED:
if (_state.compareAndSet(State.FILLING_BLOCKED,State.BLOCKED))
break loop;
break;
case FILLING_BLOCKED_INTERESTED:
if (_state.compareAndSet(State.FILLING_BLOCKED_INTERESTED,State.BLOCKED_INTERESTED))
break loop;
break;
case FILLING_INTERESTED:
if (_state.compareAndSet(State.FILLING_INTERESTED,State.INTERESTED))
{
@ -266,7 +397,7 @@ public abstract class AbstractConnection implements Connection
}
}
else
LOG.warn(new Throwable());
LOG.warn(new IllegalStateException());
}
@Override

View File

@ -35,7 +35,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@ -167,20 +166,6 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
selector.submit(selector.new Accept(channel));
}
/**
* <p>Registers a channel to perform non-blocking read/write operations.</p>
* <p>This method is called just after a channel has been accepted by {@link ServerSocketChannel#accept()},
* or just after having performed a blocking connect via {@link Socket#connect(SocketAddress, int)}.</p>
*
* @param channel the channel to register
* @param attachment An attachment to be passed via the selection key to the {@link SelectorManager#newConnection(SocketChannel, EndPoint, Object)} method.
*/
public void accept(final SocketChannel channel, Object attachment)
{
final ManagedSelector selector = chooseSelector();
selector.submit(selector.new Accept(channel, attachment));
}
@Override
protected void doStart() throws Exception
{
@ -699,18 +684,10 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
private class Accept implements Runnable
{
private final SocketChannel _channel;
private final Object _attachment;
public Accept(SocketChannel channel)
{
this._channel = channel;
this._attachment = null;
}
public Accept(SocketChannel channel, Object attachment)
{
this._channel = channel;
this._attachment = attachment;
}
@Override
@ -718,7 +695,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
{
try
{
SelectionKey key = _channel.register(_selector, 0, _attachment);
SelectionKey key = _channel.register(_selector, 0, null);
EndPoint endpoint = createEndPoint(_channel, key);
key.attach(endpoint);
}

View File

@ -284,7 +284,7 @@ public abstract class AbstractJettyMojo extends AbstractMojo
/**
* A wrapper for the Server object
*/
protected JettyServer server = JettyServer.getInstance();
protected JettyServer server = new JettyServer();
/**
@ -494,6 +494,8 @@ public abstract class AbstractJettyMojo extends AbstractMojo
String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
httpConnector.setPort(Integer.parseInt(tmp.trim()));
}
if (httpConnector.getServer() == null)
httpConnector.setServer(this.server);
this.server.addConnector(httpConnector);
}
@ -509,7 +511,8 @@ public abstract class AbstractJettyMojo extends AbstractMojo
String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
httpConnector.setPort(Integer.parseInt(tmp.trim()));
}
if (httpConnector.getServer() == null)
httpConnector.setServer(this.server);
this.server.setConnectors(new Connector[] {httpConnector});
}

View File

@ -38,16 +38,6 @@ public class JettyServer extends org.eclipse.jetty.server.Server
{
public static final JettyServer __instance = new JettyServer();
/**
* Singleton instance
* @return
*/
public static JettyServer getInstance()
{
return __instance;
}
private RequestLog requestLog;
private ContextHandlerCollection contexts;
@ -56,7 +46,7 @@ public class JettyServer extends org.eclipse.jetty.server.Server
/**
*
*/
private JettyServer()
public JettyServer()
{
super();
setStopAtShutdown(true);

View File

@ -19,21 +19,259 @@
package org.eclipse.jetty.maven.plugin;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.thread.Scheduler;
/**
* MavenServerConnector
*
*
* As the ServerConnector class does not have a no-arg constructor, and moreover requires
* the server instance passed in to all its constructors, it cannot
* be referenced in the pom.xml. This class wraps a ServerConnector, delaying setting the
* server instance. Only a few of the setters from the ServerConnector class are supported.
*/
public class MavenServerConnector extends ServerConnector
public class MavenServerConnector extends AbstractLifeCycle implements Connector
{
public static int DEFAULT_PORT = 8080;
public static String DEFAULT_PORT_STR = String.valueOf(DEFAULT_PORT);
public static int DEFAULT_MAX_IDLE_TIME = 30000;
private Server server;
private ServerConnector delegate;
private String host;
private String name;
private int port;
private long idleTimeout;
private int lingerTime;
public MavenServerConnector()
{
super(JettyServer.getInstance());
}
public void setServer(Server server)
{
this.server = server;
}
public void setHost(String host)
{
this.host = host;
}
public String getHost()
{
return this.host;
}
public void setPort(int port)
{
this.port = port;
}
public int getPort ()
{
return this.port;
}
public void setName (String name)
{
this.name = name;
}
public void setIdleTimeout(long idleTimeout)
{
this.idleTimeout = idleTimeout;
}
public void setSoLingerTime(int lingerTime)
{
this.lingerTime = lingerTime;
}
@Override
protected void doStart() throws Exception
{
if (this.server == null)
throw new IllegalStateException("Server not set for MavenServerConnector");
this.delegate = new ServerConnector(this.server);
this.delegate.setName(this.name);
this.delegate.setPort(this.port);
this.delegate.setHost(this.host);
this.delegate.setIdleTimeout(idleTimeout);
this.delegate.setSoLingerTime(lingerTime);
this.delegate.start();
super.doStart();
}
@Override
protected void doStop() throws Exception
{
this.delegate.stop();
super.doStop();
this.delegate = null;
}
/**
* @see org.eclipse.jetty.util.component.Graceful#shutdown()
*/
@Override
public Future<Void> shutdown()
{
checkDelegate();
return this.delegate.shutdown();
}
/**
* @see org.eclipse.jetty.server.Connector#getServer()
*/
@Override
public Server getServer()
{
return this.server;
}
/**
* @see org.eclipse.jetty.server.Connector#getExecutor()
*/
@Override
public Executor getExecutor()
{
checkDelegate();
return this.delegate.getExecutor();
}
/**
* @see org.eclipse.jetty.server.Connector#getScheduler()
*/
@Override
public Scheduler getScheduler()
{
checkDelegate();
return this.delegate.getScheduler();
}
/**
* @see org.eclipse.jetty.server.Connector#getByteBufferPool()
*/
@Override
public ByteBufferPool getByteBufferPool()
{
checkDelegate();
return this.delegate.getByteBufferPool();
}
/**
* @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.String)
*/
@Override
public ConnectionFactory getConnectionFactory(String nextProtocol)
{
checkDelegate();
return this.delegate.getConnectionFactory(nextProtocol);
}
/**
* @see org.eclipse.jetty.server.Connector#getConnectionFactory(java.lang.Class)
*/
@Override
public <T> T getConnectionFactory(Class<T> factoryType)
{
checkDelegate();
return this.delegate.getConnectionFactory(factoryType);
}
/**
* @see org.eclipse.jetty.server.Connector#getDefaultConnectionFactory()
*/
@Override
public ConnectionFactory getDefaultConnectionFactory()
{
checkDelegate();
return getDefaultConnectionFactory();
}
/**
* @see org.eclipse.jetty.server.Connector#getConnectionFactories()
*/
@Override
public Collection<ConnectionFactory> getConnectionFactories()
{
checkDelegate();
return this.delegate.getConnectionFactories();
}
/**
* @see org.eclipse.jetty.server.Connector#getProtocols()
*/
@Override
public List<String> getProtocols()
{
checkDelegate();
return this.delegate.getProtocols();
}
/**
* @see org.eclipse.jetty.server.Connector#getIdleTimeout()
*/
@Override
@ManagedAttribute("maximum time a connection can be idle before being closed (in ms)")
public long getIdleTimeout()
{
checkDelegate();
return this.delegate.getIdleTimeout();
}
/**
* @see org.eclipse.jetty.server.Connector#getTransport()
*/
@Override
public Object getTransport()
{
checkDelegate();
return this.delegate.getTransport();
}
/**
* @see org.eclipse.jetty.server.Connector#getConnectedEndPoints()
*/
@Override
public Collection<EndPoint> getConnectedEndPoints()
{
checkDelegate();
return this.delegate.getConnectedEndPoints();
}
/**
* @see org.eclipse.jetty.server.Connector#getName()
*/
@Override
public String getName()
{
return this.name;
}
private void checkDelegate() throws IllegalStateException
{
if (this.delegate == null)
throw new IllegalStateException ("MavenServerConnector delegate not ready");
}
}

View File

@ -55,7 +55,7 @@ public class Starter
private List<File> jettyXmls; // list of jetty.xml config files to apply - Mandatory
private File contextXml; //name of context xml file to configure the webapp - Mandatory
private JettyServer server;
private JettyServer server = new JettyServer();
private JettyWebAppContext webApp;
@ -120,8 +120,6 @@ public class Starter
{
LOG.debug("Starting Jetty Server ...");
this.server = JettyServer.getInstance();
//apply any configs from jetty.xml files first
applyJettyXml ();
@ -132,6 +130,7 @@ public class Starter
{
//if a SystemProperty -Djetty.port=<portnum> has been supplied, use that as the default port
MavenServerConnector httpConnector = new MavenServerConnector();
httpConnector.setServer(this.server);
String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
httpConnector.setPort(Integer.parseInt(tmp.trim()));
connectors = new Connector[] {httpConnector};

View File

@ -149,7 +149,7 @@ public class MongoSessionManager extends NoSqlSessionManager
}
else
{
version = new Long(((Long)version).intValue() + 1);
version = new Long(((Number)version).longValue() + 1);
update.put("$inc",__version_1);
}

View File

@ -236,7 +236,7 @@ public class DefaultJettyAtJettyHomeHelper
// can define their own configuration.
if ((enUrls == null || !enUrls.hasMoreElements()))
{
String tmp = DEFAULT_JETTYHOME+etcFile;
String tmp = DEFAULT_JETTYHOME+(DEFAULT_JETTYHOME.endsWith("/")?"":"/")+etcFile;
enUrls = BundleFileLocatorHelperFactory.getFactory().getHelper().findEntries(configurationBundle, tmp);
LOG.info("Configuring jetty from bundle: "
+ configurationBundle.getSymbolicName()

View File

@ -27,9 +27,7 @@
</goals>
<configuration>
<includes>**</includes>
<excludes>**/MANIFEST.MF</excludes>
<excludes>**/ECLIPSEF.RSA</excludes>
<excludes>**/ECLIPSEF.SF</excludes>
<excludes>**/MANIFEST.MF,META-INF/*.RSA,META-INF/*.DSA,META-INF/*.SF</excludes>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>

View File

@ -21,11 +21,11 @@ package org.eclipse.jetty.security.authentication;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.BitSet;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@ -53,24 +53,40 @@ import org.eclipse.jetty.util.security.Credential;
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
*
* The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)}
* using the name "maxNonceAge"
* using the name "maxNonceAge". The nonce max count can be set with {@link SecurityHandler#setInitParameter(String, String)}
* using the name "maxNonceCount". When the age or count is exceeded, the nonce is considered stale.
*/
public class DigestAuthenticator extends LoginAuthenticator
{
private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
SecureRandom _random = new SecureRandom();
private long _maxNonceAgeMs = 60*1000;
private ConcurrentMap<String, Nonce> _nonceCount = new ConcurrentHashMap<String, Nonce>();
private int _maxNC=1024;
private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
private static class Nonce
{
final String _nonce;
final long _ts;
AtomicInteger _nc=new AtomicInteger();
public Nonce(String nonce, long ts)
final BitSet _seen;
public Nonce(String nonce, long ts, int size)
{
_nonce=nonce;
_ts=ts;
_seen = new BitSet(size);
}
public boolean seen(int count)
{
synchronized (this)
{
if (count>=_seen.size())
return true;
boolean s=_seen.get(count);
_seen.set(count);
return s;
}
}
}
@ -91,14 +107,34 @@ public class DigestAuthenticator extends LoginAuthenticator
String mna=configuration.getInitParameter("maxNonceAge");
if (mna!=null)
{
synchronized (this)
{
_maxNonceAgeMs=Long.valueOf(mna);
}
String mnc=configuration.getInitParameter("maxNonceCount");
if (mnc!=null)
{
_maxNC=Integer.valueOf(mnc);
}
}
/* ------------------------------------------------------------ */
public int getMaxNonceCount()
{
return _maxNC;
}
/* ------------------------------------------------------------ */
public void setMaxNonceCount(int maxNC)
{
_maxNC = maxNC;
}
/* ------------------------------------------------------------ */
public long getMaxNonceAge()
{
return _maxNonceAgeMs;
}
/* ------------------------------------------------------------ */
public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
{
@ -238,9 +274,9 @@ public class DigestAuthenticator extends LoginAuthenticator
byte[] nounce = new byte[24];
_random.nextBytes(nounce);
nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp());
nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
}
while (_nonceCount.putIfAbsent(nonce._nonce,nonce)!=null);
while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
_nonceQueue.add(nonce);
return nonce._nonce;
@ -255,34 +291,27 @@ public class DigestAuthenticator extends LoginAuthenticator
private int checkNonce(Digest digest, Request request)
{
// firstly let's expire old nonces
long expired;
synchronized (this)
{
expired = request.getTimeStamp()-_maxNonceAgeMs;
}
long expired = request.getTimeStamp()-_maxNonceAgeMs;
Nonce nonce=_nonceQueue.peek();
while (nonce!=null && nonce._ts<expired)
{
_nonceQueue.remove(nonce);
_nonceCount.remove(nonce._nonce);
_nonceMap.remove(nonce._nonce);
nonce=_nonceQueue.peek();
}
// Now check the requested nonce
try
{
nonce = _nonceCount.get(digest.nonce);
nonce = _nonceMap.get(digest.nonce);
if (nonce==null)
return 0;
long count = Long.parseLong(digest.nc,16);
if (count>Integer.MAX_VALUE)
if (count>=_maxNC)
return 0;
int old=nonce._nc.get();
while (!nonce._nc.compareAndSet(old,(int)count))
old=nonce._nc.get();
if (count<=old)
if (nonce.seen((int)count))
return -1;
return 1;
@ -383,6 +412,7 @@ public class DigestAuthenticator extends LoginAuthenticator
return false;
}
@Override
public String toString()
{
return username + "," + response;

View File

@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.matchers.JUnitMatchers.containsString;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -36,12 +37,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.security.authentication.DigestAuthenticator;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
@ -55,7 +59,10 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.util.security.Password;
import org.junit.After;
import org.junit.Before;
@ -288,7 +295,6 @@ public class ConstraintTest
@Test
public void testBasic() throws Exception
{
List<ConstraintMapping> list = new ArrayList<ConstraintMapping>(_security.getConstraintMappings());
Constraint constraint6 = new Constraint();
@ -332,14 +338,11 @@ public class ConstraintTest
_server.start();
String response;
/*
response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
*/
response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
/*
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
@ -354,8 +357,7 @@ public class ConstraintTest
"Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
*/
/*
// test admin
response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
@ -386,28 +388,159 @@ public class ConstraintTest
response = _connector.getResponses("GET /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" +
"\r\n");
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat(response,startsWith("HTTP/1.1 200 OK"));
//check POST is in role user
response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
"\r\n");
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat(response,startsWith("HTTP/1.1 200 OK"));
//check POST can be in role foo too
response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user3:password") + "\r\n" +
"\r\n");
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
assertThat(response,startsWith("HTTP/1.1 200 OK"));
//check HEAD cannot be in role user
response = _connector.getResponses("HEAD /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
"\r\n");
assertTrue(response.startsWith("HTTP/1.1 200 OK"));*/
assertThat(response,startsWith("HTTP/1.1 403 "));
}
private static String CNONCE="1234567890";
private String digest(String nonce, String username,String password,String uri,String nc) throws Exception
{
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] ha1;
// calc A1 digest
md.update(username.getBytes(StringUtil.__ISO_8859_1));
md.update((byte) ':');
md.update("TestRealm".getBytes(StringUtil.__ISO_8859_1));
md.update((byte) ':');
md.update(password.getBytes(StringUtil.__ISO_8859_1));
ha1 = md.digest();
// calc A2 digest
md.reset();
md.update("GET".getBytes(StringUtil.__ISO_8859_1));
md.update((byte) ':');
md.update(uri.getBytes(StringUtil.__ISO_8859_1));
byte[] ha2 = md.digest();
// calc digest
// request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
// nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
// <">
// request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
// ) > <">
md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
md.update((byte) ':');
md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
md.update((byte) ':');
md.update(nc.getBytes(StringUtil.__ISO_8859_1));
md.update((byte) ':');
md.update(CNONCE.getBytes(StringUtil.__ISO_8859_1));
md.update((byte) ':');
md.update("auth".getBytes(StringUtil.__ISO_8859_1));
md.update((byte) ':');
md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
byte[] digest = md.digest();
// check digest
return TypeUtil.toString(digest, 16);
}
@Test
public void testDigest() throws Exception
{
DigestAuthenticator authenticator = new DigestAuthenticator();
authenticator.setMaxNonceCount(5);
_security.setAuthenticator(authenticator);
_security.setStrict(false);
_server.start();
String response;
response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
assertThat(response,containsString("WWW-Authenticate: Digest realm=\"TestRealm\""));
Pattern nonceP = Pattern.compile("nonce=\"([^\"]*)\",");
Matcher matcher = nonceP.matcher(response);
assertTrue(matcher.find());
String nonce=matcher.group(1);
//wrong password
String digest= digest(nonce,"user","WRONG","/ctx/auth/info","1");
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
"nc=1, "+
"nonce=\""+nonce+"\", "+
"response=\""+digest+"\"\r\n"+
"\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
// right password
digest= digest(nonce,"user","password","/ctx/auth/info","2");
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
"nc=2, "+
"nonce=\""+nonce+"\", "+
"response=\""+digest+"\"\r\n"+
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
// once only
digest= digest(nonce,"user","password","/ctx/auth/info","2");
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
"nc=2, "+
"nonce=\""+nonce+"\", "+
"response=\""+digest+"\"\r\n"+
"\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
// increasing
digest= digest(nonce,"user","password","/ctx/auth/info","4");
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
"nc=4, "+
"nonce=\""+nonce+"\", "+
"response=\""+digest+"\"\r\n"+
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
// out of order
digest= digest(nonce,"user","password","/ctx/auth/info","3");
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
"nc=3, "+
"nonce=\""+nonce+"\", "+
"response=\""+digest+"\"\r\n"+
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
// stale
digest= digest(nonce,"user","password","/ctx/auth/info","5");
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
"Authorization: Digest username=\"user\", qop=auth, cnonce=\"1234567890\", uri=\"/ctx/auth/info\", realm=\"TestRealm\", "+
"nc=5, "+
"nonce=\""+nonce+"\", "+
"response=\""+digest+"\"\r\n"+
"\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
assertThat(response,containsString("stale=true"));
}
@Test
public void testFormDispatch() throws Exception
@ -964,32 +1097,32 @@ public class ConstraintTest
String response;
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
assertTrue(response.startsWith("HTTP/1.1 403"));
assertThat(response,startsWith("HTTP/1.1 403"));
_config.setSecurePort(8443);
_config.setSecureScheme("https");
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
assertTrue(response.startsWith("HTTP/1.1 302 Found"));
assertThat(response,startsWith("HTTP/1.1 302 Found"));
assertTrue(response.indexOf("Location") > 0);
assertTrue(response.indexOf(":8443/ctx/data/info") > 0);
_config.setSecurePort(443);
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\n\r\n");
assertTrue(response.startsWith("HTTP/1.1 302 Found"));
assertThat(response,startsWith("HTTP/1.1 302 Found"));
assertTrue(response.indexOf("Location") > 0);
assertTrue(response.indexOf(":443/ctx/data/info") < 0);
_config.setSecurePort(8443);
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n");
assertTrue(response.startsWith("HTTP/1.1 302 Found"));
assertThat(response,startsWith("HTTP/1.1 302 Found"));
assertTrue(response.indexOf("Location") > 0);
assertTrue(response.indexOf("https://wobble.com:8443/ctx/data/info") > 0);
_config.setSecurePort(443);
response = _connector.getResponses("GET /ctx/data/info HTTP/1.0\r\nHost: wobble.com\r\n\r\n");
System.err.println(response);
assertTrue(response.startsWith("HTTP/1.1 302 Found"));
assertThat(response,startsWith("HTTP/1.1 302 Found"));
assertTrue(response.indexOf("Location") > 0);
assertTrue(response.indexOf(":443") < 0);
assertTrue(response.indexOf("https://wobble.com/ctx/data/info") > 0);

View File

@ -33,7 +33,7 @@
</Arg>
<Set name="host"><Property name="jetty.host" /></Set>
<Set name="port"><Property name="jetty.port" default="8080" /></Set>
<Set name="idleTimeout">30000</Set>
<Set name="idleTimeout"><Property name="http.timeout" default="30000"/></Set>
</New>
</Arg>
</Call>

View File

@ -10,12 +10,12 @@
<Arg>
<New class="org.eclipse.jetty.server.LowResourceMonitor">
<Arg name="server"><Ref refid='Server'/></Arg>
<Set name="period">1000</Set>
<Set name="lowResourcesIdleTimeout">200</Set>
<Set name="monitorThreads">true</Set>
<Set name="maxConnections">0</Set>
<Set name="maxMemory">0</Set>
<Set name="maxLowResourcesTime">5000</Set>
<Set name="period"><Property name="lowresources.period" default="1000"/></Set>
<Set name="lowResourcesIdleTimeout"><Property name="lowresources.lowResourcesIdleTimeout" default="200"/></Set>
<Set name="monitorThreads"><Property name="lowresources.monitorThreads" default="true"/></Set>
<Set name="maxConnections"><Property name="lowresources.maxConnections" default="0"/></Set>
<Set name="maxMemory"><Property name="lowresources.maxMemory" default="0"/></Set>
<Set name="maxLowResourcesTime"><Property name="lowresources.maxLowResourcesTime" default="5000"/></Set>
</New>
</Arg>
</Call>

View File

@ -17,9 +17,9 @@
<New id="RequestLogImpl" class="org.eclipse.jetty.server.AsyncNCSARequestLog">
<Set name="filename"><Property name="jetty.logs" default="./logs" />/yyyy_mm_dd.request.log</Set>
<Set name="filenameDateFormat">yyyy_MM_dd</Set>
<Set name="retainDays">90</Set>
<Set name="append">true</Set>
<Set name="extended">true</Set>
<Set name="retainDays"><Property name="requestlog.retain" default="90"/></Set>
<Set name="append"><Property name="requestlog.append" default="false"/></Set>
<Set name="extended"><Property name="requestlog.extended" default="false"/></Set>
<Set name="logCookies">false</Set>
<Set name="LogTimeZone">GMT</Set>
</New>

View File

@ -12,4 +12,7 @@
<Set name="handler"><Ref refid="oldhandler" /></Set>
</New>
</Set>
<Call class="org.eclipse.jetty.server.ConnectorStatistics" name="addToAllConnectors">
<Arg><Ref refid="Server"/></Arg>
</Call>
</Configure>

View File

@ -44,9 +44,9 @@
<!-- =========================================================== -->
<Arg name="threadpool">
<New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
<Arg name="minThreads" type="int">10</Arg>
<Arg name="maxThreads" type="int">200</Arg>
<Arg name="idleTimeout" type="int">60000</Arg>
<Arg name="minThreads" type="int"><Property name="threads.min" default="10"/></Arg>
<Arg name="maxThreads" type="int"><Property name="threads.max" default="200"/></Arg>
<Arg name="idleTimeout" type="int"><Property name="threads.timeout" default="60000"/></Arg>
<Set name="detailedDump">false</Set>
</New>
</Arg>

View File

@ -23,14 +23,22 @@ import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Container;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.statistic.CounterStatistic;
import org.eclipse.jetty.util.statistic.SampleStatistic;
/* ------------------------------------------------------------ */
/** A Connector.Listener that gathers Connector and Connections Statistics.
* Adding an instance of this class as with {@link AbstractConnector#addBean(Object)}
* will register the listener with all connections accepted by that connector.
*/
@ManagedObject("Connector Statistics")
public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable, Connection.Listener
{
@ -52,78 +60,93 @@ public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable,
connectionClosed(System.currentTimeMillis()-connection.getCreatedTimeStamp(),connection.getMessagesIn(),connection.getMessagesOut());
}
@ManagedAttribute("Total number of bytes received by this connector")
public int getBytesIn()
{
// TODO
return -1;
}
@ManagedAttribute("Total number of bytes sent by this connector")
public int getBytesOut()
{
// TODO
return -1;
}
@ManagedAttribute("Total number of connections seen by this connector")
public int getConnections()
{
return (int)_connectionStats.getTotal();
}
@ManagedAttribute("Connection duraton maximum in ms")
public long getConnectionsDurationMax()
{
return _connectionDurationStats.getMax();
}
@ManagedAttribute("Connection duraton mean in ms")
public double getConnectionsDurationMean()
{
return _connectionDurationStats.getMean();
}
@ManagedAttribute("Connection duraton standard deviation")
public double getConnectionsDurationStdDev()
{
return _connectionDurationStats.getStdDev();
}
@ManagedAttribute("Connection duraton total of all connections in ms")
public long getConnectionsDurationTotal()
{
return _connectionDurationStats.getTotal();
}
public int getConnectionsMessagesInMax()
{
return (int)_messagesIn.getMax();
}
public double getConnectionsMessagesInMean()
{
return _messagesIn.getMean();
}
public double getConnectionsMessagesInStdDev()
{
return _messagesIn.getStdDev();
}
public int getConnectionsOpen()
{
return (int)_connectionStats.getCurrent();
}
public int getConnectionsOpenMax()
{
return (int)_connectionStats.getMax();
}
@ManagedAttribute("Messages In for all connections")
public int getMessagesIn()
{
return (int)_messagesIn.getTotal();
}
@ManagedAttribute("Messages In per connection maximum")
public int getConnectionsMessagesInMax()
{
return (int)_messagesIn.getMax();
}
@ManagedAttribute("Messages In per connection mean")
public double getConnectionsMessagesInMean()
{
return _messagesIn.getMean();
}
@ManagedAttribute("Messages In per connection standard deviation")
public double getConnectionsMessagesInStdDev()
{
return _messagesIn.getStdDev();
}
@ManagedAttribute("Connections open")
public int getConnectionsOpen()
{
return (int)_connectionStats.getCurrent();
}
@ManagedAttribute("Connections open maximum")
public int getConnectionsOpenMax()
{
return (int)_connectionStats.getMax();
}
@ManagedAttribute("Messages Out for all connections")
public int getMessagesOut()
{
return (int)_messagesIn.getTotal();
}
@ManagedAttribute("Connection statistics started ms since epoch")
public long getStartedMillis()
{
long start = _startMillis.get();
@ -141,6 +164,7 @@ public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable,
{
}
@ManagedOperation("Reset the statistics")
public void reset()
{
_startMillis.set(System.currentTimeMillis());
@ -178,7 +202,6 @@ public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable,
}
}
@Override
@ManagedOperation("dump thread state")
public String dump()
@ -192,4 +215,13 @@ public class ConnectorStatistics extends AbstractLifeCycle implements Dumpable,
ContainerLifeCycle.dumpObject(out,this);
ContainerLifeCycle.dump(out,indent,Arrays.asList(new String[]{"connections="+_connectionStats,"duration="+_connectionDurationStats,"in="+_messagesIn,"out="+_messagesOut}));
}
public static void addToAllConnectors(Server server)
{
for (Connector connector : server.getConnectors())
{
if (connector instanceof Container)
((Container)connector).addBean(new ConnectorStatistics());
}
}
}

View File

@ -40,8 +40,10 @@ import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.HttpChannelState.Next;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
@ -215,6 +217,15 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
@Override
public void run()
{
handle();
}
/* ------------------------------------------------------------ */
/**
* @return True if the channel is ready to continue handling (ie it is not suspended)
*/
public boolean handle()
{
LOG.debug("{} handle enter", this);
@ -227,15 +238,16 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
Thread.currentThread().setName(threadName + " - " + _uri);
}
try
{
// Loop here to handle async request redispatches.
// The loop is controlled by the call to async.unhandle in the
// finally block below. Unhandle will return false only if an async dispatch has
// already happened when unhandle is called.
boolean handling = _state.handling();
HttpChannelState.Next next = _state.handling();
try
{
while (handling && getServer().isRunning())
while (next==Next.CONTINUE && getServer().isRunning())
{
try
{
@ -285,9 +297,11 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
}
finally
{
handling = !_state.unhandle();
next = _state.unhandle();
}
}
if (next==Next.WAIT)
return false;
}
finally
{
@ -295,7 +309,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
Thread.currentThread().setName(threadName);
setCurrentHttpChannel(null);
if (_state.isCompleting())
if (next==Next.COMPLETE)
{
try
{
@ -318,13 +332,21 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
}
finally
{
_request.setHandled(true);
_transport.completed();
next=Next.RECYCLE;
}
}
if (next==Next.RECYCLE)
{
_request.setHandled(true);
_transport.completed();
}
LOG.debug("{} handle exit", this);
}
return true;
}
/**
@ -569,10 +591,9 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
try
{
if (_state.handling())
if (_state.handling()==Next.CONTINUE)
{
commitResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
_state.unhandle();
}
}
catch (IOException e)
@ -581,6 +602,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
}
finally
{
if (_state.unhandle()==Next.COMPLETE)
_state.completed();
}
}
@ -664,4 +686,13 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
{
return _connector.getScheduler();
}
/* ------------------------------------------------------------ */
/**
* @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
*/
public boolean useDirectBuffers()
{
return getEndPoint() instanceof ChannelEndPoint;
}
}

View File

@ -72,6 +72,14 @@ public class HttpChannelState
COMPLETED // Request is complete
}
public enum Next
{
CONTINUE, // Continue handling the channel
WAIT, // Wait for further events
COMPLETE, // Complete the channel
RECYCLE, // Channel is completed
}
private final HttpChannel<?> _channel;
private List<AsyncListener> _lastAsyncListeners;
private List<AsyncListener> _asyncListeners;
@ -154,9 +162,9 @@ public class HttpChannelState
}
/**
* @return true if the handling of the request should proceed
* @return Next handling of the request should proceed
*/
protected boolean handling()
protected Next handling()
{
synchronized (this)
{
@ -178,12 +186,16 @@ public class HttpChannelState
case COMPLETECALLED:
_state=State.COMPLETING;
return false;
return Next.COMPLETE;
case COMPLETING:
return Next.COMPLETE;
case ASYNCWAIT:
case COMPLETING:
return Next.WAIT;
case COMPLETED:
return false;
return Next.RECYCLE;
case REDISPATCH:
_state=State.REDISPATCHED;
@ -194,7 +206,7 @@ public class HttpChannelState
}
_responseWrapped=false;
return true;
return Next.CONTINUE;
}
}
@ -255,10 +267,10 @@ public class HttpChannelState
* Signal that the HttpConnection has finished handling the request.
* For blocking connectors, this call may block if the request has
* been suspended (startAsync called).
* @return true if handling is complete, false if the request should
* @return next actions
* be handled again (eg because of a resume that happened before unhandle was called)
*/
protected boolean unhandle()
protected Next unhandle()
{
synchronized (this)
{
@ -267,7 +279,7 @@ public class HttpChannelState
case REDISPATCHED:
case DISPATCHED:
_state=State.COMPLETING;
return true;
return Next.COMPLETE;
case IDLE:
throw new IllegalStateException(this.getStatusString());
@ -277,25 +289,25 @@ public class HttpChannelState
_state=State.ASYNCWAIT;
scheduleTimeout();
if (_state==State.ASYNCWAIT)
return true;
return Next.WAIT;
else if (_state==State.COMPLETECALLED)
{
_state=State.COMPLETING;
return true;
return Next.COMPLETE;
}
_initial=false;
_state=State.REDISPATCHED;
return false;
return Next.CONTINUE;
case REDISPATCHING:
_initial=false;
_state=State.REDISPATCHED;
return false;
return Next.CONTINUE;
case COMPLETECALLED:
_initial=false;
_state=State.COMPLETING;
return true;
return Next.COMPLETE;
default:
throw new IllegalStateException(this.getStatusString());

View File

@ -207,8 +207,28 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// Can the parser progress (even with an empty buffer)
boolean call_channel=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
// If there is a request buffer, we are re-entering here
if (!call_channel && BufferUtil.isEmpty(_requestBuffer))
// Parse the buffer
if (call_channel)
{
// Parse as much content as there is available before calling the channel
// this is both efficient (may queue many chunks), will correctly set available for 100 continues
// and will drive the parser to completion if all content is available.
while (_parser.inContentState())
{
if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
break;
}
// The parser returned true, which indicates the channel is ready to handle a request.
// Call the channel and this will either handle the request/response to completion OR,
// if the request suspends, the request/response will be incomplete so the outer loop will exit.
boolean handle=_channel.handle();
// Return if suspended or upgraded
if (!handle || getEndPoint().getConnection()!=this)
return;
}
else if (BufferUtil.isEmpty(_requestBuffer))
{
if (_requestBuffer == null)
_requestBuffer = _bufferPool.acquire(getInputBufferSize(), REQUEST_BUFFER_DIRECT);
@ -242,31 +262,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
releaseRequestBuffer();
return;
}
// Parse what we have read
call_channel=_parser.parseNext(_requestBuffer);
}
// Parse the buffer
if (call_channel)
else
{
// Parse as much content as there is available before calling the channel
// this is both efficient (may queue many chunks), will correctly set available for 100 continues
// and will drive the parser to completion if all content is available.
while (_parser.inContentState())
{
if (!_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer))
break;
}
// The parser returned true, which indicates the channel is ready to handle a request.
// Call the channel and this will either handle the request/response to completion OR,
// if the request suspends, the request/response will be incomplete so the outer loop will exit.
_channel.run();
// Return if suspended or upgraded
if (_channel.getState().isSuspended() || getEndPoint().getConnection()!=this)
// TODO work out how we can get here and a better way to handle it
LOG.warn("Unexpected state: "+this+ " "+_channel+" "+_channel.getRequest());
if (!_channel.getState().isSuspended())
getEndPoint().close();
return;
}
}
@ -563,7 +565,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
}
// Wait until we can read
getEndPoint().fillInterested(_readBlocker);
block(_readBlocker);
LOG.debug("{} block readable on {}",this,_readBlocker);
_readBlocker.block();

View File

@ -48,9 +48,6 @@ import org.eclipse.jetty.util.resource.Resource;
*/
public class HttpOutput extends ServletOutputStream
{
private static final boolean OUTPUT_BUFFER_DIRECT=false;
private static final boolean CHANNEL_BUFFER_DIRECT=true;
private static final boolean STREAM_BUFFER_DIRECT=false;
private static Logger LOG = Log.getLogger(HttpOutput.class);
private final HttpChannel<?> _channel;
private boolean _closed;
@ -168,8 +165,9 @@ public class HttpOutput extends ServletOutputStream
return;
}
// Allocate an aggregate buffer
_aggregate = _channel.getByteBufferPool().acquire(size, OUTPUT_BUFFER_DIRECT);
// Allocate an aggregate buffer.
// Never direct as it is slow to do little writes to a direct buffer.
_aggregate = _channel.getByteBufferPool().acquire(size, false);
}
// Do we have space to aggregate ?
@ -209,8 +207,10 @@ public class HttpOutput extends ServletOutputStream
if (isClosed())
throw new EOFException("Closed");
// Allocate an aggregate buffer.
// Never direct as it is slow to do little writes to a direct buffer.
if (_aggregate == null)
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), OUTPUT_BUFFER_DIRECT);
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
BufferUtil.append(_aggregate, (byte)b);
_written++;
@ -259,7 +259,7 @@ public class HttpOutput extends ServletOutputStream
if (etag!=null)
response.getHttpFields().put(HttpHeader.ETAG,etag);
content = httpContent.getDirectBuffer();
content = _channel.useDirectBuffers()?httpContent.getDirectBuffer():null;
if (content == null)
content = httpContent.getIndirectBuffer();
if (content == null)
@ -271,7 +271,7 @@ public class HttpOutput extends ServletOutputStream
{
Resource resource = (Resource)content;
_channel.getResponse().getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, resource.lastModified());
content = resource.getInputStream();
content=resource.getInputStream(); // Closed below
}
// Process content.
@ -282,9 +282,8 @@ public class HttpOutput extends ServletOutputStream
}
else if (content instanceof ReadableByteChannel)
{
ReadableByteChannel channel = (ReadableByteChannel)content;
ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), CHANNEL_BUFFER_DIRECT);
try
ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
try (ReadableByteChannel channel = (ReadableByteChannel)content;)
{
while(channel.isOpen())
{
@ -304,12 +303,12 @@ public class HttpOutput extends ServletOutputStream
}
else if (content instanceof InputStream)
{
InputStream in = (InputStream)content;
ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), STREAM_BUFFER_DIRECT);
// allocate non direct buffer so array may be directly accessed.
ByteBuffer buffer = _channel.getByteBufferPool().acquire(getBufferSize(), false);
byte[] array = buffer.array();
int offset=buffer.arrayOffset();
int size=array.length-offset;
try
try (InputStream in = (InputStream)content;)
{
while(true)
{

View File

@ -81,7 +81,7 @@ public class InclusiveByteRange
* @param size Size of the resource.
* @return LazyList of satisfiable ranges
*/
public static List satisfiableRanges(Enumeration headers, long size)
public static List<InclusiveByteRange> satisfiableRanges(Enumeration headers, long size)
{
Object satRanges=null;

View File

@ -63,6 +63,7 @@ import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
@ -115,9 +116,9 @@ import org.eclipse.jetty.util.log.Logger;
*/
public class Request implements HttpServletRequest
{
public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig";
public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream";
public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext";
public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.jetty.multiPartInputStream";
public static final String __MULTIPART_CONTEXT = "org.eclipse.jetty.multiPartContext";
private static final Logger LOG = Log.getLogger(Request.class);
private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
@ -1040,19 +1041,8 @@ public class Request implements HttpServletRequest
@Override
public StringBuffer getRequestURL()
{
final StringBuffer url = new StringBuffer(48);
String scheme = getScheme();
int port = getServerPort();
url.append(scheme);
url.append("://");
url.append(getServerName());
if (_port > 0 && ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) || (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443)))
{
url.append(':');
url.append(_port);
}
final StringBuffer url = new StringBuffer(128);
URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
url.append(getRequestURI());
return url;
}
@ -1076,19 +1066,8 @@ public class Request implements HttpServletRequest
*/
public StringBuilder getRootURL()
{
StringBuilder url = new StringBuilder(48);
String scheme = getScheme();
int port = getServerPort();
url.append(scheme);
url.append("://");
url.append(getServerName());
if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443)))
{
url.append(':');
url.append(port);
}
StringBuilder url = new StringBuilder(128);
URIUtil.appendSchemeHostPort(url,getScheme(),getServerName(),getServerPort());
return url;
}
@ -1118,41 +1097,58 @@ public class Request implements HttpServletRequest
// Return host from absolute URI
_serverName = _uri.getHost();
_port = _uri.getPort();
if (_serverName != null)
{
_port = _uri.getPort();
return _serverName;
}
// Return host from header field
String hostPort = _fields.getStringField(HttpHeader.HOST);
_port=0;
if (hostPort != null)
{
loop: for (int i = hostPort.length(); i-- > 0;)
int len=hostPort.length();
loop: for (int i = len; i-- > 0;)
{
char ch = (char)(0xff & hostPort.charAt(i));
switch (ch)
char c2 = (char)(0xff & hostPort.charAt(i));
switch (c2)
{
case ']':
break loop;
case ':':
_serverName = hostPort.substring(0,i);
try
{
len=i;
_port = StringUtil.toInt(hostPort.substring(i+1));
}
catch (NumberFormatException e)
{
LOG.warn(e);
}
_serverName=hostPort;
_port=0;
return _serverName;
}
break loop;
}
if (_serverName == null || _port < 0)
}
if (hostPort.charAt(0)=='[')
{
_serverName = hostPort;
_port = 0;
if (hostPort.charAt(len-1)!=']')
{
LOG.warn("Bad IPv6 "+hostPort);
_serverName=hostPort;
_port=0;
return _serverName;
}
_serverName = hostPort.substring(1,len-1);
}
else if (len==hostPort.length())
_serverName=hostPort;
else
_serverName = hostPort.substring(0,len);
return _serverName;
}

View File

@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -44,6 +45,7 @@ public class CheckReverseProxyHeadersTest
"X-Forwarded-For: 10.20.30.40\n" +
"X-Forwarded-Host: example.com", new RequestValidator()
{
@Override
public void validate(HttpServletRequest request)
{
assertEquals("example.com", request.getServerName());
@ -56,6 +58,42 @@ public class CheckReverseProxyHeadersTest
}
});
// IPv6 ProxyPass from example.com:80 to localhost:8080
testRequest("Host: localhost:8080\n" +
"X-Forwarded-For: 10.20.30.40\n" +
"X-Forwarded-Host: [::1]", new RequestValidator()
{
@Override
public void validate(HttpServletRequest request)
{
assertEquals("::1", request.getServerName());
assertEquals(80, request.getServerPort());
assertEquals("10.20.30.40", request.getRemoteAddr());
assertEquals("10.20.30.40", request.getRemoteHost());
assertEquals("[::1]", request.getHeader("Host"));
assertEquals("http",request.getScheme());
assertFalse(request.isSecure());
}
});
// IPv6 ProxyPass from example.com:80 to localhost:8080
testRequest("Host: localhost:8080\n" +
"X-Forwarded-For: 10.20.30.40\n" +
"X-Forwarded-Host: [::1]:8888", new RequestValidator()
{
@Override
public void validate(HttpServletRequest request)
{
assertEquals("::1", request.getServerName());
assertEquals(8888, request.getServerPort());
assertEquals("10.20.30.40", request.getRemoteAddr());
assertEquals("10.20.30.40", request.getRemoteHost());
assertEquals("[::1]:8888", request.getHeader("Host"));
assertEquals("http",request.getScheme());
assertFalse(request.isSecure());
}
});
// ProxyPass from example.com:81 to localhost:8080
testRequest("Host: localhost:8080\n" +
"X-Forwarded-For: 10.20.30.40\n" +
@ -63,6 +101,7 @@ public class CheckReverseProxyHeadersTest
"X-Forwarded-Server: example.com\n"+
"X-Forwarded-Proto: https", new RequestValidator()
{
@Override
public void validate(HttpServletRequest request)
{
assertEquals("example.com", request.getServerName());
@ -82,6 +121,7 @@ public class CheckReverseProxyHeadersTest
"X-Forwarded-Server: example.com, rp.example.com\n"+
"X-Forwarded-Proto: https, http", new RequestValidator()
{
@Override
public void validate(HttpServletRequest request)
{
assertEquals("example.com", request.getServerName());
@ -111,7 +151,8 @@ public class CheckReverseProxyHeadersTest
try
{
server.start();
connector.getResponses("GET / HTTP/1.1\r\n" +"Connection: close\r\n" + headers + "\r\n\r\n");
connector.getResponses("GET / HTTP/1.1\r\n" +"Connection: close\r\n" + headers + "\r\n\r\n",
1000,TimeUnit.SECONDS);
Error error = validationHandler.getError();

View File

@ -135,11 +135,11 @@ public class HttpURITest
/*33*/ {"/?abc=test",null, null, null,null,"/", null,"abc=test",null},
/*34*/ {"/#fragment",null, null, null,null,"/", null,null,"fragment"},
/*35*/ {"http://192.0.0.1:8080/","http","//192.0.0.1:8080","192.0.0.1","8080","/",null,null,null},
/*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
/*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
/*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","[2001:db8::1]",null,"/",null,null,null},
/*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
/*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
/*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","2001:db8::1",null,"/",null,null,null},
/*39*/ {"//[2001:db8::1]:8080/",null,null,null,null,"//[2001:db8::1]:8080/",null,null,null},
/*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
/*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","2001:db8::1","8080","/",null,null,null},
/*41*/ {"*",null,null,null,null,"*",null, null,null}
};
@ -366,7 +366,7 @@ public class HttpURITest
{
/* 0*/ {" localhost:8080 ","localhost","8080"},
/* 1*/ {" 127.0.0.1:8080 ","127.0.0.1","8080"},
/* 2*/ {" [127::0::0::1]:8080 ","[127::0::0::1]","8080"},
/* 2*/ {" [127::0::0::1]:8080 ","127::0::0::1","8080"},
/* 3*/ {" error ",null,null},
/* 4*/ {" http://localhost:8080/ ",null,null},
};
@ -382,7 +382,7 @@ public class HttpURITest
byte[] buf = connect_tests[i][0].getBytes(StringUtil.__UTF8);
uri.parseConnect(buf,2,buf.length-4);
assertEquals("path"+i,connect_tests[i][1]+":"+connect_tests[i][2],uri.getPath());
assertEquals("path"+i,connect_tests[i][0].trim(),uri.getPath());
assertEquals("host"+i,connect_tests[i][1],uri.getHost());
assertEquals("port"+i,Integer.parseInt(connect_tests[i][2]),uri.getPort());
}

View File

@ -354,6 +354,7 @@ public class RequestTest
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
results.add(request.getRequestURL().toString());
results.add(request.getRemoteAddr());
results.add(request.getServerName());
results.add(String.valueOf(request.getServerPort()));
@ -361,70 +362,120 @@ public class RequestTest
}
};
String responses=_connector.getResponses(
results.clear();
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"\n"+
"Connection: close\n"+
"\n");
int i=0;
assertEquals("http://myhost/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("80",results.get(i++));
results.clear();
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: myhost:8888\n"+
"\n"+
"Connection: close\n"+
"\n");
i=0;
assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("8888",results.get(i++));
results.clear();
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: 1.2.3.4\n"+
"\n"+
"Connection: close\n"+
"\n");
i=0;
assertEquals("http://1.2.3.4/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
assertEquals("80",results.get(i++));
results.clear();
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: 1.2.3.4:8888\n"+
"\n"+
"Connection: close\n"+
"\n");
i=0;
assertEquals("http://1.2.3.4:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
assertEquals("8888",results.get(i++));
results.clear();
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
"\n"+
"Connection: close\n"+
"\n");
i=0;
assertEquals("http://[::1]/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("::1",results.get(i++));
assertEquals("80",results.get(i++));
results.clear();
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
"\n"+
"Connection: close\n"+
"\n");
i=0;
assertEquals("http://[::1]:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("::1",results.get(i++));
assertEquals("8888",results.get(i++));
results.clear();
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]\n"+
"x-forwarded-for: remote\n"+
"x-forwarded-proto: https\n"+
"\n"+
"Connection: close\n"+
"\n");
i=0;
assertEquals("https://[::1]/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("::1",results.get(i++));
assertEquals("443",results.get(i++));
results.clear();
_connector.getResponses(
"GET / HTTP/1.1\n"+
"Host: [::1]:8888\n"+
"Connection: close\n"+
"x-forwarded-for: remote\n"+
"x-forwarded-proto: https\n"+
"\n",10,TimeUnit.SECONDS);
"\n");
i=0;
assertEquals("https://[::1]:8888/",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("::1",results.get(i++));
assertEquals("8888",results.get(i++));
int i=0;
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("80",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++));
assertEquals("8888",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
assertEquals("80",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++));
assertEquals("8888",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("[::1]",results.get(i++));
assertEquals("80",results.get(i++));
assertEquals("0.0.0.0",results.get(i++));
assertEquals("[::1]",results.get(i++));
assertEquals("8888",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("[::1]",results.get(i++));
assertEquals("443",results.get(i++));
assertEquals("remote",results.get(i++));
assertEquals("[::1]",results.get(i++));
assertEquals("8888",results.get(i++));
}

View File

@ -432,30 +432,36 @@ public class ResponseTest
String[][] tests = {
// No cookie
{"http://myhost:8888/other/location;jsessionid=12345?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"},
{"/other/location;jsessionid=12345?name=value","http://myhost:8888/other/location;jsessionid=12345?name=value"},
{"./location;jsessionid=12345?name=value","http://myhost:8888/path/location;jsessionid=12345?name=value"},
{"/other/location;jsessionid=12345?name=value","http://@HOST@@PORT@/other/location;jsessionid=12345?name=value"},
{"./location;jsessionid=12345?name=value","http://@HOST@@PORT@/path/location;jsessionid=12345?name=value"},
// From cookie
{"/other/location","http://myhost:8888/other/location"},
{"/other/l%20cation", "http://myhost:8888/other/l%20cation"},
{"location", "http://myhost:8888/path/location"},
{"./location", "http://myhost:8888/path/location"},
{"../location", "http://myhost:8888/location"},
{"/other/l%20cation", "http://myhost:8888/other/l%20cation"},
{"l%20cation", "http://myhost:8888/path/l%20cation"},
{"./l%20cation", "http://myhost:8888/path/l%20cation"},
{"../l%20cation","http://myhost:8888/l%20cation"},
{"../locati%C3%abn", "http://myhost:8888/locati%C3%ABn"},
{"//foo.bar/some/location", "http://foo.bar/some/location"},
{"/other/location","http://@HOST@@PORT@/other/location"},
{"/other/l%20cation", "http://@HOST@@PORT@/other/l%20cation"},
{"location", "http://@HOST@@PORT@/path/location"},
{"./location", "http://@HOST@@PORT@/path/location"},
{"../location", "http://@HOST@@PORT@/location"},
{"/other/l%20cation", "http://@HOST@@PORT@/other/l%20cation"},
{"l%20cation", "http://@HOST@@PORT@/path/l%20cation"},
{"./l%20cation", "http://@HOST@@PORT@/path/l%20cation"},
{"../l%20cation","http://@HOST@@PORT@/l%20cation"},
{"../locati%C3%abn", "http://@HOST@@PORT@/locati%C3%ABn"},
{"http://somehost.com/other/location","http://somehost.com/other/location"},
};
int[] ports=new int[]{8080,80};
String[] hosts=new String[]{"myhost","192.168.0.1","0::1"};
for (int port : ports)
{
for (String host : hosts)
{
for (int i=0;i<tests.length;i++)
{
Response response = newResponse();
Request request = response.getHttpChannel().getRequest();
request.setServerName("myhost");
request.setServerPort(8888);
request.setServerName(host);
request.setServerPort(port);
request.setUri(new HttpURI("/path/info;param;jsessionid=12345?query=0&more=1#target"));
request.setContextPath("/path");
request.setRequestedSessionId("12345");
@ -469,7 +475,11 @@ public class ResponseTest
response.sendRedirect(tests[i][0]);
String location = response.getHeader("Location");
assertEquals("test-"+i,tests[i][1],location);
String expected=tests[i][1].replace("@HOST@",host.contains(":")?("["+host+"]"):host).replace("@PORT@",port==80?"":(":"+port));
assertEquals("test-"+i+" "+host+":"+port,expected,location);
}
}
}
}

View File

@ -27,7 +27,6 @@ import java.net.URL;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
@ -42,9 +41,8 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PathMap.MappedEntry;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.InclusiveByteRange;
import org.eclipse.jetty.server.ResourceCache;
@ -267,7 +265,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
throw new UnavailableException(e.toString());
}
_servletHandler= (ServletHandler) _contextHandler.getChildHandlerByClass(ServletHandler.class);
_servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
for (ServletHolder h :_servletHandler.getServlets())
if (h.getServletInstance()==this)
_defaultHolder=h;
@ -342,6 +340,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
* @param pathInContext The path to find a resource for.
* @return The resource to serve.
*/
@Override
public Resource getResource(String pathInContext)
{
Resource r=null;
@ -631,7 +630,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
{
Map.Entry entry=_servletHandler.getHolderEntry(welcome_in_context);
MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
if (entry!=null && entry.getValue()!=_defaultHolder &&
(_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
welcome_servlet=welcome_in_context;
@ -827,24 +826,10 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
boolean include,
Resource resource,
HttpContent content,
Enumeration reqRanges)
Enumeration<String> reqRanges)
throws IOException
{
boolean direct;
long content_length;
if (content==null)
{
direct=false;
content_length=resource.length();
}
else
{
// TODO sometimes we should be direct!
Connector connector = HttpChannel.getCurrentHttpChannel().getConnector();
direct=false;
content_length=content.getContentLength();
}
final long content_length = (content==null)?resource.length():content.getContentLength();
// Get the output stream (or writer)
OutputStream out =null;
@ -882,18 +867,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
((HttpOutput)out).sendContent(content);
}
else
{
ByteBuffer buffer = direct?content.getDirectBuffer():content.getIndirectBuffer();
if (buffer!=null)
{
writeHeaders(response,content,content_length);
((HttpOutput)out).sendContent(buffer);
}
else
{
writeHeaders(response,content,content_length);
resource.writeTo(out,0,content_length);
}
((HttpOutput)out).sendContent(content.getResource());
}
}
else
@ -913,7 +889,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
else
{
// Parse the satisfiable ranges
List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
// if there are no satisfiable ranges, send 416 response
if (ranges==null || ranges.size()==0)
@ -930,8 +906,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
// since were here now), send that range with a 216 response
if ( ranges.size()== 1)
{
InclusiveByteRange singleSatisfiableRange =
(InclusiveByteRange)ranges.get(0);
InclusiveByteRange singleSatisfiableRange = ranges.get(0);
long singleLength = singleSatisfiableRange.getSize(content_length);
writeHeaders(response,content,singleLength );
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
@ -946,7 +921,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
// content-length header
//
writeHeaders(response,content,-1);
String mimetype=(content.getContentType()==null?null:content.getContentType().toString());
String mimetype=(content==null||content.getContentType()==null?null:content.getContentType().toString());
if (mimetype==null)
LOG.warn("Unknown mimetype for "+request.getRequestURI());
MultiPartOutputStream multi = new MultiPartOutputStream(out);
@ -970,7 +945,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
String[] header = new String[ranges.size()];
for (int i=0;i<ranges.size();i++)
{
InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
InclusiveByteRange ibr = ranges.get(i);
header[i]=ibr.toHeaderRangeString(content_length);
length+=
((i>0)?2:0)+
@ -985,7 +960,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
for (int i=0;i<ranges.size();i++)
{
InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
InclusiveByteRange ibr = ranges.get(i);
multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
long start=ibr.getFirst(content_length);
@ -1022,7 +997,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
/* ------------------------------------------------------------ */
protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
throws IOException
{
if (content.getContentType()!=null && response.getContentType()==null)
response.setContentType(content.getContentType().toString());

View File

@ -113,10 +113,10 @@ public class ServletHandler extends ScopedHandler
private MultiMap<FilterMapping> _filterNameMappings;
private final Map<String,ServletHolder> _servletNameMap=new HashMap<>();
private PathMap _servletPathMap;
private PathMap<ServletHolder> _servletPathMap;
protected final ConcurrentMap _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
protected final Queue[] _chainLRU = new Queue[FilterMapping.ALL];
protected final ConcurrentMap<?, ?> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
protected final Queue<?>[] _chainLRU = new Queue[FilterMapping.ALL];
/* ------------------------------------------------------------ */
@ -253,7 +253,7 @@ public class ServletHandler extends ScopedHandler
* @param pathInContext Path within _context.
* @return PathMap Entries pathspec to ServletHolder
*/
public PathMap.MappedEntry getHolderEntry(String pathInContext)
public PathMap.MappedEntry<ServletHolder> getHolderEntry(String pathInContext)
{
if (_servletPathMap==null)
return null;
@ -334,10 +334,10 @@ public class ServletHandler extends ScopedHandler
if (target.startsWith("/"))
{
// Look for the servlet by path
PathMap.MappedEntry entry=getHolderEntry(target);
PathMap.MappedEntry<ServletHolder> entry=getHolderEntry(target);
if (entry!=null)
{
servlet_holder=(ServletHolder)entry.getValue();
servlet_holder=entry.getValue();
String servlet_path_spec= entry.getKey();
String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
@ -345,8 +345,8 @@ public class ServletHandler extends ScopedHandler
if (DispatcherType.INCLUDE.equals(type))
{
baseRequest.setAttribute(Dispatcher.INCLUDE_SERVLET_PATH,servlet_path);
baseRequest.setAttribute(Dispatcher.INCLUDE_PATH_INFO, path_info);
baseRequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH,servlet_path);
baseRequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, path_info);
}
else
{
@ -618,8 +618,8 @@ public class ServletHandler extends ScopedHandler
if (filters.size() > 0)
chain= new CachedChain(filters, servletHolder);
final Map<String,FilterChain> cache=_chainCache[dispatch];
final Queue<String> lru=_chainLRU[dispatch];
final Map<String,FilterChain> cache=(Map<String, FilterChain>)_chainCache[dispatch];
final Queue<String> lru=(Queue<String>)_chainLRU[dispatch];
// Do we have too many cached chains?
while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
@ -721,7 +721,7 @@ public class ServletHandler extends ScopedHandler
{
if (servlet.getClassName() == null && servlet.getForcedPath() != null)
{
ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlet.getForcedPath());
ServletHolder forced_holder = _servletPathMap.match(servlet.getForcedPath());
if (forced_holder == null || forced_holder.getClassName() == null)
{
mx.add(new IllegalStateException("No forced path servlet for " + servlet.getForcedPath()));

View File

@ -19,7 +19,6 @@
package org.eclipse.jetty.servlet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
@ -345,6 +344,45 @@ public class AsyncServletTest
Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
}
@Test
public void testAsyncRead() throws Exception
{
String header="GET /ctx/path/info?suspend=2000&resume=1500 HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"Content-Length: 10\r\n"+
"\r\n";
String body="12345678\r\n";
String close="GET /ctx/path/info HTTP/1.1\r\n"+
"Host: localhost\r\n"+
"Connection: close\r\n"+
"\r\n";
try (Socket socket = new Socket("localhost",_port);)
{
socket.setSoTimeout(10000);
socket.getOutputStream().write(header.getBytes("ISO-8859-1"));
Thread.sleep(500);
socket.getOutputStream().write(body.getBytes("ISO-8859-1"),0,2);
Thread.sleep(500);
socket.getOutputStream().write(body.getBytes("ISO-8859-1"),2,8);
socket.getOutputStream().write(close.getBytes("ISO-8859-1"));
String response = IO.toString(socket.getInputStream());
assertEquals("HTTP/1.1 200 OK",response.substring(0,15));
assertContains(
"history: REQUEST\r\n"+
"history: initial\r\n"+
"history: suspend\r\n"+
"history: async-read=10\r\n"+
"history: resume\r\n"+
"history: ASYNC\r\n"+
"history: !initial\r\n"+
"history: onComplete\r\n",response);
}
}
public synchronized String process(String query,String content) throws Exception
{
String request = "GET /ctx/path/info";
@ -364,9 +402,8 @@ public class AsyncServletTest
int port=_port;
String response=null;
try
try (Socket socket = new Socket("localhost",port);)
{
Socket socket = new Socket("localhost",port);
socket.setSoTimeout(1000000);
socket.getOutputStream().write(request.getBytes("UTF-8"));
@ -379,13 +416,12 @@ public class AsyncServletTest
throw e;
}
// System.err.println(response);
return response;
}
private static class AsyncServlet extends HttpServlet
{
private static final long serialVersionUID = -8161977157098646562L;
@ -429,7 +465,7 @@ public class AsyncServletTest
if (request.getDispatcherType()==DispatcherType.REQUEST)
{
((HttpServletResponse)response).addHeader("history","initial");
response.addHeader("history","initial");
if (read_before>0)
{
byte[] buf=new byte[read_before];
@ -442,6 +478,30 @@ public class AsyncServletTest
while(b!=-1)
b=in.read();
}
else if (request.getContentLength()>0)
{
new Thread()
{
@Override
public void run()
{
int c=0;
try
{
InputStream in=request.getInputStream();
int b=0;
while(b!=-1)
if((b=in.read())>=0)
c++;
response.addHeader("history","async-read="+c);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}.start();
}
if (suspend_for>=0)
{
@ -449,7 +509,7 @@ public class AsyncServletTest
if (suspend_for>0)
async.setTimeout(suspend_for);
async.addListener(__listener);
((HttpServletResponse)response).addHeader("history","suspend");
response.addHeader("history","suspend");
if (complete_after>0)
{
@ -527,7 +587,7 @@ public class AsyncServletTest
}
else
{
((HttpServletResponse)response).addHeader("history","!initial");
response.addHeader("history","!initial");
if (suspend2_for>=0 && request.getAttribute("2nd")==null)
{
@ -540,7 +600,7 @@ public class AsyncServletTest
async.setTimeout(suspend2_for);
}
// continuation.addContinuationListener(__listener);
((HttpServletResponse)response).addHeader("history","suspend");
response.addHeader("history","suspend");
if (complete2_after>0)
{
@ -581,7 +641,7 @@ public class AsyncServletTest
@Override
public void run()
{
((HttpServletResponse)response).addHeader("history","resume");
response.addHeader("history","resume");
async.dispatch();
}
};
@ -592,7 +652,7 @@ public class AsyncServletTest
}
else if (resume2_after==0)
{
((HttpServletResponse)response).addHeader("history","dispatch");
response.addHeader("history","dispatch");
async.dispatch();
}
}
@ -633,15 +693,11 @@ public class AsyncServletTest
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
// TODO Auto-generated method stub
}
@Override
public void onError(AsyncEvent event) throws IOException
{
// TODO Auto-generated method stub
}
@Override

View File

@ -89,6 +89,18 @@
<npn.version>1.1.5.v20130313</npn.version>
</properties>
</profile>
<profile>
<id>7u21</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_21</value>
</property>
</activation>
<properties>
<npn.version>1.1.5.v20130313</npn.version>
</properties>
</profile>
</profiles>
<modules>

View File

@ -32,7 +32,7 @@ public class PushSynInfo extends SynInfo
private int associatedStreamId;
public PushSynInfo(int associatedStreamId, PushInfo pushInfo){
super(pushInfo.getHeaders(), pushInfo.isClose());
super(pushInfo.getTimeout(), pushInfo.getUnit(), pushInfo.getHeaders(), pushInfo.isClose(), (byte)0);
this.associatedStreamId = associatedStreamId;
}

View File

@ -284,8 +284,8 @@ public class HttpTransportOverSPDY implements HttpTransport
Fields pushHeaders = createPushHeaders(scheme, host, pushResource);
final Fields pushRequestHeaders = createRequestHeaders(scheme, host, uri, pushResource);
// TODO: handle the timeout better
stream.push(new PushInfo(0, TimeUnit.MILLISECONDS, pushHeaders, false), new Promise.Adapter<Stream>()
stream.push(new PushInfo(pushHeaders, false),
new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream pushStream)

View File

@ -50,6 +50,8 @@ import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import javax.naming.OperationNotSupportedException;
/*-------------------------------------------*/
/**
* <p>
@ -176,6 +178,12 @@ public class Main
return null;
}
if (arg.startsWith("--download="))
{
download(arg);
continue;
}
if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
{
_dumpVersions = true;
@ -302,6 +310,121 @@ public class Main
return xmls;
}
private void download(String arg)
{
try
{
String[] split = arg.split(":",3);
if (split.length!=3 || "http".equalsIgnoreCase(split[0]) || !split[1].startsWith("//"))
throw new IllegalArgumentException("Not --download=<http uri>:<location>");
String location=split[2];
if (File.separatorChar!='/')
location.replaceAll("/",File.separator);
File file = new File(location);
if (Config.isDebug())
System.err.println("Download to "+file.getAbsolutePath()+(file.exists()?" Exists!":""));
if (file.exists())
return;
String[] parse = split[1].split("/",4);
String host=parse[2];
String uri="/"+split[1].substring(3+host.length());
try (Socket socket = new Socket(host,80))
{
String request="GET "+uri+" HTTP/1.0\r\n"+
"Host: "+host+"\r\n"+
"User-Agent: jetty-start.jar\r\n"+
"\r\n";
socket.getOutputStream().write(request.getBytes("ISO-8859-1"));
socket.getOutputStream().flush();
InputStream in = socket.getInputStream();
int state=0;
loop: while (state>=0)
{
char c = (char)in.read();
switch (state)
{
case 0:
c=Character.toLowerCase(c);
if (c==' ')
state=1;
else if (c!='h' && c!='t' && c!='p' && c!='/' && c!='.' && !Character.isDigit(c))
break loop;
break;
case 1:
if (c==' ')
state=2;
else if (c!='2' && c!='0')
break loop;
break;
case 2:
if (c=='\r')
state=3;
else if (c=='\n')
state=4;
break;
case 3:
if (c=='\n')
state=4;
else if (c=='\r')
state=-1;
break;
case 4:
if (c=='\r')
state=5;
else if (c=='\n')
state=-1;
else
state=2;
break;
case 5:
if (c=='\n')
state=-1;
else
state=2;
break;
}
}
if (state>=0)
throw new IOException("Bad HTTP response: "+state);
System.err.println("DOWNLOAD: "+uri+" from "+host+" to "+location);
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
byte[] buf=new byte[8192];
try (OutputStream out = new FileOutputStream(file))
{
while(!socket.isInputShutdown())
{
int len = in.read(buf);
if (len>0)
out.write(buf,0,len);
if (len<0)
break;
}
}
}
}
catch(Exception e)
{
System.err.println("ERROR: processing "+arg+"\n"+e);
e.printStackTrace();
usageExit(EXIT_USAGE);
}
}
private void usage()
{
String usageResource = "org/eclipse/jetty/start/usage.txt";

View File

@ -46,6 +46,10 @@ Command Line Options:
will NOT be read. A --ini option with no file indicates that
start.ini should not be read.
--download=<http-uri>:location
If the file does not exist at the given location, then
download it from the given http URI
System Properties:
These are set with a command line like "java -Dname=value ..." and are
accessible via the java.lang.System#getProperty(String) API.

View File

@ -657,6 +657,30 @@ public class URIUtil
return false;
}
public static void appendSchemeHostPort(StringBuilder url,String scheme,String server, int port)
{
if (server.indexOf(':')>=0&&server.charAt(0)!='[')
url.append(scheme).append("://").append('[').append(server).append(']');
else
url.append(scheme).append("://").append(server);
if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
url.append(':').append(port);
}
public static void appendSchemeHostPort(StringBuffer url,String scheme,String server, int port)
{
synchronized (url)
{
if (server.indexOf(':')>=0&&server.charAt(0)!='[')
url.append(scheme).append("://").append('[').append(server).append(']');
else
url.append(scheme).append("://").append(server);
if (port > 0 && (("http".equalsIgnoreCase(scheme) && port != 80) || ("https".equalsIgnoreCase(scheme) && port != 443)))
url.append(':').append(port);
}
}
}

View File

@ -22,8 +22,9 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParseListener;
public class ClientUpgradeResponse extends UpgradeResponse
public class ClientUpgradeResponse extends UpgradeResponse implements HttpResponseHeaderParseListener
{
private ByteBuffer remainingBuffer;
@ -43,6 +44,7 @@ public class ClientUpgradeResponse extends UpgradeResponse
throw new UnsupportedOperationException("Not supported on client implementation");
}
@Override
public void setRemainingBuffer(ByteBuffer remainingBuffer)
{
this.remainingBuffer = remainingBuffer;

View File

@ -39,11 +39,12 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser.ParseException;
import org.eclipse.jetty.websocket.common.AcceptHash;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.extensions.ExtensionStack;
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser;
import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException;
/**
* This is the initial connection handling that exists immediately after physical connection is established to destination server.
@ -92,7 +93,7 @@ public class UpgradeConnection extends AbstractConnection
this.request = connectPromise.getRequest();
// Setup the parser
this.parser = new HttpResponseHeaderParser();
this.parser = new HttpResponseHeaderParser(new ClientUpgradeResponse());
}
public void disconnect(boolean onlyOutput)
@ -173,7 +174,7 @@ public class UpgradeConnection extends AbstractConnection
{
LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
}
ClientUpgradeResponse resp = parser.parse(buffer);
ClientUpgradeResponse resp = (ClientUpgradeResponse)parser.parse(buffer);
if (resp != null)
{
// Got a response!

View File

@ -104,8 +104,8 @@ public class TimeoutTest
// Make sure idle timeout takes less than 5 total seconds
Assert.assertThat("Idle Timeout",dur,lessThanOrEqualTo(5000L));
// Client should see a close event, with status NO_CLOSE
wsocket.assertCloseCode(StatusCode.NORMAL);
// Client should see a close event, with status SHUTDOWN
wsocket.assertCloseCode(StatusCode.SHUTDOWN);
}
finally
{

View File

@ -20,13 +20,12 @@ package org.eclipse.jetty.websocket.client;
import static org.hamcrest.Matchers.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
@ -46,8 +45,8 @@ public class TrackingSocket extends WebSocketAdapter
public CountDownLatch openLatch = new CountDownLatch(1);
public CountDownLatch closeLatch = new CountDownLatch(1);
public CountDownLatch dataLatch = new CountDownLatch(1);
public BlockingQueue<String> messageQueue = new BlockingArrayQueue<String>();
public BlockingQueue<Throwable> errorQueue = new BlockingArrayQueue<>();
public EventQueue<String> messageQueue = new EventQueue<>();
public EventQueue<Throwable> errorQueue = new EventQueue<>();
public void assertClose(int expectedStatusCode, String expectedReason) throws InterruptedException
{
@ -93,29 +92,9 @@ public class TrackingSocket extends WebSocketAdapter
Assert.assertThat("Was Opened",openLatch.await(500,TimeUnit.MILLISECONDS),is(true));
}
public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException
public void awaitMessage(int expectedMessageCount, TimeUnit timeoutUnit, int timeoutDuration) throws TimeoutException, InterruptedException
{
long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit);
long now = System.currentTimeMillis();
long expireOn = now + msDur;
LOG.debug("Await Message.. Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur);
while (messageQueue.size() < expectedMessageCount)
{
try
{
TimeUnit.MILLISECONDS.sleep(20);
}
catch (InterruptedException gnore)
{
/* ignore */
}
if (!LOG.isDebugEnabled() && (System.currentTimeMillis() > expireOn))
{
throw new TimeoutException(String.format("Timeout reading all %d expected messages. (managed to only read %d messages)",expectedMessageCount,
messageQueue.size()));
}
}
messageQueue.awaitEventCount(expectedMessageCount,timeoutDuration,timeoutUnit);
}
public void clear()

View File

@ -54,6 +54,7 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.Frame.Type;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.common.AcceptHash;
@ -219,6 +220,12 @@ public class BlockheadServer
}
WebSocketFrame copy = new WebSocketFrame(frame);
incomingFrames.incomingFrame(copy);
if (frame.getType() == Type.CLOSE)
{
CloseInfo close = new CloseInfo(frame);
LOG.debug("Close frame: {}",close);
}
}
@Override

View File

@ -18,13 +18,43 @@
package org.eclipse.jetty.websocket.common;
import org.eclipse.jetty.websocket.common.io.IOState;
/**
* Connection states as outlined in <a href="https://tools.ietf.org/html/rfc6455">RFC6455</a>.
*/
public enum ConnectionState
{
/** [RFC] Initial state of a connection, the upgrade request / response is in progress */
CONNECTING,
/**
* [Impl] Intermediate state between CONNECTING and OPEN, used to indicate that a upgrade request/response is successful, but the end-user provided socket's
* onOpen code has yet to run.
* <p>
* This state is to allow the local socket to initiate messages and frames, but to NOT start reading yet.
*/
CONNECTED,
/**
* [RFC] The websocket connection is established and open.
* <p>
* This indicates that the Upgrade has succeed, and the end-user provided socket's onOpen code has completed.
* <p>
* It is now time to start reading from the remote endpoint.
*/
OPEN,
/**
* [RFC] The websocket closing handshake is started.
* <p>
* This can be considered a half-closed state.
* <p>
* When receiving this as an event on {@link IOState.ConnectionStateListener#onConnectionStateChange(ConnectionState)} a close frame should be sent using
* the {@link CloseInfo} available from {@link IOState#getCloseInfo()}
*/
CLOSING,
/**
* [RFC] The websocket connection is closed.
* <p>
* Connection should be disconnected and no further reads or writes should occur.
*/
CLOSED;
}

View File

@ -48,9 +48,11 @@ import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.io.IOState;
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
@ManagedObject
public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames
@ManagedObject("A Jetty WebSocket Session")
public class WebSocketSession extends ContainerLifeCycle implements Session, IncomingFrames, ConnectionStateListener
{
private static final Logger LOG = Log.getLogger(WebSocketSession.class);
private final URI requestURI;
@ -80,6 +82,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
this.outgoingHandler = connection;
this.incomingHandler = websocket;
this.connection.getIOState().addListener(this);
// Get the parameter map (use the jetty MultiMap to do this right)
MultiMap<String> params = new MultiMap<>();
String query = requestURI.getQuery();
@ -254,6 +258,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
return remote.getInetSocketAddress();
}
public URI getRequestURI()
{
return requestURI;
}
@Override
public UpgradeRequest getUpgradeRequest()
{
@ -281,13 +290,12 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
@Override
public void incomingError(WebSocketException e)
{
if (connection.getIOState().isInputClosed())
if (connection.getIOState().isInputAvailable())
{
return; // input is closed
}
// Forward Errors to User WebSocket Object
websocket.incomingError(e);
}
}
/**
* Incoming Raw Frames from Parser
@ -295,14 +303,12 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
@Override
public void incomingFrame(Frame frame)
{
if (connection.getIOState().isInputClosed())
if (connection.getIOState().isInputAvailable())
{
return; // input is closed
}
// Forward Frames Through Extension List
incomingHandler.incomingFrame(frame);
}
}
@Override
public boolean isOpen()
@ -332,6 +338,24 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
websocket.onClose(new CloseInfo(statusCode,reason));
}
@Override
public void onConnectionStateChange(ConnectionState state)
{
if (state == ConnectionState.CLOSED)
{
IOState ioState = this.connection.getIOState();
// The session only cares about abnormal close, as we need to notify
// the endpoint of this close scenario.
if (ioState.wasAbnormalClose())
{
CloseInfo close = ioState.getCloseInfo();
LOG.debug("Detected abnormal close: {}",close);
// notify local endpoint
notifyClose(close.getStatusCode(),close.getReason());
}
}
}
/**
* Open/Activate the session
*
@ -345,12 +369,18 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc
return;
}
// Upgrade success
connection.getIOState().onConnected();
// Connect remote
remote = new WebSocketRemoteEndpoint(connection,outgoingHandler);
// Open WebSocket
websocket.openSession(this);
// Open connection
connection.getIOState().onOpened();
if (LOG.isDebugEnabled())
{
LOG.debug("open -> {}",dump());

View File

@ -103,16 +103,7 @@ public abstract class EventDriver implements IncomingFrames
onClose(close);
// process handshake
if (session.getConnection().getIOState().onCloseHandshake(true))
{
// handshake resolved, disconnect.
session.getConnection().disconnect();
}
else
{
// respond
session.close(close.getStatusCode(),close.getReason());
}
session.getConnection().getIOState().onCloseRemote(close);
return;
}

View File

@ -38,11 +38,12 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.io.FutureWriteCallback;
import org.eclipse.jetty.websocket.common.io.IOState;
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
/**
* MuxChannel, acts as WebSocketConnection for specific sub-channel.
*/
public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken
public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendToken, ConnectionStateListener
{
private static final Logger LOG = Log.getLogger(MuxChannel.class);
@ -65,7 +66,7 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok
this.suspendToken = new AtomicBoolean(false);
this.ioState = new IOState();
ioState.setState(ConnectionState.CONNECTING);
this.ioState.addListener(this);
this.inputClosed = new AtomicBoolean(false);
this.outputClosed = new AtomicBoolean(false);
@ -88,7 +89,6 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok
@Override
public void disconnect()
{
this.ioState.setState(ConnectionState.CLOSED);
// TODO: disconnect the virtual end-point?
}
@ -173,12 +173,18 @@ public class MuxChannel implements LogicalConnection, IncomingFrames, SuspendTok
public void onClose()
{
this.ioState.setState(ConnectionState.CLOSED);
}
@Override
public void onConnectionStateChange(ConnectionState state)
{
// TODO Auto-generated method stub
}
public void onOpen()
{
this.ioState.setState(ConnectionState.OPEN);
this.ioState.onOpened();
}
/**

View File

@ -56,11 +56,12 @@ import org.eclipse.jetty.websocket.common.Generator;
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.Parser;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
/**
* Provides the implementation of {@link WebSocketConnection} within the framework of the new {@link Connection} framework of jetty-io
*/
public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection
public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, ConnectionStateListener
{
private class FlushCallback implements Callback
{
@ -141,21 +142,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
}
}
private class OnCloseCallback implements WriteCallback
{
@Override
public void writeFailed(Throwable x)
{
disconnect();
}
@Override
public void writeSuccess()
{
onWriteWebSocketClose();
}
}
public static class Stats
{
private AtomicLong countFillInterestedEvents = new AtomicLong(0);
@ -211,7 +197,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
this.extensions = new ArrayList<>();
this.suspendToken = new AtomicBoolean(false);
this.ioState = new IOState();
this.ioState.setState(ConnectionState.CONNECTING);
this.ioState.addListener(this);
this.writeBytes = new WriteBytesProvider(generator,new FlushCallback());
this.setInputBufferSize(policy.getInputBufferSize());
}
@ -247,12 +233,18 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
@Override
public void disconnect()
{
synchronized (writeBytes)
{
if (!writeBytes.isClosed())
{
writeBytes.close();
}
}
disconnect(false);
}
public void disconnect(boolean onlyOutput)
{
ioState.setState(ConnectionState.CLOSED);
EndPoint endPoint = getEndPoint();
// We need to gently close first, to allow
// SSL close alerts to be sent by Jetty
@ -276,18 +268,8 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
*/
private void enqueClose(int statusCode, String reason)
{
synchronized (writeBytes)
{
// It is possible to get close events from many different sources.
// Make sure we only sent 1 over the network.
if (writeBytes.isClosed())
{
// already sent the close
return;
}
}
CloseInfo close = new CloseInfo(statusCode,reason);
outgoingFrame(close.asFrame(),new OnCloseCallback());
ioState.onCloseLocal(close);
}
protected void execute(Runnable task)
@ -438,10 +420,31 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
public void onClose()
{
super.onClose();
this.getIOState().setState(ConnectionState.CLOSED);
writeBytes.close();
}
@Override
public void onConnectionStateChange(ConnectionState state)
{
LOG.debug("Connection State Change: {}",state);
switch (state)
{
case OPEN:
LOG.debug("fillInterested");
fillInterested();
break;
case CLOSED:
this.disconnect();
break;
case CLOSING:
CloseInfo close = ioState.getCloseInfo();
// append close frame
outgoingFrame(close.asFrame(),null);
default:
break;
}
}
@Override
public void onFillable()
{
@ -482,18 +485,16 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
public void onOpen()
{
super.onOpen();
this.ioState.setState(ConnectionState.OPEN);
LOG.debug("fillInterested");
fillInterested();
this.ioState.onOpened();
}
@Override
protected boolean onReadTimeout()
{
LOG.info("Read Timeout");
LOG.debug("Read Timeout");
IOState state = getIOState();
if ((state.getState() == ConnectionState.CLOSING) || (state.getState() == ConnectionState.CLOSED))
if ((state.getConnectionState() == ConnectionState.CLOSING) || (state.getConnectionState() == ConnectionState.CLOSED))
{
// close already initiated, extra timeouts not relevant
// allow underlying connection and endpoint to disconnect on its own
@ -501,24 +502,12 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
}
// Initiate close - politely send close frame.
// Note: it is not possible in 100% of cases during read timeout to send this close frame.
session.incomingError(new WebSocketTimeoutException("Timeout on Read"));
session.close(StatusCode.NORMAL,"Idle Timeout");
// Force closure of writeBytes
writeBytes.close();
close(StatusCode.SHUTDOWN,"Idle Timeout");
return false;
}
public void onWriteWebSocketClose()
{
if (ioState.onCloseHandshake(false))
{
disconnect();
}
}
/**
* Frame from API, User, or Internal implementation destined for network.
*/
@ -550,6 +539,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
else if (filled < 0)
{
LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
ioState.onReadEOF();
return -1;
}
else

View File

@ -19,39 +19,79 @@
package org.eclipse.jetty.websocket.common.io;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.ConnectionState;
/**
* Simple state tracker for Input / Output and {@link ConnectionState}
* Simple state tracker for Input / Output and {@link ConnectionState}.
* <p>
* Use the various known .on*() methods to trigger a state change.
* <ul>
* <li>{@link #onOpened()} - connection has been opened</li>
* </ul>
*/
public class IOState
{
/**
* The source of a close handshake. (ie: who initiated it).
*/
private static enum CloseHandshakeSource
{
/** No close handshake initiated (yet) */
NONE,
/** Local side initiated the close handshake */
LOCAL,
/** Remote side initiated the close handshake */
REMOTE,
/** An abnormal close situation (disconnect, timeout, etc...) */
ABNORMAL;
}
public static interface ConnectionStateListener
{
public void onConnectionStateChange(ConnectionState state);
}
private static final Logger LOG = Log.getLogger(IOState.class);
private ConnectionState state;
private final AtomicBoolean inputClosed;
private final AtomicBoolean outputClosed;
private final List<ConnectionStateListener> listeners = new CopyOnWriteArrayList<>();
private final AtomicBoolean inputAvailable;
private final AtomicBoolean outputAvailable;
private final AtomicReference<CloseHandshakeSource> closeHandshakeSource;
private final AtomicReference<CloseInfo> closeInfo;
private final AtomicBoolean cleanClose;
private final AtomicBoolean remoteCloseInitiated;
private final AtomicBoolean localCloseInitiated;
/**
* Create a new IOState, initialized to {@link ConnectionState#CONNECTING}
*/
public IOState()
{
this.state = ConnectionState.CONNECTING;
this.inputClosed = new AtomicBoolean(false);
this.outputClosed = new AtomicBoolean(false);
this.remoteCloseInitiated = new AtomicBoolean(false);
this.localCloseInitiated = new AtomicBoolean(false);
this.inputAvailable = new AtomicBoolean(false);
this.outputAvailable = new AtomicBoolean(false);
this.closeHandshakeSource = new AtomicReference<>(CloseHandshakeSource.NONE);
this.closeInfo = new AtomicReference<>();
this.cleanClose = new AtomicBoolean(false);
}
public void addListener(ConnectionStateListener listener)
{
listeners.add(listener);
}
public void assertInputOpen() throws IOException
{
if (isInputClosed())
if (!isInputAvailable())
{
throw new IOException("Connection input is closed");
}
@ -59,15 +99,15 @@ public class IOState
public void assertOutputOpen() throws IOException
{
if (isOutputClosed())
if (!isOutputAvailable())
{
throw new IOException("Connection output is closed");
}
}
public boolean awaitClosed(long duration)
public CloseInfo getCloseInfo()
{
return (isInputClosed() && isOutputClosed());
return closeInfo.get();
}
public ConnectionState getConnectionState()
@ -75,90 +115,283 @@ public class IOState
return state;
}
public ConnectionState getState()
{
return state;
}
public boolean isClosed()
{
return (isInputClosed() && isOutputClosed());
synchronized (state)
{
return (state == ConnectionState.CLOSED);
}
}
public boolean isCloseInitiated()
public boolean isInputAvailable()
{
return remoteCloseInitiated.get() || localCloseInitiated.get();
}
public boolean isInputClosed()
{
return inputClosed.get();
return inputAvailable.get();
}
public boolean isOpen()
{
return (getState() != ConnectionState.CLOSED);
return (getConnectionState() != ConnectionState.CLOSED);
}
public boolean isOutputClosed()
public boolean isOutputAvailable()
{
return outputClosed.get();
return outputAvailable.get();
}
private void notifyStateListeners(ConnectionState state)
{
for (ConnectionStateListener listener : listeners)
{
listener.onConnectionStateChange(state);
}
}
/**
* Test for if connection should disconnect or response on a close handshake.
*
* @param incoming
* true if incoming close
* @param close
* the close details.
* @return true if connection should be disconnected now, or false if response to close should be issued.
* A websocket connection has been disconnected for abnormal close reasons.
* <p>
* This is the low level disconnect of the socket. It could be the result of a normal close operation, from an IO error, or even from a timeout.
*/
public boolean onCloseHandshake(boolean incoming)
public void onAbnormalClose(CloseInfo close)
{
boolean in = inputClosed.get();
boolean out = outputClosed.get();
if (incoming)
ConnectionState event = null;
synchronized (this.state)
{
in = true;
this.inputClosed.set(true);
if (!localCloseInitiated.get())
if (this.state == ConnectionState.CLOSED)
{
remoteCloseInitiated.set(true);
}
}
else
{
out = true;
this.outputClosed.set(true);
if ( !remoteCloseInitiated.get() )
{
localCloseInitiated.set(true);
}
// already closed
return;
}
LOG.debug("onCloseHandshake({}), input={}, output={}",incoming,in,out);
if (this.state == ConnectionState.OPEN)
{
this.cleanClose.set(false);
}
if (in && out)
this.state = ConnectionState.CLOSED;
this.closeInfo.compareAndSet(null,close);
this.inputAvailable.set(false);
this.outputAvailable.set(false);
this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
event = this.state;
}
notifyStateListeners(event);
}
/**
* A close handshake has been issued from the local endpoint
*/
public void onCloseLocal(CloseInfo close)
{
ConnectionState event = null;
ConnectionState initialState = this.state;
if (initialState == ConnectionState.CLOSED)
{
// already closed
return;
}
if (initialState == ConnectionState.CONNECTED)
{
// fast close. a local close request from end-user onConnected() method
LOG.debug("FastClose in CONNECTED detected");
// Force the state open (to allow read/write to endpoint)
onOpened();
}
synchronized (this.state)
{
closeInfo.compareAndSet(null,close);
boolean in = inputAvailable.get();
boolean out = outputAvailable.get();
closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.LOCAL);
out = false;
outputAvailable.set(false);
LOG.debug("onCloseLocal(), input={}, output={}",in,out);
if (!in && !out)
{
LOG.debug("Close Handshake satisfied, disconnecting");
cleanClose.set(true);
return true;
this.state = ConnectionState.CLOSED;
event = this.state;
}
return false;
}
public void setConnectionState(ConnectionState connectionState)
else if (this.state == ConnectionState.OPEN)
{
this.state = connectionState;
// We are now entering CLOSING (or half-closed)
this.state = ConnectionState.CLOSING;
event = this.state;
}
}
public void setState(ConnectionState state)
// Only notify on state change events
if (event != null)
{
this.state = state;
notifyStateListeners(event);
// if SHUTDOWN, we don't expect an answer.
if (close.getStatusCode() == StatusCode.SHUTDOWN)
{
synchronized (this.state)
{
this.state = ConnectionState.CLOSED;
cleanClose.set(false);
outputAvailable.set(false);
inputAvailable.set(false);
this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
event = this.state;
}
notifyStateListeners(event);
return;
}
}
}
/**
* A close handshake has been received from the remote endpoint
*/
public void onCloseRemote(CloseInfo close)
{
ConnectionState event = null;
synchronized (this.state)
{
if (this.state == ConnectionState.CLOSED)
{
// already closed
return;
}
closeInfo.compareAndSet(null,close);
boolean in = inputAvailable.get();
boolean out = outputAvailable.get();
closeHandshakeSource.compareAndSet(CloseHandshakeSource.NONE,CloseHandshakeSource.REMOTE);
in = false;
inputAvailable.set(false);
LOG.debug("onCloseRemote(), input={}, output={}",in,out);
if (!in && !out)
{
LOG.debug("Close Handshake satisfied, disconnecting");
cleanClose.set(true);
this.state = ConnectionState.CLOSED;
event = this.state;
}
else if (this.state == ConnectionState.OPEN)
{
// We are now entering CLOSING (or half-closed)
this.state = ConnectionState.CLOSING;
event = this.state;
}
}
// Only notify on state change events
if (event != null)
{
notifyStateListeners(event);
}
}
/**
* WebSocket has successfully upgraded, but the end-user onOpen call hasn't run yet.
* <p>
* This is an intermediate state between the RFC's {@link ConnectionState#CONNECTING} and {@link ConnectionState#OPEN}
*/
public void onConnected()
{
if (this.state != ConnectionState.CONNECTING)
{
LOG.debug("Unable to set to connected, not in CONNECTING state: {}",this.state);
return;
}
ConnectionState event = null;
synchronized (this.state)
{
this.state = ConnectionState.CONNECTED;
this.inputAvailable.set(false); // cannot read (yet)
this.outputAvailable.set(true); // write allowed
event = this.state;
}
notifyStateListeners(event);
}
/**
* A websocket connection has failed its upgrade handshake, and is now closed.
*/
public void onFailedUpgrade()
{
assert (this.state == ConnectionState.CONNECTING);
ConnectionState event = null;
synchronized (this.state)
{
this.state = ConnectionState.CLOSED;
this.cleanClose.set(false);
this.inputAvailable.set(false);
this.outputAvailable.set(false);
event = this.state;
}
notifyStateListeners(event);
}
/**
* A websocket connection has finished its upgrade handshake, and is now open.
*/
public void onOpened()
{
if (this.state != ConnectionState.CONNECTED)
{
LOG.debug("Unable to open, not in CONNECTED state: {}",this.state);
return;
}
assert (this.state == ConnectionState.CONNECTED);
ConnectionState event = null;
synchronized (this.state)
{
this.state = ConnectionState.OPEN;
this.inputAvailable.set(true);
this.outputAvailable.set(true);
event = this.state;
}
notifyStateListeners(event);
}
/**
* The local endpoint has reached a read EOF.
* <p>
* This could be a normal result after a proper close handshake, or even a premature close due to a connection disconnect.
*/
public void onReadEOF()
{
ConnectionState event = null;
synchronized (this.state)
{
if (this.state == ConnectionState.CLOSED)
{
// already closed
return;
}
CloseInfo close = new CloseInfo(StatusCode.NO_CLOSE,"Read EOF");
this.cleanClose.set(false);
this.state = ConnectionState.CLOSED;
this.closeInfo.compareAndSet(null,close);
this.inputAvailable.set(false);
this.outputAvailable.set(false);
this.closeHandshakeSource.set(CloseHandshakeSource.ABNORMAL);
event = this.state;
}
notifyStateListeners(event);
}
public boolean wasAbnormalClose()
{
return closeHandshakeSource.get() == CloseHandshakeSource.ABNORMAL;
}
public boolean wasCleanClose()
@ -168,11 +401,11 @@ public class IOState
public boolean wasLocalCloseInitiated()
{
return localCloseInitiated.get();
return closeHandshakeSource.get() == CloseHandshakeSource.LOCAL;
}
public boolean wasRemoteCloseInitiated()
{
return remoteCloseInitiated.get();
return closeHandshakeSource.get() == CloseHandshakeSource.REMOTE;
}
}

View File

@ -258,6 +258,10 @@ public class WriteBytesProvider implements Callback
private void notifySafeFailure(Callback callback, Throwable t)
{
if (callback == null)
{
return;
}
try
{
callback.failed(t);

View File

@ -0,0 +1,32 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.websocket.common.io.http;
import java.nio.ByteBuffer;
public interface HttpResponseHeaderParseListener
{
void addHeader(String name, String value);
void setRemainingBuffer(ByteBuffer copy);
void setStatusCode(int statusCode);
void setStatusReason(String statusReason);
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.client.io;
package org.eclipse.jetty.websocket.common.io.http;
import java.nio.ByteBuffer;
import java.util.regex.Matcher;
@ -25,7 +25,6 @@ import java.util.regex.Pattern;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8LineParser;
import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
/**
* Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object.
@ -56,12 +55,13 @@ public class HttpResponseHeaderParser
private static final Pattern PAT_HEADER = Pattern.compile("([^:]+):\\s*(.*)");
private static final Pattern PAT_STATUS_LINE = Pattern.compile("^HTTP/1.[01]\\s+(\\d+)\\s+(.*)",Pattern.CASE_INSENSITIVE);
private ClientUpgradeResponse response;
private Utf8LineParser lineParser;
private final HttpResponseHeaderParseListener listener;
private final Utf8LineParser lineParser;
private State state;
public HttpResponseHeaderParser()
public HttpResponseHeaderParser(HttpResponseHeaderParseListener listener)
{
this.listener = listener;
this.lineParser = new Utf8LineParser();
this.state = State.STATUS_LINE;
}
@ -71,7 +71,7 @@ public class HttpResponseHeaderParser
return (state == State.END);
}
public ClientUpgradeResponse parse(ByteBuffer buf) throws ParseException
public HttpResponseHeaderParseListener parse(ByteBuffer buf) throws ParseException
{
while (!isDone() && (buf.remaining() > 0))
{
@ -84,8 +84,8 @@ public class HttpResponseHeaderParser
ByteBuffer copy = ByteBuffer.allocate(buf.remaining());
BufferUtil.put(buf,copy);
BufferUtil.flipToFlush(copy,0);
this.response.setRemainingBuffer(copy);
return this.response;
this.listener.setRemainingBuffer(copy);
return listener;
}
}
}
@ -98,22 +98,21 @@ public class HttpResponseHeaderParser
{
case STATUS_LINE:
{
this.response = new ClientUpgradeResponse();
Matcher mat = PAT_STATUS_LINE.matcher(line);
if (!mat.matches())
{
throw new ParseException("Unexpected HTTP upgrade response status line [" + line + "]");
throw new ParseException("Unexpected HTTP response status line [" + line + "]");
}
try
{
response.setStatusCode(Integer.parseInt(mat.group(1)));
listener.setStatusCode(Integer.parseInt(mat.group(1)));
}
catch (NumberFormatException e)
{
throw new ParseException("Unexpected HTTP upgrade response status code",e);
throw new ParseException("Unexpected HTTP response status code",e);
}
response.setStatusReason(mat.group(2));
listener.setStatusReason(mat.group(2));
state = State.HEADER;
break;
}
@ -130,8 +129,8 @@ public class HttpResponseHeaderParser
{
String headerName = header.group(1);
String headerValue = header.group(2);
// TODO: need to split header/value if comma delimited
response.addHeader(headerName,headerValue);
// do need to split header/value if comma delimited?
listener.addHeader(headerName,headerValue);
}
break;
}

View File

@ -116,7 +116,7 @@ public class EventDriverTest
driver.incomingFrame(new WebSocketFrame(OpCode.PING).setPayload("PING"));
driver.incomingFrame(WebSocketFrame.text("Text Me"));
driver.incomingFrame(WebSocketFrame.binary().setPayload("Hello Bin"));
driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN).asFrame());
driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN,"testcase").asFrame());
socket.capture.assertEventCount(6);
socket.capture.assertEventStartsWith(0,"onConnect(");
@ -148,13 +148,14 @@ public class EventDriverTest
}
@Test
public void testListener_Text() throws IOException
public void testListener_Text() throws Exception
{
ListenerBasicSocket socket = new ListenerBasicSocket();
EventDriver driver = wrap(socket);
try (LocalWebSocketSession conn = new LocalWebSocketSession(testname,driver))
{
conn.start();
conn.open();
driver.incomingFrame(WebSocketFrame.text("Hello World"));
driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame());

View File

@ -0,0 +1,245 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.websocket.common.io;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.LinkedList;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.ConnectionState;
import org.junit.Test;
public class IOStateTest
{
public static class StateTracker implements IOState.ConnectionStateListener
{
private LinkedList<ConnectionState> transitions = new LinkedList<>();
public void assertTransitions(ConnectionState ...states)
{
assertThat("Transitions.count",transitions.size(),is(states.length));
if (states.length > 0)
{
int len = states.length;
for (int i = 0; i < len; i++)
{
assertThat("Transitions[" + i + "]",transitions.get(i),is(states[i]));
}
}
}
public LinkedList<ConnectionState> getTransitions()
{
return transitions;
}
@Override
public void onConnectionStateChange(ConnectionState state)
{
transitions.add(state);
}
}
private void assertCleanClose(IOState state, boolean expected)
{
assertThat("State.cleanClose",state.wasCleanClose(),is(expected));
}
private void assertInputAvailable(IOState state, boolean available)
{
assertThat("State.inputAvailable",state.isInputAvailable(),is(available));
}
private void assertLocalInitiated(IOState state, boolean expected)
{
assertThat("State.localCloseInitiated",state.wasLocalCloseInitiated(),is(expected));
}
private void assertOutputAvailable(IOState state, boolean available)
{
assertThat("State.outputAvailable",state.isOutputAvailable(),is(available));
}
private void assertRemoteInitiated(IOState state, boolean expected)
{
assertThat("State.remoteCloseInitiated",state.wasRemoteCloseInitiated(),is(expected));
}
private void assertState(IOState state, ConnectionState expectedState)
{
assertThat("State",state.getConnectionState(),is(expectedState));
}
@Test
public void testConnectAbnormalClose()
{
IOState state = new IOState();
StateTracker tracker = new StateTracker();
state.addListener(tracker);
assertState(state,ConnectionState.CONNECTING);
// connect
state.onConnected();
assertInputAvailable(state,false);
assertOutputAvailable(state,true);
// open
state.onOpened();
assertInputAvailable(state,true);
assertOutputAvailable(state,true);
// disconnect
state.onAbnormalClose(new CloseInfo(StatusCode.NO_CLOSE,"Oops"));
assertInputAvailable(state,false);
assertOutputAvailable(state,false);
tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSED);
assertState(state,ConnectionState.CLOSED);
// not clean
assertCleanClose(state,false);
assertLocalInitiated(state,false);
assertRemoteInitiated(state,false);
}
@Test
public void testConnectCloseLocalInitiated()
{
IOState state = new IOState();
StateTracker tracker = new StateTracker();
state.addListener(tracker);
assertState(state,ConnectionState.CONNECTING);
// connect
state.onConnected();
assertInputAvailable(state,false);
assertOutputAvailable(state,true);
// open
state.onOpened();
assertInputAvailable(state,true);
assertOutputAvailable(state,true);
// close (local initiated)
state.onCloseLocal(new CloseInfo(StatusCode.NORMAL,"Hi"));
assertInputAvailable(state,true);
assertOutputAvailable(state,false);
assertState(state,ConnectionState.CLOSING);
// close (remote response)
state.onCloseRemote(new CloseInfo(StatusCode.NORMAL,"Hi"));
assertInputAvailable(state,false);
assertOutputAvailable(state,false);
tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSING,ConnectionState.CLOSED);
assertState(state,ConnectionState.CLOSED);
// not clean
assertCleanClose(state,true);
assertLocalInitiated(state,true);
assertRemoteInitiated(state,false);
}
@Test
public void testConnectCloseRemoteInitiated()
{
IOState state = new IOState();
StateTracker tracker = new StateTracker();
state.addListener(tracker);
assertState(state,ConnectionState.CONNECTING);
// connect
state.onConnected();
assertInputAvailable(state,false);
assertOutputAvailable(state,true);
// open
state.onOpened();
assertInputAvailable(state,true);
assertOutputAvailable(state,true);
// close (remote initiated)
state.onCloseRemote(new CloseInfo(StatusCode.NORMAL,"Hi"));
assertInputAvailable(state,false);
assertOutputAvailable(state,true);
assertState(state,ConnectionState.CLOSING);
// close (local response)
state.onCloseLocal(new CloseInfo(StatusCode.NORMAL,"Hi"));
assertInputAvailable(state,false);
assertOutputAvailable(state,false);
tracker.assertTransitions(ConnectionState.CONNECTED,ConnectionState.OPEN,ConnectionState.CLOSING,ConnectionState.CLOSED);
assertState(state,ConnectionState.CLOSED);
// not clean
assertCleanClose(state,true);
assertLocalInitiated(state,false);
assertRemoteInitiated(state,true);
}
@Test
public void testConnectFailure()
{
IOState state = new IOState();
StateTracker tracker = new StateTracker();
state.addListener(tracker);
assertState(state,ConnectionState.CONNECTING);
// fail upgrade
state.onFailedUpgrade();
tracker.assertTransitions(ConnectionState.CLOSED);
assertState(state,ConnectionState.CLOSED);
assertInputAvailable(state,false);
assertOutputAvailable(state,false);
// not clean
assertCleanClose(state,false);
assertLocalInitiated(state,false);
assertRemoteInitiated(state,false);
}
@Test
public void testInit()
{
IOState state = new IOState();
StateTracker tracker = new StateTracker();
state.addListener(tracker);
assertState(state,ConnectionState.CONNECTING);
// do nothing
tracker.assertTransitions();
assertState(state,ConnectionState.CONNECTING);
// not connected yet
assertInputAvailable(state,false);
assertOutputAvailable(state,false);
// no close yet
assertCleanClose(state,false);
assertLocalInitiated(state,false);
assertRemoteInitiated(state,false);
}
}

View File

@ -22,22 +22,25 @@ import java.net.InetSocketAddress;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.WriteCallback;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.ConnectionState;
import org.eclipse.jetty.websocket.common.LogicalConnection;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
import org.junit.rules.TestName;
public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames
public class LocalWebSocketConnection implements LogicalConnection, IncomingFrames, ConnectionStateListener
{
private static final Logger LOG = Log.getLogger(LocalWebSocketConnection.class);
private final String id;
private WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
private boolean open = false;
private IncomingFrames incoming;
private IOState ioState = new IOState();
@ -49,29 +52,33 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram
public LocalWebSocketConnection(String id)
{
this.id = id;
this.ioState.addListener(this);
}
public LocalWebSocketConnection(TestName testname)
{
this.id = testname.getMethodName();
this.ioState.addListener(this);
}
@Override
public void close()
{
open = false;
close(StatusCode.NORMAL,null);
}
@Override
public void close(int statusCode, String reason)
{
open = false;
LOG.debug("close({}, {})",statusCode,reason);
CloseInfo close = new CloseInfo(statusCode,reason);
ioState.onCloseLocal(close);
}
@Override
public void disconnect()
{
open = false;
LOG.debug("disconnect()");
}
public IncomingFrames getIncoming()
@ -131,7 +138,7 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram
@Override
public boolean isOpen()
{
return open;
return getIOState().isOpen();
}
@Override
@ -140,9 +147,31 @@ public class LocalWebSocketConnection implements LogicalConnection, IncomingFram
return false;
}
@Override
public void onConnectionStateChange(ConnectionState state)
{
LOG.debug("Connection State Change: {}",state);
switch (state)
{
case CLOSED:
this.disconnect();
break;
case CLOSING:
if (ioState.wasRemoteCloseInitiated())
{
// send response close frame
CloseInfo close = ioState.getCloseInfo();
LOG.debug("write close frame: {}",close);
ioState.onCloseLocal(close);
}
default:
break;
}
}
public void onOpen() {
LOG.debug("onOpen()");
open = true;
ioState.onOpened();
}
@Override

View File

@ -16,9 +16,10 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.client.internal.io;
package org.eclipse.jetty.websocket.common.io.http;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -27,8 +28,6 @@ import java.util.List;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.client.io.HttpResponseHeaderParser;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
@ -43,6 +42,32 @@ public class HttpResponseHeaderParserTest
buf.put(ByteBuffer.wrap(StringUtil.getBytes(line,StringUtil.__UTF8)));
}
@Test
public void testParseNotFound()
{
StringBuilder resp = new StringBuilder();
resp.append("HTTP/1.1 404 Not Found\r\n");
resp.append("Date: Fri, 26 Apr 2013 21:43:08 GMT\r\n");
resp.append("Content-Type: text/html; charset=ISO-8859-1\r\n");
resp.append("Cache-Control: must-revalidate,no-cache,no-store\r\n");
resp.append("Content-Length: 38\r\n");
resp.append("Server: Jetty(9.0.0.v20130308)\r\n");
resp.append("\r\n");
// and some body content
resp.append("What you are looking for is not here\r\n");
ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StringUtil.__UTF8_CHARSET);
HttpResponseParseCapture capture = new HttpResponseParseCapture();
HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
assertThat("Parser.parse",parser.parse(buf),notNullValue());
assertThat("Response.statusCode",capture.getStatusCode(),is(404));
assertThat("Response.statusReason",capture.getStatusReason(),is("Not Found"));
assertThat("Response.headers[Content-Length]",capture.getHeader("Content-Length"),is("38"));
assertThat("Response.remainingBuffer",capture.getRemainingBuffer().remaining(),is(38));
}
@Test
public void testParseRealWorldResponse()
{
@ -73,14 +98,14 @@ public class HttpResponseHeaderParserTest
BufferUtil.flipToFlush(buf,0);
// Parse Buffer
HttpResponseHeaderParser parser = new HttpResponseHeaderParser();
UpgradeResponse response = parser.parse(buf);
Assert.assertThat("Response",response,notNullValue());
HttpResponseParseCapture capture = new HttpResponseParseCapture();
HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
assertThat("Parser.parse",parser.parse(buf),notNullValue());
Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200));
Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK"));
Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200));
Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK"));
Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097"));
Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097"));
}
@Test
@ -122,24 +147,47 @@ public class HttpResponseHeaderParserTest
small3.position(70);
// Parse Buffer
HttpResponseHeaderParser parser = new HttpResponseHeaderParser();
UpgradeResponse response;
HttpResponseParseCapture capture = new HttpResponseParseCapture();
HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
assertThat("Parser.parse",parser.parse(buf),notNullValue());
// Parse small 1
response = parser.parse(small1);
Assert.assertThat("Small 1",response,nullValue());
Assert.assertThat("Small 1",parser.parse(small1),nullValue());
// Parse small 2
response = parser.parse(small2);
Assert.assertThat("Small 2",response,nullValue());
Assert.assertThat("Small 2",parser.parse(small2),nullValue());
// Parse small 3
response = parser.parse(small3);
Assert.assertThat("Small 3",response,notNullValue());
Assert.assertThat("Small 3",parser.parse(small3),notNullValue());
Assert.assertThat("Response.statusCode",response.getStatusCode(),is(200));
Assert.assertThat("Response.statusReason",response.getStatusReason(),is("OK"));
Assert.assertThat("Response.statusCode",capture.getStatusCode(),is(200));
Assert.assertThat("Response.statusReason",capture.getStatusReason(),is("OK"));
Assert.assertThat("Response.header[age]",response.getHeader("age"),is("518097"));
Assert.assertThat("Response.header[age]",capture.getHeader("age"),is("518097"));
}
@Test
public void testParseUpgrade()
{
// Example from RFC6455 - Section 1.2 (Protocol Overview)
StringBuilder resp = new StringBuilder();
resp.append("HTTP/1.1 101 Switching Protocols\r\n");
resp.append("Upgrade: websocket\r\n");
resp.append("Connection: Upgrade\r\n");
resp.append("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n");
resp.append("Sec-WebSocket-Protocol: chat\r\n");
resp.append("\r\n");
ByteBuffer buf = BufferUtil.toBuffer(resp.toString(),StringUtil.__UTF8_CHARSET);
HttpResponseParseCapture capture = new HttpResponseParseCapture();
HttpResponseHeaderParser parser = new HttpResponseHeaderParser(capture);
assertThat("Parser.parse",parser.parse(buf),notNullValue());
assertThat("Response.statusCode",capture.getStatusCode(),is(101));
assertThat("Response.statusReason",capture.getStatusReason(),is("Switching Protocols"));
assertThat("Response.headers[Upgrade]",capture.getHeader("Upgrade"),is("websocket"));
assertThat("Response.headers[Connection]",capture.getHeader("Connection"),is("Upgrade"));
assertThat("Buffer.remaining",buf.remaining(),is(0));
}
}

View File

@ -0,0 +1,76 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.websocket.common.io.http;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class HttpResponseParseCapture implements HttpResponseHeaderParseListener
{
private int statusCode;
private String statusReason;
private Map<String, String> headers = new HashMap<>();
private ByteBuffer remainingBuffer;
@Override
public void addHeader(String name, String value)
{
headers.put(name.toLowerCase(Locale.ENGLISH),value);
}
public String getHeader(String name)
{
return headers.get(name.toLowerCase(Locale.ENGLISH));
}
public ByteBuffer getRemainingBuffer()
{
return remainingBuffer;
}
public int getStatusCode()
{
return statusCode;
}
public String getStatusReason()
{
return statusReason;
}
@Override
public void setRemainingBuffer(ByteBuffer copy)
{
this.remainingBuffer = copy;
}
@Override
public void setStatusCode(int code)
{
this.statusCode = code;
}
@Override
public void setStatusReason(String reason)
{
this.statusReason = reason;
}
}

View File

@ -75,13 +75,6 @@ public class WebSocketServerConnection extends AbstractWebSocketConnection
super.onOpen();
}
@Override
public void onWriteWebSocketClose()
{
// as server, always disconnect if writing close
disconnect();
}
@Override
public void setNextIncomingFrames(IncomingFrames incoming)
{

View File

@ -103,7 +103,7 @@ public class AnnotatedMaxMessageSizeTest
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
WebSocketFrame tf = capture.getFrames().get(0);
WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally

View File

@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.AfterClass;
@ -60,8 +61,8 @@ public class ChromeTest
client.setProtocols("chat");
client.connect();
client.sendStandardRequest();
String response = client.expectUpgradeResponse();
Assert.assertThat("Response",response,containsString("x-webkit-deflate-frame"));
HttpResponse response = client.expectUpgradeResponse();
Assert.assertThat("Response",response.getExtensionsHeader(),containsString("x-webkit-deflate-frame"));
// Generate text frame
String msg = "this is an echo ... cho ... ho ... o";
@ -69,7 +70,7 @@ public class ChromeTest
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
WebSocketFrame tf = capture.getFrames().get(0);
WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally

View File

@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.AfterClass;
@ -80,9 +81,9 @@ public class FragmentExtensionTest
client.setTimeout(TimeUnit.SECONDS,1);
client.connect();
client.sendStandardRequest();
String resp = client.expectUpgradeResponse();
HttpResponse resp = client.expectUpgradeResponse();
Assert.assertThat("Response",resp,containsString("fragment"));
Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("fragment"));
String msg = "Sent as a long message that should be split";
client.write(WebSocketFrame.text(msg));
@ -91,7 +92,7 @@ public class FragmentExtensionTest
IncomingFramesCapture capture = client.readFrames(parts.length,TimeUnit.MILLISECONDS,1000);
for (int i = 0; i < parts.length; i++)
{
WebSocketFrame frame = capture.getFrames().get(i);
WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("text[" + i + "].payload",frame.getPayloadAsUTF8(),is(parts[i]));
}
}

View File

@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.AfterClass;
@ -64,9 +65,9 @@ public class FrameCompressionExtensionTest
client.setTimeout(TimeUnit.SECONDS,1);
client.connect();
client.sendStandardRequest();
String resp = client.expectUpgradeResponse();
HttpResponse resp = client.expectUpgradeResponse();
Assert.assertThat("Response",resp,containsString("x-webkit-deflate-frame"));
Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("x-webkit-deflate-frame"));
String msg = "Hello";
@ -74,7 +75,7 @@ public class FrameCompressionExtensionTest
client.write(WebSocketFrame.text(msg));
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
WebSocketFrame frame = capture.getFrames().get(0);
WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
// Client sends second message
@ -83,7 +84,7 @@ public class FrameCompressionExtensionTest
client.write(WebSocketFrame.text(msg));
capture = client.readFrames(1,TimeUnit.SECONDS,1);
frame = capture.getFrames().get(0);
frame = capture.getFrames().poll();
Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString()));
}
finally

View File

@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.helper.EchoServlet;
import org.eclipse.jetty.websocket.server.helper.IncomingFramesCapture;
import org.junit.AfterClass;
@ -65,14 +66,14 @@ public class IdentityExtensionTest
client.setTimeout(TimeUnit.SECONDS,1);
client.connect();
client.sendStandardRequest();
String resp = client.expectUpgradeResponse();
HttpResponse resp = client.expectUpgradeResponse();
Assert.assertThat("Response",resp,containsString("identity"));
Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("identity"));
client.write(WebSocketFrame.text("Hello"));
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
WebSocketFrame frame = capture.getFrames().get(0);
WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is("Hello"));
}
finally

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket.server;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -43,15 +45,11 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
/**
* Tests various close scenarios
*/
@Ignore
public class WebSocketCloseTest
{
@SuppressWarnings("serial")
@ -145,7 +143,7 @@ public class WebSocketCloseTest
client.expectUpgradeResponse();
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
WebSocketFrame frame = capture.getFrames().get(0);
WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL));

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.server;
import static org.hamcrest.Matchers.*;
import org.eclipse.jetty.websocket.server.blockhead.BlockheadClient;
import org.eclipse.jetty.websocket.server.blockhead.HttpResponse;
import org.eclipse.jetty.websocket.server.examples.MyEchoServlet;
import org.junit.AfterClass;
import org.junit.Assert;
@ -56,9 +57,10 @@ public class WebSocketInvalidVersionTest
{
client.connect();
client.sendStandardRequest();
String respHeader = client.readResponseHeader();
Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 400 Unsupported websocket version specification"));
Assert.assertThat("Response Header Versions",respHeader,containsString("Sec-WebSocket-Version: 13\r\n"));
HttpResponse response = client.readResponseHeader();
Assert.assertThat("Response Status Code",response.getStatusCode(),is(400));
Assert.assertThat("Response Status Reason",response.getStatusReason(),containsString("Unsupported websocket version specification"));
Assert.assertThat("Response Versions",response.getHeader("Sec-WebSocket-Version"),is("13"));
}
finally
{

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.server;
import static org.hamcrest.Matchers.*;
import java.net.URI;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
@ -96,13 +97,14 @@ public class WebSocketServerSessionTest
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(4,TimeUnit.MILLISECONDS,500);
WebSocketFrame tf = capture.getFrames().pop();
Queue<WebSocketFrame> frames = capture.getFrames();
WebSocketFrame tf = frames.poll();
Assert.assertThat("Parameter Map[snack]",tf.getPayloadAsUTF8(),is("[cashews]"));
tf = capture.getFrames().pop();
tf = frames.poll();
Assert.assertThat("Parameter Map[amount]",tf.getPayloadAsUTF8(),is("[handful]"));
tf = capture.getFrames().pop();
tf = frames.poll();
Assert.assertThat("Parameter Map[brand]",tf.getPayloadAsUTF8(),is("[off]"));
tf = capture.getFrames().pop();
tf = frames.poll();
Assert.assertThat("Parameter Map[cost]",tf.getPayloadAsUTF8(),is("<null>"));
}
finally

View File

@ -115,7 +115,7 @@ public class WebSocketServletRFCTest
// Read frame echo'd back (hopefully a single binary frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000);
Frame binmsg = capture.getFrames().get(0);
Frame binmsg = capture.getFrames().poll();
int expectedSize = buf1.length + buf2.length + buf3.length;
Assert.assertThat("BinaryFrame.payloadLength",binmsg.getPayloadLength(),is(expectedSize));
@ -181,7 +181,7 @@ public class WebSocketServletRFCTest
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
WebSocketFrame tf = capture.getFrames().get(0);
WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally
@ -209,7 +209,7 @@ public class WebSocketServletRFCTest
// Read frame (hopefully close frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
Frame cf = capture.getFrames().get(0);
Frame cf = capture.getFrames().poll();
CloseInfo close = new CloseInfo(cf);
Assert.assertThat("Close Frame.status code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
}
@ -252,7 +252,7 @@ public class WebSocketServletRFCTest
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
WebSocketFrame tf = capture.getFrames().get(0);
WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally
@ -292,7 +292,7 @@ public class WebSocketServletRFCTest
}
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
WebSocketFrame frame = capture.getFrames().get(0);
WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE));
@ -334,7 +334,7 @@ public class WebSocketServletRFCTest
}
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
WebSocketFrame frame = capture.getFrames().get(0);
WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.MESSAGE_TOO_LARGE));
@ -367,7 +367,7 @@ public class WebSocketServletRFCTest
client.writeRaw(bb);
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1);
WebSocketFrame frame = capture.getFrames().get(0);
WebSocketFrame frame = capture.getFrames().poll();
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
CloseInfo close = new CloseInfo(frame);
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.BAD_PAYLOAD));
@ -413,7 +413,7 @@ public class WebSocketServletRFCTest
// Read frame (hopefully text frame)
IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500);
WebSocketFrame tf = capture.getFrames().get(0);
WebSocketFrame tf = capture.getFrames().poll();
Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg));
}
finally

Some files were not shown because too many files have changed in this diff Show More