Merge remote-tracking branch 'origin/master' into jetty-8
This commit is contained in:
commit
2131a40559
|
@ -170,11 +170,5 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -16,8 +16,6 @@ package org.eclipse.jetty.annotations;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
||||
/**
|
||||
* AnnotationIntrospector
|
||||
*
|
||||
|
@ -35,7 +33,7 @@ public class AnnotationIntrospector
|
|||
*/
|
||||
public interface IntrospectableAnnotationHandler
|
||||
{
|
||||
public void handle(Class clazz);
|
||||
public void handle(Class<?> clazz);
|
||||
}
|
||||
|
||||
|
||||
|
@ -50,7 +48,7 @@ public class AnnotationIntrospector
|
|||
{
|
||||
private boolean _introspectAncestors;
|
||||
|
||||
public abstract void doHandle(Class clazz);
|
||||
public abstract void doHandle(Class<?> clazz);
|
||||
|
||||
|
||||
public AbstractIntrospectableAnnotationHandler(boolean introspectAncestors)
|
||||
|
@ -58,9 +56,9 @@ public class AnnotationIntrospector
|
|||
_introspectAncestors = introspectAncestors;
|
||||
}
|
||||
|
||||
public void handle(Class clazz)
|
||||
public void handle(Class<?> clazz)
|
||||
{
|
||||
Class c = clazz;
|
||||
Class<?> c = clazz;
|
||||
|
||||
//process the whole inheritance hierarchy for the class
|
||||
while (c!=null && (!c.equals(Object.class)))
|
||||
|
@ -79,7 +77,7 @@ public class AnnotationIntrospector
|
|||
_handlers.add(handler);
|
||||
}
|
||||
|
||||
public void introspect (Class clazz)
|
||||
public void introspect (Class<?> clazz)
|
||||
{
|
||||
if (_handlers == null)
|
||||
return;
|
||||
|
|
|
@ -49,7 +49,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
|
|||
* environment that will be looked up at runtime. They do
|
||||
* not specify an injection.
|
||||
*/
|
||||
public void doHandle(Class clazz)
|
||||
public void doHandle(Class<?> clazz)
|
||||
{
|
||||
if (Util.isServletType(clazz))
|
||||
{
|
||||
|
@ -65,16 +65,13 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
|
|||
}
|
||||
}
|
||||
|
||||
public void handleClass (Class clazz)
|
||||
public void handleClass (Class<?> clazz)
|
||||
{
|
||||
Resource resource = (Resource)clazz.getAnnotation(Resource.class);
|
||||
if (resource != null)
|
||||
{
|
||||
String name = resource.name();
|
||||
String mappedName = resource.mappedName();
|
||||
Resource.AuthenticationType auth = resource.authenticationType();
|
||||
Class type = resource.type();
|
||||
boolean shareable = resource.shareable();
|
||||
|
||||
if (name==null || name.trim().equals(""))
|
||||
throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
|
||||
|
@ -92,7 +89,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
|
|||
}
|
||||
}
|
||||
|
||||
public void handleField(Class clazz, Field field)
|
||||
public void handleField(Class<?> clazz, Field field)
|
||||
{
|
||||
Resource resource = (Resource)field.getAnnotation(Resource.class);
|
||||
if (resource != null)
|
||||
|
@ -118,7 +115,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
|
|||
name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
|
||||
String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
|
||||
//get the type of the Field
|
||||
Class type = field.getType();
|
||||
Class<?> type = field.getType();
|
||||
|
||||
//Servlet Spec 3.0 p. 76
|
||||
//If a descriptor has specified at least 1 injection target for this
|
||||
|
@ -207,7 +204,7 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
|
|||
* This will generate a JNDI entry, and an Injection to be
|
||||
* processed when an instance of the class is created.
|
||||
*/
|
||||
public void handleMethod(Class clazz, Method method)
|
||||
public void handleMethod(Class<?> clazz, Method method)
|
||||
{
|
||||
|
||||
Resource resource = (Resource)method.getAnnotation(Resource.class);
|
||||
|
@ -265,9 +262,9 @@ public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationH
|
|||
|
||||
name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
|
||||
String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
|
||||
Class paramType = method.getParameterTypes()[0];
|
||||
Class<?> paramType = method.getParameterTypes()[0];
|
||||
|
||||
Class resourceType = resource.type();
|
||||
Class<?> resourceType = resource.type();
|
||||
|
||||
//Servlet Spec 3.0 p. 76
|
||||
//If a descriptor has specified at least 1 injection target for this
|
||||
|
|
|
@ -35,7 +35,7 @@ public class ResourcesAnnotationHandler extends AbstractIntrospectableAnnotation
|
|||
_wac = wac;
|
||||
}
|
||||
|
||||
public void doHandle (Class clazz)
|
||||
public void doHandle (Class<?> clazz)
|
||||
{
|
||||
Resources resources = (Resources)clazz.getAnnotation(Resources.class);
|
||||
if (resources != null)
|
||||
|
@ -49,12 +49,8 @@ public class ResourcesAnnotationHandler extends AbstractIntrospectableAnnotation
|
|||
|
||||
for (int j=0;j<resArray.length;j++)
|
||||
{
|
||||
|
||||
String name = resArray[j].name();
|
||||
String mappedName = resArray[j].mappedName();
|
||||
Resource.AuthenticationType auth = resArray[j].authenticationType();
|
||||
Class type = resArray[j].type();
|
||||
boolean shareable = resArray[j].shareable();
|
||||
|
||||
if (name==null || name.trim().equals(""))
|
||||
throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
|
||||
|
|
|
@ -125,7 +125,6 @@ public class HttpURI
|
|||
int i=offset;
|
||||
int e=offset+length;
|
||||
int state=AUTH;
|
||||
int m=offset;
|
||||
_end=offset+length;
|
||||
_scheme=offset;
|
||||
_authority=offset;
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.io.OutputStream;
|
|||
* This is a byte buffer that is designed to work like a FIFO for bytes. Puts and Gets operate on different
|
||||
* pointers into the buffer and the valid _content of the buffer is always between the getIndex and the putIndex.
|
||||
*
|
||||
* This buffer interface is designed to be similar, but not dependant on the java.nio buffers, which may
|
||||
* This buffer interface is designed to be similar, but not dependent on the java.nio buffers, which may
|
||||
* be used to back an implementation of this Buffer. The main difference is that NIO buffer after a put have
|
||||
* their valid _content before the position and a flip is required to access that data.
|
||||
*
|
||||
|
@ -56,14 +56,14 @@ public interface Buffer extends Cloneable
|
|||
byte[] asArray();
|
||||
|
||||
/**
|
||||
* Get the unerlying buffer. If this buffer wraps a backing buffer.
|
||||
* Get the underlying buffer. If this buffer wraps a backing buffer.
|
||||
* @return The root backing buffer or this if there is no backing buffer;
|
||||
*/
|
||||
Buffer buffer();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return a non volitile version of this <code>Buffer</code> value
|
||||
* @return a non volatile version of this <code>Buffer</code> value
|
||||
*/
|
||||
Buffer asNonVolatileBuffer();
|
||||
|
||||
|
|
|
@ -561,7 +561,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
|
|||
if (wait>0)
|
||||
{
|
||||
long before=now;
|
||||
selected=selector.select(wait);
|
||||
selector.select(wait);
|
||||
now = System.currentTimeMillis();
|
||||
_timeout.setNow(now);
|
||||
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Jetty-OSGi-Jasper integration
|
||||
Fragment-Host: org.eclipse.jetty.osgi.boot
|
||||
Bundle-SymbolicName: org.eclipse.jetty.osgi.boot.jsp
|
||||
Bundle-Version: 8.1.3.qualifier
|
||||
Bundle-Vendor: Mort Bay Consulting
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Import-Package: com.sun.el;version="2.2.0";resolution:=optional,
|
||||
javax.el;version="2.2.0";resolution:=optional,
|
||||
javax.servlet.jsp;version="2.2.0",
|
||||
javax.servlet.jsp.el;version="2.2.0",
|
||||
javax.servlet.jsp.jstl.core;version="1.2.0";resolution:=optional,
|
||||
javax.servlet.jsp.jstl.fmt;version="1.2.0";resolution:=optional,
|
||||
javax.servlet.jsp.jstl.sql;version="1.2.0";resolution:=optional,
|
||||
javax.servlet.jsp.jstl.tlv;version="1.2.0";resolution:=optional,
|
||||
javax.servlet.jsp.resources;version="2.1.0",
|
||||
javax.servlet.jsp.tagext;version="2.1.0",
|
||||
javax.servlet.resources;version="2.6.0",
|
||||
org.apache.jasper;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.compiler;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.compiler.tagplugin;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.runtime;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.security;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.servlet;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.tagplugins.jstl;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.util;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.xmlparser;version="2.2.2";resolution:=optional,
|
||||
org.apache.taglibs.standard;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.extra.spath;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.functions;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.jstl;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.jstl.parser;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.jstl.test;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.support;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.resources;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.common.core;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.common.fmt;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.common.sql;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.common.xml;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.el.core;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.el.fmt;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.el.sql;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.el.xml;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.rt.core;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.rt.fmt;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.rt.sql;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.rt.xml;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tei;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tlv;version="1.2.0";resolution:=optional,
|
||||
org.glassfish.jsp.api;version="2.2.2";resolution:=optional
|
||||
DynamicImport-Package: org.apache.jasper.*;version="2.2.2"
|
||||
|
|
@ -46,22 +46,6 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>process-resources</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<replace file="target/classes/META-INF/MANIFEST.MF" token="Bundle-Version: 8.0.0.qualifier" value="Bundle-Version: ${parsedVersion.osgiVersion}" />
|
||||
</tasks>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
@ -85,6 +69,83 @@
|
|||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot.jsp</Bundle-SymbolicName>
|
||||
<Bundle-Name>Jetty-OSGi-Jasper Integration</Bundle-Name>
|
||||
<Bundle-Classpath></Bundle-Classpath>
|
||||
<Fragment-Host>org.eclipse.jetty.osgi.boot</Fragment-Host>
|
||||
<Export-Package>!org.eclipse.jetty.osgi.boot.*</Export-Package>
|
||||
<Import-Package>com.sun.el;resolution:=optional,
|
||||
com.sun.el.lang;resolution:=optional,
|
||||
com.sun.el.parser;resolution:=optional,
|
||||
com.sun.el.util;resolution:=optional,
|
||||
javax.el;version="2.2.0";resolution:=optional,
|
||||
javax.servlet;version="2.5.0",
|
||||
javax.servlet.jsp;version="2.2.0",
|
||||
javax.servlet.jsp.el;version="2.2.0",
|
||||
javax.servlet.jsp.jstl.core;version="1.2.0";resolution:=optional,
|
||||
javax.servlet.jsp.jstl.fmt;version="1.2.0";resolution:=optional,
|
||||
javax.servlet.jsp.jstl.sql;version="1.2.0";resolution:=optional,
|
||||
javax.servlet.jsp.jstl.tlv;version="1.2.0";resolution:=optional,
|
||||
javax.servlet.jsp.resources;version="2.2.0",
|
||||
javax.servlet.jsp.tagext;version="2.2.0",
|
||||
javax.servlet.resources;version="2.6.0",
|
||||
org.apache.jasper;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.compiler;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.compiler.tagplugin;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.runtime;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.security;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.servlet;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.tagplugins.jstl;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.util;version="2.2.2";resolution:=optional,
|
||||
org.apache.jasper.xmlparser;version="2.2.2";resolution:=optional,
|
||||
org.glassfish.jsp.api;version="2.2.2";resolution:=optional,
|
||||
org.apache.taglibs.standard;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.extra.spath;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.functions;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.jstl;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.jstl.parser;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.jstl.test;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.lang.support;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.resources;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.common.core;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.common.fmt;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.common.sql;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.common.xml;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.el.core;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.el.fmt;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.el.sql;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.el.xml;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.rt.core;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.rt.fmt;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.rt.sql;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tag.rt.xml;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tei;version="1.2.0";resolution:=optional,
|
||||
org.apache.taglibs.standard.tlv;version="1.2.0";resolution:=optional,
|
||||
!org.osgi.*,
|
||||
!org.xml.*,
|
||||
!org.eclipse.jetty.*
|
||||
</Import-Package>
|
||||
<_nouses>true</_nouses>
|
||||
<DynamicImport-Package>org.apache.jasper.*;version="2.2.2"</DynamicImport-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Jetty-OSGi-Logback integration
|
||||
Fragment-Host: org.eclipse.jetty.osgi.boot
|
||||
Bundle-SymbolicName: org.eclipse.jetty.osgi.boot.logback;singleton:=true
|
||||
Bundle-Version: 8.1.3.qualifier
|
||||
Bundle-Vendor: Mort Bay Consulting
|
||||
Bundle-RequiredExecutionEnvironment: J2SE-1.5
|
||||
Import-Package: ch.qos.logback.classic,
|
||||
ch.qos.logback.classic.joran,
|
||||
ch.qos.logback.core,
|
||||
ch.qos.logback.core.joran,
|
||||
ch.qos.logback.core.joran.spi,
|
||||
ch.qos.logback.core.spi,
|
||||
ch.qos.logback.core.util,
|
||||
ch.qos.logback.access.jetty.v7;resolution:=optional,
|
||||
org.apache.commons.logging;resolution:=optional,
|
||||
org.apache.log4j;resolution:=optional,
|
||||
org.osgi.framework,
|
||||
org.slf4j
|
|
@ -1,11 +0,0 @@
|
|||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Support for rfc66 war url scheme
|
||||
Bundle-SymbolicName: org.eclipse.jetty.osgi.boot.warurl;singleton:=true
|
||||
Bundle-Version: 8.1.3.qualifier
|
||||
Bundle-Activator: org.eclipse.jetty.osgi.boot.warurl.WarUrlActivator
|
||||
Bundle-Vendor: Mort Bay Consulting
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Import-Package: org.eclipse.jetty.util,
|
||||
org.osgi.framework,
|
||||
org.osgi.service.url
|
|
@ -25,22 +25,6 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>process-resources</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<replace file="target/classes/META-INF/MANIFEST.MF" token="Bundle-Version: 8.0.0.qualifier" value="Bundle-Version: ${parsedVersion.osgiVersion}" />
|
||||
</tasks>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
@ -64,6 +48,28 @@
|
|||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot.warurl;singleton:=true</Bundle-SymbolicName>
|
||||
<Bundle-Name>RFC66 War URL</Bundle-Name>
|
||||
<Bundle-Activator>org.eclipse.jetty.osgi.boot.warurl.WarUrlActivator</Bundle-Activator>
|
||||
<_nouses>true</_nouses>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
|
@ -73,6 +79,4 @@
|
|||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Jetty OSGi bootstrap
|
||||
Bundle-SymbolicName: org.eclipse.jetty.osgi.boot
|
||||
Bundle-Vendor: Mort Bay Consulting
|
||||
Bundle-Version: 8.1.3.qualifier
|
||||
Bundle-Activator: org.eclipse.jetty.osgi.boot.JettyBootstrapActivator
|
||||
Import-Package: javax.mail;version="1.4.0";resolution:=optional,
|
||||
javax.mail.event;version="1.4.0";resolution:=optional,
|
||||
javax.mail.internet;version="1.4.0";resolution:=optional,
|
||||
javax.mail.search;version="1.4.0";resolution:=optional,
|
||||
javax.mail.util;version="1.4.0";resolution:=optional,
|
||||
javax.servlet;version="2.6",
|
||||
javax.servlet.http;version="2.6",
|
||||
javax.transaction;version="1.1.0";resolution:=optional,
|
||||
javax.transaction.xa;version="1.1.0";resolution:=optional,
|
||||
org.eclipse.jetty.annotations;version="8.1.3";resolution:=optional,
|
||||
org.eclipse.jetty.deploy;version="8.1.3",
|
||||
org.eclipse.jetty.deploy.providers;version="8.1.3",
|
||||
org.eclipse.jetty.http;version="8.1.3",
|
||||
org.eclipse.jetty.nested;version="8.1.3";resolution:=optional,
|
||||
org.eclipse.jetty.server;version="8.1.3",
|
||||
org.eclipse.jetty.server.handler;version="8.1.3",
|
||||
org.eclipse.jetty.servlet;version="8.1.3",
|
||||
org.eclipse.jetty.util;version="8.1.3",
|
||||
org.eclipse.jetty.util.component;version="8.1.3",
|
||||
org.eclipse.jetty.util.log;version="8.1.3",
|
||||
org.eclipse.jetty.util.resource;version="8.1.3",
|
||||
org.eclipse.jetty.webapp;version="8.1.3",
|
||||
org.eclipse.jetty.xml;version="8.1.3",
|
||||
org.osgi.framework,
|
||||
org.osgi.service.cm;version="1.2.0",
|
||||
org.osgi.service.packageadmin,
|
||||
org.osgi.service.startlevel;version="1.0",
|
||||
org.osgi.service.url;version="1.0.0",
|
||||
org.osgi.util.tracker;version="1.3.0",
|
||||
org.slf4j;resolution:=optional,
|
||||
org.slf4j.helpers;resolution:=optional,
|
||||
org.slf4j.spi;resolution:=optional,
|
||||
org.xml.sax,
|
||||
org.xml.sax.helpers
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Bundle-Classpath: .
|
||||
Export-Package: org.eclipse.jetty.osgi.boot;version="8.1.3",
|
||||
org.eclipse.jetty.osgi.nested;version="8.1.3",
|
||||
org.eclipse.jetty.osgi.boot.utils;version="8.1.3",
|
||||
org.eclipse.jetty.osgi.annotations;version="8.1.3"
|
||||
DynamicImport-Package: org.eclipse.jetty.*;version="[8.1,9)"
|
|
@ -104,9 +104,11 @@
|
|||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot</Bundle-SymbolicName>
|
||||
<Export-Package>org.eclipse.jetty.osgi.boot;version="${parsedVersion.osgiVersion}",org.eclipse.jetty.osgi.boot.utils,org.eclipse.jetty.osgi.nested;version="${parsedVersion.osgiVersion}"</Export-Package>
|
||||
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot;singleton:=true</Bundle-SymbolicName>
|
||||
<Bundle-Name>Jetty OSGi Boot</Bundle-Name>
|
||||
<Bundle-Activator>org.eclipse.jetty.osgi.boot.JettyBootstrapActivator</Bundle-Activator>
|
||||
<Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
|
||||
<Export-Package>org.eclipse.jetty.osgi.boot;version="${parsedVersion.osgiVersion}",org.eclipse.jetty.osgi.boot.utils,org.eclipse.jetty.osgi.nested;version="${parsedVersion.osgiVersion}"</Export-Package>
|
||||
<!-- disable the uses directive: jetty will accomodate pretty much any versions
|
||||
of the packages it uses; no need to reflect some tight dependency determined at
|
||||
compilation time. -->
|
||||
|
@ -120,8 +122,8 @@
|
|||
javax.servlet.http;version="2.6.0",
|
||||
javax.transaction;version="1.1.0";resolution:=optional,
|
||||
javax.transaction.xa;version="1.1.0";resolution:=optional,
|
||||
org.eclipse.jetty.nested;version="8.0.0";resolution:=optional,
|
||||
org.eclipse.jetty.annotations;version="8.0.0";resolution:=optional,
|
||||
org.eclipse.jetty.nested;version="[8.1,9)";resolution:=optional,
|
||||
org.eclipse.jetty.annotations;version="[8.1,9)";resolution:=optional,
|
||||
org.osgi.framework,
|
||||
org.osgi.service.cm;version="1.2.0",
|
||||
org.osgi.service.packageadmin,
|
||||
|
@ -135,7 +137,7 @@
|
|||
org.xml.sax.helpers,
|
||||
*
|
||||
</Import-Package>
|
||||
<DynamicImport-Package>org.eclipse.jetty.*;version="[8.0.0,9.0.0)"</DynamicImport-Package>
|
||||
<DynamicImport-Package>org.eclipse.jetty.*;version="[8.1,9)"</DynamicImport-Package>
|
||||
<!--Require-Bundle/-->
|
||||
<Bundle-RequiredExecutionEnvironment>JavaSE-1.6</Bundle-RequiredExecutionEnvironment>
|
||||
</instructions>
|
||||
|
|
|
@ -241,7 +241,10 @@ public class WebBundleDeployerHelper implements IWebBundleDeployerHelper
|
|||
// classes
|
||||
// that the contributor gives access to.
|
||||
Thread.currentThread().setContextClassLoader(composite);
|
||||
|
||||
|
||||
//converts bundleentry: protocol
|
||||
baseWebappInstallURL = DefaultFileLocatorHelper.getLocalURL(baseWebappInstallURL);
|
||||
|
||||
context.setWar(baseWebappInstallURL.toString());
|
||||
context.setContextPath(contextPath);
|
||||
context.setExtraClasspath(extraClasspath);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Console
|
||||
Bundle-SymbolicName: org.eclipse.jetty.osgi.equinoxtools
|
||||
Bundle-Description: Example application: equinox console accesssible on the web
|
||||
Bundle-Version: 8.1.3.qualifier
|
||||
Bundle-Activator: org.eclipse.jetty.osgi.equinoxtools.WebEquinoxToolsActivator
|
||||
Import-Package: javax.servlet;version="2.5.0",
|
||||
javax.servlet.http;version="2.5.0",
|
||||
org.eclipse.jetty.continuation;version="8.1.3",
|
||||
org.eclipse.jetty.io;version="8.1.3",
|
||||
org.eclipse.jetty.util;version="8.1.3",
|
||||
org.eclipse.jetty.util.log;version="8.1.3",
|
||||
org.eclipse.jetty.websocket;version="8.1.3",
|
||||
org.eclipse.osgi.framework.console;version="1.1.0",
|
||||
org.osgi.framework;version="1.3.0",
|
||||
org.osgi.service.http;version="1.2.0",
|
||||
org.osgi.util.tracker;version="1.3.0"
|
||||
Export-Package: org.eclipse.jetty.osgi.equinoxtools;x-internal:=true;version="8.1.3",
|
||||
org.eclipse.jetty.osgi.equinoxtools.console;x-internal:=true;version="8.1.3"
|
||||
Bundle-RequiredExecutionEnvironment: J2SE-1.6
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: OSGi HttpService provided by equinox HttpServiceServlet deployed on jetty
|
||||
Bundle-SymbolicName: org.eclipse.jetty.osgi.httpservice
|
||||
Bundle-Version: 8.1.3.qualifier
|
||||
Bundle-Vendor: Mort Bay Consulting
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Jetty-ContextFilePath: contexts/httpservice.xml
|
||||
Import-Package: javax.servlet;version="2.6.0",
|
||||
javax.servlet.http;version="2.6.0",
|
||||
org.eclipse.equinox.http.servlet,
|
||||
org.eclipse.jetty.server;version="8.1.3",
|
||||
org.eclipse.jetty.server.handler;version="8.1.3",
|
||||
org.eclipse.jetty.servlet;version="8.1.3",
|
||||
org.eclipse.jetty.util.component;version="8.1.3"
|
||||
Export-Package: org.eclipse.jetty.osgi.httpservice;version="8.1.3"
|
||||
|
|
@ -44,7 +44,6 @@
|
|||
<phase>process-resources</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<replace file="target/classes/META-INF/MANIFEST.MF" token="Bundle-Version: 8.0.0.qualifier" value="Bundle-Version: ${parsedVersion.osgiVersion}" />
|
||||
<copy todir="target/classes/contexts">
|
||||
<fileset dir="contexts" />
|
||||
</copy>
|
||||
|
@ -79,6 +78,33 @@
|
|||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-SymbolicName>org.eclipse.jetty.osgi.httpservice</Bundle-SymbolicName>
|
||||
<Bundle-Name>OSGi HttpService</Bundle-Name>
|
||||
<Jetty-ContextFilePath>contexts/httpservice.xml</Jetty-ContextFilePath>
|
||||
<Import-Package>org.eclipse.jetty.server.handler;version="[8.1,9)",
|
||||
org.eclipse.jetty.util.component;version="[8.1,9)",
|
||||
org.eclipse.equinox.http.servlet,
|
||||
*
|
||||
</Import-Package>
|
||||
<_nouses>true</_nouses>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
|
@ -88,6 +114,4 @@
|
|||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
|
|
|
@ -31,9 +31,12 @@ import javax.security.auth.login.LoginContext;
|
|||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import org.eclipse.jetty.plus.jaas.callback.ObjectCallback;
|
||||
import org.eclipse.jetty.plus.jaas.callback.RequestParameterCallback;
|
||||
import org.eclipse.jetty.security.DefaultIdentityService;
|
||||
import org.eclipse.jetty.security.IdentityService;
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.server.AbstractHttpConnection;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
|
@ -200,6 +203,19 @@ public class JAASLoginService extends AbstractLifeCycle implements LoginService
|
|||
{
|
||||
((ObjectCallback)callback).setObject(credentials);
|
||||
}
|
||||
else if (callback instanceof RequestParameterCallback)
|
||||
{
|
||||
AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
|
||||
Request request = (connection == null? null : connection.getRequest());
|
||||
|
||||
if (request != null)
|
||||
{
|
||||
RequestParameterCallback rpc = (RequestParameterCallback)callback;
|
||||
rpc.setParameterValues(Arrays.asList(request.getParameterValues(rpc.getParameterName())));
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new UnsupportedCallbackException(callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ import javax.security.auth.Subject;
|
|||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.NameCallback;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import javax.security.auth.spi.LoginModule;
|
||||
|
@ -187,9 +188,10 @@ public abstract class AbstractLoginModule implements LoginModule
|
|||
public Callback[] configureCallbacks ()
|
||||
{
|
||||
|
||||
Callback[] callbacks = new Callback[2];
|
||||
Callback[] callbacks = new Callback[3];
|
||||
callbacks[0] = new NameCallback("Enter user name");
|
||||
callbacks[1] = new ObjectCallback();
|
||||
callbacks[2] = new PasswordCallback("Enter password", false); //only used if framework does not support the ObjectCallback
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
|
@ -215,7 +217,11 @@ public abstract class AbstractLoginModule implements LoginModule
|
|||
callbackHandler.handle(callbacks);
|
||||
|
||||
String webUserName = ((NameCallback)callbacks[0]).getName();
|
||||
Object webCredential = ((ObjectCallback)callbacks[1]).getObject();
|
||||
Object webCredential = null;
|
||||
|
||||
webCredential = ((ObjectCallback)callbacks[1]).getObject(); //first check if ObjectCallback has the credential
|
||||
if (webCredential == null)
|
||||
webCredential = ((PasswordCallback)callbacks[2]).getPassword(); //use standard PasswordCallback
|
||||
|
||||
if ((webUserName == null) || (webCredential == null))
|
||||
{
|
||||
|
|
|
@ -354,6 +354,7 @@ public class DataSourceLoginService extends MappedLoginService
|
|||
|
||||
@SuppressWarnings("unused")
|
||||
InitialContext ic = new InitialContext();
|
||||
assert ic!=null;
|
||||
|
||||
//TODO webapp scope?
|
||||
|
||||
|
|
|
@ -1396,6 +1396,7 @@ public class Request implements HttpServletRequest
|
|||
if (_attributes != null)
|
||||
_attributes.clearAttributes();
|
||||
_characterEncoding = null;
|
||||
_contextPath = null;
|
||||
if (_cookies != null)
|
||||
_cookies.reset();
|
||||
_cookiesExtracted = false;
|
||||
|
|
|
@ -727,9 +727,16 @@ public class Response implements HttpServletResponse
|
|||
{
|
||||
_characterEncoding=null;
|
||||
if (_cachedMimeType!=null)
|
||||
_connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType);
|
||||
_contentType=_cachedMimeType.toString();
|
||||
else if (_mimeType!=null)
|
||||
_contentType=_mimeType;
|
||||
else
|
||||
_connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_mimeType);
|
||||
_contentType=null;
|
||||
|
||||
if (_contentType==null)
|
||||
_connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
|
||||
else
|
||||
_connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -10,7 +10,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -45,8 +44,6 @@ import org.eclipse.jetty.util.thread.ThreadPool;
|
|||
public class ConnectHandler extends HandlerWrapper
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ConnectHandler.class);
|
||||
|
||||
private final Logger _logger = Log.getLogger(getClass().getName());
|
||||
private final SelectorManager _selectorManager = new Manager();
|
||||
private volatile int _connectTimeout = 5000;
|
||||
private volatile int _writeTimeout = 30000;
|
||||
|
@ -174,15 +171,15 @@ public class ConnectHandler extends HandlerWrapper
|
|||
{
|
||||
if (HttpMethods.CONNECT.equalsIgnoreCase(request.getMethod()))
|
||||
{
|
||||
_logger.debug("CONNECT request for {}", request.getRequestURI());
|
||||
LOG.debug("CONNECT request for {}", request.getRequestURI());
|
||||
try
|
||||
{
|
||||
handleConnect(baseRequest, request, response, request.getRequestURI());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
_logger.warn("ConnectHandler "+baseRequest.getUri()+" "+ e);
|
||||
_logger.debug(e);
|
||||
LOG.warn("ConnectHandler "+baseRequest.getUri()+" "+ e);
|
||||
LOG.debug(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -329,15 +326,15 @@ public class ConnectHandler extends HandlerWrapper
|
|||
try
|
||||
{
|
||||
// Connect to remote server
|
||||
_logger.debug("Establishing connection to {}:{}", host, port);
|
||||
LOG.debug("Establishing connection to {}:{}", host, port);
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
channel.socket().connect(new InetSocketAddress(host, port), getConnectTimeout());
|
||||
_logger.debug("Established connection to {}:{}", host, port);
|
||||
LOG.debug("Established connection to {}:{}", host, port);
|
||||
return channel;
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
_logger.debug("Failed to establish connection to " + host + ":" + port, x);
|
||||
LOG.debug("Failed to establish connection to " + host + ":" + port, x);
|
||||
try
|
||||
{
|
||||
channel.close();
|
||||
|
@ -360,7 +357,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
// so that Jetty understands that it has to upgrade the connection
|
||||
request.setAttribute("org.eclipse.jetty.io.Connection", connection);
|
||||
response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
|
||||
_logger.debug("Upgraded connection to {}", connection);
|
||||
LOG.debug("Upgraded connection to {}", connection);
|
||||
}
|
||||
|
||||
private void register(SocketChannel channel, ProxyToServerConnection proxyToServer) throws IOException
|
||||
|
@ -399,24 +396,27 @@ public class ConnectHandler extends HandlerWrapper
|
|||
return 0;
|
||||
|
||||
int length = buffer.length();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int written = endPoint.flush(buffer);
|
||||
builder.append(written);
|
||||
buffer.compact();
|
||||
if (!endPoint.isBlocking())
|
||||
final StringBuilder debug = LOG.isDebugEnabled()?new StringBuilder():null;
|
||||
int flushed = endPoint.flush(buffer);
|
||||
if (debug!=null)
|
||||
debug.append(flushed);
|
||||
|
||||
// Loop until all written
|
||||
while (buffer.length()>0 && !endPoint.isOutputShutdown())
|
||||
{
|
||||
while (buffer.space() == 0)
|
||||
if (!endPoint.isBlocking())
|
||||
{
|
||||
boolean ready = endPoint.blockWritable(getWriteTimeout());
|
||||
if (!ready)
|
||||
throw new IOException("Write timeout");
|
||||
|
||||
written = endPoint.flush(buffer);
|
||||
builder.append("+").append(written);
|
||||
buffer.compact();
|
||||
}
|
||||
flushed = endPoint.flush(buffer);
|
||||
if (debug!=null)
|
||||
debug.append("+").append(flushed);
|
||||
}
|
||||
_logger.debug("Written {}/{} bytes {}", builder, length, endPoint);
|
||||
|
||||
LOG.debug("Written {}/{} bytes {}", debug, length, endPoint);
|
||||
buffer.compact();
|
||||
return length;
|
||||
}
|
||||
|
||||
|
@ -464,12 +464,12 @@ public class ConnectHandler extends HandlerWrapper
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public class ProxyToServerConnection implements AsyncConnection
|
||||
{
|
||||
private final CountDownLatch _ready = new CountDownLatch(1);
|
||||
private final Buffer _buffer = new IndirectNIOBuffer(1024);
|
||||
private final Buffer _buffer = new IndirectNIOBuffer(4096);
|
||||
private final ConcurrentMap<String, Object> _context;
|
||||
private volatile Buffer _data;
|
||||
private volatile ClientToProxyConnection _toClient;
|
||||
|
@ -493,7 +493,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
|
||||
public Connection handle() throws IOException
|
||||
{
|
||||
_logger.debug("{}: begin reading from server", this);
|
||||
LOG.debug("{}: begin reading from server", this);
|
||||
try
|
||||
{
|
||||
writeData();
|
||||
|
@ -504,7 +504,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
|
||||
if (read == -1)
|
||||
{
|
||||
_logger.debug("{}: server closed connection {}", this, _endPoint);
|
||||
LOG.debug("{}: server closed connection {}", this, _endPoint);
|
||||
|
||||
if (_endPoint.isOutputShutdown() || !_endPoint.isOpen())
|
||||
closeClient();
|
||||
|
@ -517,32 +517,32 @@ public class ConnectHandler extends HandlerWrapper
|
|||
if (read == 0)
|
||||
break;
|
||||
|
||||
_logger.debug("{}: read from server {} bytes {}", this, read, _endPoint);
|
||||
LOG.debug("{}: read from server {} bytes {}", this, read, _endPoint);
|
||||
int written = write(_toClient._endPoint, _buffer, _context);
|
||||
_logger.debug("{}: written to {} {} bytes", this, _toClient, written);
|
||||
LOG.debug("{}: written to {} {} bytes", this, _toClient, written);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
catch (ClosedChannelException x)
|
||||
{
|
||||
_logger.debug(x);
|
||||
LOG.debug(x);
|
||||
throw x;
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
_logger.warn(this + ": unexpected exception", x);
|
||||
LOG.warn(this + ": unexpected exception", x);
|
||||
close();
|
||||
throw x;
|
||||
}
|
||||
catch (RuntimeException x)
|
||||
{
|
||||
_logger.warn(this + ": unexpected exception", x);
|
||||
LOG.warn(this + ": unexpected exception", x);
|
||||
close();
|
||||
throw x;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.debug("{}: end reading from server", this);
|
||||
LOG.debug("{}: end reading from server", this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -550,7 +550,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
private void writeData() throws IOException
|
||||
{
|
||||
// This method is called from handle() and closeServer()
|
||||
|
@ -563,7 +563,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
try
|
||||
{
|
||||
int written = write(_endPoint, _data, _context);
|
||||
_logger.debug("{}: written to server {} bytes", this, written);
|
||||
LOG.debug("{}: written to server {} bytes", this, written);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -648,7 +648,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
_logger.debug(this + ": unexpected exception closing the client", x);
|
||||
LOG.debug(this + ": unexpected exception closing the client", x);
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -657,7 +657,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
_logger.debug(this + ": unexpected exception closing the server", x);
|
||||
LOG.debug(this + ": unexpected exception closing the server", x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -683,7 +683,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
|
||||
public class ClientToProxyConnection implements AsyncConnection
|
||||
{
|
||||
private final Buffer _buffer = new IndirectNIOBuffer(1024);
|
||||
private final Buffer _buffer = new IndirectNIOBuffer(4096);
|
||||
private final ConcurrentMap<String, Object> _context;
|
||||
private final SocketChannel _channel;
|
||||
private final EndPoint _endPoint;
|
||||
|
@ -710,14 +710,14 @@ public class ConnectHandler extends HandlerWrapper
|
|||
|
||||
public Connection handle() throws IOException
|
||||
{
|
||||
_logger.debug("{}: begin reading from client", this);
|
||||
LOG.debug("{}: begin reading from client", this);
|
||||
try
|
||||
{
|
||||
if (_firstTime)
|
||||
{
|
||||
_firstTime = false;
|
||||
register(_channel, _toServer);
|
||||
_logger.debug("{}: registered channel {} with connection {}", this, _channel, _toServer);
|
||||
LOG.debug("{}: registered channel {} with connection {}", this, _channel, _toServer);
|
||||
}
|
||||
|
||||
while (true)
|
||||
|
@ -726,7 +726,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
|
||||
if (read == -1)
|
||||
{
|
||||
_logger.debug("{}: client closed connection {}", this, _endPoint);
|
||||
LOG.debug("{}: client closed connection {}", this, _endPoint);
|
||||
|
||||
if (_endPoint.isOutputShutdown() || !_endPoint.isOpen())
|
||||
closeServer();
|
||||
|
@ -739,36 +739,36 @@ public class ConnectHandler extends HandlerWrapper
|
|||
if (read == 0)
|
||||
break;
|
||||
|
||||
_logger.debug("{}: read from client {} bytes {}", this, read, _endPoint);
|
||||
LOG.debug("{}: read from client {} bytes {}", this, read, _endPoint);
|
||||
int written = write(_toServer._endPoint, _buffer, _context);
|
||||
_logger.debug("{}: written to {} {} bytes", this, _toServer, written);
|
||||
LOG.debug("{}: written to {} {} bytes", this, _toServer, written);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
catch (ClosedChannelException x)
|
||||
{
|
||||
_logger.debug(x);
|
||||
LOG.debug(x);
|
||||
closeServer();
|
||||
throw x;
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
_logger.warn(this + ": unexpected exception", x);
|
||||
LOG.warn(this + ": unexpected exception", x);
|
||||
close();
|
||||
throw x;
|
||||
}
|
||||
catch (RuntimeException x)
|
||||
{
|
||||
_logger.warn(this + ": unexpected exception", x);
|
||||
LOG.warn(this + ": unexpected exception", x);
|
||||
close();
|
||||
throw x;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.debug("{}: end reading from client", this);
|
||||
LOG.debug("{}: end reading from client", this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void onInputShutdown() throws IOException
|
||||
{
|
||||
// TODO
|
||||
|
@ -816,7 +816,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
_logger.debug(this + ": unexpected exception closing the client", x);
|
||||
LOG.debug(this + ": unexpected exception closing the client", x);
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -825,7 +825,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
_logger.debug(this + ": unexpected exception closing the server", x);
|
||||
LOG.debug(this + ": unexpected exception closing the server", x);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -201,12 +201,10 @@ public class SessionHandler extends ScopedHandler
|
|||
{
|
||||
if (access!=null)
|
||||
_sessionManager.complete(access);
|
||||
else
|
||||
{
|
||||
HttpSession session = baseRequest.getSession(false);
|
||||
if (session!=null && old_session==null)
|
||||
_sessionManager.complete(session);
|
||||
}
|
||||
|
||||
HttpSession session = baseRequest.getSession(false);
|
||||
if (session!=null && old_session==null && session!=access)
|
||||
_sessionManager.complete(session);
|
||||
|
||||
if (old_session_manager!=null && old_session_manager != _sessionManager)
|
||||
{
|
||||
|
|
|
@ -131,6 +131,39 @@ public class ResponseTest
|
|||
response.setContentType("text/json");
|
||||
response.getWriter();
|
||||
assertEquals("text/json;charset=UTF-8", response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
response.setCharacterEncoding("xyz");
|
||||
response.setContentType("foo/bar");
|
||||
assertEquals("foo/bar;charset=xyz", response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
response.setContentType("foo/bar");
|
||||
response.setCharacterEncoding("xyz");
|
||||
assertEquals("foo/bar;charset=xyz", response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
response.setCharacterEncoding("xyz");
|
||||
response.setContentType("foo/bar;charset=abc");
|
||||
assertEquals("foo/bar;charset=abc", response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
response.setContentType("foo/bar;charset=abc");
|
||||
response.setCharacterEncoding("xyz");
|
||||
assertEquals("foo/bar;charset=xyz", response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
response.setCharacterEncoding("xyz");
|
||||
response.setContentType("foo/bar");
|
||||
response.setCharacterEncoding(null);
|
||||
assertEquals("foo/bar", response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
response.setCharacterEncoding("xyz");
|
||||
response.setCharacterEncoding(null);
|
||||
response.setContentType("foo/bar");
|
||||
assertEquals("foo/bar", response.getContentType());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -89,11 +89,12 @@ public abstract class AbstractConnectHandlerTest
|
|||
headers.put(headerName.toLowerCase(), headerValue.toLowerCase());
|
||||
}
|
||||
|
||||
StringBuilder body = new StringBuilder();
|
||||
StringBuilder body;
|
||||
if (headers.containsKey("content-length"))
|
||||
{
|
||||
int readLen = 0;
|
||||
int length = Integer.parseInt(headers.get("content-length"));
|
||||
body=new StringBuilder(length);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < length; ++i)
|
||||
|
@ -101,7 +102,9 @@ public abstract class AbstractConnectHandlerTest
|
|||
char c = (char)reader.read();
|
||||
body.append(c);
|
||||
readLen++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (SocketTimeoutException e)
|
||||
{
|
||||
|
@ -111,6 +114,7 @@ public abstract class AbstractConnectHandlerTest
|
|||
}
|
||||
else if ("chunked".equals(headers.get("transfer-encoding")))
|
||||
{
|
||||
body = new StringBuilder(64*1024);
|
||||
while ((line = reader.readLine()) != null)
|
||||
{
|
||||
if ("0".equals(line))
|
||||
|
@ -120,6 +124,15 @@ public abstract class AbstractConnectHandlerTest
|
|||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Thread.sleep(5);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
int length = Integer.parseInt(line, 16);
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
|
@ -130,6 +143,7 @@ public abstract class AbstractConnectHandlerTest
|
|||
assertEquals("", line);
|
||||
}
|
||||
}
|
||||
else throw new IllegalStateException();
|
||||
|
||||
return new Response(code, headers, body.toString().trim());
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -12,7 +9,6 @@ import java.io.OutputStream;
|
|||
import java.net.Socket;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -23,9 +19,13 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||
import org.eclipse.jetty.toolchain.test.OS;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
/**
|
||||
* @version $Revision$ $Date$
|
||||
*/
|
||||
|
@ -355,6 +355,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
@Test
|
||||
public void testCONNECTAndPOSTWithBigBody() throws Exception
|
||||
{
|
||||
// Log.getLogger(ConnectHandler.class).setDebugEnabled(true);
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
|
@ -375,7 +376,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
|
||||
StringBuilder body = new StringBuilder();
|
||||
String chunk = "0123456789ABCDEF";
|
||||
for (int i = 0; i < 1024; ++i)
|
||||
for (int i = 0; i < 1024 * 1024; ++i)
|
||||
body.append(chunk);
|
||||
|
||||
request = "" +
|
||||
|
@ -488,7 +489,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
{
|
||||
// TODO needs to be further investigated
|
||||
assumeTrue(!OS.IS_OSX);
|
||||
|
||||
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
|
@ -527,7 +528,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
{
|
||||
// TODO needs to be further investigated
|
||||
assumeTrue(!OS.IS_OSX);
|
||||
|
||||
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) 2012 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.server.handler;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ConnectHandlerUnitTest
|
||||
{
|
||||
@Mock
|
||||
private EndPoint endPoint;
|
||||
|
||||
@Test
|
||||
public void testPartialWritesWithNonFullBuffer() throws IOException
|
||||
{
|
||||
ConnectHandler connectHandler = new ConnectHandler();
|
||||
final byte[] bytes = "foo bar".getBytes();
|
||||
Buffer buffer = new ByteArrayBuffer(bytes.length * 2);
|
||||
buffer.put(bytes);
|
||||
when(endPoint.flush(buffer)).thenAnswer(new Answer<Object>()
|
||||
{
|
||||
public Object answer(InvocationOnMock invocation)
|
||||
{
|
||||
Object[] args = invocation.getArguments();
|
||||
Buffer buffer = (Buffer)args[0];
|
||||
int skip = bytes.length/2;
|
||||
buffer.skip(skip);
|
||||
return skip;
|
||||
}
|
||||
});
|
||||
when(endPoint.blockWritable(anyInt())).thenReturn(true);
|
||||
|
||||
// method to test
|
||||
connectHandler.write(endPoint,buffer,null);
|
||||
|
||||
assertThat(buffer.length(),is(0));
|
||||
}
|
||||
|
||||
}
|
|
@ -6,9 +6,9 @@
|
|||
<version>8.1.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spdy-core</artifactId>
|
||||
<name>Jetty :: SPDY :: Core</name>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spdy-core</artifactId>
|
||||
<name>Jetty :: SPDY :: Core</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -16,10 +16,21 @@
|
|||
<artifactId>jetty-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
|
|
|
@ -82,7 +82,7 @@ public interface IStream extends Stream
|
|||
public void process(ControlFrame frame);
|
||||
|
||||
/**
|
||||
* <p>Processes the give data frame along with the given byte buffer,
|
||||
* <p>Processes the given data frame along with the given byte buffer,
|
||||
* for example by updating the stream's state or by calling listeners.</p>
|
||||
*
|
||||
* @param frame the data frame to process
|
||||
|
@ -90,4 +90,26 @@ public interface IStream extends Stream
|
|||
* @see #process(ControlFrame)
|
||||
*/
|
||||
public void process(DataFrame frame, ByteBuffer data);
|
||||
|
||||
/**
|
||||
* <p>Associate the given {@link IStream} to this {@link IStream}.</p>
|
||||
*
|
||||
* @param stream the stream to associate with this stream
|
||||
*/
|
||||
public void associate(IStream stream);
|
||||
|
||||
/**
|
||||
* <p>remove the given associated {@link IStream} from this stream</p>
|
||||
*
|
||||
* @param stream the stream to be removed
|
||||
*/
|
||||
public void disassociate(IStream stream);
|
||||
|
||||
/**
|
||||
* <p>Overrides Stream.getAssociatedStream() to return an instance of IStream instead of Stream
|
||||
*
|
||||
* @see Stream#getAssociatedStream()
|
||||
*/
|
||||
@Override
|
||||
public IStream getAssociatedStream();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* <p>A subclass container of {@link SynInfo} for unidirectional streams</p>
|
||||
*/
|
||||
public class PushSynInfo extends SynInfo
|
||||
{
|
||||
public static final byte FLAG_UNIDIRECTIONAL = 2;
|
||||
|
||||
private int associatedStreamId;
|
||||
|
||||
public PushSynInfo(int associatedStreamId, SynInfo synInfo){
|
||||
super(synInfo.getHeaders(), synInfo.isClose(), synInfo.getPriority());
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the close and unidirectional flags as integer
|
||||
* @see #FLAG_CLOSE
|
||||
* @see #FLAG_UNIDIRECTIONAL
|
||||
*/
|
||||
@Override
|
||||
public byte getFlags()
|
||||
{
|
||||
byte flags = super.getFlags();
|
||||
flags += FLAG_UNIDIRECTIONAL;
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id of the associated stream
|
||||
*/
|
||||
public int getAssociatedStreamId()
|
||||
{
|
||||
return associatedStreamId;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import org.eclipse.jetty.spdy.api.SessionStatus;
|
|||
|
||||
public class SessionException extends RuntimeException
|
||||
{
|
||||
|
||||
private final SessionStatus sessionStatus;
|
||||
|
||||
public SessionException(SessionStatus sessionStatus)
|
||||
|
|
|
@ -95,7 +95,8 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
private boolean flushing;
|
||||
private volatile int windowSize = 65536;
|
||||
|
||||
public StandardSession(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler, Controller<FrameBytes> controller, IdleListener idleListener, int initialStreamId, SessionFrameListener listener, Generator generator)
|
||||
public StandardSession(short version, ByteBufferPool bufferPool, Executor threadPool, ScheduledExecutorService scheduler,
|
||||
Controller<FrameBytes> controller, IdleListener idleListener, int initialStreamId, SessionFrameListener listener, Generator generator)
|
||||
{
|
||||
this.version = version;
|
||||
this.bufferPool = bufferPool;
|
||||
|
@ -109,6 +110,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
this.generator = generator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getVersion()
|
||||
{
|
||||
return version;
|
||||
|
@ -130,7 +132,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
public Future<Stream> syn(SynInfo synInfo, StreamFrameListener listener)
|
||||
{
|
||||
Promise<Stream> result = new Promise<>();
|
||||
syn(synInfo, listener, 0, TimeUnit.MILLISECONDS, result);
|
||||
syn(synInfo,listener,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -143,20 +145,18 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
// have stream3 hit the network before stream1, not only to comply with the spec
|
||||
// but also because the compression context for the headers would be wrong, as the
|
||||
// frame with a compression history will come before the first compressed frame.
|
||||
int associatedStreamId = 0;
|
||||
if (synInfo instanceof PushSynInfo)
|
||||
{
|
||||
associatedStreamId = ((PushSynInfo)synInfo).getAssociatedStreamId();
|
||||
}
|
||||
|
||||
synchronized (this)
|
||||
{
|
||||
if (synInfo.isUnidirectional())
|
||||
{
|
||||
// TODO: unidirectional functionality
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
else
|
||||
{
|
||||
int streamId = streamIds.getAndAdd(2);
|
||||
SynStreamFrame synStream = new SynStreamFrame(version, synInfo.getFlags(), streamId, 0, synInfo.getPriority(), synInfo.getHeaders());
|
||||
IStream stream = createStream(synStream, listener);
|
||||
control(stream, synStream, timeout, unit, handler, stream);
|
||||
}
|
||||
int streamId = streamIds.getAndAdd(2);
|
||||
SynStreamFrame synStream = new SynStreamFrame(version,synInfo.getFlags(),streamId,associatedStreamId,synInfo.getPriority(),synInfo.getHeaders());
|
||||
IStream stream = createStream(synStream,listener);
|
||||
control(stream,synStream,timeout,unit,handler,stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
public Future<Void> rst(RstInfo rstInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
rst(rstInfo, 0, TimeUnit.MILLISECONDS, result);
|
||||
rst(rstInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -174,16 +174,19 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
// SPEC v3, 2.2.2
|
||||
if (goAwaySent.get())
|
||||
{
|
||||
complete(handler, null);
|
||||
complete(handler,null);
|
||||
}
|
||||
else
|
||||
{
|
||||
int streamId = rstInfo.getStreamId();
|
||||
IStream stream = streams.get(streamId);
|
||||
RstStreamFrame frame = new RstStreamFrame(version,streamId,rstInfo.getStreamStatus().getCode(version));
|
||||
control(stream,frame,timeout,unit,handler,null);
|
||||
if (stream != null)
|
||||
{
|
||||
stream.process(frame);
|
||||
removeStream(stream);
|
||||
RstStreamFrame frame = new RstStreamFrame(version, streamId, rstInfo.getStreamStatus().getCode(version));
|
||||
control(null, frame, timeout, unit, handler, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,22 +194,22 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
public Future<Void> settings(SettingsInfo settingsInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
settings(settingsInfo, 0, TimeUnit.MILLISECONDS, result);
|
||||
settings(settingsInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void settings(SettingsInfo settingsInfo, long timeout, TimeUnit unit, Handler<Void> handler)
|
||||
{
|
||||
SettingsFrame frame = new SettingsFrame(version, settingsInfo.getFlags(), settingsInfo.getSettings());
|
||||
control(null, frame, timeout, unit, handler, null);
|
||||
SettingsFrame frame = new SettingsFrame(version,settingsInfo.getFlags(),settingsInfo.getSettings());
|
||||
control(null,frame,timeout,unit,handler,null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<PingInfo> ping()
|
||||
{
|
||||
Promise<PingInfo> result = new Promise<>();
|
||||
ping(0, TimeUnit.MILLISECONDS, result);
|
||||
ping(0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -215,8 +218,8 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
int pingId = pingIds.getAndAdd(2);
|
||||
PingInfo pingInfo = new PingInfo(pingId);
|
||||
PingFrame frame = new PingFrame(version, pingId);
|
||||
control(null, frame, timeout, unit, handler, pingInfo);
|
||||
PingFrame frame = new PingFrame(version,pingId);
|
||||
control(null,frame,timeout,unit,handler,pingInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -228,28 +231,28 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
private Future<Void> goAway(SessionStatus sessionStatus)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
goAway(sessionStatus, 0, TimeUnit.MILLISECONDS, result);
|
||||
goAway(sessionStatus,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void goAway(long timeout, TimeUnit unit, Handler<Void> handler)
|
||||
{
|
||||
goAway(SessionStatus.OK, timeout, unit, handler);
|
||||
goAway(SessionStatus.OK,timeout,unit,handler);
|
||||
}
|
||||
|
||||
private void goAway(SessionStatus sessionStatus, long timeout, TimeUnit unit, Handler<Void> handler)
|
||||
{
|
||||
if (goAwaySent.compareAndSet(false, true))
|
||||
if (goAwaySent.compareAndSet(false,true))
|
||||
{
|
||||
if (!goAwayReceived.get())
|
||||
{
|
||||
GoAwayFrame frame = new GoAwayFrame(version, lastStreamId.get(), sessionStatus.getCode());
|
||||
control(null, frame, timeout, unit, handler, null);
|
||||
GoAwayFrame frame = new GoAwayFrame(version,lastStreamId.get(),sessionStatus.getCode());
|
||||
control(null,frame,timeout,unit,handler,null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
complete(handler, null);
|
||||
complete(handler,null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -263,14 +266,14 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
@Override
|
||||
public void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
notifyIdle(idleListener, false);
|
||||
notifyIdle(idleListener,false);
|
||||
try
|
||||
{
|
||||
logger.debug("Processing {}", frame);
|
||||
logger.debug("Processing {}",frame);
|
||||
|
||||
if (goAwaySent.get())
|
||||
{
|
||||
logger.debug("Skipped processing of {}", frame);
|
||||
logger.debug("Skipped processing of {}",frame);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -329,21 +332,21 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
}
|
||||
finally
|
||||
{
|
||||
notifyIdle(idleListener, true);
|
||||
notifyIdle(idleListener,true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
notifyIdle(idleListener, false);
|
||||
notifyIdle(idleListener,false);
|
||||
try
|
||||
{
|
||||
logger.debug("Processing {}, {} data bytes", frame, data.remaining());
|
||||
logger.debug("Processing {}, {} data bytes",frame,data.remaining());
|
||||
|
||||
if (goAwaySent.get())
|
||||
{
|
||||
logger.debug("Skipped processing of {}", frame);
|
||||
logger.debug("Skipped processing of {}",frame);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -351,18 +354,18 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
IStream stream = streams.get(streamId);
|
||||
if (stream == null)
|
||||
{
|
||||
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
|
||||
logger.debug("Unknown stream {}", rstInfo);
|
||||
RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM);
|
||||
logger.debug("Unknown stream {}",rstInfo);
|
||||
rst(rstInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
processData(stream, frame, data);
|
||||
processData(stream,frame,data);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
notifyIdle(idleListener, true);
|
||||
notifyIdle(idleListener,true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,7 +377,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
|
||||
private void processData(IStream stream, DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
stream.process(frame, data);
|
||||
stream.process(frame,data);
|
||||
updateLastStreamId(stream);
|
||||
if (stream.isClosed())
|
||||
removeStream(stream);
|
||||
|
@ -383,43 +386,42 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
@Override
|
||||
public void onStreamException(StreamException x)
|
||||
{
|
||||
notifyOnException(listener, x);
|
||||
rst(new RstInfo(x.getStreamId(), x.getStreamStatus()));
|
||||
notifyOnException(listener,x);
|
||||
rst(new RstInfo(x.getStreamId(),x.getStreamStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionException(SessionException x)
|
||||
{
|
||||
Throwable cause = x.getCause();
|
||||
notifyOnException(listener, cause == null ? x : cause);
|
||||
notifyOnException(listener,cause == null?x:cause);
|
||||
goAway(x.getSessionStatus());
|
||||
}
|
||||
|
||||
private void onSyn(SynStreamFrame frame)
|
||||
{
|
||||
IStream stream = newStream(frame);
|
||||
stream.updateCloseState(frame.isClose(), false);
|
||||
logger.debug("Opening {}", stream);
|
||||
int streamId = frame.getStreamId();
|
||||
IStream existing = streams.putIfAbsent(streamId, stream);
|
||||
IStream stream = newStream(frame,null);
|
||||
stream.updateCloseState(frame.isClose(),false);
|
||||
logger.debug("Opening {}",stream);
|
||||
int streamId = stream.getId();
|
||||
IStream existing = streams.putIfAbsent(streamId,stream);
|
||||
if (existing != null)
|
||||
{
|
||||
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.PROTOCOL_ERROR);
|
||||
logger.debug("Duplicate stream, {}", rstInfo);
|
||||
RstInfo rstInfo = new RstInfo(streamId,StreamStatus.PROTOCOL_ERROR);
|
||||
logger.debug("Duplicate stream, {}",rstInfo);
|
||||
rst(rstInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
processSyn(listener, stream, frame);
|
||||
processSyn(listener,stream,frame);
|
||||
}
|
||||
}
|
||||
|
||||
private void processSyn(SessionFrameListener listener, IStream stream, SynStreamFrame frame)
|
||||
{
|
||||
stream.process(frame);
|
||||
SynInfo synInfo = new SynInfo(frame.getHeaders(), frame.isClose(),
|
||||
frame.isUnidirectional(), frame.getAssociatedStreamId(), frame.getPriority());
|
||||
StreamFrameListener streamListener = notifyOnSyn(listener, stream, synInfo);
|
||||
SynInfo synInfo = new SynInfo(frame.getHeaders(),frame.isClose(),frame.getPriority());
|
||||
StreamFrameListener streamListener = notifyOnSyn(listener,stream,synInfo);
|
||||
stream.setStreamFrameListener(streamListener);
|
||||
flush();
|
||||
// The onSyn() listener may have sent a frame that closed the stream
|
||||
|
@ -429,25 +431,36 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
|
||||
private IStream createStream(SynStreamFrame synStream, StreamFrameListener listener)
|
||||
{
|
||||
IStream stream = newStream(synStream);
|
||||
stream.updateCloseState(synStream.isClose(), true);
|
||||
IStream parentStream = streams.get(synStream.getAssociatedStreamId());
|
||||
|
||||
IStream stream = newStream(synStream,parentStream);
|
||||
stream.updateCloseState(synStream.isClose(),true);
|
||||
stream.setStreamFrameListener(listener);
|
||||
if (streams.putIfAbsent(synStream.getStreamId(), stream) != null)
|
||||
|
||||
if (synStream.isUnidirectional())
|
||||
{
|
||||
// unidirectional streams are implicitly half closed for the client
|
||||
stream.updateCloseState(true,false);
|
||||
if (!stream.isClosed())
|
||||
parentStream.associate(stream);
|
||||
}
|
||||
|
||||
if (streams.putIfAbsent(synStream.getStreamId(),stream) != null)
|
||||
{
|
||||
// If this happens we have a bug since we did not check that the peer's streamId was valid
|
||||
// (if we're on server, then the client sent an odd streamId and we did not check that)
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException("StreamId: " + synStream.getStreamId() + " invalid.");
|
||||
}
|
||||
|
||||
logger.debug("Created {}", stream);
|
||||
logger.debug("Created {}",stream);
|
||||
notifyStreamCreated(stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
private IStream newStream(SynStreamFrame frame)
|
||||
private IStream newStream(SynStreamFrame frame, IStream parentStream)
|
||||
{
|
||||
return new StandardStream(frame, this, windowSize);
|
||||
return new StandardStream(frame,this,windowSize,parentStream);
|
||||
}
|
||||
|
||||
private void notifyStreamCreated(IStream stream)
|
||||
|
@ -462,7 +475,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -470,13 +483,17 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
|
||||
private void removeStream(IStream stream)
|
||||
{
|
||||
if (stream.isUnidirectional())
|
||||
{
|
||||
stream.getAssociatedStream().disassociate(stream);
|
||||
}
|
||||
|
||||
IStream removed = streams.remove(stream.getId());
|
||||
if (removed != null)
|
||||
{
|
||||
assert removed == stream;
|
||||
logger.debug("Removed {}", stream);
|
||||
notifyStreamClosed(stream);
|
||||
}
|
||||
|
||||
logger.debug("Removed {}",stream);
|
||||
notifyStreamClosed(stream);
|
||||
}
|
||||
|
||||
private void notifyStreamClosed(IStream stream)
|
||||
|
@ -491,7 +508,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -503,13 +520,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
IStream stream = streams.get(streamId);
|
||||
if (stream == null)
|
||||
{
|
||||
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
|
||||
logger.debug("Unknown stream {}", rstInfo);
|
||||
RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM);
|
||||
logger.debug("Unknown stream {}",rstInfo);
|
||||
rst(rstInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
processReply(stream, frame);
|
||||
processReply(stream,frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -522,15 +539,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
|
||||
private void onRst(RstStreamFrame frame)
|
||||
{
|
||||
// TODO: implement logic to clean up unidirectional streams associated with this stream
|
||||
|
||||
IStream stream = streams.get(frame.getStreamId());
|
||||
|
||||
if (stream != null)
|
||||
stream.process(frame);
|
||||
|
||||
RstInfo rstInfo = new RstInfo(frame.getStreamId(), StreamStatus.from(frame.getVersion(), frame.getStatusCode()));
|
||||
notifyOnRst(listener, rstInfo);
|
||||
RstInfo rstInfo = new RstInfo(frame.getStreamId(),StreamStatus.from(frame.getVersion(),frame.getStatusCode()));
|
||||
notifyOnRst(listener,rstInfo);
|
||||
flush();
|
||||
|
||||
if (stream != null)
|
||||
|
@ -546,11 +561,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
windowSize = windowSizeSetting.value();
|
||||
for (IStream stream : streams.values())
|
||||
stream.updateWindowSize(windowSize - prevWindowSize);
|
||||
logger.debug("Updated window size to {}", windowSize);
|
||||
logger.debug("Updated window size to {}",windowSize);
|
||||
}
|
||||
|
||||
SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(), frame.isClearPersisted());
|
||||
notifyOnSettings(listener, settingsInfo);
|
||||
SettingsInfo settingsInfo = new SettingsInfo(frame.getSettings(),frame.isClearPersisted());
|
||||
notifyOnSettings(listener,settingsInfo);
|
||||
flush();
|
||||
}
|
||||
|
||||
|
@ -560,21 +575,21 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
if (pingId % 2 == pingIds.get() % 2)
|
||||
{
|
||||
PingInfo pingInfo = new PingInfo(frame.getPingId());
|
||||
notifyOnPing(listener, pingInfo);
|
||||
notifyOnPing(listener,pingInfo);
|
||||
flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
control(null, frame, 0, TimeUnit.MILLISECONDS, null, null);
|
||||
control(null,frame,0,TimeUnit.MILLISECONDS,null,null);
|
||||
}
|
||||
}
|
||||
|
||||
private void onGoAway(GoAwayFrame frame)
|
||||
{
|
||||
if (goAwayReceived.compareAndSet(false, true))
|
||||
if (goAwayReceived.compareAndSet(false,true))
|
||||
{
|
||||
GoAwayInfo goAwayInfo = new GoAwayInfo(frame.getLastStreamId(), SessionStatus.from(frame.getStatusCode()));
|
||||
notifyOnGoAway(listener, goAwayInfo);
|
||||
GoAwayInfo goAwayInfo = new GoAwayInfo(frame.getLastStreamId(),SessionStatus.from(frame.getStatusCode()));
|
||||
notifyOnGoAway(listener,goAwayInfo);
|
||||
flush();
|
||||
// SPDY does not require to send back a response to a GO_AWAY.
|
||||
// We notified the application of the last good stream id,
|
||||
|
@ -589,13 +604,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
IStream stream = streams.get(streamId);
|
||||
if (stream == null)
|
||||
{
|
||||
RstInfo rstInfo = new RstInfo(streamId, StreamStatus.INVALID_STREAM);
|
||||
logger.debug("Unknown stream, {}", rstInfo);
|
||||
RstInfo rstInfo = new RstInfo(streamId,StreamStatus.INVALID_STREAM);
|
||||
logger.debug("Unknown stream, {}",rstInfo);
|
||||
rst(rstInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
processHeaders(stream, frame);
|
||||
processHeaders(stream,frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -627,13 +642,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking callback with {} on listener {}", x, listener);
|
||||
logger.debug("Invoking callback with {} on listener {}",x,listener);
|
||||
listener.onException(x);
|
||||
}
|
||||
}
|
||||
catch (Exception xx)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, xx);
|
||||
logger.info("Exception while notifying listener " + listener,xx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -643,13 +658,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking callback with {} on listener {}", synInfo, listener);
|
||||
return listener.onSyn(stream, synInfo);
|
||||
logger.debug("Invoking callback with {} on listener {}",synInfo,listener);
|
||||
return listener.onSyn(stream,synInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -660,13 +675,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking callback with {} on listener {}", rstInfo, listener);
|
||||
listener.onRst(this, rstInfo);
|
||||
logger.debug("Invoking callback with {} on listener {}",rstInfo,listener);
|
||||
listener.onRst(this,rstInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,13 +691,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking callback with {} on listener {}", settingsInfo, listener);
|
||||
listener.onSettings(this, settingsInfo);
|
||||
logger.debug("Invoking callback with {} on listener {}",settingsInfo,listener);
|
||||
listener.onSettings(this,settingsInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -692,13 +707,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking callback with {} on listener {}", pingInfo, listener);
|
||||
listener.onPing(this, pingInfo);
|
||||
logger.debug("Invoking callback with {} on listener {}",pingInfo,listener);
|
||||
listener.onPing(this,pingInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -708,13 +723,13 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking callback with {} on listener {}", goAwayInfo, listener);
|
||||
listener.onGoAway(this, goAwayInfo);
|
||||
logger.debug("Invoking callback with {} on listener {}",goAwayInfo,listener);
|
||||
listener.onGoAway(this,goAwayInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -724,7 +739,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
try
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
updateLastStreamId(stream);
|
||||
if (stream.isClosed())
|
||||
removeStream(stream);
|
||||
}
|
||||
|
||||
// Synchronization is necessary, since we may have concurrent replies
|
||||
// and those needs to be generated and enqueued atomically in order
|
||||
|
@ -732,10 +751,10 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
synchronized (this)
|
||||
{
|
||||
ByteBuffer buffer = generator.control(frame);
|
||||
logger.debug("Queuing {} on {}", frame, stream);
|
||||
ControlFrameBytes<C> frameBytes = new ControlFrameBytes<>(stream, handler, context, frame, buffer);
|
||||
logger.debug("Queuing {} on {}",frame,stream);
|
||||
ControlFrameBytes<C> frameBytes = new ControlFrameBytes<>(stream,handler,context,frame,buffer);
|
||||
if (timeout > 0)
|
||||
frameBytes.task = scheduler.schedule(frameBytes, timeout, unit);
|
||||
frameBytes.task = scheduler.schedule(frameBytes,timeout,unit);
|
||||
|
||||
// Special handling for PING frames, they must be sent as soon as possible
|
||||
if (ControlFrameType.PING == frame.getType())
|
||||
|
@ -748,7 +767,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
notifyHandlerFailed(handler, x);
|
||||
notifyHandlerFailed(handler,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -761,7 +780,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
int oldValue = lastStreamId.get();
|
||||
while (streamId > oldValue)
|
||||
{
|
||||
if (lastStreamId.compareAndSet(oldValue, streamId))
|
||||
if (lastStreamId.compareAndSet(oldValue,streamId))
|
||||
break;
|
||||
oldValue = lastStreamId.get();
|
||||
}
|
||||
|
@ -771,10 +790,12 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
@Override
|
||||
public <C> void data(IStream stream, DataInfo dataInfo, long timeout, TimeUnit unit, Handler<C> handler, C context)
|
||||
{
|
||||
logger.debug("Queuing {} on {}", dataInfo, stream);
|
||||
DataFrameBytes<C> frameBytes = new DataFrameBytes<>(stream, handler, context, dataInfo);
|
||||
logger.debug("Queuing {} on {}",dataInfo,stream);
|
||||
DataFrameBytes<C> frameBytes = new DataFrameBytes<>(stream,handler,context,dataInfo);
|
||||
if (timeout > 0)
|
||||
frameBytes.task = scheduler.schedule(frameBytes, timeout, unit);
|
||||
{
|
||||
frameBytes.task = scheduler.schedule(frameBytes,timeout,unit);
|
||||
}
|
||||
append(frameBytes);
|
||||
flush();
|
||||
}
|
||||
|
@ -799,30 +820,35 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
frameBytes = queue.get(i);
|
||||
|
||||
if (stalledStreams != null && stalledStreams.contains(frameBytes.getStream()))
|
||||
IStream stream = frameBytes.getStream();
|
||||
if (stream != null && stalledStreams != null && stalledStreams.contains(stream))
|
||||
continue;
|
||||
|
||||
buffer = frameBytes.getByteBuffer();
|
||||
if (buffer != null)
|
||||
{
|
||||
queue.remove(i);
|
||||
// TODO: stream.isUniDirectional() check here is only needed for pushStreams which send a syn with close=true --> find a better solution
|
||||
if (stream != null && !streams.containsValue(stream) && !stream.isUnidirectional())
|
||||
frameBytes.fail(new StreamException(stream.getId(),StreamStatus.INVALID_STREAM));
|
||||
break;
|
||||
}
|
||||
|
||||
if (stalledStreams == null)
|
||||
stalledStreams = new HashSet<>();
|
||||
stalledStreams.add(frameBytes.getStream());
|
||||
if (stream != null)
|
||||
stalledStreams.add(stream);
|
||||
|
||||
logger.debug("Flush stalled for {}, {} frame(s) in queue", frameBytes, queue.size());
|
||||
logger.debug("Flush stalled for {}, {} frame(s) in queue",frameBytes,queue.size());
|
||||
}
|
||||
|
||||
if (buffer == null)
|
||||
return;
|
||||
|
||||
flushing = true;
|
||||
logger.debug("Flushing {}, {} frame(s) in queue", frameBytes, queue.size());
|
||||
logger.debug("Flushing {}, {} frame(s) in queue",frameBytes,queue.size());
|
||||
}
|
||||
write(buffer, this, frameBytes);
|
||||
write(buffer,this,frameBytes);
|
||||
}
|
||||
|
||||
private void append(FrameBytes frameBytes)
|
||||
|
@ -837,7 +863,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
break;
|
||||
--index;
|
||||
}
|
||||
queue.add(index, frameBytes);
|
||||
queue.add(index,frameBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -853,7 +879,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
break;
|
||||
++index;
|
||||
}
|
||||
queue.add(index, frameBytes);
|
||||
queue.add(index,frameBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -862,7 +888,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
synchronized (queue)
|
||||
{
|
||||
logger.debug("Completed write of {}, {} frame(s) in queue", frameBytes, queue.size());
|
||||
logger.debug("Completed write of {}, {} frame(s) in queue",frameBytes,queue.size());
|
||||
flushing = false;
|
||||
}
|
||||
frameBytes.complete();
|
||||
|
@ -878,8 +904,8 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
{
|
||||
if (controller != null)
|
||||
{
|
||||
logger.debug("Writing {} frame bytes of {}", buffer.remaining(), frameBytes);
|
||||
controller.write(buffer, handler, frameBytes);
|
||||
logger.debug("Writing {} frame bytes of {}",buffer.remaining(),frameBytes);
|
||||
controller.write(buffer,handler,frameBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -898,7 +924,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
public void run()
|
||||
{
|
||||
if (handler != null)
|
||||
notifyHandlerCompleted(handler, context);
|
||||
notifyHandlerCompleted(handler,context);
|
||||
flush();
|
||||
}
|
||||
});
|
||||
|
@ -909,7 +935,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
try
|
||||
{
|
||||
if (handler != null)
|
||||
notifyHandlerCompleted(handler, context);
|
||||
notifyHandlerCompleted(handler,context);
|
||||
flush();
|
||||
}
|
||||
finally
|
||||
|
@ -927,12 +953,11 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying handler " + handler, x);
|
||||
logger.info("Exception while notifying handler " + handler,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void notifyHandlerFailed(Handler handler, Throwable x)
|
||||
private <C> void notifyHandlerFailed(Handler<C> handler, Throwable x)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -941,7 +966,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
}
|
||||
catch (Exception xx)
|
||||
{
|
||||
logger.info("Exception while notifying handler " + handler, xx);
|
||||
logger.info("Exception while notifying handler " + handler,xx);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -952,6 +977,8 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
public abstract ByteBuffer getByteBuffer();
|
||||
|
||||
public abstract void complete();
|
||||
|
||||
public abstract void fail(Throwable throwable);
|
||||
}
|
||||
|
||||
private abstract class AbstractFrameBytes<C> implements FrameBytes, Runnable
|
||||
|
@ -983,16 +1010,23 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
|
||||
@Override
|
||||
public void complete()
|
||||
{
|
||||
cancelTask();
|
||||
StandardSession.this.complete(handler,context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(Throwable x)
|
||||
{
|
||||
cancelTask();
|
||||
notifyHandlerFailed(handler,x);
|
||||
}
|
||||
|
||||
private void cancelTask()
|
||||
{
|
||||
ScheduledFuture<?> task = this.task;
|
||||
if (task != null)
|
||||
task.cancel(false);
|
||||
StandardSession.this.complete(handler, context);
|
||||
}
|
||||
|
||||
protected void fail(Throwable x)
|
||||
{
|
||||
notifyHandlerFailed(handler, x);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1010,7 +1044,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
|
||||
private ControlFrameBytes(IStream stream, Handler<C> handler, C context, ControlFrame frame, ByteBuffer buffer)
|
||||
{
|
||||
super(stream, handler, context);
|
||||
super(stream,handler,context);
|
||||
this.frame = frame;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
@ -1051,7 +1085,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
|
||||
private DataFrameBytes(IStream stream, Handler<C> handler, C context, DataInfo dataInfo)
|
||||
{
|
||||
super(stream, handler, context);
|
||||
super(stream,handler,context);
|
||||
this.dataInfo = dataInfo;
|
||||
}
|
||||
|
||||
|
@ -1069,7 +1103,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
if (size > windowSize)
|
||||
size = windowSize;
|
||||
|
||||
buffer = generator.data(stream.getId(), size, dataInfo);
|
||||
buffer = generator.data(stream.getId(),size,dataInfo);
|
||||
return buffer;
|
||||
}
|
||||
catch (Throwable x)
|
||||
|
@ -1096,7 +1130,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
else
|
||||
{
|
||||
super.complete();
|
||||
stream.updateCloseState(dataInfo.isClose(), true);
|
||||
stream.updateCloseState(dataInfo.isClose(),true);
|
||||
if (stream.isClosed())
|
||||
removeStream(stream);
|
||||
}
|
||||
|
@ -1105,7 +1139,7 @@ public class StandardSession implements ISession, Parser.Listener, Handler<Stand
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("DATA bytes @%x available=%d consumed=%d on %s", dataInfo.hashCode(), dataInfo.available(), dataInfo.consumed(), getStream());
|
||||
return String.format("DATA bytes @%x available=%d consumed=%d on %s",dataInfo.hashCode(),dataInfo.available(),dataInfo.consumed(),getStream());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -33,6 +35,7 @@ import org.eclipse.jetty.spdy.api.Session;
|
|||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
import org.eclipse.jetty.spdy.frames.HeadersFrame;
|
||||
|
@ -46,18 +49,22 @@ public class StandardStream implements IStream
|
|||
{
|
||||
private static final Logger logger = Log.getLogger(Stream.class);
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
private final IStream associatedStream;
|
||||
private final SynStreamFrame frame;
|
||||
private final ISession session;
|
||||
private final AtomicInteger windowSize;
|
||||
private final Set<Stream> pushedStreams = Collections.newSetFromMap(new ConcurrentHashMap<Stream, Boolean>());
|
||||
private volatile StreamFrameListener listener;
|
||||
private volatile OpenState openState = OpenState.SYN_SENT;
|
||||
private volatile CloseState closeState = CloseState.OPENED;
|
||||
private volatile boolean reset = false;
|
||||
|
||||
public StandardStream(SynStreamFrame frame, ISession session, int windowSize)
|
||||
public StandardStream(SynStreamFrame frame, ISession session, int windowSize, IStream associatedStream)
|
||||
{
|
||||
this.frame = frame;
|
||||
this.session = session;
|
||||
this.windowSize = new AtomicInteger(windowSize);
|
||||
this.associatedStream = associatedStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,6 +73,30 @@ public class StandardStream implements IStream
|
|||
return frame.getStreamId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IStream getAssociatedStream()
|
||||
{
|
||||
return associatedStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Stream> getPushedStreams()
|
||||
{
|
||||
return pushedStreams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void associate(IStream stream)
|
||||
{
|
||||
pushedStreams.add(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disassociate(IStream stream)
|
||||
{
|
||||
pushedStreams.remove(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getPriority()
|
||||
{
|
||||
|
@ -82,7 +113,7 @@ public class StandardStream implements IStream
|
|||
public void updateWindowSize(int delta)
|
||||
{
|
||||
int size = windowSize.addAndGet(delta);
|
||||
logger.debug("Updated window size by {}, new window size {}", delta, size);
|
||||
logger.debug("Updated window size by {}, new window size {}",delta,size);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -91,14 +122,6 @@ public class StandardStream implements IStream
|
|||
return session;
|
||||
}
|
||||
|
||||
public boolean isHalfClosed()
|
||||
{
|
||||
CloseState closeState = this.closeState;
|
||||
return closeState == CloseState.LOCALLY_CLOSED ||
|
||||
closeState == CloseState.REMOTELY_CLOSED ||
|
||||
closeState == CloseState.CLOSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String key)
|
||||
{
|
||||
|
@ -108,7 +131,7 @@ public class StandardStream implements IStream
|
|||
@Override
|
||||
public void setAttribute(String key, Object value)
|
||||
{
|
||||
attributes.put(key, value);
|
||||
attributes.put(key,value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,7 +155,7 @@ public class StandardStream implements IStream
|
|||
{
|
||||
case OPENED:
|
||||
{
|
||||
closeState = local ? CloseState.LOCALLY_CLOSED : CloseState.REMOTELY_CLOSED;
|
||||
closeState = local?CloseState.LOCALLY_CLOSED:CloseState.REMOTELY_CLOSED;
|
||||
break;
|
||||
}
|
||||
case LOCALLY_CLOSED:
|
||||
|
@ -173,16 +196,16 @@ public class StandardStream implements IStream
|
|||
{
|
||||
openState = OpenState.REPLY_RECV;
|
||||
SynReplyFrame synReply = (SynReplyFrame)frame;
|
||||
updateCloseState(synReply.isClose(), false);
|
||||
ReplyInfo replyInfo = new ReplyInfo(synReply.getHeaders(), synReply.isClose());
|
||||
updateCloseState(synReply.isClose(),false);
|
||||
ReplyInfo replyInfo = new ReplyInfo(synReply.getHeaders(),synReply.isClose());
|
||||
notifyOnReply(replyInfo);
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
HeadersFrame headers = (HeadersFrame)frame;
|
||||
updateCloseState(headers.isClose(), false);
|
||||
HeadersInfo headersInfo = new HeadersInfo(headers.getHeaders(), headers.isClose(), headers.isResetCompression());
|
||||
updateCloseState(headers.isClose(),false);
|
||||
HeadersInfo headersInfo = new HeadersInfo(headers.getHeaders(),headers.isClose(),headers.isResetCompression());
|
||||
notifyOnHeaders(headersInfo);
|
||||
break;
|
||||
}
|
||||
|
@ -194,7 +217,7 @@ public class StandardStream implements IStream
|
|||
}
|
||||
case RST_STREAM:
|
||||
{
|
||||
// TODO:
|
||||
reset = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -208,15 +231,24 @@ public class StandardStream implements IStream
|
|||
@Override
|
||||
public void process(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
if (!canReceive())
|
||||
// TODO: in v3 we need to send a rst instead of just ignoring
|
||||
// ignore data frame if this stream is remotelyClosed already
|
||||
if (isHalfClosed() && !isLocallyClosed())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
|
||||
logger.debug("Ignoring received dataFrame as this stream is remotely closed: " + frame);
|
||||
return;
|
||||
}
|
||||
|
||||
updateCloseState(frame.isClose(), false);
|
||||
if (!canReceive())
|
||||
{
|
||||
logger.debug("Can't receive. Sending rst: " + frame);
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data, frame.isClose(), frame.isCompress())
|
||||
updateCloseState(frame.isClose(),false);
|
||||
|
||||
ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(data,frame.isClose(),frame.isCompress())
|
||||
{
|
||||
@Override
|
||||
public void consume(int delta)
|
||||
|
@ -243,8 +275,8 @@ public class StandardStream implements IStream
|
|||
{
|
||||
if (delta > 0)
|
||||
{
|
||||
WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(), getId(), delta);
|
||||
session.control(this, windowUpdateFrame, 0, TimeUnit.MILLISECONDS, null, null);
|
||||
WindowUpdateFrame windowUpdateFrame = new WindowUpdateFrame(session.getVersion(),getId(),delta);
|
||||
session.control(this,windowUpdateFrame,0,TimeUnit.MILLISECONDS,null,null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,13 +287,13 @@ public class StandardStream implements IStream
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking reply callback with {} on listener {}", replyInfo, listener);
|
||||
listener.onReply(this, replyInfo);
|
||||
logger.debug("Invoking reply callback with {} on listener {}",replyInfo,listener);
|
||||
listener.onReply(this,replyInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,13 +304,13 @@ public class StandardStream implements IStream
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking headers callback with {} on listener {}", frame, listener);
|
||||
listener.onHeaders(this, headersInfo);
|
||||
logger.debug("Invoking headers callback with {} on listener {}",frame,listener);
|
||||
listener.onHeaders(this,headersInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,22 +321,42 @@ public class StandardStream implements IStream
|
|||
{
|
||||
if (listener != null)
|
||||
{
|
||||
logger.debug("Invoking data callback with {} on listener {}", dataInfo, listener);
|
||||
listener.onData(this, dataInfo);
|
||||
logger.debug("Invoked data callback with {} on listener {}", dataInfo, listener);
|
||||
logger.debug("Invoking data callback with {} on listener {}",dataInfo,listener);
|
||||
listener.onData(this,dataInfo);
|
||||
logger.debug("Invoked data callback with {} on listener {}",dataInfo,listener);
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
logger.info("Exception while notifying listener " + listener, x);
|
||||
logger.info("Exception while notifying listener " + listener,x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Stream> syn(SynInfo synInfo)
|
||||
{
|
||||
Promise<Stream> result = new Promise<>();
|
||||
syn(synInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Handler<Stream> handler)
|
||||
{
|
||||
if (isClosed() || isReset())
|
||||
{
|
||||
handler.failed(new StreamException(getId(),StreamStatus.STREAM_ALREADY_CLOSED));
|
||||
return;
|
||||
}
|
||||
PushSynInfo pushSynInfo = new PushSynInfo(getId(),synInfo);
|
||||
session.syn(pushSynInfo,null,timeout,unit,handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> reply(ReplyInfo replyInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
reply(replyInfo, 0, TimeUnit.MILLISECONDS, result);
|
||||
reply(replyInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -312,16 +364,16 @@ public class StandardStream implements IStream
|
|||
public void reply(ReplyInfo replyInfo, long timeout, TimeUnit unit, Handler<Void> handler)
|
||||
{
|
||||
openState = OpenState.REPLY_SENT;
|
||||
updateCloseState(replyInfo.isClose(), true);
|
||||
SynReplyFrame frame = new SynReplyFrame(session.getVersion(), replyInfo.getFlags(), getId(), replyInfo.getHeaders());
|
||||
session.control(this, frame, timeout, unit, handler, null);
|
||||
updateCloseState(replyInfo.isClose(),true);
|
||||
SynReplyFrame frame = new SynReplyFrame(session.getVersion(),replyInfo.getFlags(),getId(),replyInfo.getHeaders());
|
||||
session.control(this,frame,timeout,unit,handler,null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> data(DataInfo dataInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
data(dataInfo, 0, TimeUnit.MILLISECONDS, result);
|
||||
data(dataInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -330,25 +382,25 @@ public class StandardStream implements IStream
|
|||
{
|
||||
if (!canSend())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
throw new IllegalStateException("Protocol violation: cannot send a DATA frame before a SYN_REPLY frame");
|
||||
}
|
||||
if (isLocallyClosed())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
throw new IllegalStateException("Protocol violation: cannot send a DATA frame on a closed stream");
|
||||
}
|
||||
|
||||
// Cannot update the close state here, because the data that we send may
|
||||
// be flow controlled, so we need the stream to update the window size.
|
||||
session.data(this, dataInfo, timeout, unit, handler, null);
|
||||
session.data(this,dataInfo,timeout,unit,handler,null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> headers(HeadersInfo headersInfo)
|
||||
{
|
||||
Promise<Void> result = new Promise<>();
|
||||
headers(headersInfo, 0, TimeUnit.MILLISECONDS, result);
|
||||
headers(headersInfo,0,TimeUnit.MILLISECONDS,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -357,18 +409,41 @@ public class StandardStream implements IStream
|
|||
{
|
||||
if (!canSend())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame before a SYN_REPLY frame");
|
||||
}
|
||||
if (isLocallyClosed())
|
||||
{
|
||||
session.rst(new RstInfo(getId(), StreamStatus.PROTOCOL_ERROR));
|
||||
session.rst(new RstInfo(getId(),StreamStatus.PROTOCOL_ERROR));
|
||||
throw new IllegalStateException("Protocol violation: cannot send a HEADERS frame on a closed stream");
|
||||
}
|
||||
|
||||
updateCloseState(headersInfo.isClose(), true);
|
||||
HeadersFrame frame = new HeadersFrame(session.getVersion(), headersInfo.getFlags(), getId(), headersInfo.getHeaders());
|
||||
session.control(this, frame, timeout, unit, handler, null);
|
||||
updateCloseState(headersInfo.isClose(),true);
|
||||
HeadersFrame frame = new HeadersFrame(session.getVersion(),headersInfo.getFlags(),getId(),headersInfo.getHeaders());
|
||||
session.control(this,frame,timeout,unit,handler,null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnidirectional()
|
||||
{
|
||||
if (associatedStream != null)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReset()
|
||||
{
|
||||
return reset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHalfClosed()
|
||||
{
|
||||
CloseState closeState = this.closeState;
|
||||
return closeState == CloseState.LOCALLY_CLOSED || closeState == CloseState.REMOTELY_CLOSED || closeState == CloseState.CLOSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -386,7 +461,7 @@ public class StandardStream implements IStream
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("stream=%d v%d %s", getId(), session.getVersion(), closeState);
|
||||
return String.format("stream=%d v%d %s",getId(),session.getVersion(),closeState);
|
||||
}
|
||||
|
||||
private boolean canSend()
|
||||
|
|
|
@ -162,7 +162,7 @@ public abstract class DataInfo
|
|||
/**
|
||||
* <p>Reads and consumes the content bytes of this {@link DataInfo} into the given {@link ByteBuffer}.</p>
|
||||
*
|
||||
* @param output the {@link ByteBuffer} to copy to bytes into
|
||||
* @param output the {@link ByteBuffer} to copy the bytes into
|
||||
* @return the number of bytes copied
|
||||
* @see #consume(int)
|
||||
*/
|
||||
|
|
|
@ -75,7 +75,7 @@ public interface Session
|
|||
* @see #syn(SynInfo, StreamFrameListener, long, TimeUnit, Handler)
|
||||
*/
|
||||
public Future<Stream> syn(SynInfo synInfo, StreamFrameListener listener);
|
||||
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_FRAME to create a new {@link Stream SPDY stream}.</p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
|
@ -90,6 +90,7 @@ public interface Session
|
|||
*/
|
||||
public void syn(SynInfo synInfo, StreamFrameListener listener, long timeout, TimeUnit unit, Handler<Stream> handler);
|
||||
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a RST_STREAM to abort a stream.</p>
|
||||
* <p>Callers may use the returned future to wait for the reset to be sent.</p>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.eclipse.jetty.spdy.api;
|
||||
|
||||
import java.nio.channels.WritePendingException;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -79,12 +80,35 @@ public interface Stream
|
|||
* @return the priority of this stream
|
||||
*/
|
||||
public byte getPriority();
|
||||
|
||||
|
||||
/**
|
||||
* @return the session this stream is associated to
|
||||
*/
|
||||
public Session getSession();
|
||||
|
||||
/**
|
||||
* <p>Initiate a unidirectional spdy pushstream associated to this stream asynchronously<p>
|
||||
* <p>Callers may use the returned future to get the pushstream once it got created</p>
|
||||
*
|
||||
* @param synInfo the metadata to send on stream creation
|
||||
* @return a future containing the stream once it got established
|
||||
* @see #syn(SynInfo, long, TimeUnit, Handler)
|
||||
*/
|
||||
public Future<Stream> syn(SynInfo synInfo);
|
||||
|
||||
/**
|
||||
* <p>Initiate a unidirectional spdy pushstream associated to this stream asynchronously<p>
|
||||
* <p>Callers may pass a non-null completion handler to be notified of when the
|
||||
* pushstream has been established.</p>
|
||||
*
|
||||
* @param synInfo the metadata to send on stream creation
|
||||
* @param timeout the operation's timeout
|
||||
* @param unit the timeout's unit
|
||||
* @param handler the completion handler that gets notified once the pushstream is established
|
||||
* @see #syn(SynInfo)
|
||||
*/
|
||||
public void syn(SynInfo synInfo, long timeout, TimeUnit unit, Handler<Stream> handler);
|
||||
|
||||
/**
|
||||
* <p>Sends asynchronously a SYN_REPLY frame in response to a SYN_STREAM frame.</p>
|
||||
* <p>Callers may use the returned future to wait for the reply to be actually sent.</p>
|
||||
|
@ -161,6 +185,16 @@ public interface Stream
|
|||
*/
|
||||
public void headers(HeadersInfo headersInfo, long timeout, TimeUnit unit, Handler<Void> handler);
|
||||
|
||||
/**
|
||||
* @return whether this stream is unidirectional or not
|
||||
*/
|
||||
public boolean isUnidirectional();
|
||||
|
||||
/**
|
||||
* @return whether this stream has been reset
|
||||
*/
|
||||
public boolean isReset();
|
||||
|
||||
/**
|
||||
* @return whether this stream has been closed by both parties
|
||||
* @see #isHalfClosed()
|
||||
|
@ -171,7 +205,6 @@ public interface Stream
|
|||
* @return whether this stream has been closed by one party only
|
||||
* @see #isClosed() * @param timeout the timeout for the stream creation
|
||||
* @param unit the timeout's unit
|
||||
|
||||
*/
|
||||
public boolean isHalfClosed();
|
||||
|
||||
|
@ -196,4 +229,15 @@ public interface Stream
|
|||
* @see #setAttribute(String, Object)
|
||||
*/
|
||||
public Object removeAttribute(String key);
|
||||
|
||||
/**
|
||||
* @return the associated parent stream or null if this is not an associated stream
|
||||
*/
|
||||
public Stream getAssociatedStream();
|
||||
|
||||
/**
|
||||
* @return associated child streams or an empty set if no associated streams exist
|
||||
*/
|
||||
public Set<Stream> getPushedStreams();
|
||||
|
||||
}
|
||||
|
|
|
@ -28,11 +28,8 @@ public class SynInfo
|
|||
* @see #getFlags()
|
||||
*/
|
||||
public static final byte FLAG_CLOSE = 1;
|
||||
public static final byte FLAG_UNIDIRECTIONAL = 2;
|
||||
|
||||
private final boolean close;
|
||||
private final boolean unidirectional;
|
||||
private final int associatedStreamId;
|
||||
private final byte priority;
|
||||
private final Headers headers;
|
||||
|
||||
|
@ -56,28 +53,28 @@ public class SynInfo
|
|||
*/
|
||||
public SynInfo(Headers headers, boolean close)
|
||||
{
|
||||
this(headers, close, false, 0, (byte)0);
|
||||
this(headers, close, (byte)0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a {@link ReplyInfo} instance with the given headers and the given close flag,
|
||||
* the given unidirectional flag, the given associated stream, and with the given priority.</p>
|
||||
*
|
||||
* @param headers the {@link Headers}
|
||||
* @param close the value of the close flag
|
||||
* @param unidirectional the value of the unidirectional flag
|
||||
* @param associatedStreamId the associated stream id
|
||||
* @param priority the priority
|
||||
* <p>
|
||||
* Creates a {@link ReplyInfo} instance with the given headers, the given close flag and with the given priority.
|
||||
* </p>
|
||||
*
|
||||
* @param headers
|
||||
* the {@link Headers}
|
||||
* @param close
|
||||
* the value of the close flag
|
||||
* @param priority
|
||||
* the priority
|
||||
*/
|
||||
public SynInfo(Headers headers, boolean close, boolean unidirectional, int associatedStreamId, byte priority)
|
||||
public SynInfo(Headers headers, boolean close, byte priority)
|
||||
{
|
||||
this.close = close;
|
||||
this.unidirectional = unidirectional;
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
this.priority = priority;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the value of the close flag
|
||||
*/
|
||||
|
@ -86,22 +83,6 @@ public class SynInfo
|
|||
return close;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of the unidirectional flag
|
||||
*/
|
||||
public boolean isUnidirectional()
|
||||
{
|
||||
return unidirectional;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the associated stream id
|
||||
*/
|
||||
public int getAssociatedStreamId()
|
||||
{
|
||||
return associatedStreamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the priority
|
||||
*/
|
||||
|
@ -117,17 +98,14 @@ public class SynInfo
|
|||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the close and unidirectional flags as integer
|
||||
* @return the close flag as integer
|
||||
* @see #FLAG_CLOSE
|
||||
* @see #FLAG_UNIDIRECTIONAL
|
||||
*/
|
||||
public byte getFlags()
|
||||
{
|
||||
byte flags = isClose() ? FLAG_CLOSE : 0;
|
||||
flags += isUnidirectional() ? FLAG_UNIDIRECTIONAL : 0;
|
||||
return flags;
|
||||
return isClose() ? FLAG_CLOSE : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,17 +29,17 @@ public class RstStreamFrame extends ControlFrame
|
|||
this.streamId = streamId;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
|
||||
public int getStreamId()
|
||||
{
|
||||
return streamId;
|
||||
}
|
||||
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import org.eclipse.jetty.spdy.PushSynInfo;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
|
||||
|
@ -62,7 +63,7 @@ public class SynStreamFrame extends ControlFrame
|
|||
|
||||
public boolean isUnidirectional()
|
||||
{
|
||||
return (getFlags() & SynInfo.FLAG_UNIDIRECTIONAL) == SynInfo.FLAG_UNIDIRECTIONAL;
|
||||
return (getFlags() & PushSynInfo.FLAG_UNIDIRECTIONAL) == PushSynInfo.FLAG_UNIDIRECTIONAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.eclipse.jetty.spdy.parser;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.CompressionFactory;
|
||||
import org.eclipse.jetty.spdy.PushSynInfo;
|
||||
import org.eclipse.jetty.spdy.StreamException;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
|
@ -131,7 +132,7 @@ public class SynStreamBodyParser extends ControlFrameBodyParser
|
|||
{
|
||||
byte flags = controlFrameParser.getFlags();
|
||||
// TODO: can it be both FIN and UNIDIRECTIONAL ?
|
||||
if (flags != 0 && flags != SynInfo.FLAG_CLOSE && flags != SynInfo.FLAG_UNIDIRECTIONAL)
|
||||
if (flags != 0 && flags != SynInfo.FLAG_CLOSE && flags != PushSynInfo.FLAG_UNIDIRECTIONAL)
|
||||
throw new IllegalArgumentException("Invalid flag " + flags + " for frame " + ControlFrameType.SYN_STREAM);
|
||||
|
||||
SynStreamFrame frame = new SynStreamFrame(version, flags, streamId, associatedStreamId, priority, new Headers(headers, true));
|
||||
|
|
|
@ -0,0 +1,457 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.Handler;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.HeadersInfo;
|
||||
import org.eclipse.jetty.spdy.api.RstInfo;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.StreamStatus;
|
||||
import org.eclipse.jetty.spdy.api.StringDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StandardSessionTest
|
||||
{
|
||||
@Mock
|
||||
private ISession sessionMock;
|
||||
private ByteBufferPool bufferPool;
|
||||
private Executor threadPool;
|
||||
private StandardSession session;
|
||||
private Generator generator;
|
||||
private ScheduledExecutorService scheduler;
|
||||
private Headers headers;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
bufferPool = new StandardByteBufferPool();
|
||||
threadPool = Executors.newCachedThreadPool();
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory.StandardCompressor());
|
||||
session = new StandardSession(SPDY.V2,bufferPool,threadPool,scheduler,new TestController(),null,1,null,generator);
|
||||
headers = new Headers();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamIsRemovedFromSessionWhenReset() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
assertThatStreamIsInSession(stream);
|
||||
assertThat("stream is not reset",stream.isReset(),is(false));
|
||||
session.rst(new RstInfo(stream.getId(),StreamStatus.STREAM_ALREADY_CLOSED));
|
||||
assertThatStreamIsNotInSession(stream);
|
||||
assertThatStreamIsReset(stream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamIsAddedAndRemovedFromSession() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
assertThatStreamIsInSession(stream);
|
||||
stream.updateCloseState(true,true);
|
||||
session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),null));
|
||||
assertThatStreamIsClosed(stream);
|
||||
assertThatStreamIsNotInSession(stream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamIsRemovedWhenHeadersWithCloseFlagAreSent() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
assertThatStreamIsInSession(stream);
|
||||
stream.updateCloseState(true,false);
|
||||
stream.headers(new HeadersInfo(headers,true));
|
||||
assertThatStreamIsClosed(stream);
|
||||
assertThatStreamIsNotInSession(stream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamIsUnidirectional() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
assertThat("stream is not unidirectional",stream.isUnidirectional(),not(true));
|
||||
Stream pushStream = createPushStream(stream);
|
||||
assertThat("pushStream is unidirectional",pushStream.isUnidirectional(),is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamCreation() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
Stream stream = createStream();
|
||||
IStream pushStream = createPushStream(stream);
|
||||
assertThat("Push stream must be associated to the first stream created",pushStream.getAssociatedStream().getId(),is(stream.getId()));
|
||||
assertThat("streamIds need to be monotonic",pushStream.getId(),greaterThan(stream.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamIsNotClosedWhenAssociatedStreamIsClosed() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
Stream pushStream = createPushStream(stream);
|
||||
assertThatStreamIsNotHalfClosed(stream);
|
||||
assertThatStreamIsNotClosed(stream);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsNotClosed(pushStream);
|
||||
|
||||
stream.updateCloseState(true,true);
|
||||
assertThatStreamIsHalfClosed(stream);
|
||||
assertThatStreamIsNotClosed(stream);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsNotClosed(pushStream);
|
||||
|
||||
session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),null));
|
||||
assertThatStreamIsClosed(stream);
|
||||
assertThatPushStreamIsNotClosed(pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePushStreamOnClosedStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
stream.updateCloseState(true,true);
|
||||
assertThatStreamIsHalfClosed(stream);
|
||||
stream.updateCloseState(true,false);
|
||||
assertThatStreamIsClosed(stream);
|
||||
createPushStreamAndMakeSureItFails(stream);
|
||||
}
|
||||
|
||||
private void createPushStreamAndMakeSureItFails(IStream stream) throws InterruptedException
|
||||
{
|
||||
final CountDownLatch failedLatch = new CountDownLatch(1);
|
||||
SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
|
||||
stream.syn(synInfo,5,TimeUnit.SECONDS,new Handler<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Stream context)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failedLatch.countDown();
|
||||
}
|
||||
});
|
||||
assertThat("pushStream creation failed",failedLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamIsAddedAndRemovedFromParentAndSessionWhenClosed() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
IStream pushStream = createPushStream(stream);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsInSession(pushStream);
|
||||
assertThatStreamIsAssociatedWithPushStream(stream,pushStream);
|
||||
session.data(pushStream,new StringDataInfo("close",true),5,TimeUnit.SECONDS,null,null);
|
||||
assertThatPushStreamIsClosed(pushStream);
|
||||
assertThatPushStreamIsNotInSession(pushStream);
|
||||
assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamIsRemovedWhenReset() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
IStream pushStream = (IStream)stream.syn(new SynInfo(false)).get();
|
||||
assertThatPushStreamIsInSession(pushStream);
|
||||
session.rst(new RstInfo(pushStream.getId(),StreamStatus.INVALID_STREAM));
|
||||
assertThatPushStreamIsNotInSession(pushStream);
|
||||
assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
|
||||
assertThatStreamIsReset(pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamWithSynInfoClosedTrue() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
SynInfo synInfo = new SynInfo(headers,true,stream.getPriority());
|
||||
IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsClosed(pushStream);
|
||||
assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
|
||||
assertThatStreamIsNotInSession(pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPushStreamSendHeadersWithCloseFlagIsRemovedFromSessionAndDisassociateFromParent() throws InterruptedException, ExecutionException,
|
||||
TimeoutException
|
||||
{
|
||||
IStream stream = createStream();
|
||||
SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
|
||||
IStream pushStream = (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
|
||||
assertThatStreamIsAssociatedWithPushStream(stream,pushStream);
|
||||
assertThatPushStreamIsInSession(pushStream);
|
||||
pushStream.headers(new HeadersInfo(headers,true));
|
||||
assertThatPushStreamIsNotInSession(pushStream);
|
||||
assertThatPushStreamIsHalfClosed(pushStream);
|
||||
assertThatPushStreamIsClosed(pushStream);
|
||||
assertThatStreamIsNotAssociatedWithPushStream(stream,pushStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatedAndClosedListenersAreCalledForNewStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch createdListenerCalledLatch = new CountDownLatch(1);
|
||||
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
|
||||
session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch));
|
||||
IStream stream = createStream();
|
||||
session.onDataFrame(new DataFrame(stream.getId(),SynInfo.FLAG_CLOSE,128),ByteBuffer.allocate(128));
|
||||
stream.data(new StringDataInfo("close",true));
|
||||
assertThat("onStreamCreated listener has been called",createdListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListenerIsCalledForResetStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
|
||||
session.addListener(new TestStreamListener(null,closedListenerCalledLatch));
|
||||
IStream stream = createStream();
|
||||
session.rst(new RstInfo(stream.getId(),StreamStatus.CANCEL_STREAM));
|
||||
assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatedAndClosedListenersAreCalledForNewPushStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch createdListenerCalledLatch = new CountDownLatch(2);
|
||||
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
|
||||
session.addListener(new TestStreamListener(createdListenerCalledLatch,closedListenerCalledLatch));
|
||||
IStream stream = createStream();
|
||||
IStream pushStream = createPushStream(stream);
|
||||
session.data(pushStream,new StringDataInfo("close",true),5,TimeUnit.SECONDS,null,null);
|
||||
assertThat("onStreamCreated listener has been called twice. Once for the stream and once for the pushStream",
|
||||
createdListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListenerIsCalledForResetPushStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch closedListenerCalledLatch = new CountDownLatch(1);
|
||||
session.addListener(new TestStreamListener(null,closedListenerCalledLatch));
|
||||
IStream stream = createStream();
|
||||
IStream pushStream = createPushStream(stream);
|
||||
session.rst(new RstInfo(pushStream.getId(),StreamStatus.CANCEL_STREAM));
|
||||
assertThatOnStreamClosedListenerHasBeenCalled(closedListenerCalledLatch);
|
||||
}
|
||||
|
||||
private class TestStreamListener extends Session.StreamListener.Adapter
|
||||
{
|
||||
private CountDownLatch createdListenerCalledLatch;
|
||||
private CountDownLatch closedListenerCalledLatch;
|
||||
|
||||
public TestStreamListener(CountDownLatch createdListenerCalledLatch, CountDownLatch closedListenerCalledLatch)
|
||||
{
|
||||
this.createdListenerCalledLatch = createdListenerCalledLatch;
|
||||
this.closedListenerCalledLatch = closedListenerCalledLatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamCreated(Stream stream)
|
||||
{
|
||||
if (createdListenerCalledLatch != null)
|
||||
createdListenerCalledLatch.countDown();
|
||||
super.onStreamCreated(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamClosed(Stream stream)
|
||||
{
|
||||
if (closedListenerCalledLatch != null)
|
||||
closedListenerCalledLatch.countDown();
|
||||
super.onStreamClosed(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testSendDataOnHalfClosedStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
SynStreamFrame synStreamFrame = new SynStreamFrame(SPDY.V2,SynInfo.FLAG_CLOSE,1,0,(byte)0,null);
|
||||
IStream stream = new StandardStream(synStreamFrame,sessionMock,8184,null);
|
||||
stream.updateCloseState(synStreamFrame.isClose(),true);
|
||||
assertThat("stream is half closed",stream.isHalfClosed(),is(true));
|
||||
stream.data(new StringDataInfo("data on half closed stream",true));
|
||||
verify(sessionMock,never()).data(any(IStream.class),any(DataInfo.class),anyInt(),any(TimeUnit.class),any(Handler.class),any(void.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("In V3 we need to rst the stream if we receive data on a remotely half closed stream.")
|
||||
public void receiveDataOnRemotelyHalfClosedStreamResetsStreamInV3() throws InterruptedException, ExecutionException
|
||||
{
|
||||
IStream stream = (IStream)session.syn(new SynInfo(false),new StreamFrameListener.Adapter()).get();
|
||||
stream.updateCloseState(true,false);
|
||||
assertThat("stream is half closed from remote side",stream.isHalfClosed(),is(true));
|
||||
stream.process(new DataFrame(stream.getId(),(byte)0,256),ByteBuffer.allocate(256));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveDataOnRemotelyClosedStreamIsIgnored() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
final CountDownLatch onDataCalledLatch = new CountDownLatch(1);
|
||||
Stream stream = session.syn(new SynInfo(false),new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
onDataCalledLatch.countDown();
|
||||
super.onData(stream,dataInfo);
|
||||
}
|
||||
}).get(5,TimeUnit.SECONDS);
|
||||
session.onControlFrame(new SynReplyFrame(SPDY.V2,SynInfo.FLAG_CLOSE,stream.getId(),headers));
|
||||
session.onDataFrame(new DataFrame(stream.getId(),(byte)0,0),ByteBuffer.allocate(128));
|
||||
assertThat("onData is never called",onDataCalledLatch.await(1,TimeUnit.SECONDS),not(true));
|
||||
}
|
||||
|
||||
private IStream createStream() throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
SynInfo synInfo = new SynInfo(headers,false,(byte)0);
|
||||
return (IStream)session.syn(synInfo,new StreamFrameListener.Adapter()).get(5,TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private IStream createPushStream(Stream stream) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
SynInfo synInfo = new SynInfo(headers,false,stream.getPriority());
|
||||
return (IStream)stream.syn(synInfo).get(5,TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static class TestController implements Controller<StandardSession.FrameBytes>
|
||||
{
|
||||
@Override
|
||||
public int write(ByteBuffer buffer, Handler<StandardSession.FrameBytes> handler, StandardSession.FrameBytes context)
|
||||
{
|
||||
handler.completed(context);
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(boolean onlyOutput)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void assertThatStreamIsClosed(IStream stream)
|
||||
{
|
||||
assertThat("stream is closed",stream.isClosed(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsReset(IStream stream)
|
||||
{
|
||||
assertThat("stream is reset",stream.isReset(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsNotInSession(IStream stream)
|
||||
{
|
||||
assertThat("stream is not in session",session.getStreams().contains(stream),not(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsInSession(IStream stream)
|
||||
{
|
||||
assertThat("stream is in session",session.getStreams().contains(stream),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsNotClosed(IStream stream)
|
||||
{
|
||||
assertThat("stream is not closed",stream.isClosed(),not(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsNotHalfClosed(IStream stream)
|
||||
{
|
||||
assertThat("stream is not halfClosed",stream.isHalfClosed(),not(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsNotClosed(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is not closed",pushStream.isClosed(),not(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsHalfClosed(IStream stream)
|
||||
{
|
||||
assertThat("stream is halfClosed",stream.isHalfClosed(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsNotAssociatedWithPushStream(IStream stream, IStream pushStream)
|
||||
{
|
||||
assertThat("pushStream is removed from parent",stream.getPushedStreams().contains(pushStream),not(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsNotInSession(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is not in session",session.getStreams().contains(pushStream.getId()),not(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsInSession(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is in session",session.getStreams().contains(pushStream),is(true));
|
||||
}
|
||||
|
||||
private void assertThatStreamIsAssociatedWithPushStream(IStream stream, Stream pushStream)
|
||||
{
|
||||
assertThat("stream is associated with pushStream",stream.getPushedStreams().contains(pushStream),is(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsClosed(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is closed",pushStream.isClosed(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatPushStreamIsHalfClosed(Stream pushStream)
|
||||
{
|
||||
assertThat("pushStream is half closed ",pushStream.isHalfClosed(),is(true));
|
||||
}
|
||||
|
||||
private void assertThatOnStreamClosedListenerHasBeenCalled(final CountDownLatch closedListenerCalledLatch) throws InterruptedException
|
||||
{
|
||||
assertThat("onStreamClosed listener has been called",closedListenerCalledLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.Handler;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class StandardStreamTest
|
||||
{
|
||||
@Mock private ISession session;
|
||||
@Mock private SynStreamFrame synStreamFrame;
|
||||
|
||||
/**
|
||||
* Test method for {@link org.eclipse.jetty.spdy.StandardStream#syn(org.eclipse.jetty.spdy.api.SynInfo)}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSyn()
|
||||
{
|
||||
Stream stream = new StandardStream(synStreamFrame,session,0,null);
|
||||
Set<Stream> streams = new HashSet<>();
|
||||
streams.add(stream);
|
||||
when(synStreamFrame.isClose()).thenReturn(false);
|
||||
SynInfo synInfo = new SynInfo(false);
|
||||
when(session.getStreams()).thenReturn(streams);
|
||||
stream.syn(synInfo);
|
||||
verify(session).syn(argThat(new PushSynInfoMatcher(stream.getId(),synInfo)),any(StreamFrameListener.class),anyLong(),any(TimeUnit.class),any(Handler.class));
|
||||
}
|
||||
|
||||
private class PushSynInfoMatcher extends ArgumentMatcher<PushSynInfo>{
|
||||
int associatedStreamId;
|
||||
SynInfo synInfo;
|
||||
|
||||
public PushSynInfoMatcher(int associatedStreamId, SynInfo synInfo)
|
||||
{
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
this.synInfo = synInfo;
|
||||
}
|
||||
@Override
|
||||
public boolean matches(Object argument)
|
||||
{
|
||||
PushSynInfo pushSynInfo = (PushSynInfo)argument;
|
||||
if(pushSynInfo.getAssociatedStreamId() != associatedStreamId){
|
||||
System.out.println("streamIds do not match!");
|
||||
return false;
|
||||
}
|
||||
if(pushSynInfo.isClose() != synInfo.isClose()){
|
||||
System.out.println("isClose doesn't match");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSynOnClosedStream(){
|
||||
IStream stream = new StandardStream(synStreamFrame,session,0,null);
|
||||
stream.updateCloseState(true,true);
|
||||
stream.updateCloseState(true,false);
|
||||
assertThat("stream expected to be closed",stream.isClosed(),is(true));
|
||||
final CountDownLatch failedLatch = new CountDownLatch(1);
|
||||
stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Handler.Adapter<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failedLatch.countDown();
|
||||
}
|
||||
});
|
||||
assertThat("PushStream creation failed", failedLatch.getCount(), equalTo(0L));
|
||||
}
|
||||
|
||||
}
|
|
@ -99,7 +99,7 @@ public class ServerUsageTest
|
|||
|
||||
Session session = stream.getSession();
|
||||
// Since it's unidirectional, no need to pass the listener
|
||||
session.syn(new SynInfo(new Headers(), false, true, stream.getId(), (byte)0), null, 0, TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
|
||||
session.syn(new SynInfo(new Headers(), false, (byte)0), null, 0, TimeUnit.MILLISECONDS, new Handler.Adapter<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Stream pushStream)
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
package org.eclipse.jetty.spdy.frames;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.spdy.StandardByteBufferPool;
|
||||
|
@ -38,7 +44,7 @@ public class RstStreamGenerateParseTest
|
|||
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory().newCompressor());
|
||||
ByteBuffer buffer = generator.control(frame1);
|
||||
|
||||
Assert.assertNotNull(buffer);
|
||||
assertThat("buffer is not null", buffer, not(nullValue()));
|
||||
|
||||
TestSPDYParserListener listener = new TestSPDYParserListener();
|
||||
Parser parser = new Parser(new StandardCompressionFactory().newDecompressor());
|
||||
|
@ -46,13 +52,13 @@ public class RstStreamGenerateParseTest
|
|||
parser.parse(buffer);
|
||||
ControlFrame frame2 = listener.getControlFrame();
|
||||
|
||||
Assert.assertNotNull(frame2);
|
||||
Assert.assertEquals(ControlFrameType.RST_STREAM, frame2.getType());
|
||||
assertThat("frame2 is not null", frame2, not(nullValue()));
|
||||
assertThat("frame2 is type RST_STREAM",ControlFrameType.RST_STREAM, equalTo(frame2.getType()));
|
||||
RstStreamFrame rstStream = (RstStreamFrame)frame2;
|
||||
Assert.assertEquals(SPDY.V2, rstStream.getVersion());
|
||||
Assert.assertEquals(streamId, rstStream.getStreamId());
|
||||
Assert.assertEquals(0, rstStream.getFlags());
|
||||
Assert.assertEquals(streamStatus, rstStream.getStatusCode());
|
||||
assertThat("rstStream version is SPDY.V2",SPDY.V2, equalTo(rstStream.getVersion()));
|
||||
assertThat("rstStream id is equal to streamId",streamId, equalTo(rstStream.getStreamId()));
|
||||
assertThat("rstStream flags are 0",(byte)0, equalTo(rstStream.getFlags()));
|
||||
assertThat("stream status is equal to rstStream statuscode",streamStatus, is(rstStream.getStatusCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -64,7 +64,13 @@
|
|||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.BytesDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.Headers;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
import org.eclipse.jetty.spdy.api.SPDY;
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.api.SessionStatus;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.StringDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
|
||||
import org.eclipse.jetty.spdy.frames.ControlFrame;
|
||||
import org.eclipse.jetty.spdy.frames.DataFrame;
|
||||
import org.eclipse.jetty.spdy.frames.GoAwayFrame;
|
||||
import org.eclipse.jetty.spdy.frames.RstStreamFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynReplyFrame;
|
||||
import org.eclipse.jetty.spdy.frames.SynStreamFrame;
|
||||
import org.eclipse.jetty.spdy.generator.Generator;
|
||||
import org.eclipse.jetty.spdy.parser.Parser;
|
||||
import org.eclipse.jetty.spdy.parser.Parser.Listener;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ClosedStreamTest extends AbstractTest
|
||||
{
|
||||
//TODO: Right now it sends a rst as the stream is unknown to the session once it's closed. But according to the spec we probably should just ignore the data?!
|
||||
@Test
|
||||
public void testDataSentOnClosedStreamIsIgnored() throws Exception
|
||||
{
|
||||
ServerSocketChannel server = ServerSocketChannel.open();
|
||||
server.bind(new InetSocketAddress("localhost", 0));
|
||||
|
||||
Session session = startClient(new InetSocketAddress("localhost", server.socket().getLocalPort()), null);
|
||||
final CountDownLatch dataLatch = new CountDownLatch(2);
|
||||
session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
dataLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
SocketChannel channel = server.accept();
|
||||
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
|
||||
channel.read(readBuffer);
|
||||
readBuffer.flip();
|
||||
int streamId = readBuffer.getInt(8);
|
||||
|
||||
Generator generator = new Generator(new StandardByteBufferPool(), new StandardCompressionFactory.StandardCompressor());
|
||||
|
||||
ByteBuffer writeBuffer = generator.control(new SynReplyFrame(SPDY.V2, (byte)0, streamId, new Headers()));
|
||||
channel.write(writeBuffer);
|
||||
|
||||
byte[] bytes = new byte[1];
|
||||
writeBuffer = generator.data(streamId, bytes.length, new BytesDataInfo(bytes, true));
|
||||
channel.write(writeBuffer);
|
||||
|
||||
// Write again to simulate the faulty condition
|
||||
writeBuffer.flip();
|
||||
channel.write(writeBuffer);
|
||||
|
||||
Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
|
||||
|
||||
writeBuffer = generator.control(new GoAwayFrame(SPDY.V2, 0, SessionStatus.OK.getCode()));
|
||||
channel.write(writeBuffer);
|
||||
channel.shutdownOutput();
|
||||
channel.close();
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendDataOnHalfClosedStreamCausesExceptionOnServer() throws Exception
|
||||
{
|
||||
final CountDownLatch replyReceivedLatch = new CountDownLatch(1);
|
||||
final CountDownLatch clientReceivedDataLatch = new CountDownLatch(1);
|
||||
final CountDownLatch exceptionWhenSendingData = new CountDownLatch(1);
|
||||
|
||||
Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
stream.reply(new ReplyInfo(true));
|
||||
try
|
||||
{
|
||||
replyReceivedLatch.await(5,TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
try
|
||||
{
|
||||
stream.data(new StringDataInfo("data send after half closed",false));
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
// we expect an exception here, but we don't want it to be logged
|
||||
exceptionWhenSendingData.countDown();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}),null);
|
||||
|
||||
Stream stream = clientSession.syn(new SynInfo(false),new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
replyReceivedLatch.countDown();
|
||||
super.onReply(stream,replyInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
clientReceivedDataLatch.countDown();
|
||||
super.onData(stream,dataInfo);
|
||||
}
|
||||
}).get();
|
||||
assertThat("reply has been received by client",replyReceivedLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThat("stream is half closed from server",stream.isHalfClosed(),is(true));
|
||||
assertThat("client has not received any data sent after stream was half closed by server",clientReceivedDataLatch.await(1,TimeUnit.SECONDS),
|
||||
is(false));
|
||||
assertThat("sending data threw an exception",exceptionWhenSendingData.await(5,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV2ReceiveDataOnHalfClosedStream() throws Exception
|
||||
{
|
||||
final CountDownLatch clientResetReceivedLatch = runReceiveDataOnHalfClosedStream(SPDY.V2);
|
||||
assertThat("server didn't receive data",clientResetReceivedLatch.await(1,TimeUnit.SECONDS),not(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("until v3 is properly implemented")
|
||||
public void testV3ReceiveDataOnHalfClosedStream() throws Exception
|
||||
{
|
||||
final CountDownLatch clientResetReceivedLatch = runReceiveDataOnHalfClosedStream(SPDY.V3);
|
||||
assertThat("server didn't receive data",clientResetReceivedLatch.await(1,TimeUnit.SECONDS),not(true));
|
||||
}
|
||||
|
||||
private CountDownLatch runReceiveDataOnHalfClosedStream(short version) throws Exception, IOException, InterruptedException
|
||||
{
|
||||
final CountDownLatch clientResetReceivedLatch = new CountDownLatch(1);
|
||||
final CountDownLatch serverReplySentLatch = new CountDownLatch(1);
|
||||
final CountDownLatch clientReplyReceivedLatch = new CountDownLatch(1);
|
||||
final CountDownLatch serverDataReceivedLatch = new CountDownLatch(1);
|
||||
|
||||
InetSocketAddress startServer = startServer(new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
stream.reply(new ReplyInfo(false));
|
||||
serverReplySentLatch.countDown();
|
||||
try
|
||||
{
|
||||
clientReplyReceivedLatch.await(5,TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
serverDataReceivedLatch.countDown();
|
||||
super.onData(stream,dataInfo);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
final SocketChannel socketChannel = SocketChannel.open(startServer);
|
||||
final Generator generator = new Generator(new StandardByteBufferPool(),new StandardCompressionFactory().newCompressor());
|
||||
ByteBuffer synData = generator.control(new SynStreamFrame(version,SynInfo.FLAG_CLOSE,1,0,(byte)0,new Headers()));
|
||||
|
||||
socketChannel.write(synData);
|
||||
|
||||
assertThat("server: syn reply is sent",serverReplySentLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
|
||||
Parser parser = new Parser(new StandardCompressionFactory.StandardDecompressor());
|
||||
parser.addListener(new Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onControlFrame(ControlFrame frame)
|
||||
{
|
||||
if (frame instanceof SynReplyFrame)
|
||||
{
|
||||
SynReplyFrame synReplyFrame = (SynReplyFrame)frame;
|
||||
clientReplyReceivedLatch.countDown();
|
||||
int streamId = synReplyFrame.getStreamId();
|
||||
ByteBuffer data = generator.data(streamId,0,new StringDataInfo("data",false));
|
||||
try
|
||||
{
|
||||
socketChannel.write(data);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else if (frame instanceof RstStreamFrame)
|
||||
{
|
||||
clientResetReceivedLatch.countDown();
|
||||
}
|
||||
super.onControlFrame(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataFrame(DataFrame frame, ByteBuffer data)
|
||||
{
|
||||
super.onDataFrame(frame,data);
|
||||
}
|
||||
});
|
||||
ByteBuffer response = ByteBuffer.allocate(28);
|
||||
socketChannel.read(response);
|
||||
response.flip();
|
||||
parser.parse(response);
|
||||
|
||||
assertThat("server didn't receive data",serverDataReceivedLatch.await(1,TimeUnit.SECONDS),not(true));
|
||||
return clientResetReceivedLatch;
|
||||
}
|
||||
|
||||
}
|
|
@ -451,7 +451,7 @@ public class FlowControlTest extends AbstractTest
|
|||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
private void expectException(Class<? extends Exception> exception, Callable command)
|
||||
private void expectException(Class<? extends Exception> exception, Callable<DataInfo> command)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -116,19 +116,20 @@ public class ProtocolViolationsTest extends AbstractTest
|
|||
stream.headers(new HeadersInfo(new Headers(), true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataSentAfterCloseIsDiscardedByRecipient() throws Exception
|
||||
@Test //TODO: throws an ISException in StandardStream.updateCloseState(). But instead we should send a rst or something to the server probably?!
|
||||
public void testServerClosesStreamTwice() throws Exception
|
||||
{
|
||||
ServerSocketChannel server = ServerSocketChannel.open();
|
||||
server.bind(new InetSocketAddress("localhost", 0));
|
||||
|
||||
Session session = startClient(new InetSocketAddress("localhost", server.socket().getLocalPort()), null);
|
||||
final CountDownLatch dataLatch = new CountDownLatch(2);
|
||||
session.syn(new SynInfo(true), new StreamFrameListener.Adapter()
|
||||
session.syn(new SynInfo(false), new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
System.out.println("ondata");
|
||||
dataLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* Copyright (c) 2012 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.Exchanger;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.spdy.api.BytesDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.DataInfo;
|
||||
import org.eclipse.jetty.spdy.api.Handler;
|
||||
import org.eclipse.jetty.spdy.api.ReplyInfo;
|
||||
import org.eclipse.jetty.spdy.api.Session;
|
||||
import org.eclipse.jetty.spdy.api.SessionFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.Stream;
|
||||
import org.eclipse.jetty.spdy.api.StreamFrameListener;
|
||||
import org.eclipse.jetty.spdy.api.StringDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PushStreamTest extends AbstractTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testSynPushStream() throws Exception
|
||||
{
|
||||
final CountDownLatch pushStreamSynLatch = new CountDownLatch(1);
|
||||
|
||||
Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
stream.reply(new ReplyInfo(false));
|
||||
stream.syn(new SynInfo(false));
|
||||
return null;
|
||||
}
|
||||
}),new SessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
pushStreamSynLatch.countDown();
|
||||
stream.reply(new ReplyInfo(false));
|
||||
return super.onSyn(stream,synInfo);
|
||||
}
|
||||
});
|
||||
|
||||
clientSession.syn(new SynInfo(false),null).get();
|
||||
assertThat("onSyn has been called",pushStreamSynLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendDataOnPushStreamAfterAssociatedStreamIsClosed() throws Exception
|
||||
{
|
||||
final Exchanger<Stream> streamExchanger = new Exchanger<>();
|
||||
final CountDownLatch pushStreamSynLatch = new CountDownLatch(1);
|
||||
final CyclicBarrier replyBarrier = new CyclicBarrier(3);
|
||||
final CyclicBarrier closeBarrier = new CyclicBarrier(3);
|
||||
final CountDownLatch streamDataSent = new CountDownLatch(2);
|
||||
final CountDownLatch pushStreamDataReceived = new CountDownLatch(2);
|
||||
final CountDownLatch exceptionCountDownLatch = new CountDownLatch(1);
|
||||
|
||||
Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
stream.reply(new ReplyInfo(false));
|
||||
try
|
||||
{
|
||||
replyBarrier.await(5,TimeUnit.SECONDS);
|
||||
return new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (dataInfo.isClose())
|
||||
{
|
||||
stream.data(new StringDataInfo("close stream",true));
|
||||
closeBarrier.await(5,TimeUnit.SECONDS);
|
||||
}
|
||||
streamDataSent.countDown();
|
||||
if (pushStreamDataReceived.getCount() == 2)
|
||||
{
|
||||
Stream pushStream = stream.syn(new SynInfo(false)).get();
|
||||
streamExchanger.exchange(pushStream,5,TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptionCountDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptionCountDownLatch.countDown();
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}),new SessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
pushStreamSynLatch.countDown();
|
||||
stream.reply(new ReplyInfo(false));
|
||||
return new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
pushStreamDataReceived.countDown();
|
||||
super.onData(stream,dataInfo);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Stream stream = clientSession.syn(new SynInfo(false),new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
replyBarrier.await(5,TimeUnit.SECONDS);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptionCountDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
closeBarrier.await(5,TimeUnit.SECONDS);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptionCountDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
}).get();
|
||||
|
||||
replyBarrier.await(5,TimeUnit.SECONDS);
|
||||
stream.data(new StringDataInfo("client data",false));
|
||||
Stream pushStream = streamExchanger.exchange(null,5,TimeUnit.SECONDS);
|
||||
pushStream.data(new StringDataInfo("first push data frame",false));
|
||||
// nasty, but less complex than using another cyclicBarrier for example
|
||||
while (pushStreamDataReceived.getCount() != 1)
|
||||
Thread.sleep(1);
|
||||
stream.data(new StringDataInfo("client close",true));
|
||||
closeBarrier.await(5,TimeUnit.SECONDS);
|
||||
assertThat("stream is closed",stream.isClosed(),is(true));
|
||||
pushStream.data(new StringDataInfo("second push data frame while associated stream has been closed already",false));
|
||||
assertThat("2 pushStream data frames have been received.",pushStreamDataReceived.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThat("2 data frames have been sent",streamDataSent.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThatNoExceptionOccured(exceptionCountDownLatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSynPushStreamOnClosedStream() throws Exception
|
||||
{
|
||||
final CountDownLatch pushStreamFailedLatch = new CountDownLatch(1);
|
||||
|
||||
Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
stream.reply(new ReplyInfo(true));
|
||||
stream.syn(new SynInfo(false),1,TimeUnit.SECONDS,new Handler.Adapter<Stream>()
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
pushStreamFailedLatch.countDown();
|
||||
}
|
||||
});
|
||||
return super.onSyn(stream,synInfo);
|
||||
}
|
||||
}),new SessionFrameListener.Adapter());
|
||||
|
||||
clientSession.syn(new SynInfo(true),null);
|
||||
assertThat("pushStream syn has failed",pushStreamFailedLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendBigDataOnPushStreamWhenAssociatedStreamIsClosed() throws Exception
|
||||
{
|
||||
final CountDownLatch streamClosedLatch = new CountDownLatch(1);
|
||||
final CountDownLatch allDataReceived = new CountDownLatch(1);
|
||||
final CountDownLatch exceptionCountDownLatch = new CountDownLatch(1);
|
||||
final Exchanger<ByteBuffer> exchanger = new Exchanger<>();
|
||||
final int dataSizeInBytes = 1024 * 1024 * 1;
|
||||
final byte[] transferBytes = createHugeByteArray(dataSizeInBytes);
|
||||
|
||||
Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
Stream pushStream = stream.syn(new SynInfo(false)).get();
|
||||
stream.reply(new ReplyInfo(true));
|
||||
// wait until stream is closed
|
||||
streamClosedLatch.await(5,TimeUnit.SECONDS);
|
||||
pushStream.data(new BytesDataInfo(transferBytes,true));
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptionCountDownLatch.countDown();
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}),new SessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
return new StreamFrameListener.Adapter()
|
||||
{
|
||||
ByteBuffer receivedBytes = ByteBuffer.allocate(dataSizeInBytes);
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
dataInfo.consumeInto(receivedBytes);
|
||||
if (dataInfo.isClose())
|
||||
{
|
||||
allDataReceived.countDown();
|
||||
try
|
||||
{
|
||||
receivedBytes.flip();
|
||||
exchanger.exchange(receivedBytes.slice(),5,TimeUnit.SECONDS);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptionCountDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Stream stream = clientSession.syn(new SynInfo(true),new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||
{
|
||||
streamClosedLatch.countDown();
|
||||
super.onReply(stream,replyInfo);
|
||||
}
|
||||
}).get();
|
||||
|
||||
ByteBuffer receivedBytes = exchanger.exchange(null,5,TimeUnit.SECONDS);
|
||||
|
||||
assertThat("received byte array is the same as transferred byte array",Arrays.equals(transferBytes,receivedBytes.array()),is(true));
|
||||
assertThat("onReply has been called to close the stream",streamClosedLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThat("stream is closed",stream.isClosed(),is(true));
|
||||
assertThat("all data has been received",allDataReceived.await(20,TimeUnit.SECONDS),is(true));
|
||||
assertThatNoExceptionOccured(exceptionCountDownLatch);
|
||||
}
|
||||
|
||||
private byte[] createHugeByteArray(int sizeInBytes)
|
||||
{
|
||||
byte[] bytes = new byte[sizeInBytes];
|
||||
new Random().nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOddEvenStreamIds() throws Exception
|
||||
{
|
||||
final CountDownLatch pushStreamIdIsEvenLatch = new CountDownLatch(3);
|
||||
|
||||
Session clientSession = startClient(startServer(new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
stream.syn(new SynInfo(false));
|
||||
return null;
|
||||
}
|
||||
}),new SessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
stream.reply(new ReplyInfo(false));
|
||||
assertStreamIdIsEven(stream);
|
||||
pushStreamIdIsEvenLatch.countDown();
|
||||
return super.onSyn(stream,synInfo);
|
||||
}
|
||||
});
|
||||
|
||||
Stream stream = clientSession.syn(new SynInfo(false),null).get();
|
||||
Stream stream2 = clientSession.syn(new SynInfo(false),null).get();
|
||||
Stream stream3 = clientSession.syn(new SynInfo(false),null).get();
|
||||
assertStreamIdIsOdd(stream);
|
||||
assertStreamIdIsOdd(stream2);
|
||||
assertStreamIdIsOdd(stream3);
|
||||
|
||||
assertThat("all pushStreams had even ids",pushStreamIdIsEvenLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
}
|
||||
|
||||
private void assertStreamIdIsEven(Stream stream)
|
||||
{
|
||||
assertThat("streamId is odd",stream.getId() % 2,is(0));
|
||||
}
|
||||
|
||||
private void assertStreamIdIsOdd(Stream stream)
|
||||
{
|
||||
assertThat("streamId is odd",stream.getId() % 2,is(1));
|
||||
}
|
||||
|
||||
private void assertThatNoExceptionOccured(final CountDownLatch exceptionCountDownLatch) throws InterruptedException
|
||||
{
|
||||
assertThat("No exception occured", exceptionCountDownLatch.await(1,TimeUnit.SECONDS),is(false));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,11 @@
|
|||
package org.eclipse.jetty.spdy;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
@ -15,7 +21,6 @@ import org.eclipse.jetty.spdy.api.StreamStatus;
|
|||
import org.eclipse.jetty.spdy.api.StringDataInfo;
|
||||
import org.eclipse.jetty.spdy.api.SynInfo;
|
||||
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ResetStreamTest extends AbstractTest
|
||||
|
@ -23,12 +28,12 @@ public class ResetStreamTest extends AbstractTest
|
|||
@Test
|
||||
public void testResetStreamIsRemoved() throws Exception
|
||||
{
|
||||
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()), null);
|
||||
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()),null);
|
||||
|
||||
Stream stream = session.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS);
|
||||
session.rst(new RstInfo(stream.getId(), StreamStatus.CANCEL_STREAM)).get(5, TimeUnit.SECONDS);
|
||||
Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
|
||||
session.rst(new RstInfo(stream.getId(),StreamStatus.CANCEL_STREAM)).get(5,TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertEquals(0, session.getStreams().size());
|
||||
assertEquals("session expected to contain 0 streams",0,session.getStreams().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -44,11 +49,11 @@ public class ResetStreamTest extends AbstractTest
|
|||
{
|
||||
Session serverSession = stream.getSession();
|
||||
serverSessionRef.set(serverSession);
|
||||
serverSession.rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM));
|
||||
serverSession.rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM));
|
||||
synLatch.countDown();
|
||||
return null;
|
||||
}
|
||||
}), new SessionFrameListener.Adapter()
|
||||
}),new SessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onRst(Session session, RstInfo rstInfo)
|
||||
|
@ -57,16 +62,17 @@ public class ResetStreamTest extends AbstractTest
|
|||
}
|
||||
});
|
||||
|
||||
clientSession.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS);
|
||||
Stream stream = clientSession.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue("syncLatch didn't count down",synLatch.await(5,TimeUnit.SECONDS));
|
||||
Session serverSession = serverSessionRef.get();
|
||||
Assert.assertEquals(0, serverSession.getStreams().size());
|
||||
assertEquals("serverSession expected to contain 0 streams",0,serverSession.getStreams().size());
|
||||
|
||||
Assert.assertTrue(rstLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue("rstLatch didn't count down",rstLatch.await(5,TimeUnit.SECONDS));
|
||||
// Need to sleep a while to give the chance to the implementation to remove the stream
|
||||
TimeUnit.SECONDS.sleep(1);
|
||||
Assert.assertEquals(0, clientSession.getStreams().size());
|
||||
assertTrue("stream is expected to be reset",stream.isReset());
|
||||
assertEquals("clientSession expected to contain 0 streams",0,clientSession.getStreams().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -83,8 +89,8 @@ public class ResetStreamTest extends AbstractTest
|
|||
try
|
||||
{
|
||||
// Refuse the stream, we must ignore data frames
|
||||
Assert.assertTrue(synLatch.await(5, TimeUnit.SECONDS));
|
||||
stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM));
|
||||
assertTrue(synLatch.await(5,TimeUnit.SECONDS));
|
||||
stream.getSession().rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM));
|
||||
return new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
|
@ -100,7 +106,7 @@ public class ResetStreamTest extends AbstractTest
|
|||
return null;
|
||||
}
|
||||
}
|
||||
}), new SessionFrameListener.Adapter()
|
||||
}),new SessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onRst(Session session, RstInfo rstInfo)
|
||||
|
@ -109,8 +115,8 @@ public class ResetStreamTest extends AbstractTest
|
|||
}
|
||||
});
|
||||
|
||||
Stream stream = session.syn(new SynInfo(false), null).get(5, TimeUnit.SECONDS);
|
||||
stream.data(new StringDataInfo("data", true), 5, TimeUnit.SECONDS, new Handler.Adapter<Void>()
|
||||
Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
|
||||
stream.data(new StringDataInfo("data",true),5,TimeUnit.SECONDS,new Handler.Adapter<Void>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Void context)
|
||||
|
@ -119,7 +125,60 @@ public class ResetStreamTest extends AbstractTest
|
|||
}
|
||||
});
|
||||
|
||||
Assert.assertTrue(rstLatch.await(5, TimeUnit.SECONDS));
|
||||
Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue("rstLatch didn't count down",rstLatch.await(5,TimeUnit.SECONDS));
|
||||
assertTrue("stream is expected to be reset",stream.isReset());
|
||||
assertFalse("dataLatch shouln't be count down",dataLatch.await(1,TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetAfterServerReceivedFirstDataFrameAndSecondDataFrameFails() throws Exception
|
||||
{
|
||||
final CountDownLatch synLatch = new CountDownLatch(1);
|
||||
final CountDownLatch dataLatch = new CountDownLatch(1);
|
||||
final CountDownLatch rstLatch = new CountDownLatch(1);
|
||||
final CountDownLatch failLatch = new CountDownLatch(1);
|
||||
Session session = startClient(startServer(new ServerSessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
|
||||
{
|
||||
synLatch.countDown();
|
||||
return new StreamFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataInfo dataInfo)
|
||||
{
|
||||
dataLatch.countDown();
|
||||
stream.getSession().rst(new RstInfo(stream.getId(),StreamStatus.REFUSED_STREAM));
|
||||
}
|
||||
};
|
||||
}
|
||||
}),new SessionFrameListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onRst(Session session, RstInfo rstInfo)
|
||||
{
|
||||
rstLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
Stream stream = session.syn(new SynInfo(false),null).get(5,TimeUnit.SECONDS);
|
||||
assertThat("syn is received by server", synLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
stream.data(new StringDataInfo("data",false),5,TimeUnit.SECONDS,null);
|
||||
assertThat("stream is reset",rstLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
stream.data(new StringDataInfo("2nd dataframe",false),5L,TimeUnit.SECONDS,new Handler.Adapter<Void>()
|
||||
{
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
assertThat("2nd data call failed",failLatch.await(5,TimeUnit.SECONDS),is(true));
|
||||
assertThat("stream is reset",stream.isReset(),is(true));
|
||||
}
|
||||
|
||||
// TODO: If server already received 2nd dataframe after it rst, it should ignore it. Not easy to do.
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ public class SSLEngineLeakTest extends AbstractTest
|
|||
|
||||
Field field = NextProtoNego.class.getDeclaredField("objects");
|
||||
field.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Object, NextProtoNego.Provider> objects = (Map<Object, NextProtoNego.Provider>)field.get(null);
|
||||
int initialSize = objects.size();
|
||||
|
||||
|
|
|
@ -139,11 +139,9 @@ public class IntrospectionUtil
|
|||
|
||||
public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict)
|
||||
{
|
||||
if (formalParams==null && actualParams==null)
|
||||
return true;
|
||||
if (formalParams==null && actualParams!=null)
|
||||
return false;
|
||||
if (formalParams!=null && actualParams==null)
|
||||
if (formalParams==null)
|
||||
return actualParams==null;
|
||||
if (actualParams==null)
|
||||
return false;
|
||||
|
||||
if (formalParams.length!=actualParams.length)
|
||||
|
@ -195,13 +193,11 @@ public class IntrospectionUtil
|
|||
|
||||
public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict)
|
||||
{
|
||||
if (formalType==null && actualType != null)
|
||||
if (formalType==null)
|
||||
return actualType==null;
|
||||
if (actualType==null)
|
||||
return false;
|
||||
if (formalType!=null && actualType==null)
|
||||
return false;
|
||||
if (formalType==null && actualType==null)
|
||||
return true;
|
||||
|
||||
|
||||
if (strict)
|
||||
return formalType.equals(actualType);
|
||||
else
|
||||
|
|
|
@ -769,14 +769,15 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
|||
{
|
||||
try
|
||||
{
|
||||
ba[n++]=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
|
||||
ba[n]=(byte)TypeUtil.parseInt(encoded,offset+i+1,2,16);
|
||||
n++;
|
||||
i+=3;
|
||||
}
|
||||
catch(NumberFormatException nfe)
|
||||
{
|
||||
ba[n-1] = (byte)'%';
|
||||
for(char next; ((next=encoded.charAt(++i+offset))!='%');)
|
||||
ba[n++] = (byte)(next=='+' ? ' ' : next);
|
||||
{
|
||||
LOG.ignore(nfe);
|
||||
ba[n++] = (byte)'%';
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -290,25 +290,26 @@ public class URLResource extends Resource
|
|||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return _url.hashCode();
|
||||
return _urlString.hashCode();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public boolean equals( Object o)
|
||||
{
|
||||
return o instanceof URLResource &&
|
||||
_url.equals(((URLResource)o)._url);
|
||||
return o instanceof URLResource && _urlString.equals(((URLResource)o)._urlString);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public boolean getUseCaches ()
|
||||
{
|
||||
return _useCaches;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public boolean isContainedIn (Resource containingResource) throws MalformedURLException
|
||||
{
|
||||
return false; //TODO gregw check this!
|
||||
return false; //TODO check this!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,7 +156,8 @@ public class URLEncodedTest
|
|||
url_encoded.decode("Name15=xx%zz", "UTF-8");
|
||||
assertEquals("encoded param size",1, url_encoded.size());
|
||||
assertEquals("encoded get", "xx%zz", url_encoded.getString("Name15"));
|
||||
|
||||
|
||||
assertEquals("%u123",UrlEncoded.decodeString("%u123",0,5,"UTF-8"));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -106,7 +106,8 @@ public class TagLibConfiguration extends AbstractConfiguration
|
|||
//Get the system classpath tlds and tell jasper about them, if jasper is on the classpath
|
||||
try
|
||||
{
|
||||
Class clazz = getClass().getClassLoader().loadClass("org.apache.jasper.compiler.TldLocationsCache");
|
||||
Class<?> clazz = getClass().getClassLoader().loadClass("org.apache.jasper.compiler.TldLocationsCache");
|
||||
assert clazz!=null;
|
||||
Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
|
||||
|
||||
Map<URI, List<String>> tldMap = new HashMap<URI, List<String>>();
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.net.MalformedURLException;
|
|||
import java.net.URL;
|
||||
import java.security.PermissionCollection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
|
@ -270,11 +271,23 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String getResourceAlias(String alias)
|
||||
public String getResourceAlias(String path)
|
||||
{
|
||||
if (_resourceAliases == null)
|
||||
return null;
|
||||
return _resourceAliases.get(alias);
|
||||
String alias = _resourceAliases.get(path);
|
||||
|
||||
int slash=path.length();
|
||||
while (alias==null)
|
||||
{
|
||||
slash=path.lastIndexOf("/",slash-1);
|
||||
if (slash<0)
|
||||
break;
|
||||
String match=_resourceAliases.get(path.substring(0,slash+1));
|
||||
if (match!=null)
|
||||
alias=match+path.substring(slash+1);
|
||||
}
|
||||
return alias;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -369,10 +369,6 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
if (!isTempWorkDirectory(tmpDir))
|
||||
{
|
||||
tmpDir.deleteOnExit();
|
||||
//TODO why is this here?
|
||||
File sentinel = new File(tmpDir, ".active");
|
||||
if(!sentinel.exists())
|
||||
sentinel.mkdir();
|
||||
}
|
||||
|
||||
if(LOG.isDebugEnabled())
|
||||
|
@ -448,24 +444,32 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
}
|
||||
else
|
||||
{
|
||||
//Use a sentinel file that will exist only whilst the extraction is taking place.
|
||||
//This will help us detect interrupted extractions.
|
||||
File extractionLock = new File (context.getTempDirectory(), ".extract_lock");
|
||||
|
||||
if (!extractedWebAppDir.exists())
|
||||
{
|
||||
//it hasn't been extracted before so extract it
|
||||
extractionLock.createNewFile();
|
||||
extractedWebAppDir.mkdir();
|
||||
LOG.info("Extract " + web_app + " to " + extractedWebAppDir);
|
||||
LOG.info("Extract " + web_app + " to " + extractedWebAppDir);
|
||||
Resource jar_web_app = JarResource.newJarResource(web_app);
|
||||
jar_web_app.copyTo(extractedWebAppDir);
|
||||
extractionLock.delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
//only extract if the war file is newer
|
||||
if (web_app.lastModified() > extractedWebAppDir.lastModified())
|
||||
//only extract if the war file is newer, or a .extract_lock file is left behind meaning a possible partial extraction
|
||||
if (web_app.lastModified() > extractedWebAppDir.lastModified() || extractionLock.exists())
|
||||
{
|
||||
extractionLock.createNewFile();
|
||||
IO.delete(extractedWebAppDir);
|
||||
extractedWebAppDir.mkdir();
|
||||
LOG.info("Extract " + web_app + " to " + extractedWebAppDir);
|
||||
Resource jar_web_app = JarResource.newJarResource(web_app);
|
||||
jar_web_app.copyTo(extractedWebAppDir);
|
||||
extractionLock.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import static org.junit.Assert.assertNotNull;
|
|||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
@ -29,6 +30,8 @@ import junit.framework.Assert;
|
|||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
||||
import org.junit.Test;
|
||||
|
||||
public class WebAppContextTest
|
||||
|
@ -137,6 +140,34 @@ public class WebAppContextTest
|
|||
Assert.assertNotNull(contextB.getServletHandler().getServletContext().getContext("/B/s"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAlias() throws Exception
|
||||
{
|
||||
File dir = File.createTempFile("dir",null);
|
||||
dir.delete();
|
||||
dir.mkdir();
|
||||
dir.deleteOnExit();
|
||||
|
||||
File webinf = new File(dir,"WEB-INF");
|
||||
webinf.mkdir();
|
||||
|
||||
File classes = new File(dir,"classes");
|
||||
classes.mkdir();
|
||||
|
||||
File someclass = new File(classes,"SomeClass.class");
|
||||
someclass.createNewFile();
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
context.setBaseResource(new ResourceCollection(dir.getAbsolutePath()));
|
||||
|
||||
context.setResourceAlias("/WEB-INF/classes/", "/classes/");
|
||||
|
||||
assertTrue(Resource.newResource(context.getServletContext().getResource("/WEB-INF/classes/SomeClass.class")).exists());
|
||||
assertTrue(Resource.newResource(context.getServletContext().getResource("/classes/SomeClass.class")).exists());
|
||||
|
||||
}
|
||||
|
||||
class ServletA extends GenericServlet
|
||||
{
|
||||
@Override
|
||||
|
|
8
pom.xml
8
pom.xml
|
@ -240,10 +240,9 @@
|
|||
<Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
|
||||
<Bundle-DocURL>${jetty.url}</Bundle-DocURL>
|
||||
<Bundle-Vendor>Eclipse Jetty Project</Bundle-Vendor>
|
||||
<Bundle-Localization>plugin</Bundle-Localization>
|
||||
<Bundle-Classpath>.</Bundle-Classpath>
|
||||
<Export-Package>${bundle-symbolic-name}.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
|
||||
<Bundle-Copyright>Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd.</Bundle-Copyright>
|
||||
<Bundle-Copyright>Copyright (c) 2008-2012 Mort Bay Consulting Pty. Ltd.</Bundle-Copyright>
|
||||
<_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
|
||||
</instructions>
|
||||
</configuration>
|
||||
|
@ -499,6 +498,11 @@
|
|||
<artifactId>junit</artifactId>
|
||||
<version>4.8.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<version>1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
|
|
|
@ -444,7 +444,19 @@ public class HttpTester
|
|||
*/
|
||||
public void setHeader(String name, String value)
|
||||
{
|
||||
if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name))
|
||||
setContentType(value);
|
||||
else
|
||||
_fields.put(name,value);
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void setContentType(String value)
|
||||
{
|
||||
_contentType = MimeTypes.CACHE.lookup(value);
|
||||
_charset = MimeTypes.getCharsetFromContentType(_contentType);
|
||||
_fields.put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -50,7 +50,25 @@ public class HttpTesterTest extends TestCase
|
|||
assertEquals(200, tester.getStatus());
|
||||
assertEquals("22", tester.getHeader("Content-Length"));
|
||||
assertEquals("text/html",tester.getContentType());
|
||||
System.err.println(tester.getContent());
|
||||
}
|
||||
|
||||
public void testSetCharset() throws Exception
|
||||
{
|
||||
String content = "123456789\uA74A";
|
||||
HttpTester tester = new HttpTester();
|
||||
tester.setVersion("HTTP/1.0");
|
||||
tester.setMethod("POST");
|
||||
tester.setHeader("Content-type", "application/json; charset=iso-8859-1");
|
||||
tester.setURI("/1/batch");
|
||||
tester.setContent(content);
|
||||
assertEquals("123456789?",tester.getContent());
|
||||
|
||||
tester.setHeader("Content-type", "application/json; charset=UTF-8");
|
||||
tester.setContent(content);
|
||||
assertEquals("123456789\uA74A",tester.getContent());
|
||||
|
||||
String request=tester.generate();
|
||||
assertTrue(request.startsWith("POST "));
|
||||
assertTrue(request.trim().endsWith(content));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package org.eclipse.jetty.server.session;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class SessionInvalidateAndCreateTest extends AbstractSessionInvalidateAndCreateTest
|
||||
{
|
||||
|
||||
@Override
|
||||
public AbstractTestServer createServer(int port, int max, int scavenge)
|
||||
{
|
||||
return new HashTestServer(port,max,scavenge);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionScavenge() throws Exception
|
||||
{
|
||||
super.testSessionScavenge();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// ========================================================================
|
||||
// Copyright 2012 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.server.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionBindingEvent;
|
||||
import javax.servlet.http.HttpSessionBindingListener;
|
||||
import javax.servlet.http.HttpSessionEvent;
|
||||
import javax.servlet.http.HttpSessionListener;
|
||||
|
||||
import org.eclipse.jetty.client.ContentExchange;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http.HttpMethods;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.SessionManager;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* AbstractSessionInvalidateAndCreateTest
|
||||
*
|
||||
* This test verifies that invalidating an existing session and creating
|
||||
* a new session within the scope of a single request will expire the
|
||||
* newly created session correctly (removed from the server and session listeners called).
|
||||
* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=377610
|
||||
*/
|
||||
public abstract class AbstractSessionInvalidateAndCreateTest
|
||||
{
|
||||
public class MySessionListener implements HttpSessionListener
|
||||
{
|
||||
List<String> destroys;
|
||||
|
||||
public void sessionCreated(HttpSessionEvent e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void sessionDestroyed(HttpSessionEvent e)
|
||||
{
|
||||
if (destroys == null)
|
||||
destroys = new ArrayList<String>();
|
||||
|
||||
destroys.add((String)e.getSession().getAttribute("identity"));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract AbstractTestServer createServer(int port, int max, int scavenge);
|
||||
|
||||
|
||||
|
||||
public void pause(int scavengePeriod)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(scavengePeriod * 2500L);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionScavenge() throws Exception
|
||||
{
|
||||
String contextPath = "";
|
||||
String servletMapping = "/server";
|
||||
int inactivePeriod = 1;
|
||||
int scavengePeriod = 2;
|
||||
AbstractTestServer server = createServer(0, inactivePeriod, scavengePeriod);
|
||||
ServletContextHandler context = server.addContext(contextPath);
|
||||
TestServlet servlet = new TestServlet();
|
||||
ServletHolder holder = new ServletHolder(servlet);
|
||||
context.addServlet(holder, servletMapping);
|
||||
MySessionListener listener = new MySessionListener();
|
||||
context.getSessionHandler().addEventListener(listener);
|
||||
server.start();
|
||||
int port1 = server.getPort();
|
||||
try
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
|
||||
client.start();
|
||||
try
|
||||
{
|
||||
String url = "http://localhost:" + port1 + contextPath + servletMapping;
|
||||
|
||||
|
||||
// Create the session
|
||||
ContentExchange exchange1 = new ContentExchange(true);
|
||||
exchange1.setMethod(HttpMethods.GET);
|
||||
exchange1.setURL(url + "?action=init");
|
||||
client.send(exchange1);
|
||||
exchange1.waitForDone();
|
||||
assertEquals(HttpServletResponse.SC_OK,exchange1.getResponseStatus());
|
||||
String sessionCookie = exchange1.getResponseFields().getStringField("Set-Cookie");
|
||||
assertTrue(sessionCookie != null);
|
||||
// Mangle the cookie, replacing Path with $Path, etc.
|
||||
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
|
||||
|
||||
|
||||
// Make a request which will invalidate the existing session and create a new one
|
||||
ContentExchange exchange2 = new ContentExchange(true);
|
||||
exchange2.setMethod(HttpMethods.GET);
|
||||
exchange2.setURL(url + "?action=test");
|
||||
exchange2.getRequestFields().add("Cookie", sessionCookie);
|
||||
client.send(exchange2);
|
||||
exchange2.waitForDone();
|
||||
assertEquals(HttpServletResponse.SC_OK,exchange2.getResponseStatus());
|
||||
|
||||
// Wait for the scavenger to run, waiting 2.5 times the scavenger period
|
||||
pause(scavengePeriod);
|
||||
|
||||
//test that the session created in the last test is scavenged:
|
||||
//the HttpSessionListener should have been called when session1 was invalidated and session2 was scavenged
|
||||
assertTrue(listener.destroys.contains("session1"));
|
||||
assertTrue(listener.destroys.contains("session2"));
|
||||
//session2's HttpSessionBindingListener should have been called when it was scavenged
|
||||
assertTrue(servlet.unbound);
|
||||
}
|
||||
finally
|
||||
{
|
||||
client.stop();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TestServlet extends HttpServlet
|
||||
{
|
||||
private boolean unbound = false;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
|
||||
{
|
||||
String action = request.getParameter("action");
|
||||
if ("init".equals(action))
|
||||
{
|
||||
HttpSession session = request.getSession(true);
|
||||
session.setAttribute("identity", "session1");
|
||||
}
|
||||
else if ("test".equals(action))
|
||||
{
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null)
|
||||
{
|
||||
session.invalidate();
|
||||
|
||||
//now make a new session
|
||||
session = request.getSession(true);
|
||||
session.setAttribute("identity", "session2");
|
||||
session.setAttribute("listener", new HttpSessionBindingListener()
|
||||
{
|
||||
|
||||
public void valueUnbound(HttpSessionBindingEvent event)
|
||||
{
|
||||
unbound = true;
|
||||
}
|
||||
|
||||
public void valueBound(HttpSessionBindingEvent event)
|
||||
{
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue