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:
commit
e7ecfd2b2b
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
#===========================================================
|
||||
# Java Server Pages
|
||||
#-----------------------------------------------------------
|
||||
OPTIONS=jsp
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
#===========================================================
|
||||
# HTTP Connector
|
||||
#-----------------------------------------------------------
|
||||
jetty.port=8080
|
||||
http.timeout=30000
|
||||
etc/jetty-http.xml
|
|
@ -0,0 +1,6 @@
|
|||
#===========================================================
|
||||
# HTTPS Connector
|
||||
# Must be used with 200-ssl.ini
|
||||
#-----------------------------------------------------------
|
||||
jetty.https.port=8443
|
||||
etc/jetty-https.xml
|
|
@ -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
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#===========================================================
|
||||
# Annotations JNDI JAAS processing
|
||||
#-----------------------------------------------------------
|
||||
OPTIONS=plus
|
||||
etc/jetty-plus.xml
|
||||
OPTIONS=annotations
|
||||
etc/jetty-annotations.xml
|
||||
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
etc/jetty-debug.xml
|
|
@ -0,0 +1 @@
|
|||
etc/jetty-ipaccess.xml
|
|
@ -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
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#===========================================================
|
||||
#-----------------------------------------------------------
|
||||
etc/jetty-stats.xml
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#===========================================================
|
||||
# Webapplication Deployer
|
||||
#-----------------------------------------------------------
|
||||
etc/jetty-deploy.xml
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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++));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -258,6 +258,10 @@ public class WriteBytesProvider implements Callback
|
|||
|
||||
private void notifySafeFailure(Callback callback, Throwable t)
|
||||
{
|
||||
if (callback == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
callback.failed(t);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue