Merge branch 'jetty-http2'

This commit is contained in:
Joakim Erdfelt 2014-09-11 05:56:55 -07:00
commit 2755746def
537 changed files with 156327 additions and 6949 deletions

View File

@ -1,4 +1,5 @@
jetty-9.2.4-SNAPSHOT
jetty-9.3.0-SNAPSHOT
jetty-9.2.3.v20140905 - 05 September 2014
+ 347110 renamed class transformer methods
@ -88,6 +89,7 @@ jetty-9.2.2.v20140723 - 23 July 2014
+ 440038 Content decoding may fail.
+ 440114 ContextHandlerCollection does not skip context wrappers
+ 440122 Remove usages of ForkInvoker.
>>>>>>> origin/master
jetty-9.2.1.v20140609 - 09 June 2014
+ 347110 Supprt ClassFileTransormers in WebAppClassLoader

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apache-jsp</artifactId>
@ -59,23 +59,6 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -2,33 +2,19 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>apache-jstl</artifactId>
<name>Apache :: JSTL module</name>
<url>http://tomcat.apache.org/taglibs/standard/</url>
<packaging>jar</packaging>
<properties>
<bundle-symbolic-name>${project.groupId}.apache.jstl</bundle-symbolic-name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>example-async-rest</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
@ -10,6 +10,9 @@
<packaging>jar</packaging>
<name>Example Async Rest :: Jar</name>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.examples.asyc.rest</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>example-async-rest</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.eclipse.jetty.example-async-rest</groupId>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.examples</groupId>
<artifactId>examples-parent</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty.examples</groupId>
<artifactId>examples-parent</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -11,6 +11,9 @@
<name>Example :: Jetty Embedded</name>
<description>Jetty Embedded Examples</description>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.examples.embedded</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -23,6 +23,7 @@ import java.lang.management.ManagementFactory;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.PropertiesConfigurationManager;
import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Handler;
@ -122,7 +123,7 @@ public class LikeJettyXml
// SSL Connector
ServerConnector sslConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory,"http/1.1"),
new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(https_config));
sslConnector.setPort(8443);
server.addConnector(sslConnector);

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.embedded;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@ -83,7 +84,7 @@ public class ManyConnectors
// We create a second ServerConnector, passing in the http configuration we just made along with the
// previously created ssl context factory. Next we set the port and a longer idle timeout.
ServerConnector https = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory,"http/1.1"),
new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(https_config));
https.setPort(8443);
https.setIdleTimeout(500000);

View File

@ -18,14 +18,14 @@
package org.eclipse.jetty.embedded;
import org.eclipse.jetty.npn.server.NPNServerConnectionFactory;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -72,8 +72,8 @@ public class SpdyConnector
new HTTPSPDYServerConnectionFactory(3,https_config,new ReferrerPushStrategy());
// NPN Factory
SPDYServerConnectionFactory.checkProtocolNegotiationAvailable();
NPNServerConnectionFactory npn =
NegotiatingServerConnectionFactory.checkProtocolNegotiationAvailable();
NPNServerConnectionFactory npn =
new NPNServerConnectionFactory(spdy3.getProtocol(),spdy2.getProtocol(),http.getDefaultProtocol());
npn.setDefaultProtocol(http.getDefaultProtocol());

View File

@ -23,6 +23,7 @@ import java.lang.management.ManagementFactory;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.npn.server.NPNServerConnectionFactory;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.AsyncNCSARequestLog;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
@ -30,6 +31,7 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@ -39,8 +41,6 @@ import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
import org.eclipse.jetty.spdy.server.SPDYServerConnectionFactory;
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
import org.eclipse.jetty.spdy.server.http.PushStrategy;
import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
@ -110,7 +110,7 @@ public class SpdyServer
// Spdy Connector
// Make sure that the required NPN implementations are available.
SPDYServerConnectionFactory.checkProtocolNegotiationAvailable();
NegotiatingServerConnectionFactory.checkProtocolNegotiationAvailable();
// A ReferrerPushStrategy is being initialized.
// See: http://www.eclipse.org/jetty/documentation/current/spdy-configuring-push.html for more details.

View File

@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>org.eclipse.jetty.examples</groupId>

View File

@ -2,13 +2,11 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-parent</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-alpn-client</artifactId>
<name>Jetty :: ALPN Client</name>
<description>Jetty ALPN client services</description>
<url>http://www.eclipse.org/jetty</url>
<name>Jetty :: ALPN :: Client</name>
<properties>
<bundle-symbolic-name>${project.groupId}.alpn.client</bundle-symbolic-name>
</properties>
@ -31,15 +29,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<!-- always include the sources to be able to prepare the eclipse-jetty-SDK feature
with a snapshot. -->
<plugin>

View File

@ -2,62 +2,16 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-parent</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-alpn-server</artifactId>
<name>Jetty :: ALPN Server</name>
<description>Jetty ALPN server services</description>
<url>http://www.eclipse.org/jetty</url>
<name>Jetty :: ALPN :: Server</name>
<properties>
<bundle-symbolic-name>${project.groupId}.alpn.server</bundle-symbolic-name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>manifest</goal>
</goals>
<configuration>
<instructions>
<Import-Package>org.eclipse.jetty.alpn,*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
<!-- always include the sources to be able to prepare the eclipse-jetty-SDK feature
with a snapshot. -->
<plugin>

View File

@ -1,19 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure id="protonego" class="org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory">
<Arg name="protocols">
<Array type="String">
<Item>spdy/3</Item>
<Item>spdy/2</Item>
<Item>http/1.1</Item>
</Array>
<Configure id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
<Call name="addConnectionFactory">
<Arg>
<New class="org.eclipse.jetty.server.SslConnectionFactory">
<Arg name="next"><Property name="protonego"/></Arg>
<Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
</New>
</Arg>
</Call>
<Call name="addConnectionFactory">
<Arg>
<New id="protonego" class="org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory">
<Arg type="String">
<Property name="alpn.protocols" default="" />
</Arg>
<Set name="defaultProtocol">
<Property name="alpn.defaultProtocol" />
</Set>
</New>
</Arg>
</Call>
<Set name="defaultProtocol">http/1.1</Set>
<!-- Enables NPN debugging on System.err -->
<!--<Set class="org.eclipse.jetty.alpn.ALPN" name="debug" type="boolean">true</Set>-->
<!-- Enables ALPN debugging on System.err -->
<!--<Set class="org.eclipse.jetty.alpn.ALPN" name="debug" type="boolean">true</Set>-->
</Configure>

View File

@ -27,13 +27,14 @@ protonego-impl/alpn-${java.version}
lib/jetty-alpn-client-${jetty.version}.jar
lib/jetty-alpn-server-${jetty.version}.jar
[xml]
etc/protonego-alpn.xml
[files]
lib/
lib/alpn/
[ini-template]
# alpn.protocols=h2-14,http/1.1
# alpn.defaultProtocol=http/1.1
[license]
ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
ALPM replaces/modifies OpenJDK classes in the java.sun.security.ssl package.

View File

@ -34,6 +34,11 @@ public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFact
{
private static final Logger LOG = Log.getLogger(ALPNServerConnectionFactory.class);
public ALPNServerConnectionFactory(String protocols)
{
this(protocols.trim().split(",", 0));
}
public ALPNServerConnectionFactory(@Name("protocols") String... protocols)
{
super("alpn", protocols);

View File

@ -2,14 +2,12 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-alpn-parent</artifactId>
<packaging>pom</packaging>
<name>Jetty :: ALPN :: Parent</name>
<description>Jetty ALPN services parent</description>
<url>http://www.eclipse.org/jetty</url>
<modules>
<module>jetty-alpn-server</module>
<module>jetty-alpn-client</module>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-annotations</artifactId>
@ -14,23 +14,6 @@
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@ -50,18 +33,6 @@
</execution>
</executions>
</plugin>
<plugin>
<!--
Required for OSGI
-->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>

View File

@ -2,13 +2,16 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-ant</artifactId>
<packaging>jar</packaging>
<name>Jetty :: Ant Plugin</name>
<properties>
<bundle-symbolic-name>org.eclipse.jetty.ant</bundle-symbolic-name>
</properties>
<build>
<plugins>
<plugin>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -15,23 +15,6 @@
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@ -49,16 +32,6 @@
</execution>
</executions>
</plugin>
<!-- Required for OSGI -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>

View File

@ -950,7 +950,7 @@ public class HttpClient extends ContainerLifeCycle
return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
}
protected boolean isDefaultPort(String scheme, int port)
public boolean isDefaultPort(String scheme, int port)
{
return HttpScheme.HTTPS.is(scheme) ? port == 443 : port == 80;
}

View File

@ -174,7 +174,7 @@ public class HttpContent implements Callback, Closeable
if (iterator instanceof Closeable)
((Closeable)iterator).close();
}
catch (Exception x)
catch (Throwable x)
{
LOG.ignore(x);
}

View File

@ -489,7 +489,7 @@ public class HttpRequest implements Request
listener.onContent(response, content);
callback.succeeded();
}
catch (Exception x)
catch (Throwable x)
{
callback.failed(x);
}
@ -705,7 +705,7 @@ public class HttpRequest implements Request
if (value == null)
return "";
String encoding = "UTF-8";
String encoding = "utf-8";
try
{
return URLEncoder.encode(value, encoding);
@ -736,7 +736,7 @@ public class HttpRequest implements Request
private String urlDecode(String value)
{
String charset = "UTF-8";
String charset = "utf-8";
try
{
return URLDecoder.decode(value, charset);

View File

@ -643,8 +643,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
content.succeeded();
process();
}
// Catch-all for runtime exceptions
catch (Exception x)
catch (Throwable x)
{
anyToFailure(x);
}

View File

@ -245,7 +245,7 @@ public interface Response
onContent(response, content);
callback.succeeded();
}
catch (Exception x)
catch (Throwable x)
{
callback.failed(x);
}

View File

@ -35,7 +35,7 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.CompletableCallback;
public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler
{
private final HttpParser parser = new HttpParser(this);
private ByteBuffer buffer;
@ -198,14 +198,11 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
}
@Override
public boolean parsedHeader(HttpField field)
public void parsedHeader(HttpField field)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return false;
responseHeader(exchange, field);
return false;
if (exchange != null)
responseHeader(exchange, field);
}
@Override

View File

@ -180,7 +180,7 @@ public class HttpSenderOverHTTP extends HttpSender
}
}
}
catch (Exception x)
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug(x);

View File

@ -131,7 +131,7 @@ public class PathContentProvider extends AbstractTypedContentProvider
close();
throw x;
}
catch (Exception x)
catch (Throwable x)
{
close();
throw (NoSuchElementException)new NoSuchElementException().initCause(x);
@ -152,7 +152,7 @@ public class PathContentProvider extends AbstractTypedContentProvider
if (channel != null)
channel.close();
}
catch (Exception x)
catch (Throwable x)
{
LOG.ignore(x);
}

View File

@ -93,7 +93,7 @@ public class HttpClientCustomProxyTest
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
if (!URI.create(baseRequest.getHttpURI().toString()).isAbsolute())
response.setStatus(HttpServletResponse.SC_USE_PROXY);
else if (serverHost.equals(request.getServerName()))
response.setStatus(status);

View File

@ -58,7 +58,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (!URI.create(baseRequest.getUri().toString()).isAbsolute())
if (!URI.create(baseRequest.getHttpURI().toString()).isAbsolute())
response.setStatus(HttpServletResponse.SC_USE_PROXY);
else if (serverHost.equals(request.getServerName()))
response.setStatus(status);

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-continuation</artifactId>
@ -14,35 +14,6 @@
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-deploy</artifactId>
@ -25,42 +25,13 @@
</goals>
<configuration>
<instructions>
<Import-Package>org.eclipse.jetty.jmx.*;version="9.1";resolution:=optional,*</Import-Package>
<Import-Package>org.eclipse.jetty.jmx.*;resolution:=optional,*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<!--
Required for OSGI
-->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<artifactId>jetty-distribution</artifactId>
<name>Jetty :: Distribution Assemblies</name>
@ -301,7 +301,7 @@
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty</includeGroupIds>
<excludeGroupIds>org.eclipse.jetty.orbit,org.eclipse.jetty.spdy,org.eclipse.jetty.websocket,org.eclipse.jetty.fcgi,org.eclipse.jetty.toolchain,org.apache.taglibs</excludeGroupIds>
<excludeGroupIds>org.eclipse.jetty.orbit,org.eclipse.jetty.http2,org.eclipse.jetty.spdy,org.eclipse.jetty.websocket,org.eclipse.jetty.fcgi,org.eclipse.jetty.toolchain,org.apache.taglibs</excludeGroupIds>
<excludeArtifactIds>jetty-all,jetty-jsp,apache-jsp,apache-jstl,jetty-start,jetty-monitor,jetty-spring</excludeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib</outputDirectory>
@ -320,6 +320,19 @@
<outputDirectory>${assembly-directory}/lib/websocket</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-lib-http2-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty.http2</includeGroupIds>
<includeArtifactIds>http2-hpack,http2-common,http2-server</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/http2</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-lib-fcgi-deps</id>
<phase>generate-resources</phase>
@ -805,6 +818,11 @@
<artifactId>jetty-rewrite</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-core</artifactId>
@ -831,6 +849,11 @@
<artifactId>jetty-alpn-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-npn-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.example-async-rest</groupId>
<artifactId>example-async-rest-webapp</artifactId>

View File

@ -0,0 +1,11 @@
#
# SSL Protocol Negotiation Module
#
[depend]
ssl
protonego
[xml]
etc/protonego-${protonego}.xml

View File

@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.fcgi</groupId>
<artifactId>fcgi-parent</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.fcgi.generator;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@ -46,7 +47,7 @@ public class ClientGenerator extends Generator
{
request &= 0xFF_FF;
Charset utf8 = Charset.forName("UTF-8");
final Charset utf8 = StandardCharsets.UTF_8;
List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
int fieldsLength = 0;
for (HttpField field : fields)

View File

@ -80,6 +80,7 @@ public class Generator
return result;
}
// TODO: rewrite this class in light of ByteBufferPool.Lease.
public static class Result implements Callback
{
private final List<Callback> callbacks = new ArrayList<>(2);

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.fcgi.generator;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@ -54,7 +55,7 @@ public class ServerGenerator extends Generator
{
request &= 0xFF_FF;
Charset utf8 = Charset.forName("UTF-8");
final Charset utf8 = StandardCharsets.UTF_8;
List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
int length = 0;

View File

@ -71,7 +71,7 @@ public class ResponseContentParser extends StreamContentParser
parsers.remove(request);
}
private class ResponseParser implements HttpParser.ResponseHandler<ByteBuffer>
private class ResponseParser implements HttpParser.ResponseHandler
{
private final HttpFields fields = new HttpFields();
private ClientParser.Listener listener;
@ -154,7 +154,7 @@ public class ResponseContentParser extends StreamContentParser
}
@Override
public boolean parsedHeader(HttpField httpField)
public void parsedHeader(HttpField httpField)
{
try
{
@ -190,7 +190,6 @@ public class ResponseContentParser extends StreamContentParser
if (LOG.isDebugEnabled())
LOG.debug("Exception while invoking listener " + listener, x);
}
return false;
}
private void notifyBegin(int code, String reason)
@ -299,7 +298,7 @@ public class ResponseContentParser extends StreamContentParser
// Methods overridden to make them visible here
private static class FCGIHttpParser extends HttpParser
{
private FCGIHttpParser(ResponseHandler<ByteBuffer> handler)
private FCGIHttpParser(ResponseHandler handler)
{
super(handler, 65 * 1024, true);
reset();

View File

@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty.fcgi</groupId>
<artifactId>fcgi-parent</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -16,22 +16,6 @@
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptorRefs>
<descriptorRef>config</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -18,18 +18,18 @@
package org.eclipse.jetty.fcgi.server;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpChannel;
@ -39,18 +39,19 @@ import org.eclipse.jetty.server.HttpTransport;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class HttpChannelOverFCGI extends HttpChannel<ByteBuffer>
public class HttpChannelOverFCGI extends HttpChannel
{
private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class);
private final List<HttpField> fields = new ArrayList<>();
private final HttpFields fields = new HttpFields();
private final Dispatcher dispatcher;
private String method;
private String path;
private String query;
private String version;
private HostPortHttpField hostPort;
public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput input)
{
super(connector, configuration, endPoint, transport, input);
this.dispatcher = new Dispatcher(connector.getExecutor(), this);
@ -67,26 +68,27 @@ public class HttpChannelOverFCGI extends HttpChannel<ByteBuffer>
else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(field.getName()))
version = field.getValue();
else
fields.add(field);
processField(field);
}
@Override
public boolean headerComplete()
private void processField(HttpField field)
{
HttpField httpField = convertHeader(field);
if (httpField != null)
{
fields.add(httpField);
if (HttpHeader.HOST.is(httpField.getName()))
hostPort = (HostPortHttpField)httpField;
}
}
public void onRequest()
{
String uri = path;
if (query != null && query.length() > 0)
uri += "?" + query;
startRequest(HttpMethod.fromString(method), method, ByteBuffer.wrap(uri.getBytes(StandardCharsets.UTF_8)),
HttpVersion.fromString(version));
for (HttpField fcgiField : fields)
{
HttpField httpField = convertHeader(fcgiField);
if (httpField != null)
parsedHeader(httpField);
}
return super.headerComplete();
// TODO https?
onRequest(new MetaData.Request(method, HttpScheme.HTTP.asString(), hostPort, uri, HttpVersion.fromString(version), fields));
}
private HttpField convertHeader(HttpField field)
@ -105,7 +107,11 @@ public class HttpChannelOverFCGI extends HttpChannel<ByteBuffer>
httpName.append(Character.toUpperCase(part.charAt(0)));
httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH));
}
return new HttpField(httpName.toString(), field.getValue());
String headerName = httpName.toString();
if (HttpHeader.HOST.is(headerName))
return new HostPortHttpField(field.getValue());
else
return new HttpField(httpName.toString(), field.getValue());
}
return null;
}

View File

@ -49,6 +49,46 @@ public class HttpTransportOverFCGI implements HttpTransport
@Override
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
{
if (info!=null)
commit(info,content,lastContent,callback);
else
{
if (head)
{
if (lastContent)
{
Generator.Result result = generateResponseContent(BufferUtil.EMPTY_BUFFER, true, callback);
flusher.flush(result);
}
else
{
// Skip content generation
callback.succeeded();
}
}
else
{
Generator.Result result = generateResponseContent(content, lastContent, callback);
flusher.flush(result);
}
if (lastContent && shutdown)
flusher.shutdown();
}
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.server.HttpTransport#push(org.eclipse.jetty.http.MetaData.Request)
*/
@Override
public void push(org.eclipse.jetty.http.MetaData.Request request)
{
// LOG.debug("ignore push in {}",this);
}
private void commit(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
{
boolean head = this.head = info.isHead();
boolean shutdown = this.shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
@ -78,32 +118,6 @@ public class HttpTransportOverFCGI implements HttpTransport
flusher.shutdown();
}
@Override
public void send(ByteBuffer content, boolean lastContent, Callback callback)
{
if (head)
{
if (lastContent)
{
Generator.Result result = generateResponseContent(BufferUtil.EMPTY_BUFFER, true, callback);
flusher.flush(result);
}
else
{
// Skip content generation
callback.succeeded();
}
}
else
{
Generator.Result result = generateResponseContent(content, lastContent, callback);
flusher.flush(result);
}
if (lastContent && shutdown)
flusher.shutdown();
}
protected Generator.Result generateResponseHeaders(HttpGenerator.ResponseInfo info, Callback callback)
{
return generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), info.getHttpFields(), callback);
@ -115,7 +129,7 @@ public class HttpTransportOverFCGI implements HttpTransport
}
@Override
public void abort()
public void abort(Throwable failure)
{
aborted = true;
}

View File

@ -29,9 +29,10 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.ByteBufferQueuedHttpInput;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpInput;
import org.eclipse.jetty.server.QueuedHttpInput;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -123,7 +124,7 @@ public class ServerFCGIConnection extends AbstractConnection
// TODO: handle flags
HttpChannelOverFCGI channel = new HttpChannelOverFCGI(connector, configuration, getEndPoint(),
new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request, sendStatus200),
new ByteBufferQueuedHttpInput());
new QueuedHttpInput());
HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel);
if (existing != null)
throw new IllegalStateException();
@ -149,8 +150,8 @@ public class ServerFCGIConnection extends AbstractConnection
LOG.debug("Request {} headers on {}", request, channel);
if (channel != null)
{
if (channel.headerComplete())
channel.dispatch();
channel.onRequest();
channel.dispatch();
}
}
@ -162,8 +163,8 @@ public class ServerFCGIConnection extends AbstractConnection
LOG.debug("Request {} {} content {} on {}", request, stream, buffer, channel);
if (channel != null)
{
if (channel.content(buffer))
channel.dispatch();
// TODO avoid creating content all the time
channel.onContent(new HttpInput.Content(buffer));
}
return false;
}
@ -176,8 +177,8 @@ public class ServerFCGIConnection extends AbstractConnection
LOG.debug("Request {} end on {}", request, channel);
if (channel != null)
{
if (channel.messageComplete())
channel.dispatch();
channel.onRequestComplete();
channel.dispatch();
}
}
@ -189,7 +190,7 @@ public class ServerFCGIConnection extends AbstractConnection
LOG.debug("Request {} failure on {}: {}", request, channel, failure);
if (channel != null)
{
channel.badMessage(400, failure.toString());
channel.onBadMessage(400, failure.toString());
}
}
}

View File

@ -19,8 +19,10 @@
package org.eclipse.jetty.fcgi.server.proxy;
import java.net.URI;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
@ -31,6 +33,7 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.proxy.AsyncProxyServlet;
import org.eclipse.jetty.proxy.ProxyServlet;
@ -127,6 +130,33 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, originalURI);
}
// If the Host header is missing, add it.
if (!proxyRequest.getHeaders().containsKey(HttpHeader.HOST.asString()))
{
String host = request.getServerName();
int port = request.getServerPort();
if (!getHttpClient().isDefaultPort(request.getScheme(), port))
host += ":" + port;
proxyRequest.header(HttpHeader.HOST, host);
proxyRequest.header(HttpHeader.X_FORWARDED_HOST, host);
}
// PHP does not like multiple Cookie headers, coalesce into one.
List<String> cookies = proxyRequest.getHeaders().getValuesList(HttpHeader.COOKIE.asString());
if (cookies.size() > 1)
{
StringBuilder builder = new StringBuilder();
for (int i = 0; i < cookies.size(); ++i)
{
if (i > 0)
builder.append("; ");
String cookie = cookies.get(i);
builder.append(cookie);
}
proxyRequest.header(HttpHeader.COOKIE, null);
proxyRequest.header(HttpHeader.COOKIE, builder.toString());
}
super.customizeProxyRequest(proxyRequest, request);
}

View File

@ -24,6 +24,7 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.fcgi.server;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

View File

@ -32,6 +32,7 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;

View File

@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.fcgi.server.proxy;
import java.io.IOException;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.fcgi.server.proxy;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import org.eclipse.jetty.server.Server;

View File

@ -3,7 +3,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -2,7 +2,7 @@
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-http-spi</artifactId>
@ -32,35 +32,6 @@
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>

View File

@ -3,7 +3,7 @@
<parent>
<artifactId>jetty-project</artifactId>
<groupId>org.eclipse.jetty</groupId>
<version>9.2.4-SNAPSHOT</version>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-http</artifactId>
@ -26,33 +26,10 @@
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>manifest</goal>
</goals>
<configuration>
<instructions>
<Import-Package>javax.servlet.*;version="[2.6.0,3.2)",javax.net.*,*</Import-Package>
</instructions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>artifact-jar</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
<execution>
<id>test-jar</id>
<goals>
@ -60,11 +37,6 @@
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>

View File

@ -0,0 +1,65 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http;
/* ------------------------------------------------------------------------------- */
public class BadMessageException extends RuntimeException
{
final int _code;
final String _reason;
public BadMessageException()
{
this(400,null);
}
public BadMessageException(int code)
{
this(code,null);
}
public BadMessageException(String reason)
{
this(400,reason);
}
public BadMessageException(int code, String reason)
{
super(code+": "+reason);
_code=code;
_reason=reason;
}
public BadMessageException(int code, String reason, Throwable cause)
{
super(code+": "+reason, cause);
_code=code;
_reason=reason;
}
public int getCode()
{
return _code;
}
public String getReason()
{
return _reason;
}
}

View File

@ -27,7 +27,7 @@ import java.util.TimeZone;
* ThreadLocal data parsers for HTTP style dates
*
*/
class DateParser
public class DateParser
{
private static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
static

View File

@ -0,0 +1,105 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http;
import org.eclipse.jetty.util.StringUtil;
/* ------------------------------------------------------------ */
/**
*/
public class HostPortHttpField extends HttpField
{
private final String _host;
private final int _port;
public HostPortHttpField(String authority)
{
this(HttpHeader.HOST,HttpHeader.HOST.asString(),authority);
}
public HostPortHttpField(HttpHeader header, String name, String authority)
{
super(header,name,authority);
try
{
if (authority.charAt(0)=='[')
{
// ipv6reference
int close=authority.lastIndexOf(']');
if (close<0)
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad ipv6");
_host=authority.substring(0,close+1);
if (authority.length()>close+1)
{
if (authority.charAt(close+1)!=':')
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad ipv6 port");
_port=StringUtil.toInt(authority,close+2);
}
else
_port=0;
}
else
{
// ipv4address or hostname
int c = authority.lastIndexOf(':');
if (c>=0)
{
_host=authority.substring(0,c);
_port=StringUtil.toInt(authority,c+1);
}
else
{
_host=authority;
_port=0;
}
}
}
catch (BadMessageException bm)
{
throw bm;
}
catch(Exception e)
{
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad HostPort",e);
}
}
/* ------------------------------------------------------------ */
/** Get the host.
* @return the host
*/
public String getHost()
{
return _host;
}
/* ------------------------------------------------------------ */
/** Get the port.
* @return the port
*/
public int getPort()
{
return _port;
}
}

View File

@ -0,0 +1,69 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import java.util.Arrays;
/* ------------------------------------------------------------ */
/**
*/
public class Http1FieldPreEncoder implements HttpFieldPreEncoder
{
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.http.HttpFieldPreEncoder#getHttpVersion()
*/
@Override
public HttpVersion getHttpVersion()
{
return HttpVersion.HTTP_1_0;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.http.HttpFieldPreEncoder#getEncodedField(org.eclipse.jetty.http.HttpHeader, java.lang.String, java.lang.String)
*/
@Override
public byte[] getEncodedField(HttpHeader header, String headerString, String value)
{
if (header!=null)
{
int cbl=header.getBytesColonSpace().length;
byte[] bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
System.arraycopy(value.getBytes(ISO_8859_1),0,bytes,cbl,value.length());
bytes[bytes.length-2]=(byte)'\r';
bytes[bytes.length-1]=(byte)'\n';
return bytes;
}
byte[] n=headerString.getBytes(ISO_8859_1);
byte[] v=value.getBytes(ISO_8859_1);
byte[] bytes=Arrays.copyOf(n,n.length+2+v.length+2);
bytes[n.length]=(byte)':';
bytes[n.length]=(byte)' ';
bytes[bytes.length-2]=(byte)'\r';
bytes[bytes.length-1]=(byte)'\n';
return bytes;
}
}

View File

@ -18,12 +18,16 @@
package org.eclipse.jetty.http;
import java.util.ArrayList;
import org.eclipse.jetty.util.StringUtil;
/* ------------------------------------------------------------ */
/** A HTTP Field
*/
public class HttpField
{
private final static String __zeroquality="q=0";
private final HttpHeader _header;
private final String _name;
private final String _value;
@ -65,6 +69,298 @@ public class HttpField
return _value;
}
public int getIntValue()
{
return Integer.valueOf(_value);
}
public long getLongValue()
{
return Long.valueOf(_value);
}
public String[] getValues()
{
ArrayList<String> list = new ArrayList<>();
int state = 0;
int start=0;
int end=0;
StringBuilder builder = new StringBuilder();
for (int i=0;i<_value.length();i++)
{
char c = _value.charAt(i);
switch(state)
{
case 0: // initial white space
switch(c)
{
case '"': // open quote
state=2;
break;
case ',': // ignore leading empty field
break;
case ' ': // more white space
case '\t':
break;
default: // character
start=i;
end=i;
state=1;
}
break;
case 1: // In token
switch(c)
{
case ',': // next field
list.add(_value.substring(start,end+1));
state=0;
break;
case ' ': // more white space
case '\t':
break;
default:
end=i;
}
break;
case 2: // In Quoted
switch(c)
{
case '\\': // next field
state=3;
break;
case '"': // end quote
list.add(builder.toString());
builder.setLength(0);
state=4;
break;
default:
builder.append(c);
}
break;
case 3: // In Quoted Quoted
builder.append(c);
state=2;
break;
case 4: // WS after end quote
switch(c)
{
case ' ': // white space
case '\t': // white space
break;
case ',': // white space
state=0;
break;
default:
throw new IllegalArgumentException("c="+(int)c);
}
break;
}
}
switch(state)
{
case 0:
break;
case 1:
list.add(_value.substring(start,end+1));
break;
case 4:
break;
default:
throw new IllegalArgumentException("state="+state);
}
return list.toArray(new String[list.size()]);
}
/* ------------------------------------------------------------ */
/** Look for a value in a possible multi valued field
* @param search Values to search for
* @return True iff the value is contained in the field value entirely or
* as an element of a quoted comma separated list. List element parameters (eg qualities) are ignored,
* except if they are q=0, in which case the item itself is ignored.
*/
public boolean contains(String search)
{
if (_value==null || search==null)
return _value==search;
if (search.length()==0)
return false;
int state=0;
int match=0;
int param=0;
for (int i=0;i<_value.length();i++)
{
char c = _value.charAt(i);
switch(state)
{
case 0: // initial white space
switch(c)
{
case '"': // open quote
match=0;
state=2;
break;
case ',': // ignore leading empty field
break;
case ';': // ignore leading empty field parameter
param=-1;
match=-1;
state=5;
break;
case ' ': // more white space
case '\t':
break;
default: // character
match = c==search.charAt(0)?1:-1;
state=1;
break;
}
break;
case 1: // In token
switch(c)
{
case ',': // next field
// Have we matched the token?
if (match==search.length())
return true;
state=0;
break;
case ';':
param=match>=0?0:-1;
state=5; // parameter
break;
default:
if (match>0)
{
if (match<search.length())
match=c==search.charAt(match)?(match+1):-1;
else if (c!=' ' && c!= '\t')
match=-1;
}
break;
}
break;
case 2: // In Quoted token
switch(c)
{
case '\\': // quoted character
state=3;
break;
case '"': // end quote
state=4;
break;
default:
if (match>=0)
{
if (match<search.length())
match=c==search.charAt(match)?(match+1):-1;
else
match=-1;
}
}
break;
case 3: // In Quoted character in quoted token
if (match>=0)
{
if (match<search.length())
match=c==search.charAt(match)?(match+1):-1;
else
match=-1;
}
state=2;
break;
case 4: // WS after end quote
switch(c)
{
case ' ': // white space
case '\t': // white space
break;
case ';':
state=5; // parameter
break;
case ',': // end token
// Have we matched the token?
if (match==search.length())
return true;
state=0;
break;
default:
// This is an illegal token, just ignore
match=-1;
}
break;
case 5: // parameter
switch(c)
{
case ',': // end token
// Have we matched the token and not q=0?
if (param!=__zeroquality.length() && match==search.length())
return true;
param=0;
state=0;
break;
case ' ': // white space
case '\t': // white space
break;
default:
if (param>=0)
{
if (param<__zeroquality.length())
param=c==__zeroquality.charAt(param)?(param+1):-1;
else if (c!='0'&&c!='.'&&c!=' ')
param=-1;
}
}
break;
default:
throw new IllegalStateException();
}
}
return param!=__zeroquality.length() && match==search.length();
}
@Override
public String toString()
{
@ -72,7 +368,7 @@ public class HttpField
return getName() + ": " + (v==null?"":v);
}
public boolean isSame(HttpField field)
public boolean isSameName(HttpField field)
{
if (field==null)
return false;
@ -85,5 +381,131 @@ public class HttpField
return false;
}
private int nameHashCode()
{
int hash=13;
int len = _name.length();
for (int i = 0; i < len; i++)
{
// simple case insensitive hash
char c = _name.charAt(i);
hash = 15 * hash + 0x0F&c;
}
return hash;
}
@Override
public int hashCode()
{
if (_header==null)
return _value.hashCode() ^ nameHashCode();
return _value.hashCode() ^ _header.hashCode();
}
@Override
public boolean equals(Object o)
{
if (o==this)
return true;
if (!(o instanceof HttpField))
return false;
HttpField field=(HttpField)o;
if (_header!=field.getHeader())
return false;
if (!_name.equalsIgnoreCase(field.getName()))
return false;
if (_value==null && field.getValue()!=null)
return false;
if (!_value.equals(field.getValue()))
return false;
return true;
}
public static class IntValueHttpField extends HttpField
{
final int _int;
public IntValueHttpField(HttpHeader header, String name, String value, int intValue)
{
super(header,name,value);
_int=intValue;
}
public IntValueHttpField(HttpHeader header, String value, int intValue)
{
this(header,header.asString(),value,Integer.valueOf(value));
}
public IntValueHttpField(HttpHeader header, String name, String value)
{
this(header,name,value,Integer.valueOf(value));
}
public IntValueHttpField(HttpHeader header, String value)
{
this(header,header.asString(),value);
}
public IntValueHttpField(HttpHeader header, int value)
{
this(header,header.asString(),value);
}
@Override
public int getIntValue()
{
return _int;
}
@Override
public long getLongValue()
{
return _int;
}
}
public static class LongValueHttpField extends HttpField
{
final long _long;
public LongValueHttpField(HttpHeader header, String name, String value, long longValue)
{
super(header,name,value);
_long=longValue;
}
public LongValueHttpField(HttpHeader header, String name, String value)
{
this(header,name,value,Long.valueOf(value));
}
public LongValueHttpField(HttpHeader header, String name, long value)
{
this(header,name,Long.toString(value),value);
}
public LongValueHttpField(HttpHeader header, String value)
{
this(header,header.asString(),value);
}
public LongValueHttpField(HttpHeader header,long value)
{
this(header,header.asString(),value);
}
@Override
public int getIntValue()
{
return (int)_long;
}
@Override
public long getLongValue()
{
return _long;
}
}
}

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http;
/* ------------------------------------------------------------ */
/** Interface to pre-encode HttpFields. Used by {@link PreEncodedHttpField}
*/
public interface HttpFieldPreEncoder
{
/* ------------------------------------------------------------ */
/** The major version this encoder is for. Both HTTP/1.0 and HTTP/1.1
* use the same field encoding, so the {@link HttpVersion#HTTP_1_0} should
* be return for all HTTP/1.x encodings.
* @return The major version this encoder is for.
*/
HttpVersion getHttpVersion();
byte[] getEncodedField(HttpHeader header, String headerString, String value);
}

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.http;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
@ -30,12 +31,10 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -52,17 +51,44 @@ import org.eclipse.jetty.util.log.Logger;
*/
public class HttpFields implements Iterable<HttpField>
{
public static final String __separators = ", \t";
private static final Logger LOG = Log.getLogger(HttpFields.class);
private final static Pattern __splitter = Pattern.compile("\\s*,\\s*");
public final static String __separators = ", \t";
private final ArrayList<HttpField> _fields = new ArrayList<>(20);
private HttpField[] _fields;
private int _size;
public HttpFields()
{
_fields=new HttpField[20];
}
/**
* Constructor.
*/
public HttpFields()
public HttpFields(int capacity)
{
_fields=new HttpField[capacity];
}
/**
* Constructor.
*/
public HttpFields(HttpFields fields)
{
_fields=Arrays.copyOf(fields._fields,fields._fields.length+10);
_size=fields._size;
}
public int size()
{
return _size;
}
@Override
public Iterator<HttpField> iterator()
{
return new Itr();
}
/**
@ -70,8 +96,8 @@ public class HttpFields implements Iterable<HttpField>
*/
public Collection<String> getFieldNamesCollection()
{
final Set<String> list = new HashSet<>(_fields.size());
for (HttpField f : _fields)
final Set<String> list = new HashSet<>(_size);
for (HttpField f : this)
{
if (f!=null)
list.add(f.getName());
@ -88,11 +114,6 @@ public class HttpFields implements Iterable<HttpField>
return Collections.enumeration(getFieldNamesCollection());
}
public int size()
{
return _fields.size();
}
/**
* Get a Field by index.
* @return A Field value or null if the Field value has not been set
@ -100,20 +121,16 @@ public class HttpFields implements Iterable<HttpField>
*/
public HttpField getField(int i)
{
return _fields.get(i);
}
@Override
public Iterator<HttpField> iterator()
{
return _fields.iterator();
if (i>=_size)
throw new NoSuchElementException();
return _fields[i];
}
public HttpField getField(HttpHeader header)
{
for (int i=0;i<_fields.size();i++)
for (int i=0;i<_size;i++)
{
HttpField f=_fields.get(i);
HttpField f=_fields[i];
if (f.getHeader()==header)
return f;
}
@ -122,21 +139,32 @@ public class HttpFields implements Iterable<HttpField>
public HttpField getField(String name)
{
for (int i=0;i<_fields.size();i++)
for (int i=0;i<_size;i++)
{
HttpField f=_fields.get(i);
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(name))
return f;
}
return null;
}
public boolean contains(HttpField field)
{
for (int i=_size;i-->0;)
{
HttpField f=_fields[i];
if (f.isSameName(field) && f.contains(field.getValue()))
return true;
}
return false;
}
public boolean contains(HttpHeader header, String value)
{
for (int i=0;i<_fields.size();i++)
for (int i=_size;i-->0;)
{
HttpField f=_fields.get(i);
if (f.getHeader()==header && contains(f,value))
HttpField f=_fields[i];
if (f.getHeader()==header && f.contains(value))
return true;
}
return false;
@ -144,69 +172,47 @@ public class HttpFields implements Iterable<HttpField>
public boolean contains(String name, String value)
{
for (int i=0;i<_fields.size();i++)
for (int i=_size;i-->0;)
{
HttpField f=_fields.get(i);
if (f.getName().equalsIgnoreCase(name) && contains(f,value))
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(name) && f.contains(value))
return true;
}
return false;
}
private boolean contains(HttpField field,String value)
{
String v = field.getValue();
if (v==null)
return false;
if (value.equalsIgnoreCase(v))
return true;
String[] split = __splitter.split(v);
for (int i = 0; split!=null && i < split.length; i++)
{
if (value.equals(split[i]))
return true;
}
return false;
}
public boolean containsKey(String name)
{
for (int i=0;i<_fields.size();i++)
for (int i=_size;i-->0;)
{
HttpField f=_fields.get(i);
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(name))
return true;
}
return false;
}
public String getStringField(HttpHeader header)
{
return getStringField(header.asString());
}
public String get(HttpHeader header)
{
return getStringField(header.asString());
for (int i=0;i<_size;i++)
{
HttpField f=_fields[i];
if (f.getHeader()==header)
return f.getValue();
}
return null;
}
public String get(String header)
{
return getStringField(header);
}
/**
* @return the value of a field, or null if not found. For multiple fields of the same name,
* only the first is returned.
* @param name the case-insensitive field name
*/
public String getStringField(String name)
{
HttpField field = getField(name);
return field==null?null:field.getValue();
for (int i=0;i<_size;i++)
{
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(header))
return f.getValue();
}
return null;
}
/**
@ -218,7 +224,7 @@ public class HttpFields implements Iterable<HttpField>
public List<String> getValuesList(String name)
{
final List<String> list = new ArrayList<>();
for (HttpField f : _fields)
for (HttpField f : this)
if (f.getName().equalsIgnoreCase(name))
list.add(f.getValue());
return list;
@ -232,9 +238,9 @@ public class HttpFields implements Iterable<HttpField>
*/
public Enumeration<String> getValues(final String name)
{
for (int i=0;i<_fields.size();i++)
for (int i=0;i<_size;i++)
{
final HttpField f = _fields.get(i);
final HttpField f = _fields[i];
if (f.getName().equalsIgnoreCase(name) && f.getValue()!=null)
{
@ -249,9 +255,9 @@ public class HttpFields implements Iterable<HttpField>
{
if (field==null)
{
while (i<_fields.size())
while (i<_size)
{
field=_fields.get(i++);
field=_fields[i++];
if (field.getName().equalsIgnoreCase(name) && field.getValue()!=null)
return true;
}
@ -272,7 +278,6 @@ public class HttpFields implements Iterable<HttpField>
}
throw new NoSuchElementException();
}
};
}
}
@ -330,22 +335,24 @@ public class HttpFields implements Iterable<HttpField>
public void put(HttpField field)
{
boolean put=false;
for (int i=_fields.size();i-->0;)
for (int i=_size;i-->0;)
{
HttpField f=_fields.get(i);
if (f.isSame(field))
HttpField f=_fields[i];
if (f.isSameName(field))
{
if (put)
_fields.remove(i);
{
System.arraycopy(_fields,i+1,_fields,i,--_size-i);
}
else
{
_fields.set(i,field);
_fields[i]=field;
put=true;
}
}
}
if (!put)
_fields.add(field);
add(field);
}
/**
@ -410,7 +417,7 @@ public class HttpFields implements Iterable<HttpField>
return;
HttpField field = new HttpField(name, value);
_fields.add(field);
add(field);
}
public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
@ -431,7 +438,7 @@ public class HttpFields implements Iterable<HttpField>
if (value == null) throw new IllegalArgumentException("null value");
HttpField field = new HttpField(header, value);
_fields.add(field);
add(field);
}
/**
@ -441,13 +448,17 @@ public class HttpFields implements Iterable<HttpField>
*/
public HttpField remove(HttpHeader name)
{
for (int i=_fields.size();i-->0;)
HttpField removed=null;
for (int i=_size;i-->0;)
{
HttpField f=_fields.get(i);
HttpField f=_fields[i];
if (f.getHeader()==name)
return _fields.remove(i);
{
removed=f;
System.arraycopy(_fields,i+1,_fields,i,--_size-i);
}
}
return null;
return removed;
}
/**
@ -457,13 +468,17 @@ public class HttpFields implements Iterable<HttpField>
*/
public HttpField remove(String name)
{
for (int i=_fields.size();i-->0;)
HttpField removed=null;
for (int i=_size;i-->0;)
{
HttpField f=_fields.get(i);
HttpField f=_fields[i];
if (f.getName().equalsIgnoreCase(name))
return _fields.remove(i);
{
removed=f;
System.arraycopy(_fields,i+1,_fields,i,--_size-i);
}
}
return null;
return removed;
}
/**
@ -476,7 +491,7 @@ public class HttpFields implements Iterable<HttpField>
public long getLongField(String name) throws NumberFormatException
{
HttpField field = getField(name);
return field==null?-1L:StringUtil.toLong(field.getValue());
return field==null?-1L:field.getLongValue();
}
/**
@ -564,13 +579,47 @@ public class HttpFields implements Iterable<HttpField>
}
@Override
public String
toString()
public int hashCode()
{
int hash=0;
for (HttpField field:_fields)
hash+=field.hashCode();
return hash;
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof HttpFields))
return false;
HttpFields that = (HttpFields)o;
// Order is not important, so we cannot rely on List.equals().
if (size() != that.size())
return false;
loop: for (HttpField fi : this)
{
for (HttpField fa : that)
{
if (fi.equals(fa))
continue loop;
}
return false;
}
return true;
}
@Override
public String toString()
{
try
{
StringBuilder buffer = new StringBuilder();
for (HttpField field : _fields)
for (HttpField field : this)
{
if (field != null)
{
@ -592,21 +641,24 @@ public class HttpFields implements Iterable<HttpField>
}
}
/**
* Clear the header.
*/
public void clear()
{
_fields.clear();
_size=0;
}
public void add(HttpField field)
{
_fields.add(field);
if (_size==_fields.length)
_fields=Arrays.copyOf(_fields,_size*2);
_fields[_size++]=field;
}
public void addAll(HttpFields fields)
{
for (int i=0;i<fields._size;i++)
add(fields._fields[i]);
}
/**
* Add fields from another HttpFields instance. Single valued fields are replaced, while all
* others are added.
@ -780,5 +832,34 @@ public class HttpFields implements Iterable<HttpField>
}
private class Itr implements Iterator<HttpField>
{
int _cursor; // index of next element to return
int _last=-1;
public boolean hasNext()
{
return _cursor != _size;
}
public HttpField next()
{
int i = _cursor;
if (i >= _size)
throw new NoSuchElementException();
_cursor = i + 1;
return _fields[_last=i];
}
public void remove()
{
if (_last<0)
throw new IllegalStateException();
System.arraycopy(_fields,_last+1,_fields,_last,--_size-_last);
_cursor=_last;
_last=-1;
}
}
}

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.http;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
@ -585,6 +584,10 @@ public class HttpGenerator
{
for (HttpField field : _info.getHttpFields())
{
String v = field.getValue();
if (v==null || v.length()==0)
continue; // rfc7230 does not allow no value
HttpHeader h = field.getHeader();
switch (h==null?HttpHeader.UNKNOWN:h)
@ -1054,9 +1057,9 @@ public class HttpGenerator
public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
{
if (field instanceof CachedHttpField)
if (field instanceof PreEncodedHttpField)
{
((CachedHttpField)field).putTo(bufferInFillMode);
((PreEncodedHttpField)field).putTo(bufferInFillMode,HttpVersion.HTTP_1_0);
}
else
{
@ -1086,23 +1089,4 @@ public class HttpGenerator
}
BufferUtil.putCRLF(bufferInFillMode);
}
public static class CachedHttpField extends HttpField
{
private final byte[] _bytes;
public CachedHttpField(HttpHeader header,String value)
{
super(header,value);
int cbl=header.getBytesColonSpace().length;
_bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2);
System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
_bytes[_bytes.length-2]=(byte)'\r';
_bytes[_bytes.length-1]=(byte)'\n';
}
public void putTo(ByteBuffer bufferInFillMode)
{
bufferInFillMode.put(_bytes);
}
}
}

View File

@ -111,16 +111,26 @@ public enum HttpHeader
X_POWERED_BY("X-Powered-By"),
/* ------------------------------------------------------------ */
/** HTTP2 Fields.
*/
C_METHOD(":method"),
C_SCHEME(":scheme"),
C_AUTHORITY(":authority"),
C_PATH(":path"),
C_STATUS(":status"),
UNKNOWN("::UNKNOWN::");
/* ------------------------------------------------------------ */
public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(512);
public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(520);
static
{
for (HttpHeader header : HttpHeader.values())
if (header!=UNKNOWN)
CACHE.put(header.toString(),header);
if (!CACHE.put(header.toString(),header))
throw new IllegalStateException();
}
private final String _string;

View File

@ -61,7 +61,7 @@ public enum HttpScheme
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return _string.equalsIgnoreCase(s);
return s!=null && _string.equalsIgnoreCase(s);
}
public String asString()

View File

@ -29,10 +29,12 @@ package org.eclipse.jetty.http;
* <th>Enum</th>
* <th>Code</th>
* <th>Message</th>
* <th>
* <th>
* <a href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a></th>
* <th>
* <a href="http://tools.ietf.org/html/rfc7231">RFC 7231 - HTTP/1.1 Semantics and Content</a></th>
* <th>
* <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a></th>
* <a href="http://tools.ietf.org/html/rfc7238">RFC 7238 - HTTP/1.1 Permanent Redirect</a></th>
* <th>
* <a href="http://tools.ietf.org/html/rfc2518">RFC 2518 - WEBDAV</a></th>
* </tr>
@ -48,7 +50,7 @@ package org.eclipse.jetty.http;
* <td>Continue</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">Sec. 10.1.1</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.2.1">Sec. 6.2.1</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -57,7 +59,7 @@ package org.eclipse.jetty.http;
* <td>Switching Protocols</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">Sec. 10.1.2</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.2.2">Sec. 6.2.2</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -82,7 +84,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">Sec. 10.2.1</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.3.1">Sec. 6.3.1</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -92,7 +94,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">Sec. 10.2.2</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.3.2">Sec. 6.3.2</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -102,7 +104,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">Sec. 10.2.3</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.3.3">Sec. 6.3.3</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -111,7 +113,7 @@ package org.eclipse.jetty.http;
* <td>Non Authoritative Information</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">Sec. 10.2.4</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.3.4">Sec. 6.3.4</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -121,7 +123,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">Sec. 10.2.5</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.3.5">Sec. 6.3.5</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -130,7 +132,7 @@ package org.eclipse.jetty.http;
* <td>Reset Content</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">Sec. 10.2.6</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.3.6">Sec. 6.3.6</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -139,7 +141,7 @@ package org.eclipse.jetty.http;
* <td>Partial Content</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">Sec. 10.2.7</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.3.7">Sec. 6.3.7</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -175,7 +177,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">Sec. 10.3.1</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.4.1">Sec. 6.4.1</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -185,7 +187,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">Sec. 10.3.2</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.4.2">Sec. 6.4.2</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -203,7 +205,7 @@ package org.eclipse.jetty.http;
* <td>Found</td>
* <td>(was "<code>302 Moved Temporarily</code>")</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">Sec. 10.3.3</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.4.3">Sec. 6.4.3</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -212,7 +214,7 @@ package org.eclipse.jetty.http;
* <td>See Other</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">Sec. 10.3.4</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.4.4">Sec. 6.4.4</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -222,7 +224,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">Sec. 10.3.5</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.4.5">Sec. 6.4.5</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -231,7 +233,7 @@ package org.eclipse.jetty.http;
* <td>Use Proxy</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">Sec. 10.3.6</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.4.6">Sec. 6.4.6</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -240,7 +242,7 @@ package org.eclipse.jetty.http;
* <td><em>(Unused)</em></td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.3.7">Sec. 10.3.7</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.4.7">Sec. 6.4.7</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -249,7 +251,17 @@ package org.eclipse.jetty.http;
* <td>Temporary Redirect</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">Sec. 10.3.8</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.4.8">Sec. 6.4.8</a></td>
* <td>&nbsp;</td>
* </tr>
*
* <tr>
* <td>{@link #PERMANENT_REDIRECT_308}</td>
* <td>307</td>
* <td>Permanent Redirect</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc7238">RFC7238</a></td>
* <td>&nbsp;</td>
* </tr>
*
@ -265,7 +277,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">Sec. 10.4.1</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.1">Sec. 6.5.1</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -275,7 +287,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">Sec. 10.4.2</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.2">Sec. 6.5.2</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -285,7 +297,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">Sec. 10.4.3</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.3">Sec. 6.5.3</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -295,7 +307,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">Sec. 10.4.4</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.4">Sec. 6.5.4</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -305,7 +317,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">Sec. 10.4.5</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.5">Sec. 6.5.5</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -314,7 +326,7 @@ package org.eclipse.jetty.http;
* <td>Method Not Allowed</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">Sec. 10.4.6</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.6">Sec. 6.5.6</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -323,7 +335,7 @@ package org.eclipse.jetty.http;
* <td>Not Acceptable</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">Sec. 10.4.7</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.7">Sec. 6.5.7</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -332,7 +344,7 @@ package org.eclipse.jetty.http;
* <td>Proxy Authentication Required</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">Sec. 10.4.8</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.8">Sec. 6.5.8</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -341,7 +353,7 @@ package org.eclipse.jetty.http;
* <td>Request Timeout</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">Sec. 10.4.9</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.5.9">Sec. 6.5.9</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -350,7 +362,7 @@ package org.eclipse.jetty.http;
* <td>Conflict</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">Sec. 10.4.10</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.10">Sec. 10.4.10</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -360,7 +372,7 @@ package org.eclipse.jetty.http;
* <td>Gone</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">Sec. 10.4.11</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.11">Sec. 10.4.11</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -370,7 +382,7 @@ package org.eclipse.jetty.http;
* <td>Length Required</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">Sec. 10.4.12</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.12">Sec. 10.4.12</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -380,7 +392,7 @@ package org.eclipse.jetty.http;
* <td>Precondition Failed</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">Sec. 10.4.13</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.13">Sec. 10.4.13</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -390,7 +402,7 @@ package org.eclipse.jetty.http;
* <td>Request Entity Too Large</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">Sec. 10.4.14</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.14">Sec. 10.4.14</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -400,7 +412,7 @@ package org.eclipse.jetty.http;
* <td>Request-URI Too Long</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">Sec. 10.4.15</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.15">Sec. 10.4.15</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -410,7 +422,7 @@ package org.eclipse.jetty.http;
* <td>Unsupported Media Type</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">Sec. 10.4.16</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.16">Sec. 10.4.16</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -420,7 +432,7 @@ package org.eclipse.jetty.http;
* <td>Requested Range Not Satisfiable</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">Sec. 10.4.17</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.17">Sec. 10.4.17</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -430,7 +442,7 @@ package org.eclipse.jetty.http;
* <td>Expectation Failed</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">Sec. 10.4.18</a>
* <a href="http://tools.ietf.org/html/rfc7231#section-10.4.18">Sec. 10.4.18</a>
* </td>
* <td>&nbsp;</td>
* </tr>
@ -537,7 +549,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">Sec. 10.5.1</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.6.1">Sec. 6.6.1</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -547,7 +559,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">Sec. 10.5.2</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.6.2">Sec. 6.6.2</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -557,7 +569,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">Sec. 10.5.3</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.6.3">Sec. 6.6.3</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -567,7 +579,7 @@ package org.eclipse.jetty.http;
* <td>
* <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">Sec. 10.5.4</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.6.4">Sec. 6.6.4</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -576,7 +588,7 @@ package org.eclipse.jetty.http;
* <td>Gateway Timeout</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">Sec. 10.5.5</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.6.5">Sec. 6.6.5</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -585,7 +597,7 @@ package org.eclipse.jetty.http;
* <td>HTTP Version Not Supported</td>
* <td>&nbsp;</td>
* <td>
* <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">Sec. 10.5.6</a></td>
* <a href="http://tools.ietf.org/html/rfc7231#section-6.6.6">Sec. 6.6.6</a></td>
* <td>&nbsp;</td>
* </tr>
* <tr>
@ -633,6 +645,7 @@ public class HttpStatus
public final static int NOT_MODIFIED_304 = 304;
public final static int USE_PROXY_305 = 305;
public final static int TEMPORARY_REDIRECT_307 = 307;
public final static int PERMANENT_REDIRECT_308 = 308;
public final static int BAD_REQUEST_400 = 400;
public final static int UNAUTHORIZED_401 = 401;
@ -683,7 +696,7 @@ public class HttpStatus
/*
* --------------------------------------------------------------------
* Informational messages in 1xx series. As defined by ... RFC 1945 -
* HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
* HTTP/1.0 RFC 7231 - HTTP/1.1 RFC 2518 - WebDAV
*/
/** <code>100 Continue</code> */
@ -696,7 +709,7 @@ public class HttpStatus
/*
* --------------------------------------------------------------------
* Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0
* RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
* RFC 7231 - HTTP/1.1 RFC 2518 - WebDAV
*/
/** <code>200 OK</code> */
@ -719,7 +732,7 @@ public class HttpStatus
/*
* --------------------------------------------------------------------
* Redirection messages in 3xx series. As defined by ... RFC 1945 -
* HTTP/1.0 RFC 2616 - HTTP/1.1
* HTTP/1.0 RFC 7231 - HTTP/1.1
*/
/** <code>300 Mutliple Choices</code> */
@ -738,11 +751,13 @@ public class HttpStatus
USE_PROXY(USE_PROXY_305, "Use Proxy"),
/** <code>307 Temporary Redirect</code> */
TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"),
/** <code>308 Permanent Redirect</code> */
PERMANET_REDIRECT(PERMANENT_REDIRECT_308, "Permanent Redirect"),
/*
* --------------------------------------------------------------------
* Client Error messages in 4xx series. As defined by ... RFC 1945 -
* HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
* HTTP/1.0 RFC 7231 - HTTP/1.1 RFC 2518 - WebDAV
*/
/** <code>400 Bad Request</code> */
@ -791,7 +806,7 @@ public class HttpStatus
/*
* --------------------------------------------------------------------
* Server Error messages in 5xx series. As defined by ... RFC 1945 -
* HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
* HTTP/1.0 RFC 7231 - HTTP/1.1 RFC 2518 - WebDAV
*/
/** <code>500 Server Error</code> */
@ -844,7 +859,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Informational</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
* and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
* and <a href="http://tools.ietf.org/html/rfc7231">RFC 7231 -
* HTTP/1.1</a>.
*
* @return true if within range of codes that belongs to
@ -859,7 +874,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Success</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
* and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
* and <a href="http://tools.ietf.org/html/rfc7231">RFC 7231 -
* HTTP/1.1</a>.
*
* @return true if within range of codes that belongs to
@ -874,7 +889,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Redirection</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
* and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
* and <a href="http://tools.ietf.org/html/rfc7231">RFC 7231 -
* HTTP/1.1</a>.
*
* @return true if within range of codes that belongs to
@ -889,7 +904,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Client Error</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
* and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
* and <a href="http://tools.ietf.org/html/rfc7231">RFC 7231 -
* HTTP/1.1</a>.
*
* @return true if within range of codes that belongs to
@ -904,7 +919,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Server Error</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
* and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
* and <a href="http://tools.ietf.org/html/rfc7231">RFC 7231 -
* HTTP/1.1</a>.
*
* @return true if within range of codes that belongs to
@ -958,7 +973,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Informational</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
* href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
* href="http://tools.ietf.org/html/rfc7231">RFC 7231 - HTTP/1.1</a>.
*
* @param code
* the code to test.
@ -974,7 +989,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Success</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
* href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
* href="http://tools.ietf.org/html/rfc7231">RFC 7231 - HTTP/1.1</a>.
*
* @param code
* the code to test.
@ -990,7 +1005,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Redirection</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
* href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
* href="http://tools.ietf.org/html/rfc7231">RFC 7231 - HTTP/1.1</a>.
*
* @param code
* the code to test.
@ -1006,7 +1021,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Client Error</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
* href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
* href="http://tools.ietf.org/html/rfc7231">RFC 7231 - HTTP/1.1</a>.
*
* @param code
* the code to test.
@ -1022,7 +1037,7 @@ public class HttpStatus
* Simple test against an code to determine if it falls into the
* <code>Server Error</code> message category as defined in the <a
* href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
* href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
* href="http://tools.ietf.org/html/rfc7231">RFC 7231 - HTTP/1.1</a>.
*
* @param code
* the code to test.

View File

@ -73,7 +73,7 @@ public class HttpTester
}
public abstract static class Message extends HttpFields implements HttpParser.HttpHandler<ByteBuffer>
public abstract static class Message extends HttpFields implements HttpParser.HttpHandler
{
ByteArrayOutputStream _content;
HttpVersion _version=HttpVersion.HTTP_1_0;
@ -132,10 +132,9 @@ public class HttpTester
}
}
@Override
public boolean parsedHeader(HttpField field)
public void parsedHeader(HttpField field)
{
put(field.getName(),field.getValue());
return false;
}
@Override
@ -250,16 +249,16 @@ public class HttpTester
}
public static class Request extends Message implements HttpParser.RequestHandler<ByteBuffer>
public static class Request extends Message implements HttpParser.RequestHandler
{
private String _method;
private String _uri;
@Override
public boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version)
public boolean startRequest(String method, String uri, HttpVersion version)
{
_method=methodString;
_uri=BufferUtil.toUTF8String(uri);
_method=method;
_uri=uri.toString();
_version=version;
return false;
}
@ -300,15 +299,9 @@ public class HttpTester
{
put(name,value);
}
@Override
public boolean parsedHostHeader(String host,int port)
{
return false;
}
}
public static class Response extends Message implements HttpParser.ResponseHandler<ByteBuffer>
public static class Response extends Message implements HttpParser.ResponseHandler
{
private int _status;
private String _reason;

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@ public enum HttpVersion
HTTP_0_9("HTTP/0.9",9),
HTTP_1_0("HTTP/1.0",10),
HTTP_1_1("HTTP/1.1",11),
HTTP_2_0("HTTP/2.0",20);
HTTP_2("HTTP/2",20);
/* ------------------------------------------------------------ */
public final static Trie<HttpVersion> CACHE= new ArrayTrie<HttpVersion>();
@ -74,7 +74,7 @@ public enum HttpVersion
switch(bytes[position+7])
{
case '0':
return HTTP_2_0;
return HTTP_2;
}
break;
}

View File

@ -0,0 +1,316 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http;
import java.util.Iterator;
public class MetaData implements Iterable<HttpField>
{
private HttpVersion _httpVersion;
private HttpFields _fields;
/* ------------------------------------------------------------ */
public MetaData()
{
}
/* ------------------------------------------------------------ */
public MetaData(HttpVersion version, HttpFields fields)
{
_httpVersion = version;
_fields = fields;
}
/* ------------------------------------------------------------ */
public boolean isRequest()
{
return false;
}
/* ------------------------------------------------------------ */
public boolean isResponse()
{
return false;
}
/* ------------------------------------------------------------ */
/** Get the httpVersion.
* @return the httpVersion
*/
public HttpVersion getVersion()
{
return _httpVersion;
}
/* ------------------------------------------------------------ */
/** Set the httpVersion.
* @param httpVersion the httpVersion to set
*/
public void setHttpVersion(HttpVersion httpVersion)
{
_httpVersion = httpVersion;
}
/* ------------------------------------------------------------ */
/** Get the fields.
* @return the fields
*/
public HttpFields getFields()
{
return _fields;
}
/* ------------------------------------------------------------ */
/** Set the fields.
* @param fields the fields to set
*/
public void setFields(HttpFields fields)
{
_fields = fields;
}
/* ------------------------------------------------------------ */
public Iterator<HttpField> iterator()
{
return getFields().iterator();
}
/* ------------------------------------------------------------ */
@Override
public int hashCode()
{
return 31 * getVersion().hashCode() + getFields().hashCode();
}
/* ------------------------------------------------------------ */
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof MetaData))
return false;
MetaData that = (MetaData)o;
if (getVersion() != that.getVersion())
return false;
return getFields().equals(that.getFields());
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
StringBuilder out = new StringBuilder();
for (HttpField field: this)
out.append(field).append(System.lineSeparator());
return out.toString();
}
/* -------------------------------------------------------- */
/* -------------------------------------------------------- */
/* -------------------------------------------------------- */
public static class Request extends MetaData
{
private String _method;
private HttpURI _uri;
public Request()
{
}
/* ------------------------------------------------------------ */
/**
* @param method
* @param uri
* @param version
* @param fields
*/
public Request(String method, HttpURI uri, HttpVersion version, HttpFields fields)
{
super(version,fields);
_method = method;
_uri = uri;
}
/* ------------------------------------------------------------ */
public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields)
{
this(method,new HttpURI(scheme==null?null:scheme.asString(),hostPort.getHost(),hostPort.getPort(),uri),version,fields);
}
/* ------------------------------------------------------------ */
public Request(String method, String scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields)
{
this(method,new HttpURI(scheme,hostPort.getHost(),hostPort.getPort(),uri),version,fields);
}
/* ------------------------------------------------------------ */
@Override
public boolean isRequest()
{
return true;
}
/* ------------------------------------------------------------ */
/** Get the method.
* @return the method
*/
public String getMethod()
{
return _method;
}
/* ------------------------------------------------------------ */
/** Set the method.
* @param method the method to set
*/
public void setMethod(String method)
{
_method = method;
}
/* ------------------------------------------------------------ */
/** Get the uri.
* @return the uri
*/
public HttpURI getURI()
{
return _uri;
}
/* ------------------------------------------------------------ */
/** Set the uri.
* @param uri the uri to set
*/
public void setURI(HttpURI uri)
{
_uri = uri;
}
/* ------------------------------------------------------------ */
@Override
public int hashCode()
{
return ((super.hashCode()*31)+_method.hashCode())*31+_uri.hashCode();
}
/* ------------------------------------------------------------ */
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof MetaData.Request))
return false;
MetaData.Request that = (MetaData.Request)o;
if (!getMethod().equals(that.getMethod()) ||
!getURI().equals(that.getURI()))
return false;
return super.equals(o);
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return String.format("%s %s %s%s%s",
getMethod(), getURI(), getVersion(), System.lineSeparator(), super.toString());
}
}
/* -------------------------------------------------------- */
/* -------------------------------------------------------- */
/* -------------------------------------------------------- */
public static class Response extends MetaData
{
private int _status;
public Response()
{
}
/* ------------------------------------------------------------ */
/**
* @param version
* @param fields
* @param status
*/
public Response(HttpVersion version, int status, HttpFields fields)
{
super(version,fields);
_status=status;
}
/* ------------------------------------------------------------ */
@Override
public boolean isResponse()
{
return true;
}
/* ------------------------------------------------------------ */
/** Get the status.
* @return the status
*/
public int getStatus()
{
return _status;
}
/* ------------------------------------------------------------ */
/** Set the status.
* @param status the status to set
*/
public void setStatus(int status)
{
_status = status;
}
/* ------------------------------------------------------------ */
@Override
public int hashCode()
{
return 31 * getStatus() + super.hashCode();
}
/* ------------------------------------------------------------ */
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof MetaData.Response))
return false;
MetaData.Response that = (MetaData.Response)o;
if (getStatus() != that.getStatus())
return false;
return super.equals(o);
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return String.format("%s %d%s%s",getVersion(), getStatus(), System.lineSeparator(), super.toString());
}
}
}

View File

@ -56,20 +56,20 @@ public class MimeTypes
TEXT_JSON("text/json",StandardCharsets.UTF_8),
APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
TEXT_HTML_8859_1("text/html; charset=ISO-8859-1",TEXT_HTML),
TEXT_HTML_UTF_8("text/html; charset=UTF-8",TEXT_HTML),
TEXT_HTML_8859_1("text/html;charset=iso-8859-1",TEXT_HTML),
TEXT_HTML_UTF_8("text/html;charset=utf-8",TEXT_HTML),
TEXT_PLAIN_8859_1("text/plain; charset=ISO-8859-1",TEXT_PLAIN),
TEXT_PLAIN_UTF_8("text/plain; charset=UTF-8",TEXT_PLAIN),
TEXT_PLAIN_8859_1("text/plain;charset=iso-8859-1",TEXT_PLAIN),
TEXT_PLAIN_UTF_8("text/plain;charset=utf-8",TEXT_PLAIN),
TEXT_XML_8859_1("text/xml; charset=ISO-8859-1",TEXT_XML),
TEXT_XML_UTF_8("text/xml; charset=UTF-8",TEXT_XML),
TEXT_XML_8859_1("text/xml;charset=iso-8859-1",TEXT_XML),
TEXT_XML_UTF_8("text/xml;charset=utf-8",TEXT_XML),
TEXT_JSON_8859_1("text/json; charset=ISO-8859-1",TEXT_JSON),
TEXT_JSON_UTF_8("text/json; charset=UTF-8",TEXT_JSON),
TEXT_JSON_8859_1("text/json;charset=iso-8859-1",TEXT_JSON),
TEXT_JSON_UTF_8("text/json;charset=utf-8",TEXT_JSON),
APPLICATION_JSON_8859_1("text/json; charset=ISO-8859-1",APPLICATION_JSON),
APPLICATION_JSON_UTF_8("text/json; charset=UTF-8",APPLICATION_JSON);
APPLICATION_JSON_8859_1("text/json;charset=iso-8859-1",APPLICATION_JSON),
APPLICATION_JSON_UTF_8("text/json;charset=utf-8",APPLICATION_JSON);
/* ------------------------------------------------------------ */
@ -77,6 +77,7 @@ public class MimeTypes
private final Type _base;
private final ByteBuffer _buffer;
private final Charset _charset;
private final String _charsetString;
private final boolean _assumedCharset;
private final HttpField _field;
@ -87,8 +88,9 @@ public class MimeTypes
_buffer=BufferUtil.toBuffer(s);
_base=this;
_charset=null;
_charsetString=null;
_assumedCharset=false;
_field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
_field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
@ -97,10 +99,11 @@ public class MimeTypes
_string=s;
_buffer=BufferUtil.toBuffer(s);
_base=base;
int i=s.indexOf("; charset=");
_charset=Charset.forName(s.substring(i+10));
int i=s.indexOf(";charset=");
_charset=Charset.forName(s.substring(i+9));
_charsetString=_charset==null?null:_charset.toString().toLowerCase();
_assumedCharset=false;
_field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
_field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
@ -110,8 +113,9 @@ public class MimeTypes
_base=this;
_buffer=BufferUtil.toBuffer(s);
_charset=cs;
_charsetString=_charset==null?null:_charset.toString().toLowerCase();
_assumedCharset=true;
_field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
_field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
@ -126,6 +130,12 @@ public class MimeTypes
return _charset;
}
/* ------------------------------------------------------------ */
public String getCharsetString()
{
return _charsetString;
}
/* ------------------------------------------------------------ */
public boolean is(String s)
{
@ -181,8 +191,9 @@ public class MimeTypes
int charset=type.toString().indexOf(";charset=");
if (charset>0)
{
CACHE.put(type.toString().replace(";charset=","; charset="),type);
TYPES.put(type.toString().replace(";charset=","; charset="),type.asBuffer());
String alt=type.toString().replace(";charset=","; charset=");
CACHE.put(alt,type);
TYPES.put(alt,type.asBuffer());
}
}

View File

@ -0,0 +1,95 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** Pre encoded HttpField.
* <p>A HttpField that will be cached and used many times can be created as
* a {@link PreEncodedHttpField}, which will use the {@link HttpFieldPreEncoder}
* instances discovered by the {@link ServiceLoader} to pre-encode the header
* for each version of HTTP in use. This will save garbage
* and CPU each time the field is encoded into a response.
* </p>
*/
public class PreEncodedHttpField extends HttpField
{
private final static Logger LOG = Log.getLogger(PreEncodedHttpField.class);
private final static HttpFieldPreEncoder[] __encoders;
static
{
List<HttpFieldPreEncoder> encoders = new ArrayList<>();
Iterator<HttpFieldPreEncoder> iter = ServiceLoader.load(HttpFieldPreEncoder.class,PreEncodedHttpField.class.getClassLoader()).iterator();
while (iter.hasNext())
{
try
{
encoders.add(iter.next());
}
catch(Error|RuntimeException e)
{
LOG.debug(e);
}
}
// TODO avoid needing this catch all
if (encoders.size()==0)
encoders.add(new Http1FieldPreEncoder());
LOG.debug("HttpField encoders loaded: {}",encoders);
__encoders = encoders.toArray(new HttpFieldPreEncoder[encoders.size()]);
}
private final byte[][] _encodedField=new byte[2][];
public PreEncodedHttpField(HttpHeader header,String name,String value)
{
super(header,name, value);
for (HttpFieldPreEncoder e:__encoders)
{
_encodedField[e.getHttpVersion()==HttpVersion.HTTP_2?1:0]=e.getEncodedField(header,header.asString(),value);
}
}
public PreEncodedHttpField(HttpHeader header,String value)
{
this(header,header.asString(),value);
}
public PreEncodedHttpField(String name,String value)
{
this(null,name,value);
}
public void putTo(ByteBuffer bufferInFillMode, HttpVersion version)
{
bufferInFillMode.put(_encodedField[version==HttpVersion.HTTP_2?1:0]);
}
}

View File

@ -0,0 +1 @@
org.eclipse.jetty.http.Http1FieldPreEncoder

View File

@ -1,4 +1,4 @@
text/html = ISO-8859-1
text/plain = ISO-8859-1
text/xml = UTF-8
text/json = UTF-8
text/html = iso-8859-1
text/plain = iso-8859-1
text/xml = utf-8
text/json = utf-8

View File

@ -0,0 +1,154 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.junit.Test;
/**
*
*/
public class HttpFieldTest
{
@Test
public void testContainsSimple() throws Exception
{
HttpField field = new HttpField("name","somevalue");
assertTrue(field.contains("somevalue"));
assertFalse(field.contains("other"));
assertFalse(field.contains("some"));
assertFalse(field.contains("value"));
assertFalse(field.contains("v"));
assertFalse(field.contains(""));
assertFalse(field.contains(null));
}
@Test
public void testContainsList() throws Exception
{
HttpField field = new HttpField("name",",aaa,bbb,ccc, ddd , e e, \"\\\"f,f\\\"\", ");
assertTrue(field.contains("aaa"));
assertTrue(field.contains("bbb"));
assertTrue(field.contains("ccc"));
assertTrue(field.contains("ddd"));
assertTrue(field.contains("e e"));
assertTrue(field.contains("\"f,f\""));
assertFalse(field.contains(""));
assertFalse(field.contains("aa"));
assertFalse(field.contains("bb"));
assertFalse(field.contains("cc"));
assertFalse(field.contains(null));
}
@Test
public void testQualityContainsList() throws Exception
{
HttpField field;
field = new HttpField("name","yes");
assertTrue(field.contains("yes"));
assertFalse(field.contains("no"));
field = new HttpField("name",",yes,");
assertTrue(field.contains("yes"));
assertFalse(field.contains("no"));
field = new HttpField("name","other,yes,other");
assertTrue(field.contains("yes"));
assertFalse(field.contains("no"));
field = new HttpField("name","other, yes ,other");
assertTrue(field.contains("yes"));
assertFalse(field.contains("no"));
field = new HttpField("name","other, y s ,other");
assertTrue(field.contains("y s"));
assertFalse(field.contains("no"));
field = new HttpField("name","other, \"yes\" ,other");
assertTrue(field.contains("yes"));
assertFalse(field.contains("no"));
field = new HttpField("name","other, \"\\\"yes\\\"\" ,other");
assertTrue(field.contains("\"yes\""));
assertFalse(field.contains("no"));
field = new HttpField("name",";no,yes,;no");
assertTrue(field.contains("yes"));
assertFalse(field.contains("no"));
field = new HttpField("name","no;q=0,yes;q=1,no; q = 0");
assertTrue(field.contains("yes"));
assertFalse(field.contains("no"));
field = new HttpField("name","no;q=0.0000,yes;q=0.0001,no; q = 0.00000");
assertTrue(field.contains("yes"));
assertFalse(field.contains("no"));
}
@Test
public void testValues()
{
String[] values = new HttpField("name","value").getValues();
assertEquals(1,values.length);
assertEquals("value",values[0]);
values = new HttpField("name","a,b,c").getValues();
assertEquals(3,values.length);
assertEquals("a",values[0]);
assertEquals("b",values[1]);
assertEquals("c",values[2]);
values = new HttpField("name","a,\"x,y,z\",c").getValues();
assertEquals(3,values.length);
assertEquals("a",values[0]);
assertEquals("x,y,z",values[1]);
assertEquals("c",values[2]);
values = new HttpField("name","a,\"x,\\\"p,q\\\",z\",c").getValues();
assertEquals(3,values.length);
assertEquals("a",values[0]);
assertEquals("x,\"p,q\",z",values[1]);
assertEquals("c",values[2]);
}
@Test
public void testCachedField()
{
PreEncodedHttpField field = new PreEncodedHttpField(HttpHeader.ACCEPT,"something");
ByteBuffer buf = BufferUtil.allocate(256);
BufferUtil.clearToFill(buf);
field.putTo(buf,HttpVersion.HTTP_1_0);
BufferUtil.flipToFlush(buf,0);
String s=BufferUtil.toString(buf);
assertEquals("Accept: something\r\n",s);
}
}

View File

@ -27,6 +27,9 @@ import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.List;
import java.util.NoSuchElementException;
import org.eclipse.jetty.util.BufferUtil;
import org.hamcrest.Matchers;
import org.junit.Assert;
@ -45,9 +48,10 @@ public class HttpFieldsTest
header.put("name0", "value:0");
header.put("name1", "value1");
assertEquals("value:0",header.getStringField("name0"));
assertEquals("value1",header.getStringField("name1"));
assertNull(header.getStringField("name2"));
assertEquals(2,header.size());
assertEquals("value:0",header.get("name0"));
assertEquals("value1",header.get("name1"));
assertNull(header.get("name2"));
int matches=0;
Enumeration<String> e = header.getFieldNames();
@ -65,6 +69,7 @@ public class HttpFieldsTest
assertEquals(true, e.hasMoreElements());
assertEquals(e.nextElement(), "value:0");
assertEquals(false, e.hasMoreElements());
}
@Test
@ -96,10 +101,45 @@ public class HttpFieldsTest
header.put("name0", "value0");
header.put("name1", "value1");
assertEquals("value0",header.getStringField("name0"));
assertEquals("value0",header.getStringField("Name0"));
assertEquals("value1",header.getStringField("name1"));
assertEquals("value1",header.getStringField("Name1"));
assertEquals("value0",header.get("name0"));
assertEquals("value0",header.get("Name0"));
assertEquals("value1",header.get("name1"));
assertEquals("value1",header.get("Name1"));
assertEquals(null,header.get("Name2"));
assertEquals("value0",header.getField("name0").getValue());
assertEquals("value0",header.getField("Name0").getValue());
assertEquals("value1",header.getField("name1").getValue());
assertEquals("value1",header.getField("Name1").getValue());
assertEquals(null,header.getField("Name2"));
assertEquals("value0",header.getField(0).getValue());
assertEquals("value1",header.getField(1).getValue());
try
{
header.getField(2);
Assert.fail();
}
catch(NoSuchElementException e)
{}
}
@Test
public void testGetKnown() throws Exception
{
HttpFields header = new HttpFields();
header.put("Connection", "value0");
header.put(HttpHeader.ACCEPT, "value1");
assertEquals("value0",header.get(HttpHeader.CONNECTION));
assertEquals("value1",header.get(HttpHeader.ACCEPT));
assertEquals("value0",header.getField(HttpHeader.CONNECTION).getValue());
assertEquals("value1",header.getField(HttpHeader.ACCEPT).getValue());
assertEquals(null,header.getField(HttpHeader.AGE));
assertEquals(null,header.get(HttpHeader.AGE));
}
@Test
@ -150,16 +190,16 @@ public class HttpFieldsTest
header.put("name1", "xxxxxx");
header.put("name2", "value2");
assertEquals("value0",header.getStringField("name0"));
assertEquals("xxxxxx",header.getStringField("name1"));
assertEquals("value2",header.getStringField("name2"));
assertEquals("value0",header.get("name0"));
assertEquals("xxxxxx",header.get("name1"));
assertEquals("value2",header.get("name2"));
header.put("name1", "value1");
assertEquals("value0",header.getStringField("name0"));
assertEquals("value1",header.getStringField("name1"));
assertEquals("value2",header.getStringField("name2"));
assertNull(header.getStringField("name3"));
assertEquals("value0",header.get("name0"));
assertEquals("value1",header.get("name1"));
assertEquals("value2",header.get("name2"));
assertNull(header.get("name3"));
int matches=0;
Enumeration<String> e = header.getFieldNames();
@ -185,22 +225,22 @@ public class HttpFieldsTest
@Test
public void testRemovePut() throws Exception
{
HttpFields header = new HttpFields();
HttpFields header = new HttpFields(1);
header.put("name0", "value0");
header.put("name1", "value1");
header.put("name2", "value2");
assertEquals("value0",header.getStringField("name0"));
assertEquals("value1",header.getStringField("name1"));
assertEquals("value2",header.getStringField("name2"));
assertEquals("value0",header.get("name0"));
assertEquals("value1",header.get("name1"));
assertEquals("value2",header.get("name2"));
header.remove("name1");
assertEquals("value0",header.getStringField("name0"));
assertNull(header.getStringField("name1"));
assertEquals("value2",header.getStringField("name2"));
assertNull(header.getStringField("name3"));
assertEquals("value0",header.get("name0"));
assertNull(header.get("name1"));
assertEquals("value2",header.get("name2"));
assertNull(header.get("name3"));
int matches=0;
Enumeration<String> e = header.getFieldNames();
@ -229,16 +269,16 @@ public class HttpFieldsTest
fields.add("name1", "valueA");
fields.add("name2", "value2");
assertEquals("value0",fields.getStringField("name0"));
assertEquals("valueA",fields.getStringField("name1"));
assertEquals("value2",fields.getStringField("name2"));
assertEquals("value0",fields.get("name0"));
assertEquals("valueA",fields.get("name1"));
assertEquals("value2",fields.get("name2"));
fields.add("name1", "valueB");
assertEquals("value0",fields.getStringField("name0"));
assertEquals("valueA",fields.getStringField("name1"));
assertEquals("value2",fields.getStringField("name2"));
assertNull(fields.getStringField("name3"));
assertEquals("value0",fields.get("name0"));
assertEquals("valueA",fields.get("name1"));
assertEquals("value2",fields.get("name2"));
assertNull(fields.get("name3"));
int matches=0;
Enumeration<String> e = fields.getFieldNames();
@ -301,9 +341,29 @@ public class HttpFieldsTest
assertEquals(true, e.hasMoreElements());
assertEquals(e.nextElement(), "value1D");
assertEquals(false, e.hasMoreElements());
}
@Test
public void testGetQualityValues() throws Exception
{
HttpFields fields = new HttpFields();
fields.put("some", "value");
fields.add("name", "zero;q=0.9,four;q=0.1");
fields.put("other", "value");
fields.add("name", "nothing;q=0");
fields.add("name", "one;q=0.4");
fields.add("name", "three;x=y;q=0.2;a=b,two;q=0.3");
List<String> list = HttpFields.qualityList(fields.getValues("name",","));
assertEquals("zero",HttpFields.valueParameters(list.get(0),null));
assertEquals("one",HttpFields.valueParameters(list.get(1),null));
assertEquals("two",HttpFields.valueParameters(list.get(2),null));
assertEquals("three",HttpFields.valueParameters(list.get(3),null));
assertEquals("four",HttpFields.valueParameters(list.get(4),null));
}
@Test
public void testDateFields() throws Exception
{
@ -343,7 +403,7 @@ public class HttpFieldsTest
assertEquals(951825600000L,d5);
fields.putDateField("D2",d1);
assertEquals("Fri, 31 Dec 1999 23:59:59 GMT",fields.getStringField("D2"));
assertEquals("Fri, 31 Dec 1999 23:59:59 GMT",fields.get("D2"));
}
@Test
@ -352,16 +412,16 @@ public class HttpFieldsTest
HttpFields fields = new HttpFields();
fields.putDateField("Dzero",0);
assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Dzero"));
assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.get("Dzero"));
fields.putDateField("Dminus",-1);
assertEquals("Wed, 31 Dec 1969 23:59:59 GMT",fields.getStringField("Dminus"));
assertEquals("Wed, 31 Dec 1969 23:59:59 GMT",fields.get("Dminus"));
fields.putDateField("Dminus",-1000);
assertEquals("Wed, 31 Dec 1969 23:59:59 GMT",fields.getStringField("Dminus"));
assertEquals("Wed, 31 Dec 1969 23:59:59 GMT",fields.get("Dminus"));
fields.putDateField("Dancient",Long.MIN_VALUE);
assertEquals("Sun, 02 Dec 55 16:47:04 GMT",fields.getStringField("Dancient"));
assertEquals("Sun, 02 Dec 55 16:47:04 GMT",fields.get("Dancient"));
}
@Test
@ -371,15 +431,33 @@ public class HttpFieldsTest
header.put("I1", "42");
header.put("I2", " 43 99");
header.put("I3", "-44;");
header.put("I3", "-44");
header.put("I4", " - 45abc");
header.put("N1", " - ");
header.put("N2", "xx");
long i1=header.getLongField("I1");
long i2=header.getLongField("I2");
try
{
header.getLongField("I2");
assertTrue(false);
}
catch(NumberFormatException e)
{
assertTrue(true);
}
long i3=header.getLongField("I3");
long i4=header.getLongField("I4");
try
{
header.getLongField("I4");
assertTrue(false);
}
catch(NumberFormatException e)
{
assertTrue(true);
}
try{
header.getLongField("N1");
@ -400,14 +478,12 @@ public class HttpFieldsTest
}
assertEquals(42,i1);
assertEquals(43,i2);
assertEquals(-44,i3);
assertEquals(-45,i4);
header.putLongField("I5", 46);
header.putLongField("I6",-47);
assertEquals("46",header.getStringField("I5"));
assertEquals("-47",header.getStringField("I6"));
assertEquals("46",header.get("I5"));
assertEquals("-47",header.get("I6"));
}
@ -416,20 +492,38 @@ public class HttpFieldsTest
{
HttpFields header = new HttpFields();
header.add("0", "");
header.add("1", ",");
header.add("2", ",,");
header.add("3", "abc");
header.add("4", "def");
header.add("5", "abc,def,hig");
header.add("6", "abc");
header.add("6", "def");
header.add("6", "hig");
header.add("n0", "");
header.add("n1", ",");
header.add("n2", ",,");
header.add("N3", "abc");
header.add("N4", "def");
header.add("n5", "abc,def,hig");
header.add("N6", "abc");
header.add("n6", "def");
header.add("N6", "hig");
header.add("n7", "abc , def;q=0.9 , hig");
header.add("n8", "abc , def;q=0 , hig");
header.add(HttpHeader.ACCEPT, "abc , def;q=0 , hig");
for (int i=0;i<7;i++)
for (int i=0;i<8;i++)
{
assertFalse(""+i,header.contains(""+i,"xyz"));
assertEquals(""+i,i>=4,header.contains(""+i,"def"));
assertTrue(header.containsKey("n"+i));
assertTrue(header.containsKey("N"+i));
assertFalse(""+i,header.contains("n"+i,"xyz"));
assertEquals(""+i,i>=4,header.contains("n"+i,"def"));
}
assertTrue(header.contains(new HttpField("N5","def")));
assertTrue(header.contains(new HttpField("accept","abc")));
assertTrue(header.contains(HttpHeader.ACCEPT,"abc"));
assertFalse(header.contains(new HttpField("N5","xyz")));
assertFalse(header.contains(new HttpField("N8","def")));
assertFalse(header.contains(HttpHeader.ACCEPT,"def"));
assertFalse(header.contains(HttpHeader.AGE,"abc"));
assertFalse(header.containsKey("n11"));
}
}

View File

@ -18,6 +18,13 @@
package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.equalTo;
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.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
@ -31,13 +38,6 @@ import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
public class HttpGeneratorServerHTTPTest
{
@ -216,7 +216,7 @@ public class HttpGeneratorServerHTTPTest
}
}
private class Handler implements HttpParser.ResponseHandler<ByteBuffer>
private class Handler implements HttpParser.ResponseHandler
{
@Override
public boolean content(ByteBuffer ref)
@ -247,9 +247,8 @@ public class HttpGeneratorServerHTTPTest
}
@Override
public boolean parsedHeader(HttpField field)
public void parsedHeader(HttpField field)
{
return false;
}
@Override

View File

@ -18,6 +18,12 @@
package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
@ -25,12 +31,6 @@ import org.eclipse.jetty.util.BufferUtil;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class HttpGeneratorServerTest
{
@Test

View File

@ -18,7 +18,10 @@
package org.eclipse.jetty.http;
import static org.junit.Assert.*;
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.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@ -68,7 +71,7 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /foo HTTP/1.0\015\012" + "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
@ -83,13 +86,11 @@ public class HttpParserTest
ByteBuffer buffer= BufferUtil.toBuffer("GET /999\015\012");
_versionOrReason= null;
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/999", _uriOrStatus);
assertEquals(null, _versionOrReason);
assertEquals(-1, _headers);
assertEquals("HTTP/0.9 not supported", _bad);
}
@Test
@ -98,13 +99,10 @@ public class HttpParserTest
ByteBuffer buffer= BufferUtil.toBuffer("POST /222 \015\012");
_versionOrReason= null;
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
assertEquals("/222", _uriOrStatus);
assertEquals(null, _versionOrReason);
assertEquals(-1, _headers);
assertEquals("HTTP/0.9 not supported", _bad);
}
@Test
@ -112,7 +110,7 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /fo\u0690 HTTP/1.0\015\012" + "\015\012",StandardCharsets.UTF_8);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
@ -126,7 +124,7 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /foo?param=\u0690 HTTP/1.0\015\012" + "\015\012",StandardCharsets.UTF_8);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
@ -140,7 +138,7 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abcdef/ HTTP/1.0\015\012" + "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("POST", _methodOrVersion);
@ -153,7 +151,7 @@ public class HttpParserTest
public void testConnect() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer("CONNECT 192.168.1.2:80 HTTP/1.1\015\012" + "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("CONNECT", _methodOrVersion);
@ -171,7 +169,7 @@ public class HttpParserTest
"Connection: close\015\012" +
"\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
@ -186,6 +184,73 @@ public class HttpParserTest
assertEquals("close", _val[1]);
assertEquals(1, _headers);
}
@Test
public void test7230NoContinuations() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
"Name: value\015\012" +
" extra\015\012" +
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
Assert.assertThat(_bad,Matchers.notNullValue());
Assert.assertThat(_bad,Matchers.containsString("Bad Continuation"));
}
@Test
public void test7230NoWhiteSpaceInName() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
" Name: value\015\012" +
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
Assert.assertThat(_bad,Matchers.notNullValue());
Assert.assertThat(_bad,Matchers.containsString("Bad"));
init();
buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
"N ame: value\015\012" +
"\015\012");
handler = new Handler();
parser= new HttpParser(handler);
parseAll(parser,buffer);
Assert.assertThat(_bad,Matchers.containsString("Illegal character"));
init();
buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
"Name : value\015\012" +
"\015\012");
handler = new Handler();
parser= new HttpParser(handler);
parseAll(parser,buffer);
Assert.assertThat(_bad,Matchers.containsString("Illegal character"));
}
@Test
public void testHeaderParseDirect() throws Exception
@ -194,13 +259,11 @@ public class HttpParserTest
"GET / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
"Header1: value1\015\012" +
"Header 2 : value 2a \015\012" +
" value 2b \015\012" +
"Header3: \015\012" +
"Header4 \015\012" +
" value4\015\012" +
"Server5 : notServer\015\012" +
"Host Header: notHost\015\012" +
"Header2: value 2a \015\012" +
"Header3: 3\015\012" +
"Header4:value4\015\012" +
"Server5: notServer\015\012" +
"HostHeader: notHost\015\012" +
"Connection: close\015\012" +
"Accept-Encoding: gzip, deflated\015\012" +
"Accept: unknown\015\012" +
@ -210,7 +273,7 @@ public class HttpParserTest
BufferUtil.put(b0,buffer);
BufferUtil.flipToFlush(buffer,pos);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
@ -221,15 +284,15 @@ public class HttpParserTest
assertEquals("localhost", _val[0]);
assertEquals("Header1", _hdr[1]);
assertEquals("value1", _val[1]);
assertEquals("Header 2", _hdr[2]);
assertEquals("value 2a value 2b", _val[2]);
assertEquals("Header2", _hdr[2]);
assertEquals("value 2a", _val[2]);
assertEquals("Header3", _hdr[3]);
assertEquals(null, _val[3]);
assertEquals("3", _val[3]);
assertEquals("Header4", _hdr[4]);
assertEquals("value4", _val[4]);
assertEquals("Server5", _hdr[5]);
assertEquals("notServer", _val[5]);
assertEquals("Host Header", _hdr[6]);
assertEquals("HostHeader", _hdr[6]);
assertEquals("notHost", _val[6]);
assertEquals("Connection", _hdr[7]);
assertEquals("close", _val[7]);
@ -247,18 +310,16 @@ public class HttpParserTest
"GET / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
"Header1: value1\015\012" +
"Header 2 : value 2a \015\012" +
" value 2b \015\012" +
"Header3: \015\012" +
"Header4 \015\012" +
" value4\015\012" +
"Server5 : notServer\015\012" +
"Host Header: notHost\015\012" +
"Header2: value 2a \015\012" +
"Header3: 3\015\012" +
"Header4:value4\015\012" +
"Server5: notServer\015\012" +
"HostHeader: notHost\015\012" +
"Connection: close\015\012" +
"Accept-Encoding: gzip, deflated\015\012" +
"Accept: unknown\015\012" +
"\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
@ -269,15 +330,15 @@ public class HttpParserTest
assertEquals("localhost", _val[0]);
assertEquals("Header1", _hdr[1]);
assertEquals("value1", _val[1]);
assertEquals("Header 2", _hdr[2]);
assertEquals("value 2a value 2b", _val[2]);
assertEquals("Header2", _hdr[2]);
assertEquals("value 2a", _val[2]);
assertEquals("Header3", _hdr[3]);
assertEquals(null, _val[3]);
assertEquals("3", _val[3]);
assertEquals("Header4", _hdr[4]);
assertEquals("value4", _val[4]);
assertEquals("Server5", _hdr[5]);
assertEquals("notServer", _val[5]);
assertEquals("Host Header", _hdr[6]);
assertEquals("HostHeader", _hdr[6]);
assertEquals("notHost", _val[6]);
assertEquals("Connection", _hdr[7]);
assertEquals("close", _val[7]);
@ -297,18 +358,16 @@ public class HttpParserTest
"GET / HTTP/1.0\n" +
"Host: localhost\n" +
"Header1: value1\n" +
"Header 2 : value 2a \n" +
" value 2b \n" +
"Header3: \n" +
"Header4 \n" +
" value4\n" +
"Server5 : notServer\n" +
"Host Header: notHost\n" +
"Header2: value 2a value 2b \n" +
"Header3: 3\n" +
"Header4:value4\n" +
"Server5: notServer\n" +
"HostHeader: notHost\n" +
"Connection: close\n" +
"Accept-Encoding: gzip, deflated\n" +
"Accept: unknown\n" +
"\n");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
@ -319,15 +378,15 @@ public class HttpParserTest
assertEquals("localhost", _val[0]);
assertEquals("Header1", _hdr[1]);
assertEquals("value1", _val[1]);
assertEquals("Header 2", _hdr[2]);
assertEquals("Header2", _hdr[2]);
assertEquals("value 2a value 2b", _val[2]);
assertEquals("Header3", _hdr[3]);
assertEquals(null, _val[3]);
assertEquals("3", _val[3]);
assertEquals("Header4", _hdr[4]);
assertEquals("value4", _val[4]);
assertEquals("Server5", _hdr[5]);
assertEquals("notServer", _val[5]);
assertEquals("Host Header", _hdr[6]);
assertEquals("HostHeader", _hdr[6]);
assertEquals("notHost", _val[6]);
assertEquals("Connection", _hdr[7]);
assertEquals("close", _val[7]);
@ -347,7 +406,7 @@ public class HttpParserTest
"Name1: \"value\t1\"\n" +
"Name2: \"value\t2A\",\"value,2B\"\t\n" +
"\n");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
@ -376,7 +435,7 @@ public class HttpParserTest
BufferUtil.put(BufferUtil.toBuffer(" \r\n\r\n"),buffer);
BufferUtil.flipToFlush(buffer,0);
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
@ -397,7 +456,7 @@ public class HttpParserTest
ByteBuffer buffer= BufferUtil.toBuffer(
"G\u00e6T / HTTP/1.0\r\nHeader0: value0\r\n\n\n");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertThat(_bad,Matchers.notNullValue());
@ -409,7 +468,7 @@ public class HttpParserTest
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / H\u00e6P/1.0\r\nHeader0: value0\r\n\n\n");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertThat(_bad,Matchers.notNullValue());
@ -422,7 +481,7 @@ public class HttpParserTest
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\r\nH\u00e6der0: value0\r\n\n\n");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertThat(_bad,Matchers.notNullValue());
@ -437,7 +496,7 @@ public class HttpParserTest
"Header: value\talternate\r\n" +
"\n\n");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
@ -458,7 +517,7 @@ public class HttpParserTest
"HOST: localhost\015\012" +
"cOnNeCtIoN: ClOsE\015\012"+
"\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,-1,false);
parseAll(parser,buffer);
@ -480,7 +539,7 @@ public class HttpParserTest
"HOST: localhost\015\012" +
"cOnNeCtIoN: ClOsE\015\012"+
"\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,-1,true);
parseAll(parser,buffer);
@ -501,11 +560,9 @@ public class HttpParserTest
"XXXXSPLIT / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
"Header1: value1\015\012" +
"Header2 : value 2a \015\012" +
" value 2b \015\012" +
"Header3: \015\012" +
"Header4 \015\012" +
" value4\015\012" +
"Header2: value 2a \015\012" +
"Header3: 3\015\012" +
"Header4:value4\015\012" +
"Server5: notServer\015\012" +
"\015\012ZZZZ");
buffer.position(2);
@ -514,7 +571,7 @@ public class HttpParserTest
for (int i=0;i<buffer.capacity()-4;i++)
{
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
// System.err.println(BufferUtil.toDetailString(buffer));
@ -539,9 +596,9 @@ public class HttpParserTest
assertEquals("Header1", _hdr[1]);
assertEquals("value1", _val[1]);
assertEquals("Header2", _hdr[2]);
assertEquals("value 2a value 2b", _val[2]);
assertEquals("value 2a", _val[2]);
assertEquals("Header3", _hdr[3]);
assertEquals(null, _val[3]);
assertEquals("3", _val[3]);
assertEquals("Header4", _hdr[4]);
assertEquals("value4", _val[4]);
assertEquals("Server5", _hdr[5]);
@ -564,7 +621,7 @@ public class HttpParserTest
+ "1a\015\012"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ\015\012"
+ "0\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
@ -580,7 +637,7 @@ public class HttpParserTest
@Test
public void testStartEOF() throws Exception
{
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.atEOF();
parser.parseNext(BufferUtil.EMPTY_BUFFER);
@ -597,7 +654,7 @@ public class HttpParserTest
+ "Content-Length: 20\015\012"
+ "\015\012"
+ "0123456789");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.atEOF();
parseAll(parser,buffer);
@ -620,7 +677,7 @@ public class HttpParserTest
+ "\015\012"
+ "a;\015\012"
+ "0123456789\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.atEOF();
parseAll(parser,buffer);
@ -668,7 +725,7 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("GET", _methodOrVersion);
@ -736,7 +793,7 @@ public class HttpParserTest
+ "0123456789\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer0);
parser.atEOF();
@ -782,7 +839,7 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@ -801,7 +858,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@ -825,7 +882,7 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@ -858,7 +915,7 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@ -879,7 +936,7 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@ -899,7 +956,7 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.atEOF();
parser.parseNext(buffer);
@ -921,7 +978,7 @@ public class HttpParserTest
+ "Content-Length: 10\015\012"
+ "\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@ -940,7 +997,7 @@ public class HttpParserTest
+ "Transfer-Encoding: chunked\015\012"
+ "\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("HTTP/1.1", _methodOrVersion);
@ -963,7 +1020,7 @@ public class HttpParserTest
+ "HTTP/1.1 400 OK\015\012"); // extra data causes close ??
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -991,7 +1048,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1011,7 +1068,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1030,7 +1087,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1049,7 +1106,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1068,7 +1125,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.ResponseHandler<ByteBuffer> handler = new Handler();
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1087,7 +1144,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1121,7 +1178,7 @@ public class HttpParserTest
+ "Connection: close\r"
+ "\r");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1157,7 +1214,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1176,7 +1233,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1195,7 +1252,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
@ -1214,7 +1271,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("host",_host);
@ -1229,7 +1286,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("No Host",_bad);
@ -1244,7 +1301,7 @@ public class HttpParserTest
"GET http://host/ HTTP/1.0\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
Assert.assertNull(_bad);
@ -1260,7 +1317,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("No Host",_bad);
@ -1275,7 +1332,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("192.168.0.1",_host);
@ -1291,7 +1348,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("[::1]",_host);
@ -1307,10 +1364,10 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("Bad IPv6 Host header",_bad);
Assert.assertThat(_bad,Matchers.containsString("Bad"));
}
@Test
@ -1322,7 +1379,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("myhost",_host);
@ -1334,14 +1391,14 @@ public class HttpParserTest
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.1\015\012"
+ "Host: myhost:xxx\015\012"
+ "Host: myhost:testBadPort\015\012"
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("Bad Host header",_bad);
Assert.assertThat(_bad,Matchers.containsString("Bad Host"));
}
@Test
@ -1353,7 +1410,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("192.168.0.1",_host);
@ -1369,7 +1426,7 @@ public class HttpParserTest
+ "Connection: close\015\012"
+ "\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);
assertEquals("[::1]",_host);
@ -1384,7 +1441,7 @@ public class HttpParserTest
"Host: www.smh.com.au\r\n"+
"\r\n");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("www.smh.com.au",parser.getFieldCache().get("Host: www.smh.com.au").getValue());
@ -1486,7 +1543,7 @@ public class HttpParserTest
private boolean _headerCompleted;
private boolean _messageCompleted;
private class Handler implements HttpParser.RequestHandler<ByteBuffer>, HttpParser.ResponseHandler<ByteBuffer>, HttpParser.ProxyHandler
private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ProxyHandler
{
private HttpFields fields;
String _proxy;
@ -1504,14 +1561,14 @@ public class HttpParserTest
}
@Override
public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
public boolean startRequest(String method, String uri, HttpVersion version)
{
_fields.clear();
_headers= -1;
_hdr= new String[10];
_val= new String[10];
_methodOrVersion= method;
_uriOrStatus= BufferUtil.toUTF8String(uri);
_uriOrStatus= uri.toString();
_versionOrReason= version==null?null:version.asString();
fields=new HttpFields();
@ -1522,21 +1579,19 @@ public class HttpParserTest
}
@Override
public boolean parsedHeader(HttpField field)
public void parsedHeader(HttpField field)
{
_fields.add(field);
//System.err.println("header "+name+": "+value);
_hdr[++_headers]= field.getName();
_val[_headers]= field.getValue();
return false;
}
@Override
public boolean parsedHostHeader(String host,int port)
{
_host=host;
_port=port;
return false;
if (field instanceof HostPortHttpField)
{
HostPortHttpField hpfield = (HostPortHttpField)field;
_host=hpfield.getHost();
_port=hpfield.getPort();
}
}
@Override

View File

@ -20,9 +20,18 @@
package org.eclipse.jetty.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.Utf8Appendable;
import org.junit.Assert;
import org.junit.Test;
@ -32,10 +41,10 @@ public class HttpURITest
String[][] tests=
{
{"/path/to/context",null,null,"-1","/path/to/context",null,null,null},
{"http://example.com/path/to/context;param?query=%22value%22#fragment","http","example.com","-1","/path/to/context","param","query=%22value%22","fragment"},
{"http://[::1]/path/to/context;param?query=%22value%22#fragment","http","[::1]","-1","/path/to/context","param","query=%22value%22","fragment"},
{"http://example.com:8080/path/to/context;param?query=%22value%22#fragment","http","example.com","8080","/path/to/context","param","query=%22value%22","fragment"},
{"http://[::1]:8080/path/to/context;param?query=%22value%22#fragment","http","[::1]","8080","/path/to/context","param","query=%22value%22","fragment"},
{"http://example.com/path/to/context;param?query=%22value%22#fragment","http","example.com","-1","/path/to/context;param","param","query=%22value%22","fragment"},
{"http://[::1]/path/to/context;param?query=%22value%22#fragment","http","[::1]","-1","/path/to/context;param","param","query=%22value%22","fragment"},
{"http://example.com:8080/path/to/context;param?query=%22value%22#fragment","http","example.com","8080","/path/to/context;param","param","query=%22value%22","fragment"},
{"http://[::1]:8080/path/to/context;param?query=%22value%22#fragment","http","[::1]","8080","/path/to/context;param","param","query=%22value%22","fragment"},
};
public static int
@ -65,7 +74,8 @@ public class HttpURITest
{
for (String[] test:tests)
{
HttpURI uri = new HttpURI(new URI(test[INPUT]));
URI u=new URI(test[INPUT]);
HttpURI uri = new HttpURI(u);
assertEquals(test[SCHEME], uri.getScheme());
assertEquals(test[HOST], uri.getHost());
@ -74,6 +84,211 @@ public class HttpURITest
assertEquals(test[PARAM], uri.getParam());
assertEquals(test[QUERY], uri.getQuery());
assertEquals(test[FRAGMENT], uri.getFragment());
assertEquals(u,uri.toURI());
}
}
private final String[][] path_tests=
{
/* 0*/ {"/path/info",null,null,null,null,"/path/info",null,null,null},
/* 1*/ {"/path/info#fragment",null,null,null,null,"/path/info",null,null,"fragment"},
/* 2*/ {"/path/info?query",null,null,null,null,"/path/info",null,"query",null},
/* 3*/ {"/path/info?query#fragment",null,null,null,null,"/path/info",null,"query","fragment"},
/* 4*/ {"/path/info;param",null,null,null,null,"/path/info;param","param",null,null},
/* 5*/ {"/path/info;param#fragment",null,null,null,null,"/path/info;param","param",null,"fragment"},
/* 6*/ {"/path/info;param?query",null,null,null,null,"/path/info;param","param","query",null},
/* 7*/ {"/path/info;param?query#fragment",null,null,null,null,"/path/info;param","param","query","fragment"},
/* 8*/ {"//host/path/info",null,null,null,null,"//host/path/info",null,null,null},
/* 9*/ {"//user@host/path/info",null,null,null,null,"//user@host/path/info",null,null,null},
/*10*/ {"//user@host:8080/path/info",null,null,null,null,"//user@host:8080/path/info",null,null,null},
/*11*/ {"//host:8080/path/info",null,null,null,null,"//host:8080/path/info",null,null,null},
/*12*/ {"http:/path/info","http",null,null,null,"/path/info",null,null,null},
/*13*/ {"http:/path/info#fragment","http",null,null,null,"/path/info",null,null,"fragment"},
/*14*/ {"http:/path/info?query","http",null,null,null,"/path/info",null,"query",null},
/*15*/ {"http:/path/info?query#fragment","http",null,null,null,"/path/info",null,"query","fragment"},
/*16*/ {"http:/path/info;param","http",null,null,null,"/path/info;param","param",null,null},
/*17*/ {"http:/path/info;param#fragment","http",null,null,null,"/path/info;param","param",null,"fragment"},
/*18*/ {"http:/path/info;param?query","http",null,null,null,"/path/info;param","param","query",null},
/*19*/ {"http:/path/info;param?query#fragment","http",null,null,null,"/path/info;param","param","query","fragment"},
/*20*/ {"http://user@host:8080/path/info;param?query#fragment","http","//user@host:8080","host","8080","/path/info;param","param","query","fragment"},
/*21*/ {"xxxxx://user@host:8080/path/info;param?query#fragment","xxxxx","//user@host:8080","host","8080","/path/info;param","param","query","fragment"},
/*22*/ {"http:///;?#","http","//","",null,"/;","","",""},
/*23*/ {"/path/info?a=?query",null,null,null,null,"/path/info",null,"a=?query",null},
/*24*/ {"/path/info?a=;query",null,null,null,null,"/path/info",null,"a=;query",null},
/*25*/ {"//host:8080//",null,null,null,null,"//host:8080//",null,null,null},
/*26*/ {"file:///path/info","file","//","",null,"/path/info",null,null,null},
/*27*/ {"//",null,null,null,null,"//",null,null,null},
/*28*/ {"http://localhost/","http","//localhost","localhost",null,"/",null,null,null},
/*29*/ {"http://localhost:8080/", "http", "//localhost:8080", "localhost","8080","/", null, null,null},
/*30*/ {"http://localhost/?x=y", "http", "//localhost", "localhost",null,"/", null,"x=y",null},
/*31*/ {"/;param",null, null, null,null,"/;param", "param",null,null},
/*32*/ {"/?x=y",null, null, null,null,"/", null,"x=y",null},
/*33*/ {"/?abc=test",null, null, null,null,"/", null,"abc=test",null},
/*34*/ {"/#fragment",null, null, null,null,"/", null,null,"fragment"},
/*35*/ {"http://192.0.0.1:8080/","http","//192.0.0.1:8080","192.0.0.1","8080","/",null,null,null},
/*36*/ {"http://[2001:db8::1]:8080/","http","//[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
/*37*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
/*38*/ {"http://[2001:db8::1]/","http","//[2001:db8::1]","[2001:db8::1]",null,"/",null,null,null},
/*39*/ {"//[2001:db8::1]:8080/",null,null,null,null,"//[2001:db8::1]:8080/",null,null,null},
/*40*/ {"http://user@[2001:db8::1]:8080/","http","//user@[2001:db8::1]:8080","[2001:db8::1]","8080","/",null,null,null},
/*41*/ {"*",null,null,null,null,"*",null, null,null}
};
@Test
public void testPathURIs() throws Exception
{
HttpURI uri = new HttpURI();
for (int t=0;t<path_tests.length;t++)
{
uri.parse(path_tests[t][0]);
assertEquals(t+" "+path_tests[t][0],path_tests[t][1],uri.getScheme());
assertEquals(t+" "+path_tests[t][0],path_tests[t][3],uri.getHost());
assertEquals(t+" "+path_tests[t][0],path_tests[t][4]==null?-1:Integer.parseInt(path_tests[t][4]),uri.getPort());
assertEquals(t+" "+path_tests[t][0],path_tests[t][5],uri.getPath());
assertEquals(t+" "+path_tests[t][0],path_tests[t][6],uri.getParam());
assertEquals(t+" "+path_tests[t][0],path_tests[t][7],uri.getQuery());
assertEquals(t+" "+path_tests[t][0],path_tests[t][8],uri.getFragment());
assertEquals(path_tests[t][0], uri.toString());
}
}
@Test
public void testInvalidAddress() throws Exception
{
assertInvalidURI("http://[ffff::1:8080/", "Invalid URL; no closing ']' -- should throw exception");
assertInvalidURI("**", "only '*', not '**'");
assertInvalidURI("*/", "only '*', not '*/'");
}
private void assertInvalidURI(String invalidURI, String message)
{
HttpURI uri = new HttpURI();
try
{
uri.parse(invalidURI);
fail(message);
}
catch (IllegalArgumentException e)
{
assertTrue(true);
}
}
@Test
public void testUnicodeErrors() throws UnsupportedEncodingException
{
String uri="http://server/path?invalid=data%uXXXXhere%u000";
try
{
URLDecoder.decode(uri,"UTF-8");
Assert.assertTrue(false);
}
catch (IllegalArgumentException e)
{
}
HttpURI huri=new HttpURI(uri);
MultiMap<String> params = new MultiMap<>();
huri.decodeQueryTo(params);
assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
huri=new HttpURI(uri);
params = new MultiMap<>();
huri.decodeQueryTo(params,StandardCharsets.UTF_8);
assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
}
@Test
public void testExtB() throws Exception
{
for (String value: new String[]{"a","abcdABCD","\u00C0","\u697C","\uD869\uDED5","\uD840\uDC08"} )
{
HttpURI uri = new HttpURI("/path?value="+URLEncoder.encode(value,"UTF-8"));
MultiMap<String> parameters = new MultiMap<>();
uri.decodeQueryTo(parameters,StandardCharsets.UTF_8);
assertEquals(value,parameters.getString("value"));
}
}
@Test
public void testParams() throws Exception
{
HttpURI uri = new HttpURI("/foo/bar");
assertEquals("/foo/bar",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
assertEquals(null,uri.getParam());
uri = new HttpURI("/foo/bar;jsessionid=12345");
assertEquals("/foo/bar;jsessionid=12345",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
assertEquals("jsessionid=12345",uri.getParam());
uri = new HttpURI("/foo;abc=123/bar;jsessionid=12345");
assertEquals("/foo;abc=123/bar;jsessionid=12345",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
assertEquals("jsessionid=12345",uri.getParam());
uri = new HttpURI("/foo;abc=123/bar;jsessionid=12345?name=value");
assertEquals("/foo;abc=123/bar;jsessionid=12345",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
assertEquals("jsessionid=12345",uri.getParam());
uri = new HttpURI("/foo;abc=123/bar;jsessionid=12345#target");
assertEquals("/foo;abc=123/bar;jsessionid=12345",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
assertEquals("jsessionid=12345",uri.getParam());
}
@Test
public void testMutableURI()
{
HttpURI uri = new HttpURI("/foo/bar");
assertEquals("/foo/bar",uri.toString());
assertEquals("/foo/bar",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
uri.setScheme("http");
assertEquals("http:/foo/bar",uri.toString());
assertEquals("/foo/bar",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
uri.setAuthority("host",0);
assertEquals("http://host/foo/bar",uri.toString());
assertEquals("/foo/bar",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
uri.setAuthority("host",8888);
assertEquals("http://host:8888/foo/bar",uri.toString());
assertEquals("/foo/bar",uri.getPath());
assertEquals("/foo/bar",uri.getDecodedPath());
uri.setPathQuery("/f%30%30;p0/bar;p1;p2");
assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2",uri.toString());
assertEquals("/f%30%30;p0/bar;p1;p2",uri.getPath());
assertEquals("/f00/bar",uri.getDecodedPath());
assertEquals("p2",uri.getParam());
assertEquals(null,uri.getQuery());
uri.setPathQuery("/f%30%30;p0/bar;p1;p2?name=value");
assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2?name=value",uri.toString());
assertEquals("/f%30%30;p0/bar;p1;p2",uri.getPath());
assertEquals("/f00/bar",uri.getDecodedPath());
assertEquals("p2",uri.getParam());
assertEquals("name=value",uri.getQuery());
uri.setQuery("other=123456");
assertEquals("http://host:8888/f%30%30;p0/bar;p1;p2?other=123456",uri.toString());
assertEquals("/f%30%30;p0/bar;p1;p2",uri.getPath());
assertEquals("/f00/bar",uri.getDecodedPath());
assertEquals("p2",uri.getParam());
assertEquals("other=123456",uri.getQuery());
}
}

View File

@ -90,9 +90,9 @@ public class MimeTypesTest
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = abc"));
assertEquals("abc",MimeTypes.getCharsetFromContentType("foo/bar other = param ; charset = \"abc\" ; some=else"));
assertEquals(null,MimeTypes.getCharsetFromContentType("foo/bar"));
assertEquals("UTF-8",MimeTypes.getCharsetFromContentType("foo/bar;charset=uTf8"));
assertEquals("UTF-8",MimeTypes.getCharsetFromContentType("foo/bar;other=\"charset=abc\";charset=uTf8"));
assertEquals("UTF-8",MimeTypes.getCharsetFromContentType("text/html;charset=utf-8"));
assertEquals("utf-8",MimeTypes.getCharsetFromContentType("foo/bar;charset=uTf8"));
assertEquals("utf-8",MimeTypes.getCharsetFromContentType("foo/bar;other=\"charset=abc\";charset=uTf8"));
assertEquals("utf-8",MimeTypes.getCharsetFromContentType("text/html;charset=utf-8"));
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-parent</artifactId>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>http2-client</artifactId>
<name>Jetty :: HTTP2 :: Client</name>
<properties>
<bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
</properties>
<build>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,175 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
import org.eclipse.jetty.http2.ErrorCodes;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
public class HTTP2Client extends ContainerLifeCycle
{
private final Queue<ISession> sessions = new ConcurrentLinkedQueue<>();
private final SelectorManager selector;
private final ByteBufferPool byteBufferPool;
private long idleTimeout;
public HTTP2Client()
{
this(new QueuedThreadPool());
}
public HTTP2Client(Executor executor)
{
addBean(executor);
Scheduler scheduler = new ScheduledExecutorScheduler();
addBean(scheduler, true);
this.selector = new ClientSelectorManager(executor, scheduler);
addBean(selector, true);
this.byteBufferPool = new MappedByteBufferPool();
addBean(byteBufferPool, true);
}
@Override
protected void doStop() throws Exception
{
closeConnections();
super.doStop();
}
public void connect(InetSocketAddress address, Session.Listener listener, Promise<Session> promise)
{
connect(null, address, listener, promise);
}
public void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise)
{
try
{
SocketChannel channel = SocketChannel.open();
channel.socket().setTcpNoDelay(true);
channel.configureBlocking(false);
Map<String, Object> context = new HashMap<>();
context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, this);
context.put(HTTP2ClientConnectionFactory.SESSION_LISTENER_CONTEXT_KEY, listener);
context.put(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY, promise);
if (sslContextFactory != null)
context.put(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY, sslContextFactory);
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, address.getHostString());
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, address.getPort());
if (channel.connect(address))
selector.accept(channel, context);
else
selector.connect(channel, context);
}
catch (Throwable x)
{
promise.failed(x);
}
}
private void closeConnections()
{
for (ISession session : sessions)
session.close(ErrorCodes.NO_ERROR, null, Callback.Adapter.INSTANCE);
sessions.clear();
}
public long getIdleTimeout()
{
return idleTimeout;
}
public void setIdleTimeout(long idleTimeout)
{
this.idleTimeout = idleTimeout;
}
public boolean addSession(ISession session)
{
return sessions.offer(session);
}
public boolean removeSession(ISession session)
{
return sessions.remove(session);
}
private class ClientSelectorManager extends SelectorManager
{
private ClientSelectorManager(Executor executor, Scheduler scheduler)
{
super(executor, scheduler);
}
@Override
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
{
return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout());
}
@Override
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
@SuppressWarnings("unchecked")
Map<String, Object> context = (Map<String, Object>)attachment;
context.put(HTTP2ClientConnectionFactory.BYTE_BUFFER_POOL_CONTEXT_KEY, byteBufferPool);
context.put(HTTP2ClientConnectionFactory.EXECUTOR_CONTEXT_KEY, getExecutor());
context.put(HTTP2ClientConnectionFactory.SCHEDULER_CONTEXT_KEY, getScheduler());
ClientConnectionFactory factory = new HTTP2ClientConnectionFactory();
SslContextFactory sslContextFactory = (SslContextFactory)context.get(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY);
if (sslContextFactory != null)
{
ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), factory, "h2-14");
factory = new SslClientConnectionFactory(sslContextFactory, byteBufferPool, getExecutor(), alpn);
}
return factory.newConnection(endpoint, context);
}
}
}

View File

@ -0,0 +1,134 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.jetty.http2.FlowControl;
import org.eclipse.jetty.http2.HTTP2Connection;
import org.eclipse.jetty.http2.HTTP2FlowControl;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.frames.PrefaceFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.thread.Scheduler;
public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
{
public static final String CLIENT_CONTEXT_KEY = "http2.client";
public static final String BYTE_BUFFER_POOL_CONTEXT_KEY = "http2.client.byteBufferPool";
public static final String EXECUTOR_CONTEXT_KEY = "http2.client.executor";
public static final String SCHEDULER_CONTEXT_KEY = "http2.client.scheduler";
public static final String SESSION_LISTENER_CONTEXT_KEY = "http2.client.sessionListener";
public static final String SESSION_PROMISE_CONTEXT_KEY = "http2.client.sessionPromise";
private int initialSessionWindow = FlowControl.DEFAULT_WINDOW_SIZE;
@Override
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
{
HTTP2Client client = (HTTP2Client)context.get(CLIENT_CONTEXT_KEY);
ByteBufferPool byteBufferPool = (ByteBufferPool)context.get(BYTE_BUFFER_POOL_CONTEXT_KEY);
Executor executor = (Executor)context.get(EXECUTOR_CONTEXT_KEY);
Scheduler scheduler = (Scheduler)context.get(SCHEDULER_CONTEXT_KEY);
Session.Listener listener = (Session.Listener)context.get(SESSION_LISTENER_CONTEXT_KEY);
@SuppressWarnings("unchecked")
Promise<Session> promise = (Promise<Session>)context.get(SESSION_PROMISE_CONTEXT_KEY);
Generator generator = new Generator(byteBufferPool, 4096);
HTTP2ClientSession session = new HTTP2ClientSession(scheduler, endPoint, generator, listener, new HTTP2FlowControl(FlowControl.DEFAULT_WINDOW_SIZE));
Parser parser = new Parser(byteBufferPool, session, 4096, 8192);
return new HTTP2ClientConnection(client, byteBufferPool, executor, endPoint, parser, session, 8192, promise, listener);
}
public int getInitialSessionWindow()
{
return initialSessionWindow;
}
public void setInitialSessionWindow(int initialSessionWindow)
{
this.initialSessionWindow = initialSessionWindow;
}
private class HTTP2ClientConnection extends HTTP2Connection implements Callback
{
private final HTTP2Client client;
private final Promise<Session> promise;
private final Session.Listener listener;
public HTTP2ClientConnection(HTTP2Client client, ByteBufferPool byteBufferPool, Executor executor, EndPoint endpoint, Parser parser, ISession session, int bufferSize, Promise<Session> promise, Session.Listener listener)
{
super(byteBufferPool, executor, endpoint, parser, session, bufferSize);
this.client = client;
this.promise = promise;
this.listener = listener;
}
@Override
public void onOpen()
{
super.onOpen();
Map<Integer, Integer> settings = listener.onPreface(getSession());
if (settings == null)
settings = Collections.emptyMap();
PrefaceFrame prefaceFrame = new PrefaceFrame();
SettingsFrame settingsFrame = new SettingsFrame(settings, false);
int windowDelta = getInitialSessionWindow() - FlowControl.DEFAULT_WINDOW_SIZE;
if (windowDelta > 0)
getSession().control(null, this, prefaceFrame, settingsFrame, new WindowUpdateFrame(0, windowDelta));
else
getSession().control(null, this, prefaceFrame, settingsFrame);
}
@Override
public void onClose()
{
super.onClose();
client.removeSession(getSession());
}
@Override
public void succeeded()
{
client.addSession(getSession());
promise.succeeded(getSession());
}
@Override
public void failed(Throwable x)
{
close();
promise.failed(x);
}
}
}

View File

@ -0,0 +1,124 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import org.eclipse.jetty.http2.FlowControl;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.IStream;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
public class HTTP2ClientSession extends HTTP2Session
{
private static final Logger LOG = Log.getLogger(HTTP2ClientSession.class);
public HTTP2ClientSession(Scheduler scheduler, EndPoint endPoint, Generator generator, Listener listener, FlowControl flowControl)
{
super(scheduler, endPoint, generator, listener, flowControl, 1);
}
@Override
public boolean onHeaders(HeadersFrame frame)
{
if (LOG.isDebugEnabled())
LOG.debug("Received {}", frame);
int streamId = frame.getStreamId();
IStream stream = getStream(streamId);
if (stream == null)
{
if (LOG.isDebugEnabled())
LOG.debug("Ignoring {}, stream #{} not found", frame, streamId);
}
else
{
stream.updateClose(frame.isEndStream(), false);
stream.process(frame, Callback.Adapter.INSTANCE);
notifyHeaders(stream, frame);
if (stream.isClosed())
removeStream(stream, false);
}
return false;
}
private void notifyHeaders(IStream stream, HeadersFrame frame)
{
Stream.Listener listener = stream.getListener();
if (listener == null)
return;
try
{
listener.onHeaders(stream, frame);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
}
}
@Override
public boolean onPushPromise(PushPromiseFrame frame)
{
if (LOG.isDebugEnabled())
LOG.debug("Received {}", frame);
int streamId = frame.getStreamId();
int pushStreamId = frame.getPromisedStreamId();
IStream stream = getStream(streamId);
if (stream == null)
{
if (LOG.isDebugEnabled())
LOG.debug("Ignoring {}, stream #{} not found", frame, streamId);
}
else
{
IStream pushStream = createRemoteStream(pushStreamId);
pushStream.updateClose(true, true);
pushStream.process(frame, Callback.Adapter.INSTANCE);
Stream.Listener listener = notifyPush(stream, pushStream, frame);
pushStream.setListener(listener);
if (pushStream.isClosed())
removeStream(pushStream, false);
}
return false;
}
private Stream.Listener notifyPush(IStream stream, IStream pushStream, PushPromiseFrame frame)
{
Stream.Listener listener = stream.getListener();
if (listener == null)
return null;
try
{
return listener.onPush(pushStream, frame);
}
catch (Throwable x)
{
LOG.info("Failure while notifying listener " + listener, x);
return null;
}
}
}

View File

@ -0,0 +1,122 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
public class AbstractTest
{
protected ServerConnector connector;
protected String servletPath = "/test";
protected HTTP2Client client;
private Server server;
protected void startServer(HttpServlet servlet) throws Exception
{
prepareServer(new HTTP2ServerConnectionFactory(new HttpConfiguration()));
ServletContextHandler context = new ServletContextHandler(server, "/", true, false);
context.addServlet(new ServletHolder(servlet), servletPath + "/*");
customizeContext(context);
prepareClient();
server.start();
client.start();
}
protected void customizeContext(ServletContextHandler context)
{
}
protected void startServer(ServerSessionListener listener) throws Exception
{
prepareServer(new RawHTTP2ServerConnectionFactory(listener));
prepareClient();
server.start();
client.start();
}
private void prepareServer(ConnectionFactory connectionFactory)
{
QueuedThreadPool serverExecutor = new QueuedThreadPool();
serverExecutor.setName("server");
server = new Server(serverExecutor);
connector = new ServerConnector(server, connectionFactory);
server.addConnector(connector);
}
private void prepareClient()
{
QueuedThreadPool clientExecutor = new QueuedThreadPool();
clientExecutor.setName("client");
client = new HTTP2Client(clientExecutor);
}
protected Session newClient(Session.Listener listener) throws Exception
{
String host = "localhost";
int port = connector.getLocalPort();
InetSocketAddress address = new InetSocketAddress(host, port);
FuturePromise<Session> promise = new FuturePromise<>();
client.connect(address, listener, promise);
return promise.get(5, TimeUnit.SECONDS);
}
protected MetaData.Request newRequest(String method, HttpFields fields)
{
return newRequest(method, "", fields);
}
protected MetaData.Request newRequest(String method, String pathInfo, HttpFields fields)
{
String host = "localhost";
int port = connector.getLocalPort();
String authority = host + ":" + port;
return new MetaData.Request(method, HttpScheme.HTTP, new HostPortHttpField(authority), servletPath + pathInfo, HttpVersion.HTTP_2, fields);
}
@After
public void dispose() throws Exception
{
client.stop();
server.stop();
}
}

View File

@ -0,0 +1,85 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.ssl.SslContextFactory;
public class Client
{
public static void main(String[] args) throws Exception
{
HTTP2Client client = new HTTP2Client();
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setIncludeProtocols("TLSv1.2", "TLSv1.1", "TLSv1");
client.addBean(sslContextFactory);
client.start();
String host = "webtide.com";
int port = 443;
FuturePromise<Session> sessionPromise = new FuturePromise<>();
client.connect(sslContextFactory, new InetSocketAddress(host, port), new ServerSessionListener.Adapter(), sessionPromise);
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
HttpFields requestFields = new HttpFields();
requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
MetaData.Request metaData = new MetaData.Request("GET", new HttpURI("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields);
HeadersFrame headersFrame = new HeadersFrame(0, metaData, null, true);
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(headersFrame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
System.err.println(frame.getMetaData());
if (frame.isEndStream())
latch.countDown();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
latch.countDown();
}
});
latch.await(5, TimeUnit.SECONDS);
client.stop();
}
}

View File

@ -16,22 +16,18 @@
// ========================================================================
//
package org.eclipse.jetty.security;
package org.eclipse.jetty.http2.client;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
* @deprecated
*/
public interface CrossContextPsuedoSession<T>
public class EmptyHttpServlet extends HttpServlet
{
T fetch(HttpServletRequest request);
void store(T data, HttpServletResponse response);
void clear(HttpServletRequest request);
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
}
}

View File

@ -0,0 +1,711 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCodes;
import org.eclipse.jetty.http2.FlowControl;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.ISession;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Promise;
import org.junit.Assert;
import org.junit.Test;
public class FlowControlTest extends AbstractTest
{
@Override
public void dispose() throws Exception
{
// Allow WINDOW_UPDATE frames to be sent/received to avoid exception stack traces.
Thread.sleep(1000);
super.dispose();
}
@Test
public void testFlowControlWithConcurrentSettings() throws Exception
{
// Initial window is 64 KiB. We allow the client to send 1024 B
// then we change the window to 512 B. At this point, the client
// must stop sending data (although the initial window allows it).
final int size = 512;
// We get 3 data frames: the first of 1024 and 2 of 512 each
// after the flow control window has been reduced.
final CountDownLatch dataLatch = new CountDownLatch(3);
final AtomicReference<Callback> callbackRef = new AtomicReference<>();
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
{
HttpFields fields = new HttpFields();
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, 200, fields);
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, true);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
return new Stream.Listener.Adapter()
{
private final AtomicInteger dataFrames = new AtomicInteger();
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
dataLatch.countDown();
int dataFrameCount = dataFrames.incrementAndGet();
if (dataFrameCount == 1)
{
callbackRef.set(callback);
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, size);
stream.getSession().settings(new SettingsFrame(settings, false), Callback.Adapter.INSTANCE);
// Do not succeed the callback here.
}
else if (dataFrameCount > 1)
{
// Consume the data.
callback.succeeded();
}
}
};
}
});
// Two SETTINGS frames, the initial one and the one we send from the server.
final CountDownLatch settingsLatch = new CountDownLatch(2);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
{
settingsLatch.countDown();
}
});
MetaData.Request request = newRequest("POST", new HttpFields());
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(new HeadersFrame(0, request, null, false), promise, new Stream.Listener.Adapter());
Stream stream = promise.get(5, TimeUnit.SECONDS);
// Send first chunk that exceeds the window.
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(size * 2), false), Callback.Adapter.INSTANCE);
settingsLatch.await(5, TimeUnit.SECONDS);
// Send the second chunk of data, must not arrive since we're flow control stalled on the client.
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(size * 2), true), Callback.Adapter.INSTANCE);
Assert.assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
// Consume the data arrived to server, this will resume flow control on the client.
callbackRef.get().succeeded();
Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testServerFlowControlOneBigWrite() throws Exception
{
final int windowSize = 1536;
final int length = 5 * windowSize;
final CountDownLatch settingsLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
{
settingsLatch.countDown();
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
{
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(length), true);
stream.data(dataFrame, Callback.Adapter.INSTANCE);
return null;
}
});
Session session = newClient(new Session.Listener.Adapter());
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, windowSize);
session.settings(new SettingsFrame(settings, false), Callback.Adapter.INSTANCE);
Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
final CountDownLatch dataLatch = new CountDownLatch(1);
final Exchanger<Callback> exchanger = new Exchanger<>();
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
private AtomicInteger dataFrames = new AtomicInteger();
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
try
{
int dataFrames = this.dataFrames.incrementAndGet();
if (dataFrames == 1 || dataFrames == 2)
{
// Do not consume the data frame.
// We should then be flow-control stalled.
exchanger.exchange(callback);
}
else if (dataFrames == 3 || dataFrames == 4 || dataFrames == 5)
{
// Consume totally.
callback.succeeded();
if (frame.isEndStream())
dataLatch.countDown();
}
else
{
Assert.fail();
}
}
catch (InterruptedException x)
{
callback.failed(x);
}
}
});
Callback callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the first chunk.
callback.succeeded();
callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the second chunk.
callback.succeeded();
Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testClientFlowControlOneBigWrite() throws Exception
{
final int windowSize = 1536;
final Exchanger<Callback> exchanger = new Exchanger<>();
final CountDownLatch settingsLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, windowSize);
return settings;
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
{
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
return new Stream.Listener.Adapter()
{
private AtomicInteger dataFrames = new AtomicInteger();
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
try
{
int dataFrames = this.dataFrames.incrementAndGet();
if (dataFrames == 1 || dataFrames == 2)
{
// Do not consume the data frame.
// We should then be flow-control stalled.
exchanger.exchange(callback);
}
else if (dataFrames == 3 || dataFrames == 4 || dataFrames == 5)
{
// Consume totally.
callback.succeeded();
if (frame.isEndStream())
dataLatch.countDown();
}
else
{
Assert.fail();
}
}
catch (InterruptedException x)
{
callback.failed(x);
}
}
};
}
});
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
{
settingsLatch.countDown();
}
});
Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, null);
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
final int length = 5 * windowSize;
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(length), true);
stream.data(dataFrame, Callback.Adapter.INSTANCE);
Callback callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the first chunk.
callback.succeeded();
callback = exchanger.exchange(null, 5, TimeUnit.SECONDS);
checkThatWeAreFlowControlStalled(exchanger);
// Consume the second chunk.
callback.succeeded();
Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
}
private void checkThatWeAreFlowControlStalled(Exchanger<Callback> exchanger) throws Exception
{
try
{
exchanger.exchange(null, 1, TimeUnit.SECONDS);
}
catch (TimeoutException x)
{
// Expected.
}
}
@Test
public void testSessionStalledStallsNewStreams() throws Exception
{
final int windowSize = 1024;
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
{
MetaData.Request request = (MetaData.Request)requestFrame.getMetaData();
if ("POST".equalsIgnoreCase(request.getMethod()))
{
// Send data to consume the session window.
ByteBuffer data = ByteBuffer.allocate(FlowControl.DEFAULT_WINDOW_SIZE - windowSize);
DataFrame dataFrame = new DataFrame(stream.getId(), data, true);
stream.data(dataFrame, Callback.Adapter.INSTANCE);
return null;
}
else
{
// For every stream, send down half the window size of data.
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(windowSize / 2), true);
stream.data(dataFrame, Callback.Adapter.INSTANCE);
return null;
}
}
});
Session session = newClient(new Session.Listener.Adapter());
// First request is just to consume the session window.
final CountDownLatch prepareLatch = new CountDownLatch(1);
MetaData.Request request1 = newRequest("POST", new HttpFields());
session.newStream(new HeadersFrame(0, request1, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
// Do not consume the data to reduce the session window.
if (frame.isEndStream())
prepareLatch.countDown();
}
});
Assert.assertTrue(prepareLatch.await(5, TimeUnit.SECONDS));
final AtomicReference<Callback> callbackRef2 = new AtomicReference<>();
final AtomicReference<Callback> callbackRef3 = new AtomicReference<>();
// Second request will consume half the session window.
MetaData.Request request2 = newRequest("GET", new HttpFields());
session.newStream(new HeadersFrame(0, request2, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
// Do not consume it to stall flow control.
callbackRef2.set(callback);
}
});
// Third request will consume the session window, which is now stalled.
// A fourth request will not be able to receive data.
MetaData.Request request3 = newRequest("GET", new HttpFields());
session.newStream(new HeadersFrame(0, request3, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
// Do not consume it to stall flow control.
callbackRef3.set(callback);
}
});
// Fourth request is now stalled.
final CountDownLatch latch = new CountDownLatch(1);
MetaData.Request request4 = newRequest("GET", new HttpFields());
session.newStream(new HeadersFrame(0, request4, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
latch.countDown();
}
});
// Verify that the data does not arrive because the server session is stalled.
Assert.assertFalse(latch.await(1, TimeUnit.SECONDS));
// Consume the data of the third response.
// This will open up the session window, allowing the third stream to send data.
Callback callback2 = callbackRef3.getAndSet(null);
if (callback2 != null)
callback2.succeeded();
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testServerSendsBigContent() throws Exception
{
final byte[] data = new byte[1024 * 1024];
new Random().nextBytes(data);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
{
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.wrap(data), true);
stream.data(dataFrame, Callback.Adapter.INSTANCE);
return null;
}
});
Session session = newClient(new Session.Listener.Adapter());
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
final byte[] bytes = new byte[data.length];
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
private int received;
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
int remaining = frame.remaining();
frame.getData().get(bytes, received, remaining);
this.received += remaining;
callback.succeeded();
if (frame.isEndStream())
latch.countDown();
}
});
Assert.assertTrue(latch.await(15, TimeUnit.SECONDS));
Assert.assertArrayEquals(data, bytes);
}
@Test
public void testServerTwoDataFramesWithStalledSession() throws Exception
{
// Frames in queue = DATA1, DATA2.
// Server writes part of DATA1, then stalls.
// A window update unstalls the session, verify that the data is correctly sent.
Random random = new Random();
final byte[] chunk1 = new byte[1024];
random.nextBytes(chunk1);
final byte[] chunk2 = new byte[1024];
random.nextBytes(chunk2);
final AtomicReference<CountDownLatch> settingsLatch = new AtomicReference<>(new CountDownLatch(1));
final CountDownLatch dataLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
{
settingsLatch.get().countDown();
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(chunk1), false), Callback.Adapter.INSTANCE);
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(chunk2), true), Callback.Adapter.INSTANCE);
dataLatch.countDown();
return null;
}
});
Session session = newClient(new Session.Listener.Adapter());
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, 0);
session.settings(new SettingsFrame(settings, false), Callback.Adapter.INSTANCE);
Assert.assertTrue(settingsLatch.get().await(5, TimeUnit.SECONDS));
byte[] content = new byte[chunk1.length + chunk2.length];
final ByteBuffer buffer = ByteBuffer.wrap(content);
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
final CountDownLatch responseLatch = new CountDownLatch(1);
session.newStream(requestFrame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
buffer.put(frame.getData());
callback.succeeded();
if (frame.isEndStream())
responseLatch.countDown();
}
});
Assert.assertTrue(dataLatch.await(5, TimeUnit.SECONDS));
// Now we have the 2 DATA frames queued in the server.
// Partially unstall the first DATA frame.
settingsLatch.set(new CountDownLatch(1));
settings.clear();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, chunk1.length / 2);
session.settings(new SettingsFrame(settings, false), Callback.Adapter.INSTANCE);
Assert.assertTrue(settingsLatch.get().await(5, TimeUnit.SECONDS));
Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testClientSendingInitialSmallWindow() throws Exception
{
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
MetaData metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
return new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
// Since we echo back the data
// asynchronously we must copy it.
ByteBuffer data = frame.getData();
ByteBuffer copy = ByteBuffer.allocateDirect(data.remaining());
copy.put(data).flip();
stream.data(new DataFrame(stream.getId(), copy, frame.isEndStream()), callback);
}
};
}
});
final int initialWindow = 16;
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, initialWindow);
return settings;
}
});
byte[] requestData = new byte[initialWindow * 4];
new Random().nextBytes(requestData);
byte[] responseData = new byte[requestData.length];
final ByteBuffer responseContent = ByteBuffer.wrap(responseData);
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
responseContent.put(frame.getData());
callback.succeeded();
if (frame.isEndStream())
latch.countDown();
}
});
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
ByteBuffer requestContent = ByteBuffer.wrap(requestData);
DataFrame dataFrame = new DataFrame(stream.getId(), requestContent, true);
stream.data(dataFrame, Callback.Adapter.INSTANCE);
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
responseContent.flip();
Assert.assertArrayEquals(requestData, responseData);
}
@Test
public void testClientExceedingSessionWindow() throws Exception
{
// On server, we don't consume the data.
startServer(new ServerSessionListener.Adapter());
final CountDownLatch closeLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
{
if (frame.getError() == ErrorCodes.FLOW_CONTROL_ERROR)
closeLatch.countDown();
}
});
// Consume the whole session and stream window.
MetaData.Request metaData = newRequest("POST", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter());
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControl.DEFAULT_WINDOW_SIZE);
stream.data(new DataFrame(stream.getId(), data, false), Callback.Adapter.INSTANCE);
// Now the client is supposed to not send more frames, but what if it does ?
HTTP2Session http2Session = (HTTP2Session)session;
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(connector.getByteBufferPool());
ByteBuffer extraData = ByteBuffer.allocate(1024);
http2Session.getGenerator().data(lease, new DataFrame(stream.getId(), extraData, true), extraData.remaining());
List<ByteBuffer> buffers = lease.getByteBuffers();
http2Session.getEndPoint().write(Callback.Adapter.INSTANCE, buffers.toArray(new ByteBuffer[buffers.size()]));
// Expect the connection to be closed.
Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testClientExceedingStreamWindow() throws Exception
{
// On server, we don't consume the data.
startServer(new ServerSessionListener.Adapter()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
// Enlarge the session window.
((ISession)session).updateRecvWindow(FlowControl.DEFAULT_WINDOW_SIZE);
return super.onPreface(session);
}
});
final CountDownLatch closeLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
{
if (frame.getError() == ErrorCodes.FLOW_CONTROL_ERROR)
closeLatch.countDown();
}
});
// Consume the whole stream window.
MetaData.Request metaData = newRequest("POST", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, false);
FuturePromise<Stream> streamPromise = new FuturePromise<>();
session.newStream(requestFrame, streamPromise, new Stream.Listener.Adapter());
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.allocate(FlowControl.DEFAULT_WINDOW_SIZE);
stream.data(new DataFrame(stream.getId(), data, false), Callback.Adapter.INSTANCE);
// Now the client is supposed to not send more frames, but what if it does ?
HTTP2Session http2Session = (HTTP2Session)session;
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(connector.getByteBufferPool());
ByteBuffer extraData = ByteBuffer.allocate(1024);
http2Session.getGenerator().data(lease, new DataFrame(stream.getId(), extraData, true), extraData.remaining());
List<ByteBuffer> buffers = lease.getByteBuffers();
http2Session.getEndPoint().write(Callback.Adapter.INSTANCE, buffers.toArray(new ByteBuffer[buffers.size()]));
// Expect the connection to be closed.
Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,172 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.junit.Assert;
import org.junit.Test;
public class HTTP2Test extends AbstractTest
{
@Test
public void testRequestNoContentResponseNoContent() throws Exception
{
startServer(new EmptyHttpServlet());
Session session = newClient(new Session.Listener.Adapter());
HttpFields fields = new HttpFields();
MetaData.Request metaData = newRequest("GET", fields);
HeadersFrame frame = new HeadersFrame(1, metaData, null, true);
final CountDownLatch latch = new CountDownLatch(1);
session.newStream(frame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
Assert.assertTrue(stream.isClosed());
Assert.assertTrue(stream.getId() > 0);
Assert.assertTrue(frame.isEndStream());
Assert.assertEquals(stream.getId(), frame.getStreamId());
Assert.assertTrue(frame.getMetaData().isResponse());
MetaData.Response response = (MetaData.Response)frame.getMetaData();
Assert.assertEquals(200, response.getStatus());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testRequestNoContentResponseContent() throws Exception
{
final byte[] content = "Hello World!".getBytes(StandardCharsets.UTF_8);
startServer(new HttpServlet()
{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getOutputStream().write(content);
}
});
Session session = newClient(new Session.Listener.Adapter());
HttpFields fields = new HttpFields();
MetaData.Request metaData = newRequest("GET", fields);
HeadersFrame frame = new HeadersFrame(1, metaData, null, true);
final CountDownLatch latch = new CountDownLatch(2);
session.newStream(frame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
Assert.assertFalse(stream.isClosed());
Assert.assertTrue(stream.getId() > 0);
Assert.assertFalse(frame.isEndStream());
Assert.assertEquals(stream.getId(), frame.getStreamId());
Assert.assertTrue(frame.getMetaData().isResponse());
MetaData.Response response = (MetaData.Response)frame.getMetaData();
Assert.assertEquals(200, response.getStatus());
latch.countDown();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
Assert.assertTrue(stream.isClosed());
Assert.assertTrue(frame.isEndStream());
Assert.assertEquals(ByteBuffer.wrap(content), frame.getData());
callback.succeeded();
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testMultipleRequests() throws Exception
{
final String downloadBytes = "X-Download";
startServer(new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
int download = request.getIntHeader(downloadBytes);
byte[] content = new byte[download];
new Random().nextBytes(content);
response.getOutputStream().write(content);
}
});
int requests = 20;
Session session = newClient(new Session.Listener.Adapter());
Random random = new Random();
HttpFields fields = new HttpFields();
fields.putLongField(downloadBytes, random.nextInt(128 * 1024));
fields.put("User-Agent", "HTTP2Client/" + Jetty.VERSION);
MetaData.Request metaData = newRequest("GET", fields);
HeadersFrame frame = new HeadersFrame(1, metaData, null, true);
final CountDownLatch latch = new CountDownLatch(requests);
for (int i = 0; i < requests; ++i)
{
session.newStream(frame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
latch.countDown();
}
});
}
Assert.assertTrue(latch.await(requests, TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,499 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertThat;
public class IdleTimeoutTest extends AbstractTest
{
private final int idleTimeout = 1000;
@Test
public void testServerEnforcingIdleTimeout() throws Exception
{
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
{
stream.setIdleTimeout(10 * idleTimeout);
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
return null;
}
});
connector.setIdleTimeout(idleTimeout);
final CountDownLatch latch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
{
if (session.isClosed() && ((HTTP2Session)session).isDisconnected())
latch.countDown();
}
});
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter());
Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
Thread.sleep(1000);
}
@Test
public void testServerEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
{
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.setIdleTimeout(10 * idleTimeout);
return null;
}
});
connector.setIdleTimeout(idleTimeout);
final CountDownLatch latch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
{
if (session.isClosed() && ((HTTP2Session)session).isDisconnected())
latch.countDown();
}
});
// The request is not replied, and the server should idle timeout.
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter());
Assert.assertTrue(latch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Test
public void testServerNotEnforcingIdleTimeoutWithinCallback() throws Exception
{
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
try
{
stream.setIdleTimeout(10 * idleTimeout);
// Stay in the callback for more than the idleTimeout.
Thread.sleep(2 * idleTimeout);
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
return null;
}
catch (InterruptedException x)
{
Assert.fail();
return null;
}
}
});
connector.setIdleTimeout(idleTimeout);
final CountDownLatch closeLatch = new CountDownLatch(1);
Session session = newClient(new ServerSessionListener.Adapter()
{
@Override
public void onClose(Session session, GoAwayFrame frame)
{
closeLatch.countDown();
}
});
final CountDownLatch replyLatch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
replyLatch.countDown();
}
});
Assert.assertTrue(replyLatch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
// Just make sure onClose() has never been called, but don't wait too much
Assert.assertFalse(closeLatch.await(idleTimeout / 2, TimeUnit.MILLISECONDS));
}
@Test
public void testClientEnforcingIdleTimeout() throws Exception
{
final CountDownLatch closeLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.setIdleTimeout(10 * idleTimeout);
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
return null;
}
@Override
public void onClose(Session session, GoAwayFrame frame)
{
if (session.isClosed() && ((HTTP2Session)session).isDisconnected())
closeLatch.countDown();
}
});
client.setIdleTimeout(idleTimeout);
Session session = newClient(new Session.Listener.Adapter());
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter());
Assert.assertTrue(closeLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
Assert.assertTrue(session.isClosed());
}
@Test
public void testClientEnforcingIdleTimeoutWithUnrespondedStream() throws Exception
{
final CountDownLatch closeLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.setIdleTimeout(10 * idleTimeout);
return null;
}
@Override
public void onClose(Session session, GoAwayFrame frame)
{
if (session.isClosed() && ((HTTP2Session)session).isDisconnected())
closeLatch.countDown();
}
});
client.setIdleTimeout(idleTimeout);
Session session = newClient(new Session.Listener.Adapter());
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter());
Assert.assertTrue(closeLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Test
public void testClientNotEnforcingIdleTimeoutWithinCallback() throws Exception
{
final CountDownLatch closeLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.setIdleTimeout(10 * idleTimeout);
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
return null;
}
@Override
public void onClose(Session session, GoAwayFrame frame)
{
closeLatch.countDown();
}
});
client.setIdleTimeout(idleTimeout);
Session session = newClient(new Session.Listener.Adapter());
final CountDownLatch replyLatch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
stream.setIdleTimeout(10 * idleTimeout);
}
}, new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
try
{
// Stay in the callback for more than idleTimeout.
Thread.sleep(2 * idleTimeout);
replyLatch.countDown();
}
catch (InterruptedException e)
{
Assert.fail();
}
}
});
Assert.assertFalse(closeLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
Assert.assertTrue(replyLatch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Test
public void testClientEnforcingStreamIdleTimeout() throws Exception
{
final int idleTimeout = 1000;
startServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
try
{
Thread.sleep(2 * idleTimeout);
}
catch (InterruptedException x)
{
throw new RuntimeException(x);
}
}
});
Session session = newClient(new Session.Listener.Adapter());
final CountDownLatch dataLatch = new CountDownLatch(1);
final CountDownLatch timeoutLatch = new CountDownLatch(1);
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, true);
session.newStream(requestFrame, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
stream.setIdleTimeout(idleTimeout);
}
}, new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
dataLatch.countDown();
}
@Override
public void onFailure(Stream stream, Throwable x)
{
assertThat(x, instanceOf(TimeoutException.class));
timeoutLatch.countDown();
}
});
Assert.assertTrue(timeoutLatch.await(5, TimeUnit.SECONDS));
// We must not receive any DATA frame.
Assert.assertFalse(dataLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
// Stream must be gone.
Assert.assertTrue(session.getStreams().isEmpty());
// Session must not be closed, nor disconnected.
Assert.assertFalse(session.isClosed());
Assert.assertFalse(((HTTP2Session)session).isDisconnected());
}
@Test
public void testServerEnforcingStreamIdleTimeout() throws Exception
{
final CountDownLatch timeoutLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.setIdleTimeout(idleTimeout);
return new Stream.Listener.Adapter()
{
@Override
public void onFailure(Stream stream, Throwable x)
{
timeoutLatch.countDown();
}
};
}
});
final CountDownLatch resetLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onReset(Session session, ResetFrame frame)
{
resetLatch.countDown();
}
});
MetaData.Request metaData = newRequest("GET", new HttpFields());
// Stream does not end here, but we won't send any DATA frame.
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, false);
session.newStream(requestFrame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter());
Assert.assertTrue(timeoutLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
// Stream must be gone.
Assert.assertTrue(session.getStreams().isEmpty());
// Session must not be closed, nor disconnected.
Assert.assertFalse(session.isClosed());
Assert.assertFalse(((HTTP2Session)session).isDisconnected());
}
@Test
public void testStreamIdleTimeoutIsNotEnforcedWhenReceiving() throws Exception
{
final CountDownLatch timeoutLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
stream.setIdleTimeout(idleTimeout);
return new Stream.Listener.Adapter()
{
@Override
public void onFailure(Stream stream, Throwable x)
{
timeoutLatch.countDown();
}
};
}
});
Session session = newClient(new Session.Listener.Adapter());
MetaData.Request metaData = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, metaData, null, false);
session.newStream(requestFrame, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(final Stream stream)
{
sleep(idleTimeout / 2);
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false), new Callback.Adapter()
{
private int sends;
@Override
public void succeeded()
{
sleep(idleTimeout / 2);
boolean last = ++sends == 2;
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), last), last ? INSTANCE : this);
}
});
}
}, new Stream.Listener.Adapter());
Assert.assertFalse(timeoutLatch.await(1, TimeUnit.SECONDS));
}
private void sleep(long value)
{
try
{
TimeUnit.MILLISECONDS.sleep(value);
}
catch (InterruptedException x)
{
Assert.fail();
}
}
}

View File

@ -0,0 +1,58 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.PingFrame;
import org.eclipse.jetty.util.Callback;
import org.junit.Assert;
import org.junit.Test;
public class PingTest extends AbstractTest
{
@Test
public void testPing() throws Exception
{
startServer(new ServerSessionListener.Adapter());
final byte[] payload = new byte[8];
new Random().nextBytes(payload);
final CountDownLatch latch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onPing(Session session, PingFrame frame)
{
Assert.assertTrue(frame.isReply());
Assert.assertArrayEquals(payload, frame.getPayload());
latch.countDown();
}
});
PingFrame frame = new PingFrame(payload, false);
session.ping(frame, Callback.Adapter.INSTANCE);
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,244 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.io.IOException;
import java.util.EnumSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCodes;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlets.PushCacheFilter;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.junit.Assert;
import org.junit.Test;
public class PushCacheFilterTest extends AbstractTest
{
@Override
protected void customizeContext(ServletContextHandler context)
{
context.addFilter(PushCacheFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
}
@Test
public void testPush() throws Exception
{
final String primaryResource = "/primary.html";
final String secondaryResource = "/secondary.png";
final byte[] secondaryData = "SECONDARY".getBytes("UTF-8");
startServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String requestURI = req.getRequestURI();
ServletOutputStream output = resp.getOutputStream();
if (requestURI.endsWith(primaryResource))
output.print("<html><head></head><body>PRIMARY</body></html>");
else if (requestURI.endsWith(secondaryResource))
output.write(secondaryData);
}
});
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary and secondary resource to build the cache.
final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(0, primaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
{
// Request for the secondary resource.
HttpFields secondaryFields = new HttpFields();
secondaryFields.put(HttpHeader.REFERER, primaryURI);
MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
session.newStream(new HeadersFrame(0, secondaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
warmupLatch.countDown();
}
});
}
}
});
Assert.assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
// Request again the primary resource, we should get the secondary resource pushed.
primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
final CountDownLatch pushLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(0, primaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
{
return new Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
pushLatch.countDown();
}
};
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
primaryResponseLatch.countDown();
}
});
Assert.assertTrue(pushLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testPushIsReset() throws Exception
{
final String primaryResource = "/primary.html";
final String secondaryResource = "/secondary.png";
final byte[] secondaryData = "SECONDARY".getBytes("UTF-8");
startServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String requestURI = req.getRequestURI();
ServletOutputStream output = resp.getOutputStream();
if (requestURI.endsWith(primaryResource))
output.print("<html><head></head><body>PRIMARY</body></html>");
else if (requestURI.endsWith(secondaryResource))
output.write(secondaryData);
}
});
final Session session = newClient(new Session.Listener.Adapter());
// Request for the primary and secondary resource to build the cache.
final String primaryURI = "http://localhost:" + connector.getLocalPort() + servletPath + primaryResource;
HttpFields primaryFields = new HttpFields();
MetaData.Request primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch warmupLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(0, primaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
{
// Request for the secondary resource.
HttpFields secondaryFields = new HttpFields();
secondaryFields.put(HttpHeader.REFERER, primaryURI);
MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
session.newStream(new HeadersFrame(0, secondaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
warmupLatch.countDown();
}
});
}
}
});
Assert.assertTrue(warmupLatch.await(5, TimeUnit.SECONDS));
// Request again the primary resource, we should get the secondary resource pushed.
primaryRequest = newRequest("GET", primaryResource, primaryFields);
final CountDownLatch primaryResponseLatch = new CountDownLatch(1);
final CountDownLatch pushLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(0, primaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
{
// Reset the stream as soon as we see the push.
ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCodes.REFUSED_STREAM_ERROR);
stream.reset(resetFrame, Callback.Adapter.INSTANCE);
return new Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
pushLatch.countDown();
}
};
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
primaryResponseLatch.countDown();
}
});
// We should not receive pushed data that we reset.
Assert.assertFalse(pushLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue(primaryResponseLatch.await(5, TimeUnit.SECONDS));
// Make sure the session is sane by requesting the secondary resource.
HttpFields secondaryFields = new HttpFields();
secondaryFields.put(HttpHeader.REFERER, primaryURI);
MetaData.Request secondaryRequest = newRequest("GET", secondaryResource, secondaryFields);
final CountDownLatch secondaryResponseLatch = new CountDownLatch(1);
session.newStream(new HeadersFrame(0, secondaryRequest, null, true), new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
if (frame.isEndStream())
secondaryResponseLatch.countDown();
}
});
Assert.assertTrue(secondaryResponseLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,188 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.junit.Assert;
import org.junit.Test;
public class StreamCountTest extends AbstractTest
{
@Test
public void testServersAllowsOneStreamEnforcedByClient() throws Exception
{
startServer(new ServerSessionListener.Adapter()
{
@Override
public Map<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.MAX_CONCURRENT_STREAMS, 1);
return settings;
}
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
return new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
if (frame.isEndStream())
{
HttpFields fields = new HttpFields();
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, fields);
stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), callback);
}
}
};
}
});
final CountDownLatch settingsLatch = new CountDownLatch(1);
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onSettings(Session session, SettingsFrame frame)
{
settingsLatch.countDown();
}
});
Assert.assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
HttpFields fields = new HttpFields();
MetaData.Request metaData = newRequest("GET", fields);
HeadersFrame frame1 = new HeadersFrame(1, metaData, null, false);
FuturePromise<Stream> streamPromise1 = new FuturePromise<>();
final CountDownLatch responseLatch = new CountDownLatch(1);
session.newStream(frame1, streamPromise1, new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
if (frame.isEndStream())
responseLatch.countDown();
}
});
Stream stream1 = streamPromise1.get(5, TimeUnit.SECONDS);
HeadersFrame frame2 = new HeadersFrame(3, metaData, null, false);
FuturePromise<Stream> streamPromise2 = new FuturePromise<>();
session.newStream(frame2, streamPromise2, new Stream.Listener.Adapter());
try
{
streamPromise2.get(5, TimeUnit.SECONDS);
Assert.fail();
}
catch (ExecutionException x)
{
// Expected
}
stream1.data(new DataFrame(stream1.getId(), BufferUtil.EMPTY_BUFFER, true), new Callback.Adapter());
Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testServersAllowsOneStreamEnforcedByServer() throws Exception
{
final CountDownLatch resetLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
HTTP2Session session = (HTTP2Session)stream.getSession();
session.setMaxRemoteStreams(1);
return new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
if (frame.isEndStream())
{
HttpFields fields = new HttpFields();
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, fields);
stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), callback);
}
}
};
}
});
Session session = newClient(new Session.Listener.Adapter()
{
@Override
public void onReset(Session session, ResetFrame frame)
{
resetLatch.countDown();
}
});
HttpFields fields = new HttpFields();
MetaData.Request metaData = newRequest("GET", fields);
HeadersFrame frame1 = new HeadersFrame(1, metaData, null, false);
FuturePromise<Stream> streamPromise1 = new FuturePromise<>();
final CountDownLatch responseLatch = new CountDownLatch(1);
session.newStream(frame1, streamPromise1, new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
if (frame.isEndStream())
responseLatch.countDown();
}
});
Stream stream1 = streamPromise1.get(5, TimeUnit.SECONDS);
HeadersFrame frame2 = new HeadersFrame(3, metaData, null, false);
FuturePromise<Stream> streamPromise2 = new FuturePromise<>();
session.newStream(frame2, streamPromise2, new Stream.Listener.Adapter());
streamPromise2.get(5, TimeUnit.SECONDS);
Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
stream1.data(new DataFrame(stream1.getId(), BufferUtil.EMPTY_BUFFER, true), new Callback.Adapter());
Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,185 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2.client;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCodes;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.FuturePromise;
import org.junit.Assert;
import org.junit.Test;
public class StreamResetTest extends AbstractTest
{
@Test
public void testStreamSendingResetIsRemoved() throws Exception
{
startServer(new ServerSessionListener.Adapter());
Session client = newClient(new Session.Listener.Adapter());
MetaData.Request request = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(requestFrame, promise, new Stream.Listener.Adapter());
Stream stream = promise.get(5, TimeUnit.SECONDS);
ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCodes.CANCEL_STREAM_ERROR);
FutureCallback resetCallback = new FutureCallback();
stream.reset(resetFrame, resetCallback);
resetCallback.get(5, TimeUnit.SECONDS);
// After reset the stream should be gone.
Assert.assertEquals(0, client.getStreams().size());
}
@Test
public void testStreamReceivingResetIsRemoved() throws Exception
{
final AtomicReference<Stream> streamRef = new AtomicReference<>();
final CountDownLatch resetLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public void onReset(Session session, ResetFrame frame)
{
Stream stream = session.getStream(frame.getStreamId());
Assert.assertNotNull(stream);
Assert.assertTrue(stream.isReset());
streamRef.set(stream);
resetLatch.countDown();
}
});
Session client = newClient(new Session.Listener.Adapter());
MetaData.Request request = newRequest("GET", new HttpFields());
HeadersFrame requestFrame = new HeadersFrame(0, request, null, false);
FuturePromise<Stream> promise = new FuturePromise<>();
client.newStream(requestFrame, promise, new Stream.Listener.Adapter());
Stream stream = promise.get(5, TimeUnit.SECONDS);
ResetFrame resetFrame = new ResetFrame(stream.getId(), ErrorCodes.CANCEL_STREAM_ERROR);
stream.reset(resetFrame, Callback.Adapter.INSTANCE);
Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
Stream serverStream = streamRef.get();
Assert.assertEquals(0, serverStream.getSession().getStreams().size());
}
@Test
public void testStreamResetDoesNotCloseConnection() throws Exception
{
final CountDownLatch serverResetLatch = new CountDownLatch(1);
final CountDownLatch serverDataLatch = new CountDownLatch(1);
startServer(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame requestFrame)
{
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, 200, new HttpFields());
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false);
stream.headers(responseFrame, Callback.Adapter.INSTANCE);
return new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), true), Callback.Adapter.INSTANCE);
serverDataLatch.countDown();
}
};
}
@Override
public void onReset(Session session, ResetFrame frame)
{
Stream stream = session.getStream(frame.getStreamId());
// Simulate that there is pending data to send.
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), true), new Callback.Adapter()
{
@Override
public void failed(Throwable x)
{
serverResetLatch.countDown();
}
});
}
});
Session client = newClient(new Session.Listener.Adapter());
MetaData.Request request1 = newRequest("GET", new HttpFields());
HeadersFrame requestFrame1 = new HeadersFrame(0, request1, null, false);
FuturePromise<Stream> promise1 = new FuturePromise<>();
final CountDownLatch stream1HeadersLatch = new CountDownLatch(1);
final CountDownLatch stream1DataLatch = new CountDownLatch(1);
client.newStream(requestFrame1, promise1, new Stream.Listener.Adapter()
{
@Override
public void onHeaders(Stream stream, HeadersFrame frame)
{
stream1HeadersLatch.countDown();
}
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
stream1DataLatch.countDown();
}
});
Stream stream1 = promise1.get(5, TimeUnit.SECONDS);
Assert.assertTrue(stream1HeadersLatch.await(5, TimeUnit.SECONDS));
MetaData.Request request2 = newRequest("GET", new HttpFields());
HeadersFrame requestFrame2 = new HeadersFrame(0, request2, null, false);
FuturePromise<Stream> promise2 = new FuturePromise<>();
final CountDownLatch stream2DataLatch = new CountDownLatch(1);
client.newStream(requestFrame2, promise2, new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
stream2DataLatch.countDown();
}
});
Stream stream2 = promise2.get(5, TimeUnit.SECONDS);
ResetFrame resetFrame = new ResetFrame(stream1.getId(), ErrorCodes.CANCEL_STREAM_ERROR);
stream1.reset(resetFrame, Callback.Adapter.INSTANCE);
Assert.assertTrue(serverResetLatch.await(5, TimeUnit.SECONDS));
// Stream MUST NOT receive data sent by server after reset.
Assert.assertFalse(stream1DataLatch.await(1, TimeUnit.SECONDS));
// The other stream should still be working.
stream2.data(new DataFrame(stream2.getId(), ByteBuffer.allocate(16), true), Callback.Adapter.INSTANCE);
Assert.assertTrue(serverDataLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(stream2DataLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.http2.hpack.LEVEL=INFO
org.eclipse.jetty.http2.LEVEL=INFO

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-parent</artifactId>
<version>9.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>http2-common</artifactId>
<name>Jetty :: HTTP2 :: Common</name>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-hpack</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<bundle-symbolic-name>${project.groupId}.common</bundle-symbolic-name>
</properties>
<build>
</build>
</project>

View File

@ -0,0 +1,24 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2;
public enum CloseState
{
NOT_CLOSED, LOCALLY_CLOSED, REMOTELY_CLOSED, CLOSED
}

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// Copyright (c) 1995-2014 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.http2;
public interface ErrorCodes
{
public static final int NO_ERROR = 0;
public static final int PROTOCOL_ERROR = 1;
public static final int INTERNAL_ERROR = 2;
public static final int FLOW_CONTROL_ERROR = 3;
public static final int SETTINGS_TIMEOUT_ERROR = 4;
public static final int STREAM_CLOSED_ERROR = 5;
public static final int FRAME_SIZE_ERROR = 6;
public static final int REFUSED_STREAM_ERROR = 7;
public static final int CANCEL_STREAM_ERROR = 8;
public static final int COMPRESSION_ERROR = 9;
public static final int HTTP_CONNECT_ERROR = 10;
public static final int ENHANCE_YOUR_CALM_ERROR = 11;
public static final int INADEQUATE_SECURITY_ERROR = 12;
}

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