Merge branch 'master' into release-9
This commit is contained in:
commit
206b2ebf35
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.1.0-SNAPSHOT</version>
|
<version>9.1.2-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<excludes>**/MANIFEST.MF,javax/**</excludes>
|
<excludes>**/MANIFEST.MF,javax/**</excludes>
|
||||||
<excludeArtifactIds>javax</excludeArtifactIds>
|
<excludeArtifactIds>javax</excludeArtifactIds>
|
||||||
<excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
|
<excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn,org.slf4j,org.ow2.asm</excludeGroupIds>
|
||||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||||
<overWriteReleases>false</overWriteReleases>
|
<overWriteReleases>false</overWriteReleases>
|
||||||
<overWriteSnapshots>true</overWriteSnapshots>
|
<overWriteSnapshots>true</overWriteSnapshots>
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
<excludes>META-INF/**,**/Servlet3Continuation*,**/Jetty6Continuation*</excludes>
|
<excludes>META-INF/**,**/Servlet3Continuation*,**/Jetty6Continuation*</excludes>
|
||||||
<includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
|
<includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.websocket</includeGroupIds>
|
||||||
<excludeArtifactIds>javax</excludeArtifactIds>
|
<excludeArtifactIds>javax</excludeArtifactIds>
|
||||||
<excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
|
<excludeGroupIds>javax,org.eclipse.jetty.orbit,org.mortbay.jetty.npn,org.slf4j,org.ow2.asm</excludeGroupIds>
|
||||||
<outputDirectory>${project.build.directory}/sources</outputDirectory>
|
<outputDirectory>${project.build.directory}/sources</outputDirectory>
|
||||||
<overWriteReleases>true</overWriteReleases>
|
<overWriteReleases>true</overWriteReleases>
|
||||||
<overWriteSnapshots>true</overWriteSnapshots>
|
<overWriteSnapshots>true</overWriteSnapshots>
|
||||||
|
|
0
jetty-ant/src/main/java/org/eclipse/jetty/ant/types/SystemProperties.java
Executable file → Normal file
0
jetty-ant/src/main/java/org/eclipse/jetty/ant/types/SystemProperties.java
Executable file → Normal file
|
@ -27,6 +27,7 @@ import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
@ -179,9 +180,12 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
||||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
HttpClient client = destination.getHttpClient();
|
HttpClient client = destination.getHttpClient();
|
||||||
ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||||
org.eclipse.jetty.io.Connection oldConnection = endPoint.getConnection();
|
HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
|
||||||
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
|
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
|
||||||
Helper.replaceConnection(oldConnection, newConnection);
|
Helper.replaceConnection(oldConnection, newConnection);
|
||||||
|
// Avoid setting fill interest in the old Connection,
|
||||||
|
// without closing the underlying EndPoint.
|
||||||
|
oldConnection.softClose();
|
||||||
LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
|
LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client.http;
|
package org.eclipse.jetty.client.http;
|
||||||
|
|
||||||
import java.util.Enumeration;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpChannel;
|
import org.eclipse.jetty.client.HttpChannel;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
@ -79,28 +77,13 @@ public class HttpChannelOverHTTP extends HttpChannel
|
||||||
public void exchangeTerminated(Result result)
|
public void exchangeTerminated(Result result)
|
||||||
{
|
{
|
||||||
super.exchangeTerminated(result);
|
super.exchangeTerminated(result);
|
||||||
|
boolean close = result.isFailed();
|
||||||
if (result.isSucceeded())
|
|
||||||
{
|
|
||||||
HttpFields responseHeaders = result.getResponse().getHeaders();
|
HttpFields responseHeaders = result.getResponse().getHeaders();
|
||||||
Enumeration<String> values = responseHeaders.getValues(HttpHeader.CONNECTION.asString(), ",");
|
close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||||
if (values != null)
|
if (close)
|
||||||
{
|
|
||||||
while (values.hasMoreElements())
|
|
||||||
{
|
|
||||||
if (HttpHeaderValue.CLOSE.asString().equalsIgnoreCase(values.nextElement()))
|
|
||||||
{
|
|
||||||
connection.close();
|
connection.close();
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connection.release();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
connection.release();
|
||||||
connection.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -76,7 +76,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
||||||
fillInterested();
|
fillInterested();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isClosed()
|
public boolean isClosed()
|
||||||
{
|
{
|
||||||
return closed.get();
|
return closed.get();
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
if (closed.compareAndSet(false, true))
|
if (softClose())
|
||||||
{
|
{
|
||||||
getHttpDestination().close(this);
|
getHttpDestination().close(this);
|
||||||
getEndPoint().shutdownOutput();
|
getEndPoint().shutdownOutput();
|
||||||
|
@ -130,6 +130,11 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean softClose()
|
||||||
|
{
|
||||||
|
return closed.compareAndSet(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -48,6 +48,7 @@ import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.ContentProvider;
|
import org.eclipse.jetty.client.api.ContentProvider;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Destination;
|
import org.eclipse.jetty.client.api.Destination;
|
||||||
|
@ -58,12 +59,16 @@ import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||||
|
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
import org.eclipse.jetty.util.FuturePromise;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -1073,4 +1078,86 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHTTP10WithKeepAliveAndContentLength() throws Exception
|
||||||
|
{
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
// Send the headers at this point, then write the content
|
||||||
|
byte[] content = "TEST".getBytes("UTF-8");
|
||||||
|
response.setContentLength(content.length);
|
||||||
|
response.flushBuffer();
|
||||||
|
response.getOutputStream().write(content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.version(HttpVersion.HTTP_1_0)
|
||||||
|
.header(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString())
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertTrue(response.getHeaders().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHTTP10WithKeepAliveAndNoContentLength() throws Exception
|
||||||
|
{
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
// Send the headers at this point, then write the content
|
||||||
|
response.flushBuffer();
|
||||||
|
response.getOutputStream().print("TEST");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
FuturePromise<Connection> promise = new FuturePromise<>();
|
||||||
|
Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
|
||||||
|
destination.newConnection(promise);
|
||||||
|
try (Connection connection = promise.get(5, TimeUnit.SECONDS))
|
||||||
|
{
|
||||||
|
long timeout = 5000;
|
||||||
|
Request request = client.newRequest(destination.getHost(), destination.getPort())
|
||||||
|
.scheme(destination.getScheme())
|
||||||
|
.version(HttpVersion.HTTP_1_0)
|
||||||
|
.header(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString())
|
||||||
|
.timeout(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
FutureResponseListener listener = new FutureResponseListener(request);
|
||||||
|
connection.send(request, listener);
|
||||||
|
ContentResponse response = listener.get(2 * timeout, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
// The parser notifies end-of-content and therefore the CompleteListener
|
||||||
|
// before closing the connection, so we need to wait before checking
|
||||||
|
// that the connection is closed to avoid races.
|
||||||
|
Thread.sleep(1000);
|
||||||
|
Assert.assertTrue(((HttpConnectionOverHTTP)connection).isClosed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHTTP10WithKeepAliveAndNoContent() throws Exception
|
||||||
|
{
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.version(HttpVersion.HTTP_1_0)
|
||||||
|
.header(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString())
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertTrue(response.getHeaders().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -427,7 +427,7 @@
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
|
<includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
|
||||||
<includeArtifactIds>javax.activation,javax.mail.glassfish</includeArtifactIds>
|
<includeArtifactIds>javax.mail.glassfish</includeArtifactIds>
|
||||||
<includeTypes>jar</includeTypes>
|
<includeTypes>jar</includeTypes>
|
||||||
<outputDirectory>${assembly-directory}/lib/jndi</outputDirectory>
|
<outputDirectory>${assembly-directory}/lib/jndi</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -440,7 +440,34 @@
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<includeGroupIds>org.eclipse.jetty.orbit,org.glassfish.web, org.glassfish, javax.el, javax.servlet.jsp, org.eclipse.jetty.toolchain</includeGroupIds>
|
<includeGroupIds>org.eclipse.jetty.orbit,org.glassfish.web, org.glassfish, javax.el, javax.servlet.jsp, org.eclipse.jetty.toolchain</includeGroupIds>
|
||||||
<includeArtifactIds>javax.servlet.jsp.jstl,org.apache.taglibs.standard.glassfish,org.eclipse.jdt.core, javax.servlet.jsp-api, javax.servlet.jsp, jetty-jsp-jdt, javax.el-api, javax.el</includeArtifactIds>
|
<includeArtifactIds>org.eclipse.jdt.core, javax.servlet.jsp-api, javax.servlet.jsp, jetty-jsp-jdt, javax.el-api, javax.el</includeArtifactIds>
|
||||||
|
<includeTypes>jar</includeTypes>
|
||||||
|
<outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>copy-jstl-api</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
|
||||||
|
<includeArtifactIds>javax.servlet.jsp.jstl</includeArtifactIds>
|
||||||
|
<prependGroupId>true</prependGroupId>
|
||||||
|
<includeTypes>jar</includeTypes>
|
||||||
|
<outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>copy-jstl-impl</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includeGroupIds>org.glassfish.web</includeGroupIds>
|
||||||
|
<includeArtifactIds>javax.servlet.jsp.jstl</includeArtifactIds>
|
||||||
<includeTypes>jar</includeTypes>
|
<includeTypes>jar</includeTypes>
|
||||||
<outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
|
<outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -551,29 +578,27 @@
|
||||||
</build>
|
</build>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Orbit Deps -->
|
<!-- Orbit Deps -->
|
||||||
<dependency>
|
|
||||||
<groupId>javax.annotation</groupId>
|
|
||||||
<artifactId>javax.annotation-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
|
||||||
<artifactId>javax.activation</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
<groupId>org.eclipse.jetty.orbit</groupId>
|
||||||
<artifactId>javax.mail.glassfish</artifactId>
|
<artifactId>javax.mail.glassfish</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>javax.transaction</groupId>
|
|
||||||
<artifactId>javax.transaction-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
<groupId>org.eclipse.jetty.orbit</groupId>
|
||||||
<artifactId>javax.security.auth.message</artifactId>
|
<artifactId>javax.security.auth.message</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
<groupId>javax.annotation</groupId>
|
||||||
<artifactId>org.apache.taglibs.standard.glassfish</artifactId>
|
<artifactId>javax.annotation-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.transaction</groupId>
|
||||||
|
<artifactId>javax.transaction-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.web</groupId>
|
||||||
|
<artifactId>javax.servlet.jsp.jstl</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -105,7 +105,6 @@ public class HttpGenerator
|
||||||
_persistent = null;
|
_persistent = null;
|
||||||
_contentPrepared = 0;
|
_contentPrepared = 0;
|
||||||
_needCRLF = false;
|
_needCRLF = false;
|
||||||
_noContent=false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -744,13 +743,14 @@ public class HttpGenerator
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No idea, so we must assume that a body is coming
|
// No idea, so we must assume that a body is coming.
|
||||||
_endOfContent = (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal() ) ? EndOfContent.EOF_CONTENT : EndOfContent.CHUNKED_CONTENT;
|
_endOfContent = EndOfContent.CHUNKED_CONTENT;
|
||||||
if (response!=null && _endOfContent==EndOfContent.EOF_CONTENT)
|
// HTTP 1.0 does not understand chunked content, so we must use EOF content.
|
||||||
{
|
// For a request with HTTP 1.0 & Connection: keep-alive
|
||||||
_endOfContent=EndOfContent.NO_CONTENT;
|
// we *must* close the connection, otherwise the client
|
||||||
_noContent=true;
|
// has no way to detect the end of the content.
|
||||||
}
|
if (!isPersistent() || _info.getHttpVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
|
||||||
|
_endOfContent = EndOfContent.EOF_CONTENT;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -825,7 +825,7 @@ public class HttpGenerator
|
||||||
header.put(CONNECTION_KEEP_ALIVE);
|
header.put(CONNECTION_KEEP_ALIVE);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_CLOSE.length-2);
|
header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
|
||||||
header.put((byte)',');
|
header.put((byte)',');
|
||||||
header.put(StringUtil.getBytes(connection.toString()));
|
header.put(StringUtil.getBytes(connection.toString()));
|
||||||
header.put(CRLF);
|
header.put(CRLF);
|
||||||
|
@ -833,7 +833,7 @@ public class HttpGenerator
|
||||||
}
|
}
|
||||||
else if (connection!=null)
|
else if (connection!=null)
|
||||||
{
|
{
|
||||||
header.put(CONNECTION_);
|
header.put(HttpHeader.CONNECTION.getBytesColonSpace());
|
||||||
header.put(StringUtil.getBytes(connection.toString()));
|
header.put(StringUtil.getBytes(connection.toString()));
|
||||||
header.put(CRLF);
|
header.put(CRLF);
|
||||||
}
|
}
|
||||||
|
@ -872,7 +872,6 @@ public class HttpGenerator
|
||||||
private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
|
private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
|
||||||
private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
|
private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
|
||||||
private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
|
private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
|
||||||
private static final byte[] CONNECTION_ = StringUtil.getBytes("Connection: ");
|
|
||||||
private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
|
private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
|
||||||
private static final byte[] CRLF = StringUtil.getBytes("\015\012");
|
private static final byte[] CRLF = StringUtil.getBytes("\015\012");
|
||||||
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
|
private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
|
||||||
|
|
|
@ -0,0 +1,368 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.Collection;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
@Parameter(value = 0)
|
||||||
|
public Run run;
|
||||||
|
private String _content;
|
||||||
|
private String _reason;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHTTP() throws Exception
|
||||||
|
{
|
||||||
|
Handler handler = new Handler();
|
||||||
|
|
||||||
|
HttpGenerator gen = new HttpGenerator();
|
||||||
|
|
||||||
|
String t = run.toString();
|
||||||
|
|
||||||
|
run.result.getHttpFields().clear();
|
||||||
|
|
||||||
|
String response = run.result.build(run.httpVersion, gen, "OK\r\nTest", run.connection.val, null, run.chunks);
|
||||||
|
|
||||||
|
if (run.httpVersion == 9)
|
||||||
|
{
|
||||||
|
assertFalse(t, gen.isPersistent());
|
||||||
|
if (run.result._body != null)
|
||||||
|
assertEquals(t, run.result._body, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpParser parser = new HttpParser(handler);
|
||||||
|
parser.setHeadResponse(run.result._head);
|
||||||
|
|
||||||
|
parser.parseNext(BufferUtil.toBuffer(response));
|
||||||
|
|
||||||
|
if (run.result._body != null)
|
||||||
|
assertEquals(t, run.result._body, this._content);
|
||||||
|
|
||||||
|
if (run.httpVersion == 10)
|
||||||
|
assertTrue(t, gen.isPersistent() || run.result._contentLength >= 0 || EnumSet.of(ConnectionType.CLOSE, ConnectionType.KEEP_ALIVE, ConnectionType.NONE).contains(run.connection));
|
||||||
|
else
|
||||||
|
assertTrue(t, gen.isPersistent() || EnumSet.of(ConnectionType.CLOSE, ConnectionType.TE_CLOSE).contains(run.connection));
|
||||||
|
|
||||||
|
if (run.httpVersion > 9)
|
||||||
|
assertEquals("OK??Test", _reason);
|
||||||
|
|
||||||
|
if (_content == null)
|
||||||
|
assertTrue(t, run.result._body == null);
|
||||||
|
else
|
||||||
|
assertThat(t, run.result._contentLength, either(equalTo(_content.length())).or(equalTo(-1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Result
|
||||||
|
{
|
||||||
|
private HttpFields _fields = new HttpFields();
|
||||||
|
private final String _body;
|
||||||
|
private final int _code;
|
||||||
|
private String _connection;
|
||||||
|
private int _contentLength;
|
||||||
|
private String _contentType;
|
||||||
|
private final boolean _head;
|
||||||
|
private String _other;
|
||||||
|
private String _te;
|
||||||
|
|
||||||
|
private Result(int code, String contentType, int contentLength, String content, boolean head)
|
||||||
|
{
|
||||||
|
_code = code;
|
||||||
|
_contentType = contentType;
|
||||||
|
_contentLength = contentLength;
|
||||||
|
_other = "value";
|
||||||
|
_body = content;
|
||||||
|
_head = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String build(int version, HttpGenerator gen, String reason, String connection, String te, int nchunks) throws Exception
|
||||||
|
{
|
||||||
|
String response = "";
|
||||||
|
_connection = connection;
|
||||||
|
_te = te;
|
||||||
|
|
||||||
|
if (_contentType != null)
|
||||||
|
_fields.put("Content-Type", _contentType);
|
||||||
|
if (_contentLength >= 0)
|
||||||
|
_fields.put("Content-Length", "" + _contentLength);
|
||||||
|
if (_connection != null)
|
||||||
|
_fields.put("Connection", _connection);
|
||||||
|
if (_te != null)
|
||||||
|
_fields.put("Transfer-Encoding", _te);
|
||||||
|
if (_other != null)
|
||||||
|
_fields.put("Other", _other);
|
||||||
|
|
||||||
|
ByteBuffer source = _body == null ? null : BufferUtil.toBuffer(_body);
|
||||||
|
ByteBuffer[] chunks = new ByteBuffer[nchunks];
|
||||||
|
ByteBuffer content = null;
|
||||||
|
int c = 0;
|
||||||
|
if (source != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < nchunks; i++)
|
||||||
|
{
|
||||||
|
chunks[i] = source.duplicate();
|
||||||
|
chunks[i].position(i * (source.capacity() / nchunks));
|
||||||
|
if (i > 0)
|
||||||
|
chunks[i - 1].limit(chunks[i].position());
|
||||||
|
}
|
||||||
|
content = chunks[c++];
|
||||||
|
}
|
||||||
|
ByteBuffer header = null;
|
||||||
|
ByteBuffer chunk = null;
|
||||||
|
HttpGenerator.ResponseInfo info = null;
|
||||||
|
|
||||||
|
loop:
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// if we have unwritten content
|
||||||
|
if (source != null && content != null && content.remaining() == 0 && c < nchunks)
|
||||||
|
content = chunks[c++];
|
||||||
|
|
||||||
|
// Generate
|
||||||
|
boolean last = !BufferUtil.hasContent(content);
|
||||||
|
|
||||||
|
HttpGenerator.Result result = gen.generateResponse(info, header, chunk, content, last);
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case NEED_INFO:
|
||||||
|
info = new HttpGenerator.ResponseInfo(HttpVersion.fromVersion(version), _fields, _contentLength, _code, reason, _head);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case NEED_HEADER:
|
||||||
|
header = BufferUtil.allocate(2048);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case NEED_CHUNK:
|
||||||
|
chunk = BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case FLUSH:
|
||||||
|
if (BufferUtil.hasContent(header))
|
||||||
|
{
|
||||||
|
response += BufferUtil.toString(header);
|
||||||
|
header.position(header.limit());
|
||||||
|
}
|
||||||
|
if (BufferUtil.hasContent(chunk))
|
||||||
|
{
|
||||||
|
response += BufferUtil.toString(chunk);
|
||||||
|
chunk.position(chunk.limit());
|
||||||
|
}
|
||||||
|
if (BufferUtil.hasContent(content))
|
||||||
|
{
|
||||||
|
response += BufferUtil.toString(content);
|
||||||
|
content.position(content.limit());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CONTINUE:
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case SHUTDOWN_OUT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DONE:
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "[" + _code + "," + _contentType + "," + _contentLength + "," + (_body == null ? "null" : "content") + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpFields getHttpFields()
|
||||||
|
{
|
||||||
|
return _fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Handler implements HttpParser.ResponseHandler<ByteBuffer>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean content(ByteBuffer ref)
|
||||||
|
{
|
||||||
|
if (_content == null)
|
||||||
|
_content = "";
|
||||||
|
_content += BufferUtil.toString(ref);
|
||||||
|
ref.position(ref.limit());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void earlyEOF()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean headerComplete()
|
||||||
|
{
|
||||||
|
_content = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean messageComplete()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean parsedHeader(HttpField field)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean startResponse(HttpVersion version, int status, String reason)
|
||||||
|
{
|
||||||
|
_reason = reason;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void badMessage(int status, String reason)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeaderCacheSize()
|
||||||
|
{
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static String CONTENT = "The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n";
|
||||||
|
|
||||||
|
private static class Run
|
||||||
|
{
|
||||||
|
public static Run[] as(Result result, int ver, int chunks, ConnectionType connection)
|
||||||
|
{
|
||||||
|
Run run = new Run();
|
||||||
|
run.result = result;
|
||||||
|
run.httpVersion = ver;
|
||||||
|
run.chunks = chunks;
|
||||||
|
run.connection = connection;
|
||||||
|
return new Run[]{run};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result result;
|
||||||
|
private ConnectionType connection;
|
||||||
|
private int httpVersion;
|
||||||
|
private int chunks;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("result=%s,version=%d,chunks=%d,connection=%s", result, httpVersion, chunks, connection.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ConnectionType
|
||||||
|
{
|
||||||
|
NONE(null, 9, 10, 11),
|
||||||
|
KEEP_ALIVE("keep-alive", 9, 10, 11),
|
||||||
|
CLOSE("close", 9, 10, 11),
|
||||||
|
TE_CLOSE("TE, close", 11);
|
||||||
|
|
||||||
|
private String val;
|
||||||
|
private int[] supportedHttpVersions;
|
||||||
|
|
||||||
|
private ConnectionType(String val, int... supportedHttpVersions)
|
||||||
|
{
|
||||||
|
this.val = val;
|
||||||
|
this.supportedHttpVersions = supportedHttpVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSupportedByHttp(int version)
|
||||||
|
{
|
||||||
|
for (int supported : supportedHttpVersions)
|
||||||
|
{
|
||||||
|
if (supported == version)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameters(name = "{0}")
|
||||||
|
public static Collection<Run[]> data()
|
||||||
|
{
|
||||||
|
Result[] results = {
|
||||||
|
new Result(200, null, -1, null, false),
|
||||||
|
new Result(200, null, -1, CONTENT, false),
|
||||||
|
new Result(200, null, CONTENT.length(), null, true),
|
||||||
|
new Result(200, null, CONTENT.length(), CONTENT, false),
|
||||||
|
new Result(200, "text/html", -1, null, true),
|
||||||
|
new Result(200, "text/html", -1, CONTENT, false),
|
||||||
|
new Result(200, "text/html", CONTENT.length(), null, true),
|
||||||
|
new Result(200, "text/html", CONTENT.length(), CONTENT, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
List<Run[]> data = new ArrayList<>();
|
||||||
|
|
||||||
|
// For each test result
|
||||||
|
for (Result result : results)
|
||||||
|
{
|
||||||
|
// Loop over HTTP versions
|
||||||
|
for (int v = 9; v <= 11; v++)
|
||||||
|
{
|
||||||
|
// Loop over chunks
|
||||||
|
for (int chunks = 1; chunks <= 6; chunks++)
|
||||||
|
{
|
||||||
|
// Loop over Connection values
|
||||||
|
for (ConnectionType connection : ConnectionType.values())
|
||||||
|
{
|
||||||
|
if (connection.isSupportedByHttp(v))
|
||||||
|
{
|
||||||
|
data.add(Run.as(result, v, chunks, connection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,298 +18,21 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
package org.eclipse.jetty.http;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.either;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.not;
|
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
|
||||||
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.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
|
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
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
|
public class HttpGeneratorServerTest
|
||||||
{
|
{
|
||||||
private class Handler implements HttpParser.ResponseHandler<ByteBuffer>
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public boolean content(ByteBuffer ref)
|
|
||||||
{
|
|
||||||
if (_content == null)
|
|
||||||
_content = "";
|
|
||||||
_content += BufferUtil.toString(ref);
|
|
||||||
ref.position(ref.limit());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void earlyEOF()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean headerComplete()
|
|
||||||
{
|
|
||||||
_content = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean messageComplete()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean parsedHeader(HttpField field)
|
|
||||||
{
|
|
||||||
_hdr.add(field.getName());
|
|
||||||
_val.add(field.getValue());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean startResponse(HttpVersion version, int status, String reason)
|
|
||||||
{
|
|
||||||
_version = version;
|
|
||||||
_status = status;
|
|
||||||
_reason = reason;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void badMessage(int status, String reason)
|
|
||||||
{
|
|
||||||
throw new IllegalStateException(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHeaderCacheSize()
|
|
||||||
{
|
|
||||||
return 256;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TR
|
|
||||||
{
|
|
||||||
private HttpFields _fields = new HttpFields();
|
|
||||||
private final String _body;
|
|
||||||
private final int _code;
|
|
||||||
String _connection;
|
|
||||||
int _contentLength;
|
|
||||||
String _contentType;
|
|
||||||
private final boolean _head;
|
|
||||||
String _other;
|
|
||||||
String _te;
|
|
||||||
|
|
||||||
private TR(int code, String contentType, int contentLength, String content, boolean head)
|
|
||||||
{
|
|
||||||
_code = code;
|
|
||||||
_contentType = contentType;
|
|
||||||
_contentLength = contentLength;
|
|
||||||
_other = "value";
|
|
||||||
_body = content;
|
|
||||||
_head = head;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String build(int version, HttpGenerator gen, String reason, String connection, String te, int nchunks) throws Exception
|
|
||||||
{
|
|
||||||
String response = "";
|
|
||||||
_connection = connection;
|
|
||||||
_te = te;
|
|
||||||
|
|
||||||
if (_contentType != null)
|
|
||||||
_fields.put("Content-Type", _contentType);
|
|
||||||
if (_contentLength >= 0)
|
|
||||||
_fields.put("Content-Length", "" + _contentLength);
|
|
||||||
if (_connection != null)
|
|
||||||
_fields.put("Connection", _connection);
|
|
||||||
if (_te != null)
|
|
||||||
_fields.put("Transfer-Encoding", _te);
|
|
||||||
if (_other != null)
|
|
||||||
_fields.put("Other", _other);
|
|
||||||
|
|
||||||
ByteBuffer source = _body == null ? null : BufferUtil.toBuffer(_body);
|
|
||||||
ByteBuffer[] chunks = new ByteBuffer[nchunks];
|
|
||||||
ByteBuffer content = null;
|
|
||||||
int c = 0;
|
|
||||||
if (source != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < nchunks; i++)
|
|
||||||
{
|
|
||||||
chunks[i] = source.duplicate();
|
|
||||||
chunks[i].position(i * (source.capacity() / nchunks));
|
|
||||||
if (i > 0)
|
|
||||||
chunks[i - 1].limit(chunks[i].position());
|
|
||||||
}
|
|
||||||
content = chunks[c++];
|
|
||||||
// System.err.printf("content %d %s%n",c,BufferUtil.toDetailString(content));
|
|
||||||
}
|
|
||||||
ByteBuffer header = null;
|
|
||||||
ByteBuffer chunk = null;
|
|
||||||
HttpGenerator.ResponseInfo info = null;
|
|
||||||
|
|
||||||
loop:
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// if we have unwritten content
|
|
||||||
if (source != null && content != null && content.remaining() == 0 && c < nchunks)
|
|
||||||
{
|
|
||||||
content = chunks[c++];
|
|
||||||
// System.err.printf("content %d %s%n",c,BufferUtil.toDetailString(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate
|
|
||||||
boolean last = !BufferUtil.hasContent(content);
|
|
||||||
|
|
||||||
HttpGenerator.Result result = gen.generateResponse(info, header, chunk, content, last);
|
|
||||||
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case NEED_INFO:
|
|
||||||
info = new HttpGenerator.ResponseInfo(HttpVersion.fromVersion(version), _fields, _contentLength, _code, reason, _head);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case NEED_HEADER:
|
|
||||||
header = BufferUtil.allocate(2048);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case NEED_CHUNK:
|
|
||||||
chunk = BufferUtil.allocate(HttpGenerator.CHUNK_SIZE);
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case FLUSH:
|
|
||||||
if (BufferUtil.hasContent(header))
|
|
||||||
{
|
|
||||||
response += BufferUtil.toString(header);
|
|
||||||
header.position(header.limit());
|
|
||||||
}
|
|
||||||
if (BufferUtil.hasContent(chunk))
|
|
||||||
{
|
|
||||||
response += BufferUtil.toString(chunk);
|
|
||||||
chunk.position(chunk.limit());
|
|
||||||
}
|
|
||||||
if (BufferUtil.hasContent(content))
|
|
||||||
{
|
|
||||||
response += BufferUtil.toString(content);
|
|
||||||
content.position(content.limit());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CONTINUE:
|
|
||||||
continue;
|
|
||||||
|
|
||||||
case SHUTDOWN_OUT:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DONE:
|
|
||||||
break loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "[" + _code + "," + _contentType + "," + _contentLength + "," + (_body == null ? "null" : "content") + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpFields getHttpFields()
|
|
||||||
{
|
|
||||||
return _fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final static String[] connections = {null, "keep-alive", "close", "TE, close"};
|
|
||||||
public final static String CONTENT = "The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n";
|
|
||||||
|
|
||||||
private final List<String> _hdr = new ArrayList<>();
|
|
||||||
private final List<String> _val = new ArrayList<>();
|
|
||||||
private String _content;
|
|
||||||
private String _reason;
|
|
||||||
private int _status;
|
|
||||||
private HttpVersion _version;
|
|
||||||
private final TR[] tr =
|
|
||||||
{
|
|
||||||
/* 0 */ new TR(200, null, -1, null, false),
|
|
||||||
/* 1 */ new TR(200, null, -1, CONTENT, false),
|
|
||||||
/* 2 */ new TR(200, null, CONTENT.length(), null, true),
|
|
||||||
/* 3 */ new TR(200, null, CONTENT.length(), CONTENT, false),
|
|
||||||
/* 4 */ new TR(200, "text/html", -1, null, true),
|
|
||||||
/* 5 */ new TR(200, "text/html", -1, CONTENT, false),
|
|
||||||
/* 6 */ new TR(200, "text/html", CONTENT.length(), null, true),
|
|
||||||
/* 7 */ new TR(200, "text/html", CONTENT.length(), CONTENT, false),
|
|
||||||
};
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testHTTP() throws Exception
|
|
||||||
{
|
|
||||||
Handler handler = new Handler();
|
|
||||||
|
|
||||||
// Loop over HTTP versions
|
|
||||||
for (int v = 9; v <= 11; v++)
|
|
||||||
{
|
|
||||||
// For each test result
|
|
||||||
for (int r = 0; r < tr.length; r++)
|
|
||||||
{
|
|
||||||
HttpGenerator gen = new HttpGenerator();
|
|
||||||
|
|
||||||
// Loop over chunks
|
|
||||||
for (int chunks = 1; chunks <= 6; chunks++)
|
|
||||||
{
|
|
||||||
// Loop over Connection values
|
|
||||||
for (int c = 0; c < (v == 11 ? connections.length : (connections.length - 1)); c++)
|
|
||||||
{
|
|
||||||
String t = "v=" + v + ",chunks=" + chunks + ",connection=" + connections[c] + ",tr=" + r + "=" + tr[r];
|
|
||||||
|
|
||||||
gen.reset();
|
|
||||||
tr[r].getHttpFields().clear();
|
|
||||||
|
|
||||||
String response = tr[r].build(v, gen, "OK\r\nTest", connections[c], null, chunks);
|
|
||||||
|
|
||||||
if (v == 9)
|
|
||||||
{
|
|
||||||
assertFalse(t, gen.isPersistent());
|
|
||||||
if (tr[r]._body != null)
|
|
||||||
assertEquals(t, tr[r]._body, response);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpParser parser = new HttpParser(handler);
|
|
||||||
parser.setHeadResponse(tr[r]._head);
|
|
||||||
|
|
||||||
parser.parseNext(BufferUtil.toBuffer(response));
|
|
||||||
|
|
||||||
if (tr[r]._body != null)
|
|
||||||
assertEquals(t, tr[r]._body, this._content);
|
|
||||||
|
|
||||||
if (v == 10)
|
|
||||||
assertTrue(t, gen.isPersistent() || tr[r]._contentLength >= 0 || c == 2 || c == 0);
|
|
||||||
else
|
|
||||||
assertTrue(t, gen.isPersistent() || c == 2 || c == 3);
|
|
||||||
|
|
||||||
if (v > 9)
|
|
||||||
assertEquals("OK??Test", _reason);
|
|
||||||
|
|
||||||
if (_content == null)
|
|
||||||
assertTrue(t, tr[r]._body == null);
|
|
||||||
else
|
|
||||||
assertThat(t, tr[r]._contentLength, either(equalTo(_content.length())).or(equalTo(-1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSendServerXPoweredBy() throws Exception
|
public void testSendServerXPoweredBy() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -365,8 +88,7 @@ public class HttpGeneratorServerTest
|
||||||
|
|
||||||
HttpGenerator gen = new HttpGenerator();
|
HttpGenerator gen = new HttpGenerator();
|
||||||
|
|
||||||
HttpGenerator.Result
|
HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true);
|
||||||
result = gen.generateResponse(null, null, null, null, true);
|
|
||||||
assertEquals(HttpGenerator.Result.NEED_INFO, result);
|
assertEquals(HttpGenerator.Result.NEED_INFO, result);
|
||||||
assertEquals(HttpGenerator.State.START, gen.getState());
|
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||||
|
|
||||||
|
@ -399,8 +121,7 @@ public class HttpGeneratorServerTest
|
||||||
|
|
||||||
HttpGenerator gen = new HttpGenerator();
|
HttpGenerator gen = new HttpGenerator();
|
||||||
|
|
||||||
HttpGenerator.Result
|
HttpGenerator.Result result = gen.generateResponse(null, null, null, null, true);
|
||||||
result = gen.generateResponse(null, null, null, null, true);
|
|
||||||
assertEquals(HttpGenerator.Result.NEED_INFO, result);
|
assertEquals(HttpGenerator.Result.NEED_INFO, result);
|
||||||
assertEquals(HttpGenerator.State.START, gen.getState());
|
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||||
|
|
||||||
|
@ -495,9 +216,7 @@ public class HttpGeneratorServerTest
|
||||||
ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
|
ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
|
||||||
HttpGenerator gen = new HttpGenerator();
|
HttpGenerator gen = new HttpGenerator();
|
||||||
|
|
||||||
HttpGenerator.Result
|
HttpGenerator.Result result = gen.generateResponse(null, null, null, content0, false);
|
||||||
|
|
||||||
result = gen.generateResponse(null, null, null, content0, false);
|
|
||||||
assertEquals(HttpGenerator.Result.NEED_INFO, result);
|
assertEquals(HttpGenerator.Result.NEED_INFO, result);
|
||||||
assertEquals(HttpGenerator.State.START, gen.getState());
|
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||||
|
|
||||||
|
@ -540,15 +259,12 @@ public class HttpGeneratorServerTest
|
||||||
@Test
|
@Test
|
||||||
public void test100ThenResponseWithContent() throws Exception
|
public void test100ThenResponseWithContent() throws Exception
|
||||||
{
|
{
|
||||||
|
|
||||||
ByteBuffer header = BufferUtil.allocate(4096);
|
ByteBuffer header = BufferUtil.allocate(4096);
|
||||||
ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
|
ByteBuffer content0 = BufferUtil.toBuffer("Hello World! ");
|
||||||
ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
|
ByteBuffer content1 = BufferUtil.toBuffer("The quick brown fox jumped over the lazy dog. ");
|
||||||
HttpGenerator gen = new HttpGenerator();
|
HttpGenerator gen = new HttpGenerator();
|
||||||
|
|
||||||
HttpGenerator.Result
|
HttpGenerator.Result result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, null, null, null, false);
|
||||||
|
|
||||||
result = gen.generateResponse(HttpGenerator.CONTINUE_100_INFO, null, null, null, false);
|
|
||||||
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
|
assertEquals(HttpGenerator.Result.NEED_HEADER, result);
|
||||||
assertEquals(HttpGenerator.State.START, gen.getState());
|
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||||
|
|
||||||
|
@ -563,7 +279,6 @@ public class HttpGeneratorServerTest
|
||||||
|
|
||||||
assertThat(out, containsString("HTTP/1.1 100 Continue"));
|
assertThat(out, containsString("HTTP/1.1 100 Continue"));
|
||||||
|
|
||||||
|
|
||||||
result = gen.generateResponse(null, null, null, content0, false);
|
result = gen.generateResponse(null, null, null, content0, false);
|
||||||
assertEquals(HttpGenerator.Result.NEED_INFO, result);
|
assertEquals(HttpGenerator.Result.NEED_INFO, result);
|
||||||
assertEquals(HttpGenerator.State.START, gen.getState());
|
assertEquals(HttpGenerator.State.START, gen.getState());
|
||||||
|
@ -603,4 +318,22 @@ public class HttpGeneratorServerTest
|
||||||
assertThat(out, containsString("Content-Length: 59"));
|
assertThat(out, containsString("Content-Length: 59"));
|
||||||
assertThat(out, containsString("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. "));
|
assertThat(out, containsString("\r\n\r\nHello World! The quick brown fox jumped over the lazy dog. "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionKeepAliveWithAdditionalCustomValue() throws Exception
|
||||||
|
{
|
||||||
|
HttpGenerator generator = new HttpGenerator();
|
||||||
|
|
||||||
|
HttpFields fields = new HttpFields();
|
||||||
|
fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
|
||||||
|
String customValue = "test";
|
||||||
|
fields.add(HttpHeader.CONNECTION, customValue);
|
||||||
|
ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_0, fields, -1, 200, "OK", false);
|
||||||
|
ByteBuffer header = BufferUtil.allocate(4096);
|
||||||
|
HttpGenerator.Result result = generator.generateResponse(info, header, null, null, true);
|
||||||
|
Assert.assertSame(HttpGenerator.Result.FLUSH, result);
|
||||||
|
String headers = BufferUtil.toString(header);
|
||||||
|
Assert.assertTrue(headers.contains(HttpHeaderValue.KEEP_ALIVE.asString()));
|
||||||
|
Assert.assertTrue(headers.contains(customValue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements By
|
||||||
LeakTrackingByteBufferPool.this.leaked(leakInfo);
|
LeakTrackingByteBufferPool.this.leaked(leakInfo);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final ByteBufferPool delegate;
|
private final ByteBufferPool delegate;
|
||||||
|
|
||||||
public LeakTrackingByteBufferPool(ByteBufferPool delegate)
|
public LeakTrackingByteBufferPool(ByteBufferPool delegate)
|
||||||
|
@ -51,20 +52,22 @@ public class LeakTrackingByteBufferPool extends ContainerLifeCycle implements By
|
||||||
{
|
{
|
||||||
ByteBuffer buffer = delegate.acquire(size, direct);
|
ByteBuffer buffer = delegate.acquire(size, direct);
|
||||||
if (!leakDetector.acquired(buffer))
|
if (!leakDetector.acquired(buffer))
|
||||||
LOG.info("ByteBuffer {}@{} not tracked", buffer, System.identityHashCode(buffer));
|
LOG.warn("ByteBuffer {}@{} not tracked", buffer, System.identityHashCode(buffer));
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release(ByteBuffer buffer)
|
public void release(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
return;
|
||||||
if (!leakDetector.released(buffer))
|
if (!leakDetector.released(buffer))
|
||||||
LOG.info("ByteBuffer {}@{} released but not acquired", buffer, System.identityHashCode(buffer));
|
LOG.warn("ByteBuffer {}@{} released but not acquired", buffer, System.identityHashCode(buffer));
|
||||||
delegate.release(buffer);
|
delegate.release(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void leaked(LeakDetector.LeakInfo leakInfo)
|
protected void leaked(LeakDetector<ByteBuffer>.LeakInfo leakInfo)
|
||||||
{
|
{
|
||||||
LOG.info("ByteBuffer " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
|
LOG.warn("ByteBuffer " + leakInfo.getResourceDescription() + " leaked at:", leakInfo.getStackFrames());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,8 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- JSTL Impl -->
|
<!-- JSTL Impl -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
<groupId>org.glassfish.web</groupId>
|
||||||
<artifactId>org.apache.taglibs.standard.glassfish</artifactId>
|
<artifactId>javax.servlet.jsp.jstl</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- EL Api -->
|
<!-- EL Api -->
|
||||||
|
|
|
@ -125,11 +125,11 @@
|
||||||
<artifactId>jetty-jsp</artifactId>
|
<artifactId>jetty-jsp</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<!-- dependency>
|
||||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
<groupId>org.eclipse.jetty.orbit</groupId>
|
||||||
<artifactId>javax.activation</artifactId>
|
<artifactId>javax.activation</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.transaction</groupId>
|
<groupId>javax.transaction</groupId>
|
||||||
<artifactId>javax.transaction-api</artifactId>
|
<artifactId>javax.transaction-api</artifactId>
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
<!-- uncomment the following lines, changing log interval (in milliseconds) -->
|
<!-- uncomment the following lines, changing log interval (in milliseconds) -->
|
||||||
<!-- and log threshold (in percent) as desired. -->
|
<!-- and log threshold (in percent) as desired. -->
|
||||||
<!--
|
<!--
|
||||||
<Set name="logInterval">10000</Arg>
|
<Set name="logInterval">10000</Set>
|
||||||
<Set name="logThreshold">1</Arg>
|
<Set name="logThreshold">1</Set>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- To enable detail dump of the server whenever a thread is detected as spinning, -->
|
<!-- To enable detail dump of the server whenever a thread is detected as spinning, -->
|
||||||
|
|
|
@ -7,7 +7,7 @@ server
|
||||||
client
|
client
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
lib/jetty-monitor-${jetty.version}.jar
|
lib/monitor/jetty-monitor-${jetty.version}.jar
|
||||||
|
|
||||||
[xml]
|
[xml]
|
||||||
etc/jetty-monitor.xml
|
etc/jetty-monitor.xml
|
|
@ -100,10 +100,10 @@
|
||||||
javax.servlet.jsp;version="[2.3,2.4)",
|
javax.servlet.jsp;version="[2.3,2.4)",
|
||||||
javax.servlet.jsp.el;version="[2.3,2.4)",
|
javax.servlet.jsp.el;version="[2.3,2.4)",
|
||||||
javax.servlet.jsp.tagext;version="[2.3,2.4)",
|
javax.servlet.jsp.tagext;version="[2.3,2.4)",
|
||||||
javax.servlet.jsp.jstl.core;version="1.2.0";resolution:=optional,
|
javax.servlet.jsp.jstl.core;version="1.2";resolution:=optional,
|
||||||
javax.servlet.jsp.jstl.fmt;version="1.2.0";resolution:=optional,
|
javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional,
|
||||||
javax.servlet.jsp.jstl.sql;version="1.2.0";resolution:=optional,
|
javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional,
|
||||||
javax.servlet.jsp.jstl.tlv;version="1.2.0";resolution:=optional,
|
javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional,
|
||||||
org.apache.jasper;version="[2.3.2,2.4)";resolution:=optional,
|
org.apache.jasper;version="[2.3.2,2.4)";resolution:=optional,
|
||||||
org.apache.jasper.compiler;version="[2.3.2,2.4)";resolution:=optional,
|
org.apache.jasper.compiler;version="[2.3.2,2.4)";resolution:=optional,
|
||||||
org.apache.jasper.compiler.tagplugin;version="[2.3.2,2.4)";resolution:=optional,
|
org.apache.jasper.compiler.tagplugin;version="[2.3.2,2.4)";resolution:=optional,
|
||||||
|
@ -114,29 +114,29 @@
|
||||||
org.apache.jasper.util;version="[2.3.2,2.4)";resolution:=optional,
|
org.apache.jasper.util;version="[2.3.2,2.4)";resolution:=optional,
|
||||||
org.apache.jasper.xmlparser;version="[2.3.2,2.4)";resolution:=optional,
|
org.apache.jasper.xmlparser;version="[2.3.2,2.4)";resolution:=optional,
|
||||||
org.glassfish.jsp.api;version="[2.3.2,2.4)";resolution:=optional,
|
org.glassfish.jsp.api;version="[2.3.2,2.4)";resolution:=optional,
|
||||||
org.apache.taglibs.standard;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.extra.spath;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.functions;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.functions;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.lang.jstl;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.lang.jstl;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.lang.jstl.parser;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.lang.jstl.parser;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.lang.jstl.test;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.lang.jstl.test;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.lang.support;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.lang.support;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.resources;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.resources;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.common.core;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.common.core;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.common.fmt;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.common.fmt;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.common.sql;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.common.sql;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.common.xml;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.common.xml;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.el.core;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.el.core;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.el.fmt;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.el.fmt;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.el.sql;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.el.sql;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.el.xml;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.el.xml;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.rt.core;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.rt.core;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.rt.fmt;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.rt.fmt;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.rt.sql;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.rt.sql;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tag.rt.xml;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tei;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tei;version="1.2";resolution:=optional,
|
||||||
org.apache.taglibs.standard.tlv;version="1.2.0";resolution:=optional,
|
org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional,
|
||||||
org.osgi.*,
|
org.osgi.*,
|
||||||
org.xml.*;resolution:=optional,
|
org.xml.*;resolution:=optional,
|
||||||
org.xml.sax.*;resolution:=optional,
|
org.xml.sax.*;resolution:=optional,
|
||||||
|
|
|
@ -25,6 +25,8 @@ import org.eclipse.jetty.annotations.AnnotationParser.Handler;
|
||||||
import org.eclipse.jetty.annotations.ClassNameResolver;
|
import org.eclipse.jetty.annotations.ClassNameResolver;
|
||||||
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
|
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
|
||||||
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
|
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
|
@ -37,6 +39,8 @@ import org.osgi.framework.Constants;
|
||||||
*/
|
*/
|
||||||
public class AnnotationConfiguration extends org.eclipse.jetty.annotations.AnnotationConfiguration
|
public class AnnotationConfiguration extends org.eclipse.jetty.annotations.AnnotationConfiguration
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(org.eclipse.jetty.annotations.AnnotationConfiguration.class);
|
||||||
|
|
||||||
public class BundleParserTask extends ParserTask
|
public class BundleParserTask extends ParserTask
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -51,7 +55,11 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
|
||||||
{
|
{
|
||||||
org.eclipse.jetty.osgi.annotations.AnnotationParser osgiAnnotationParser = (org.eclipse.jetty.osgi.annotations.AnnotationParser)_parser;
|
org.eclipse.jetty.osgi.annotations.AnnotationParser osgiAnnotationParser = (org.eclipse.jetty.osgi.annotations.AnnotationParser)_parser;
|
||||||
Bundle bundle = osgiAnnotationParser.getBundle(_resource);
|
Bundle bundle = osgiAnnotationParser.getBundle(_resource);
|
||||||
|
if (_stat != null)
|
||||||
|
_stat.start();
|
||||||
osgiAnnotationParser.parse(_handlers, bundle, _resolver);
|
osgiAnnotationParser.parse(_handlers, bundle, _resolver);
|
||||||
|
if (_stat != null)
|
||||||
|
_stat.end();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -178,7 +186,12 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
|
||||||
|
|
||||||
ClassNameResolver classNameResolver = createClassNameResolver(context);
|
ClassNameResolver classNameResolver = createClassNameResolver(context);
|
||||||
if (_parserTasks != null)
|
if (_parserTasks != null)
|
||||||
_parserTasks.add(new BundleParserTask(parser, handlers, bundleRes, classNameResolver));
|
{
|
||||||
|
BundleParserTask task = new BundleParserTask(parser, handlers, bundleRes, classNameResolver);
|
||||||
|
_parserTasks.add(task);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
task.setStatistic(new TimeStatistic());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -77,6 +77,10 @@ public class TestJettyOSGiBootWebAppAsService
|
||||||
options.add(CoreOptions.junitBundles());
|
options.add(CoreOptions.junitBundles());
|
||||||
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
||||||
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
|
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
|
||||||
|
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
|
||||||
|
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
|
||||||
|
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
|
||||||
|
|
||||||
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
||||||
|
|
||||||
String logLevel = "WARN";
|
String logLevel = "WARN";
|
||||||
|
@ -123,7 +127,7 @@ public class TestJettyOSGiBootWebAppAsService
|
||||||
//jsp bundles
|
//jsp bundles
|
||||||
res.add(mavenBundle().groupId("javax.servlet.jsp").artifactId("javax.servlet.jsp-api").versionAsInProject());
|
res.add(mavenBundle().groupId("javax.servlet.jsp").artifactId("javax.servlet.jsp-api").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
|
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.taglibs.standard.glassfish").versionAsInProject());
|
res.add(mavenBundle().groupId("org.glassfish.web").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.glassfish").artifactId("javax.el").versionAsInProject());
|
res.add(mavenBundle().groupId("org.glassfish").artifactId("javax.el").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
|
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.toolchain").artifactId("jetty-jsp-fragment").versionAsInProject().noStart());
|
res.add(mavenBundle().groupId("org.eclipse.jetty.toolchain").artifactId("jetty-jsp-fragment").versionAsInProject().noStart());
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.ops4j.pax.exam.CoreOptions;
|
||||||
import org.ops4j.pax.exam.Option;
|
import org.ops4j.pax.exam.Option;
|
||||||
import org.ops4j.pax.exam.junit.Configuration;
|
import org.ops4j.pax.exam.junit.Configuration;
|
||||||
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
|
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
|
||||||
|
import org.osgi.framework.Bundle;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,6 +71,10 @@ public class TestJettyOSGiBootWithJsp
|
||||||
options.add(CoreOptions.junitBundles());
|
options.add(CoreOptions.junitBundles());
|
||||||
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
|
||||||
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
|
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*"));
|
||||||
|
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res","com.sun.org.apache.xml.internal.utils",
|
||||||
|
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
|
||||||
|
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
|
||||||
|
|
||||||
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
|
||||||
|
|
||||||
String logLevel = "WARN";
|
String logLevel = "WARN";
|
||||||
|
@ -139,7 +144,7 @@ public class TestJettyOSGiBootWithJsp
|
||||||
//jetty jsp bundles
|
//jetty jsp bundles
|
||||||
res.add(mavenBundle().groupId("javax.servlet.jsp").artifactId("javax.servlet.jsp-api").versionAsInProject());
|
res.add(mavenBundle().groupId("javax.servlet.jsp").artifactId("javax.servlet.jsp-api").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
|
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.apache.taglibs.standard.glassfish").versionAsInProject());
|
res.add(mavenBundle().groupId("org.glassfish.web").artifactId("javax.servlet.jsp.jstl").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.glassfish").artifactId("javax.el").versionAsInProject());
|
res.add(mavenBundle().groupId("org.glassfish").artifactId("javax.el").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
|
res.add(mavenBundle().groupId("org.eclipse.jetty.orbit").artifactId("org.eclipse.jdt.core").versionAsInProject());
|
||||||
res.add(mavenBundle().groupId("org.eclipse.jetty.toolchain").artifactId("jetty-jsp-fragment").versionAsInProject().noStart());
|
res.add(mavenBundle().groupId("org.eclipse.jetty.toolchain").artifactId("jetty-jsp-fragment").versionAsInProject().noStart());
|
||||||
|
@ -167,6 +172,7 @@ public class TestJettyOSGiBootWithJsp
|
||||||
TestOSGiUtil.testHttpServiceGreetings(bundleContext, "http", TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT);
|
TestOSGiUtil.testHttpServiceGreetings(bundleContext, "http", TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testJspDump() throws Exception
|
public void testJspDump() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -79,13 +79,12 @@
|
||||||
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
||||||
<Set name="secureScheme">https</Set>
|
<Set name="secureScheme">https</Set>
|
||||||
<Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
|
<Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
|
||||||
<Set name="outputBufferSize">32768</Set>
|
<Set name="outputBufferSize"><Property name="jetty.output.buffer.size" default="32768" /></Set>
|
||||||
<Set name="requestHeaderSize">8192</Set>
|
<Set name="requestHeaderSize"><Property name="jetty.request.header.size" default="8192" /></Set>
|
||||||
<Set name="responseHeaderSize">8192</Set>
|
<Set name="responseHeaderSize"><Property name="jetty.response.header.size" default="8192" /></Set>
|
||||||
<Set name="sendServerVersion">true</Set>
|
<Set name="sendServerVersion"><Property name="jetty.send.server.version" default="true" /></Set>
|
||||||
<Set name="sendDateHeader">false</Set>
|
<Set name="sendDateHeader"><Property name="jetty.send.date.header" default="false" /></Set>
|
||||||
<Set name="headerCacheSize">512</Set>
|
<Set name="headerCacheSize">512</Set>
|
||||||
|
|
||||||
<!-- Uncomment to enable handling of X-Forwarded- style headers
|
<!-- Uncomment to enable handling of X-Forwarded- style headers
|
||||||
<Call name="addCustomizer">
|
<Call name="addCustomizer">
|
||||||
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
|
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
|
||||||
|
|
|
@ -21,13 +21,25 @@ lib/jetty-io-${jetty.version}.jar
|
||||||
etc/jetty.xml
|
etc/jetty.xml
|
||||||
|
|
||||||
[ini-template]
|
[ini-template]
|
||||||
|
##
|
||||||
## Server Threading Configuration
|
## Server Threading Configuration
|
||||||
|
##
|
||||||
# minimum number of threads
|
# minimum number of threads
|
||||||
threads.min=10
|
threads.min=10
|
||||||
# maximum number of threads
|
# maximum number of threads
|
||||||
threads.max=200
|
threads.max=200
|
||||||
# thread idle timeout in milliseconds
|
# thread idle timeout in milliseconds
|
||||||
threads.timeout=60000
|
threads.timeout=60000
|
||||||
|
# buffer size for output
|
||||||
|
jetty.output.buffer.size=32768
|
||||||
|
# request header buffer size
|
||||||
|
jetty.request.header.size=8192
|
||||||
|
# response header buffer size
|
||||||
|
jetty.response.header.size=8192
|
||||||
|
# should jetty send the server version header?
|
||||||
|
jetty.send.server.version=true
|
||||||
|
# should jetty send the date header?
|
||||||
|
jetty.send.date.header=false
|
||||||
# What host to listen on (leave commented to listen on all interfaces)
|
# What host to listen on (leave commented to listen on all interfaces)
|
||||||
#jetty.host=myhost.com
|
#jetty.host=myhost.com
|
||||||
# Dump the state of the Jetty server, components, and webapps after startup
|
# Dump the state of the Jetty server, components, and webapps after startup
|
||||||
|
|
|
@ -189,9 +189,10 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
||||||
for (ConnectionFactory factory:factories)
|
for (ConnectionFactory factory:factories)
|
||||||
addConnectionFactory(factory);
|
addConnectionFactory(factory);
|
||||||
|
|
||||||
|
int cores = Runtime.getRuntime().availableProcessors();
|
||||||
if (acceptors < 0)
|
if (acceptors < 0)
|
||||||
acceptors=Math.max(1,(Runtime.getRuntime().availableProcessors()) / 2);
|
acceptors = 1 + cores / 16;
|
||||||
if (acceptors > 2 * Runtime.getRuntime().availableProcessors())
|
if (acceptors > 2 * cores)
|
||||||
LOG.warn("Acceptors should be <= 2*availableProcessors: " + this);
|
LOG.warn("Acceptors should be <= 2*availableProcessors: " + this);
|
||||||
_acceptors = new Thread[acceptors];
|
_acceptors = new Thread[acceptors];
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
package org.eclipse.jetty.server;
|
package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
import javax.servlet.ReadListener;
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
|
|
||||||
|
@ -29,38 +29,25 @@ import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>{@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.</p>
|
* {@link HttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.
|
||||||
* <p>{@link HttpInput} holds a queue of items passed to it by calls to {@link #content(T)}.</p>
|
* <p/>
|
||||||
* <p>{@link HttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
|
* Content may arrive in patterns such as [content(), content(), messageComplete()] so that this class
|
||||||
* but simply holds references to the item, thus the caller must organize for those buffers to valid while
|
* maintains two states: the content state that tells whether there is content to consume and the EOF
|
||||||
* held by this class.</p>
|
* state that tells whether an EOF has arrived.
|
||||||
* <p>To assist the caller, subclasses may override methods {@link #onContentQueued(T)},
|
* Only once the content has been consumed the content state is moved to the EOF state.
|
||||||
* {@link #onContentConsumed(T)} and {@link #onAllContentConsumed()} that can be implemented so that the
|
|
||||||
* caller will know when buffers are queued and consumed.</p>
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @author gregw
|
|
||||||
*
|
|
||||||
* @param <T>
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @author gregw
|
|
||||||
*
|
|
||||||
* @param <T>
|
|
||||||
*/
|
*/
|
||||||
public abstract class HttpInput<T> extends ServletInputStream implements Runnable
|
public abstract class HttpInput<T> extends ServletInputStream implements Runnable
|
||||||
{
|
{
|
||||||
private final static Logger LOG = Log.getLogger(HttpInput.class);
|
private final static Logger LOG = Log.getLogger(HttpInput.class);
|
||||||
|
|
||||||
private final byte[] _oneByteBuffer = new byte[1];
|
private final byte[] _oneByteBuffer = new byte[1];
|
||||||
private HttpChannelState _channelState;
|
|
||||||
private Throwable _onError;
|
|
||||||
private ReadListener _listener;
|
|
||||||
private boolean _notReady;
|
|
||||||
|
|
||||||
protected State _state = BLOCKING;
|
|
||||||
private State _eof=null;
|
|
||||||
private final Object _lock;
|
private final Object _lock;
|
||||||
|
private HttpChannelState _channelState;
|
||||||
|
private ReadListener _listener;
|
||||||
|
private Throwable _onError;
|
||||||
|
private boolean _notReady;
|
||||||
|
private State _contentState = STREAM;
|
||||||
|
private State _eofState;
|
||||||
private long _contentRead;
|
private long _contentRead;
|
||||||
|
|
||||||
protected HttpInput()
|
protected HttpInput()
|
||||||
|
@ -73,6 +60,14 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
_lock = lock == null ? this : lock;
|
_lock = lock == null ? this : lock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void init(HttpChannelState state)
|
||||||
|
{
|
||||||
|
synchronized (lock())
|
||||||
|
{
|
||||||
|
_channelState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final Object lock()
|
public final Object lock()
|
||||||
{
|
{
|
||||||
return _lock;
|
return _lock;
|
||||||
|
@ -82,50 +77,15 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
{
|
{
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
_state = BLOCKING;
|
_listener = null;
|
||||||
_eof=null;
|
|
||||||
_onError = null;
|
_onError = null;
|
||||||
|
_notReady = false;
|
||||||
|
_contentState = STREAM;
|
||||||
|
_eofState = null;
|
||||||
_contentRead = 0;
|
_contentRead = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Access the next content to be consumed from. Returning the next item does not consume it
|
|
||||||
* and it may be returned multiple times until it is consumed. Calls to {@link #get(Object, byte[], int, int)}
|
|
||||||
* or {@link #consume(Object, int)} are required to consume data from the content.
|
|
||||||
* @return Content or null if none available.
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
protected abstract T nextContent() throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience method to call nextContent and to check the return value, which if null then the
|
|
||||||
* a check is made for EOF and the state changed accordingly.
|
|
||||||
* @see #nextContent()
|
|
||||||
* @return Content or null if none available.
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
protected T getNextContent() throws IOException
|
|
||||||
{
|
|
||||||
T content=nextContent();
|
|
||||||
|
|
||||||
if (content==null && _eof!=null)
|
|
||||||
{
|
|
||||||
LOG.debug("{} eof {}",this,_eof);
|
|
||||||
_state=_eof;
|
|
||||||
_eof=null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException
|
|
||||||
{
|
|
||||||
int read = read(_oneByteBuffer, 0, 1);
|
|
||||||
return read < 0 ? -1 : 0xff & _oneByteBuffer[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available()
|
public int available()
|
||||||
{
|
{
|
||||||
|
@ -143,46 +103,115 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException
|
||||||
|
{
|
||||||
|
int read = read(_oneByteBuffer, 0, 1);
|
||||||
|
return read < 0 ? -1 : _oneByteBuffer[0] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b, int off, int len) throws IOException
|
public int read(byte[] b, int off, int len) throws IOException
|
||||||
{
|
{
|
||||||
T item = null;
|
|
||||||
int l;
|
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
// System.err.printf("read s=%s q=%d e=%s%n",_state,_inputQ.size(),_eof);
|
T item = getNextContent();
|
||||||
|
|
||||||
// Get the current head of the input Q
|
|
||||||
item = getNextContent();
|
|
||||||
|
|
||||||
// If we have no item
|
|
||||||
if (item == null)
|
if (item == null)
|
||||||
{
|
{
|
||||||
_state.waitForContent(this);
|
_contentState.waitForContent(this);
|
||||||
item = getNextContent();
|
item = getNextContent();
|
||||||
if (item == null)
|
if (item == null)
|
||||||
return _state.noContent();
|
return _contentState.noContent();
|
||||||
}
|
}
|
||||||
|
int l = get(item, b, off, len);
|
||||||
l=get(item, b, off, len);
|
|
||||||
_contentRead += l;
|
_contentRead += l;
|
||||||
|
|
||||||
}
|
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience method to call nextContent and to check the return value, which if null then the
|
||||||
|
* a check is made for EOF and the state changed accordingly.
|
||||||
|
*
|
||||||
|
* @return Content or null if none available.
|
||||||
|
* @throws IOException
|
||||||
|
* @see #nextContent()
|
||||||
|
*/
|
||||||
|
protected T getNextContent() throws IOException
|
||||||
|
{
|
||||||
|
T content = nextContent();
|
||||||
|
if (content == null)
|
||||||
|
{
|
||||||
|
synchronized (lock())
|
||||||
|
{
|
||||||
|
if (_eofState != null)
|
||||||
|
{
|
||||||
|
LOG.debug("{} eof {}", this, _eofState);
|
||||||
|
_contentState = _eofState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the next content to be consumed from. Returning the next item does not consume it
|
||||||
|
* and it may be returned multiple times until it is consumed.
|
||||||
|
* <p/>
|
||||||
|
* Calls to {@link #get(Object, byte[], int, int)}
|
||||||
|
* or {@link #consume(Object, int)} are required to consume data from the content.
|
||||||
|
*
|
||||||
|
* @return the content or null if none available.
|
||||||
|
* @throws IOException if retrieving the content fails
|
||||||
|
*/
|
||||||
|
protected abstract T nextContent() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param item the content
|
||||||
|
* @return how many bytes remain in the given content
|
||||||
|
*/
|
||||||
protected abstract int remaining(T item);
|
protected abstract int remaining(T item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the given content into the given byte buffer.
|
||||||
|
*
|
||||||
|
* @param item the content to copy from
|
||||||
|
* @param buffer the buffer to copy into
|
||||||
|
* @param offset the buffer offset to start copying from
|
||||||
|
* @param length the space available in the buffer
|
||||||
|
* @return the number of bytes actually copied
|
||||||
|
*/
|
||||||
protected abstract int get(T item, byte[] buffer, int offset, int length);
|
protected abstract int get(T item, byte[] buffer, int offset, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumes the given content.
|
||||||
|
*
|
||||||
|
* @param item the content to consume
|
||||||
|
* @param length the number of bytes to consume
|
||||||
|
*/
|
||||||
protected abstract void consume(T item, int length);
|
protected abstract void consume(T item, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks until some content or some end-of-file event arrives.
|
||||||
|
*
|
||||||
|
* @throws IOException if the wait is interrupted
|
||||||
|
*/
|
||||||
protected abstract void blockForContent() throws IOException;
|
protected abstract void blockForContent() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds some content to this input stream.
|
||||||
|
*
|
||||||
|
* @param item the content to add
|
||||||
|
*/
|
||||||
|
public abstract void content(T item);
|
||||||
|
|
||||||
protected boolean onAsyncRead()
|
protected boolean onAsyncRead()
|
||||||
|
{
|
||||||
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
if (_listener == null)
|
if (_listener == null)
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
_channelState.onReadPossible();
|
_channelState.onReadPossible();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -195,14 +224,10 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add some content to the input stream
|
/**
|
||||||
* @param item
|
* This method should be called to signal that an EOF has been
|
||||||
*/
|
* detected before all the expected content arrived.
|
||||||
public abstract void content(T item);
|
* <p/>
|
||||||
|
|
||||||
|
|
||||||
/** This method should be called to signal to the HttpInput
|
|
||||||
* that an EOF has arrived before all the expected content.
|
|
||||||
* Typically this will result in an EOFException being thrown
|
* Typically this will result in an EOFException being thrown
|
||||||
* from a subsequent read rather than a -1 return.
|
* from a subsequent read rather than a -1 return.
|
||||||
*/
|
*/
|
||||||
|
@ -210,29 +235,35 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
{
|
{
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
if (_eof==null || !_eof.isEOF())
|
if (!isEOF())
|
||||||
{
|
{
|
||||||
LOG.debug("{} early EOF", this);
|
LOG.debug("{} early EOF", this);
|
||||||
_eof=EARLY_EOF;
|
_eofState = EARLY_EOF;
|
||||||
if (_listener!=null)
|
if (_listener == null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
_channelState.onReadPossible();
|
_channelState.onReadPossible();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be called to signal that all the expected
|
||||||
|
* content arrived.
|
||||||
|
*/
|
||||||
public void messageComplete()
|
public void messageComplete()
|
||||||
{
|
{
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
if (_eof==null || !_eof.isEOF())
|
if (!isEOF())
|
||||||
{
|
{
|
||||||
LOG.debug("{} EOF", this);
|
LOG.debug("{} EOF", this);
|
||||||
_eof=EOF;
|
_eofState = EOF;
|
||||||
if (_listener!=null)
|
if (_listener == null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
_channelState.onReadPossible();
|
_channelState.onReadPossible();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void consumeAll()
|
public void consumeAll()
|
||||||
{
|
{
|
||||||
|
@ -244,7 +275,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
{
|
{
|
||||||
T item = getNextContent();
|
T item = getNextContent();
|
||||||
if (item == null)
|
if (item == null)
|
||||||
_state.waitForContent(this);
|
_contentState.waitForContent(this);
|
||||||
else
|
else
|
||||||
consume(item, remaining(item));
|
consume(item, remaining(item));
|
||||||
}
|
}
|
||||||
|
@ -256,36 +287,49 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether an EOF has been detected, even though there may be content to consume.
|
||||||
|
*/
|
||||||
|
public boolean isEOF()
|
||||||
|
{
|
||||||
|
synchronized (lock())
|
||||||
|
{
|
||||||
|
return _eofState != null && _eofState.isEOF();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFinished()
|
public boolean isFinished()
|
||||||
{
|
{
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
return _state.isEOF();
|
return _contentState.isEOF();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReady()
|
public boolean isReady()
|
||||||
{
|
{
|
||||||
|
boolean finished;
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
|
if (_contentState.isEOF())
|
||||||
|
return true;
|
||||||
if (_listener == null )
|
if (_listener == null )
|
||||||
return true;
|
return true;
|
||||||
int available = available();
|
if (available() > 0)
|
||||||
if (available>0)
|
|
||||||
return true;
|
return true;
|
||||||
if (!_notReady)
|
if (_notReady)
|
||||||
{
|
return false;
|
||||||
_notReady = true;
|
_notReady = true;
|
||||||
if (_state.isEOF())
|
finished = isFinished();
|
||||||
|
}
|
||||||
|
if (finished)
|
||||||
_channelState.onReadPossible();
|
_channelState.onReadPossible();
|
||||||
else
|
else
|
||||||
unready();
|
unready();
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected void unready()
|
protected void unready()
|
||||||
{
|
{
|
||||||
|
@ -294,18 +338,16 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
@Override
|
@Override
|
||||||
public void setReadListener(ReadListener readListener)
|
public void setReadListener(ReadListener readListener)
|
||||||
{
|
{
|
||||||
if (readListener==null)
|
readListener = Objects.requireNonNull(readListener);
|
||||||
throw new NullPointerException("readListener==null");
|
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
if (_state!=BLOCKING)
|
if (_contentState != STREAM)
|
||||||
throw new IllegalStateException("state="+_state);
|
throw new IllegalStateException("state=" + _contentState);
|
||||||
_state=ASYNC;
|
_contentState = ASYNC;
|
||||||
_listener = readListener;
|
_listener = readListener;
|
||||||
_notReady = true;
|
_notReady = true;
|
||||||
|
|
||||||
_channelState.onReadPossible();
|
|
||||||
}
|
}
|
||||||
|
_channelState.onReadPossible();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void failed(Throwable x)
|
public void failed(Throwable x)
|
||||||
|
@ -322,40 +364,41 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
final boolean available;
|
final Throwable error;
|
||||||
|
final ReadListener listener;
|
||||||
|
boolean available = false;
|
||||||
final boolean eof;
|
final boolean eof;
|
||||||
final Throwable x;
|
|
||||||
|
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
if (!_notReady || _listener == null)
|
if (!_notReady || _listener == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
x=_onError;
|
error = _onError;
|
||||||
T item;
|
listener = _listener;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
item = getNextContent();
|
T item = getNextContent();
|
||||||
|
available = item != null && remaining(item) > 0;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
item=null;
|
|
||||||
failed(e);
|
failed(e);
|
||||||
}
|
}
|
||||||
available= item!=null && remaining(item)>0;
|
|
||||||
|
|
||||||
eof = !available && _state.isEOF();
|
eof = !available && isFinished();
|
||||||
_notReady = !available && !eof;
|
_notReady = !available && !eof;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (x!=null)
|
if (error != null)
|
||||||
_listener.onError(x);
|
listener.onError(error);
|
||||||
else if (available)
|
else if (available)
|
||||||
_listener.onDataAvailable();
|
listener.onDataAvailable();
|
||||||
else if (eof)
|
else if (eof)
|
||||||
_listener.onAllDataRead();
|
listener.onAllDataRead();
|
||||||
else
|
else
|
||||||
unready();
|
unready();
|
||||||
}
|
}
|
||||||
|
@ -363,11 +406,11 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
{
|
{
|
||||||
LOG.warn(e.toString());
|
LOG.warn(e.toString());
|
||||||
LOG.debug(e);
|
LOG.debug(e);
|
||||||
_listener.onError(e);
|
listener.onError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class State
|
protected static abstract class State
|
||||||
{
|
{
|
||||||
public void waitForContent(HttpInput<?> in) throws IOException
|
public void waitForContent(HttpInput<?> in) throws IOException
|
||||||
{
|
{
|
||||||
|
@ -384,16 +427,17 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static final State BLOCKING= new State()
|
protected static final State STREAM = new State()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void waitForContent(HttpInput<?> in) throws IOException
|
public void waitForContent(HttpInput<?> input) throws IOException
|
||||||
{
|
{
|
||||||
in.blockForContent();
|
input.blockForContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "OPEN";
|
return "STREAM";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -404,6 +448,7 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
@ -418,11 +463,13 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
{
|
{
|
||||||
throw new EofException();
|
throw new EofException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEOF()
|
public boolean isEOF()
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "EARLY_EOF";
|
return "EARLY_EOF";
|
||||||
|
@ -442,13 +489,4 @@ public abstract class HttpInput<T> extends ServletInputStream implements Runnabl
|
||||||
return "EOF";
|
return "EOF";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public void init(HttpChannelState state)
|
|
||||||
{
|
|
||||||
synchronized (lock())
|
|
||||||
{
|
|
||||||
_channelState=state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,13 +63,15 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
private volatile Throwable _onError;
|
private volatile Throwable _onError;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ACTION OPEN ASYNC READY PENDING UNREADY
|
ACTION OPEN ASYNC READY PENDING UNREADY CLOSED
|
||||||
-------------------------------------------------------------------------------
|
-----------------------------------------------------------------------------------------------------
|
||||||
setWriteListener() READY->owp ise ise ise ise
|
setWriteListener() READY->owp ise ise ise ise ise
|
||||||
write() OPEN ise PENDING wpe wpe
|
write() OPEN ise PENDING wpe wpe eof
|
||||||
flush() OPEN ise PENDING wpe wpe
|
flush() OPEN ise PENDING wpe wpe eof
|
||||||
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false
|
close() CLOSED CLOSED CLOSED CLOSED wpe CLOSED
|
||||||
write completed - - - ASYNC READY->owp
|
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
|
||||||
|
write completed - - - ASYNC READY->owp -
|
||||||
|
|
||||||
*/
|
*/
|
||||||
enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, CLOSED }
|
enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, CLOSED }
|
||||||
private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
|
private final AtomicReference<OutputState> _state=new AtomicReference<>(OutputState.OPEN);
|
||||||
|
@ -131,9 +133,18 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
OutputState state=_state.get();
|
loop: while(true)
|
||||||
while(state!=OutputState.CLOSED)
|
|
||||||
{
|
{
|
||||||
|
OutputState state=_state.get();
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case CLOSED:
|
||||||
|
break loop;
|
||||||
|
|
||||||
|
case UNREADY:
|
||||||
|
throw new WritePendingException(); // TODO ?
|
||||||
|
|
||||||
|
default:
|
||||||
if (_state.compareAndSet(state,OutputState.CLOSED))
|
if (_state.compareAndSet(state,OutputState.CLOSED))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -148,16 +159,25 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
releaseBuffer();
|
releaseBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state=_state.get();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called to indicated that the output is already closed and the state needs to be updated to match */
|
/* Called to indicated that the output is already closed and the state needs to be updated to match */
|
||||||
void closed()
|
void closed()
|
||||||
{
|
{
|
||||||
OutputState state=_state.get();
|
loop: while(true)
|
||||||
while(state!=OutputState.CLOSED)
|
|
||||||
{
|
{
|
||||||
|
OutputState state=_state.get();
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case CLOSED:
|
||||||
|
break loop;
|
||||||
|
|
||||||
|
case UNREADY:
|
||||||
|
throw new WritePendingException(); // TODO ?
|
||||||
|
|
||||||
|
default:
|
||||||
if (_state.compareAndSet(state,OutputState.CLOSED))
|
if (_state.compareAndSet(state,OutputState.CLOSED))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -172,7 +192,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
releaseBuffer();
|
releaseBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state=_state.get();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,8 +687,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
return false;
|
return false;
|
||||||
case UNREADY:
|
case UNREADY:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -680,11 +701,13 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
{
|
{
|
||||||
Throwable th=_onError;
|
Throwable th=_onError;
|
||||||
_onError=null;
|
_onError=null;
|
||||||
_writeListener.onError(th);
|
_writeListener.onError(new IOException(th));
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
if (_state.get()==OutputState.READY)
|
|
||||||
|
switch(_state.get())
|
||||||
{
|
{
|
||||||
|
case READY:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_writeListener.onWritePossible();
|
_writeListener.onWritePossible();
|
||||||
|
@ -694,9 +717,33 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
_writeListener.onError(e);
|
_writeListener.onError(e);
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CLOSED:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new Throwable().printStackTrace();
|
||||||
|
// even though a write is not possible, because a close has
|
||||||
|
// occurred, we need to call onWritePossible to tell async
|
||||||
|
// producer that the last write completed.
|
||||||
|
_writeListener.onWritePossible();
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
_writeListener.onError(e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("%s@%x{%s}",this.getClass().getSimpleName(),hashCode(),_state.get());
|
||||||
|
}
|
||||||
|
|
||||||
private abstract class AsyncICB extends IteratingCallback
|
private abstract class AsyncICB extends IteratingCallback
|
||||||
{
|
{
|
||||||
|
@ -722,7 +769,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CLOSED:
|
case CLOSED:
|
||||||
_onError=new EofException("Closed");
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.NetworkTrafficListener;
|
||||||
|
import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
|
||||||
|
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||||
|
import org.eclipse.jetty.io.SelectorManager;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A specialized version of {@link ServerConnector} that supports {@link NetworkTrafficListener}s.</p>
|
||||||
|
* <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
|
||||||
|
* been started without causing {@link java.util.ConcurrentModificationException}s.</p>
|
||||||
|
*/
|
||||||
|
public class NetworkTrafficServerConnector extends ServerConnector
|
||||||
|
{
|
||||||
|
private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public NetworkTrafficServerConnector(Server server)
|
||||||
|
{
|
||||||
|
this(server, null, null, null, 0, 0, new HttpConnectionFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
|
||||||
|
{
|
||||||
|
super(server, sslContextFactory, connectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkTrafficServerConnector(Server server, ConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
super(server, connectionFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkTrafficServerConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
|
||||||
|
{
|
||||||
|
super(server, executor, scheduler, pool, acceptors, selectors, factories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkTrafficServerConnector(Server server, SslContextFactory sslContextFactory)
|
||||||
|
{
|
||||||
|
super(server, sslContextFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param listener the listener to add
|
||||||
|
*/
|
||||||
|
public void addNetworkTrafficListener(NetworkTrafficListener listener)
|
||||||
|
{
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param listener the listener to remove
|
||||||
|
*/
|
||||||
|
public void removeNetworkTrafficListener(NetworkTrafficListener listener)
|
||||||
|
{
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
|
||||||
|
{
|
||||||
|
NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
|
||||||
|
endPoint.notifyOpened();
|
||||||
|
return endPoint;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,21 +21,19 @@ package org.eclipse.jetty.server;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
|
|
||||||
import javax.servlet.ServletInputStream;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.util.ArrayQueue;
|
import org.eclipse.jetty.util.ArrayQueue;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>{@link QueuedHttpInput} provides an implementation of {@link ServletInputStream} for {@link HttpChannel}.</p>
|
* {@link QueuedHttpInput} holds a queue of items passed to it by calls to {@link #content(Object)}.
|
||||||
* <p>{@link QueuedHttpInput} holds a queue of items passed to it by calls to {@link #content(Object)}.</p>
|
* <p/>
|
||||||
* <p>{@link QueuedHttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
|
* {@link QueuedHttpInput} stores the items directly; if the items contain byte buffers, it does not copy them
|
||||||
* but simply holds references to the item, thus the caller must organize for those buffers to valid while
|
* but simply holds references to the item, thus the caller must organize for those buffers to valid while
|
||||||
* held by this class.</p>
|
* held by this class.
|
||||||
* <p>To assist the caller, subclasses may override methods {@link #onAsyncRead()},
|
* <p/>
|
||||||
* {@link #onContentConsumed(Object)} and {@link #onAllContentConsumed()} that can be implemented so that the
|
* To assist the caller, subclasses may override methods {@link #onAsyncRead()}, {@link #onContentConsumed(Object)}
|
||||||
* caller will know when buffers are queued and consumed.</p>
|
* that can be implemented so that the caller will know when buffers are queued and consumed.
|
||||||
*/
|
*/
|
||||||
public abstract class QueuedHttpInput<T> extends HttpInput<T>
|
public abstract class QueuedHttpInput<T> extends HttpInput<T>
|
||||||
{
|
{
|
||||||
|
@ -44,21 +42,37 @@ public abstract class QueuedHttpInput<T> extends HttpInput<T>
|
||||||
private final ArrayQueue<T> _inputQ = new ArrayQueue<>(lock());
|
private final ArrayQueue<T> _inputQ = new ArrayQueue<>(lock());
|
||||||
|
|
||||||
public QueuedHttpInput()
|
public QueuedHttpInput()
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void content(T item)
|
||||||
|
{
|
||||||
|
// The buffer is not copied here. This relies on the caller not recycling the buffer
|
||||||
|
// until the it is consumed. The onContentConsumed and onAllContentConsumed() callbacks are
|
||||||
|
// the signals to the caller that the buffers can be recycled.
|
||||||
|
|
||||||
|
synchronized (lock())
|
||||||
|
{
|
||||||
|
boolean wasEmpty = _inputQ.isEmpty();
|
||||||
|
_inputQ.add(item);
|
||||||
|
LOG.debug("{} queued {}", this, item);
|
||||||
|
if (wasEmpty)
|
||||||
|
{
|
||||||
|
if (!onAsyncRead())
|
||||||
|
lock().notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void recycle()
|
public void recycle()
|
||||||
{
|
{
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
T item = _inputQ.peekUnsafe();
|
T item = _inputQ.pollUnsafe();
|
||||||
while (item != null)
|
while (item != null)
|
||||||
{
|
{
|
||||||
_inputQ.pollUnsafe();
|
|
||||||
onContentConsumed(item);
|
onContentConsumed(item);
|
||||||
|
item = _inputQ.pollUnsafe();
|
||||||
item = _inputQ.peekUnsafe();
|
|
||||||
if (item == null)
|
|
||||||
onAllContentConsumed();
|
|
||||||
}
|
}
|
||||||
super.recycle();
|
super.recycle();
|
||||||
}
|
}
|
||||||
|
@ -67,30 +81,27 @@ public abstract class QueuedHttpInput<T> extends HttpInput<T>
|
||||||
@Override
|
@Override
|
||||||
protected T nextContent()
|
protected T nextContent()
|
||||||
{
|
{
|
||||||
|
synchronized (lock())
|
||||||
|
{
|
||||||
|
// Items are removed only when they are fully consumed.
|
||||||
T item = _inputQ.peekUnsafe();
|
T item = _inputQ.peekUnsafe();
|
||||||
|
// Skip consumed items at the head of the queue.
|
||||||
// Skip empty items at the head of the queue
|
|
||||||
while (item != null && remaining(item) == 0)
|
while (item != null && remaining(item) == 0)
|
||||||
{
|
{
|
||||||
_inputQ.pollUnsafe();
|
_inputQ.pollUnsafe();
|
||||||
onContentConsumed(item);
|
onContentConsumed(item);
|
||||||
LOG.debug("{} consumed {}", this, item);
|
LOG.debug("{} consumed {}", this, item);
|
||||||
item = _inputQ.peekUnsafe();
|
item = _inputQ.peekUnsafe();
|
||||||
|
|
||||||
// If that was the last item then notify
|
|
||||||
if (item==null)
|
|
||||||
onAllContentConsumed();
|
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
protected abstract void onContentConsumed(T item);
|
|
||||||
|
|
||||||
protected void blockForContent() throws IOException
|
protected void blockForContent() throws IOException
|
||||||
{
|
{
|
||||||
synchronized (lock())
|
synchronized (lock())
|
||||||
{
|
{
|
||||||
while (_inputQ.isEmpty() && !_state.isEOF())
|
while (_inputQ.isEmpty() && !isFinished() && !isEOF())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -105,40 +116,12 @@ public abstract class QueuedHttpInput<T> extends HttpInput<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
/* ------------------------------------------------------------ */
|
* Callback that signals that the given content has been consumed.
|
||||||
/** Called by this HttpInput to signal all available content has been consumed
|
*
|
||||||
|
* @param item the consumed content
|
||||||
*/
|
*/
|
||||||
protected void onAllContentConsumed()
|
protected abstract void onContentConsumed(T item);
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/** Add some content to the input stream
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
public void content(T item)
|
|
||||||
{
|
|
||||||
// The buffer is not copied here. This relies on the caller not recycling the buffer
|
|
||||||
// until the it is consumed. The onContentConsumed and onAllContentConsumed() callbacks are
|
|
||||||
// the signals to the caller that the buffers can be recycled.
|
|
||||||
|
|
||||||
synchronized (lock())
|
|
||||||
{
|
|
||||||
boolean empty=_inputQ.isEmpty();
|
|
||||||
|
|
||||||
_inputQ.add(item);
|
|
||||||
|
|
||||||
if (empty)
|
|
||||||
{
|
|
||||||
if (!onAsyncRead())
|
|
||||||
lock().notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug("{} queued {}", this, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void earlyEOF()
|
public void earlyEOF()
|
||||||
{
|
{
|
||||||
|
@ -157,5 +140,4 @@ public abstract class QueuedHttpInput<T> extends HttpInput<T>
|
||||||
lock().notify();
|
lock().notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ public class ServerConnector extends AbstractNetworkConnector
|
||||||
@Name("factories") ConnectionFactory... factories)
|
@Name("factories") ConnectionFactory... factories)
|
||||||
{
|
{
|
||||||
super(server,executor,scheduler,bufferPool,acceptors,factories);
|
super(server,executor,scheduler,bufferPool,acceptors,factories);
|
||||||
_manager = new ServerConnectorManager(getExecutor(), getScheduler(), selectors >= 0 ? selectors : Runtime.getRuntime().availableProcessors());
|
_manager = new ServerConnectorManager(getExecutor(), getScheduler(), selectors > 0 ? selectors : Runtime.getRuntime().availableProcessors());
|
||||||
addBean(_manager, true);
|
addBean(_manager, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ package org.eclipse.jetty.server.handler;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -2261,9 +2263,44 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
||||||
@Override
|
@Override
|
||||||
public ClassLoader getClassLoader()
|
public ClassLoader getClassLoader()
|
||||||
{
|
{
|
||||||
|
if (!_enabled)
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
|
//no security manager just return the classloader
|
||||||
|
if (System.getSecurityManager() == null)
|
||||||
|
return _classLoader;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//check to see if the classloader of the caller is the same as the context
|
||||||
|
//classloader, or a parent of it
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Class reflect = Loader.loadClass(getClass(), "sun.reflect.Reflection");
|
||||||
|
Method getCallerClass = reflect.getMethod("getCallerClass", Integer.TYPE);
|
||||||
|
Class caller = (Class)getCallerClass.invoke(null, 2);
|
||||||
|
|
||||||
|
boolean ok = false;
|
||||||
|
ClassLoader callerLoader = caller.getClassLoader();
|
||||||
|
while (!ok && callerLoader != null)
|
||||||
|
{
|
||||||
|
if (callerLoader == _classLoader)
|
||||||
|
ok = true;
|
||||||
|
else
|
||||||
|
callerLoader = callerLoader.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok)
|
||||||
|
return _classLoader;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Unable to check classloader of caller",e);
|
||||||
|
}
|
||||||
|
|
||||||
AccessController.checkPermission(new RuntimePermission("getClassLoader"));
|
AccessController.checkPermission(new RuntimePermission("getClassLoader"));
|
||||||
return _classLoader;
|
return _classLoader;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JspConfigDescriptor getJspConfigDescriptor()
|
public JspConfigDescriptor getJspConfigDescriptor()
|
||||||
|
|
|
@ -18,43 +18,29 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.server.nio;
|
package org.eclipse.jetty.server.nio;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.SelectionKey;
|
|
||||||
import java.nio.channels.SocketChannel;
|
|
||||||
import java.util.ConcurrentModificationException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.NetworkTrafficListener;
|
|
||||||
import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
|
|
||||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
|
||||||
import org.eclipse.jetty.io.SelectorManager;
|
|
||||||
import org.eclipse.jetty.server.ConnectionFactory;
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.NetworkTrafficServerConnector;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.Scheduler;
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>A specialized version of {@link ServerConnector} that supports {@link NetworkTrafficListener}s.</p>
|
* @deprecated use {@link org.eclipse.jetty.server.NetworkTrafficServerConnector} instead.
|
||||||
* <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
|
|
||||||
* been started without causing {@link ConcurrentModificationException}s.</p>
|
|
||||||
*/
|
*/
|
||||||
public class NetworkTrafficSelectChannelConnector extends ServerConnector
|
@Deprecated
|
||||||
|
public class NetworkTrafficSelectChannelConnector extends NetworkTrafficServerConnector
|
||||||
{
|
{
|
||||||
private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<NetworkTrafficListener>();
|
|
||||||
|
|
||||||
public NetworkTrafficSelectChannelConnector(Server server)
|
public NetworkTrafficSelectChannelConnector(Server server)
|
||||||
{
|
{
|
||||||
this(server,null,null,null,0,0,new HttpConnectionFactory());
|
super(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
|
public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory, SslContextFactory sslContextFactory)
|
||||||
{
|
{
|
||||||
super(server,sslContextFactory,connectionFactory);
|
super(server, connectionFactory, sslContextFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory)
|
public NetworkTrafficSelectChannelConnector(Server server, ConnectionFactory connectionFactory)
|
||||||
|
@ -62,8 +48,7 @@ public class NetworkTrafficSelectChannelConnector extends ServerConnector
|
||||||
super(server, connectionFactory);
|
super(server, connectionFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkTrafficSelectChannelConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors,
|
public NetworkTrafficSelectChannelConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool pool, int acceptors, int selectors, ConnectionFactory... factories)
|
||||||
ConnectionFactory... factories)
|
|
||||||
{
|
{
|
||||||
super(server, executor, scheduler, pool, acceptors, selectors, factories);
|
super(server, executor, scheduler, pool, acceptors, selectors, factories);
|
||||||
}
|
}
|
||||||
|
@ -72,29 +57,4 @@ public class NetworkTrafficSelectChannelConnector extends ServerConnector
|
||||||
{
|
{
|
||||||
super(server, sslContextFactory);
|
super(server, sslContextFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param listener the listener to add
|
|
||||||
*/
|
|
||||||
public void addNetworkTrafficListener(NetworkTrafficListener listener)
|
|
||||||
{
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param listener the listener to remove
|
|
||||||
*/
|
|
||||||
public void removeNetworkTrafficListener(NetworkTrafficListener listener)
|
|
||||||
{
|
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.ManagedSelector selectSet, SelectionKey key) throws IOException
|
|
||||||
{
|
|
||||||
NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
|
|
||||||
endPoint.notifyOpened();
|
|
||||||
return endPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,12 @@ public class HashedSession extends AbstractSession
|
||||||
* data stored in the session that is not serializable. */
|
* data stored in the session that is not serializable. */
|
||||||
private transient boolean _saveFailed = false;
|
private transient boolean _saveFailed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if an attempt has been made to de-idle a session and it failed. Once
|
||||||
|
* true, the session will not be attempted to be de-idled again.
|
||||||
|
*/
|
||||||
|
private transient boolean _deIdleFailed = false;
|
||||||
|
|
||||||
/* ------------------------------------------------------------- */
|
/* ------------------------------------------------------------- */
|
||||||
protected HashedSession(HashSessionManager hashSessionManager, HttpServletRequest request)
|
protected HashedSession(HashSessionManager hashSessionManager, HttpServletRequest request)
|
||||||
{
|
{
|
||||||
|
@ -68,7 +74,7 @@ public class HashedSession extends AbstractSession
|
||||||
/* ------------------------------------------------------------- */
|
/* ------------------------------------------------------------- */
|
||||||
protected void checkValid()
|
protected void checkValid()
|
||||||
{
|
{
|
||||||
if (_hashSessionManager._idleSavePeriodMs!=0)
|
if (!_deIdleFailed && _hashSessionManager._idleSavePeriodMs!=0)
|
||||||
deIdle();
|
deIdle();
|
||||||
super.checkValid();
|
super.checkValid();
|
||||||
}
|
}
|
||||||
|
@ -196,7 +202,7 @@ public class HashedSession extends AbstractSession
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public synchronized void deIdle()
|
public synchronized void deIdle()
|
||||||
{
|
{
|
||||||
if (isIdled())
|
if (isIdled() && !_deIdleFailed)
|
||||||
{
|
{
|
||||||
// Access now to prevent race with idling period
|
// Access now to prevent race with idling period
|
||||||
access(System.currentTimeMillis());
|
access(System.currentTimeMillis());
|
||||||
|
@ -225,6 +231,7 @@ public class HashedSession extends AbstractSession
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
deIdleFailed();
|
||||||
LOG.warn("Problem de-idling session " + super.getId(), e);
|
LOG.warn("Problem de-idling session " + super.getId(), e);
|
||||||
if (fis != null) IO.close(fis);//Must ensure closed before invalidate
|
if (fis != null) IO.close(fis);//Must ensure closed before invalidate
|
||||||
invalidate();
|
invalidate();
|
||||||
|
@ -265,4 +272,15 @@ public class HashedSession extends AbstractSession
|
||||||
_saveFailed = true;
|
_saveFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public synchronized void deIdleFailed()
|
||||||
|
{
|
||||||
|
_deIdleFailed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public synchronized boolean isDeIdleFailed()
|
||||||
|
{
|
||||||
|
return _deIdleFailed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,7 +299,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager
|
||||||
if (_dbAdaptor == null)
|
if (_dbAdaptor == null)
|
||||||
throw new IllegalStateException ("No DBAdaptor");
|
throw new IllegalStateException ("No DBAdaptor");
|
||||||
String longType = _dbAdaptor.getLongType();
|
String longType = _dbAdaptor.getLongType();
|
||||||
return "alter table "+getTableName()+" add "+longType+" not null default "+MAX_INTERVAL_NOT_SET;
|
return "alter table "+getTableName()+" add "+getMaxIntervalColumn()+" "+longType+" not null default "+MAX_INTERVAL_NOT_SET;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkNotNull(String s)
|
private void checkNotNull(String s)
|
||||||
|
|
|
@ -95,49 +95,49 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
/**
|
/**
|
||||||
* If dirty, session needs to be (re)persisted
|
* If dirty, session needs to be (re)persisted
|
||||||
*/
|
*/
|
||||||
private boolean _dirty=false;
|
protected boolean _dirty=false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time in msec since the epoch that a session cookie was set for this session
|
* Time in msec since the epoch that a session cookie was set for this session
|
||||||
*/
|
*/
|
||||||
private long _cookieSet;
|
protected long _cookieSet;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time in msec since the epoch that the session will expire
|
* Time in msec since the epoch that the session will expire
|
||||||
*/
|
*/
|
||||||
private long _expiryTime;
|
protected long _expiryTime;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time in msec since the epoch that the session was last persisted
|
* Time in msec since the epoch that the session was last persisted
|
||||||
*/
|
*/
|
||||||
private long _lastSaved;
|
protected long _lastSaved;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique identifier of the last node to host the session
|
* Unique identifier of the last node to host the session
|
||||||
*/
|
*/
|
||||||
private String _lastNode;
|
protected String _lastNode;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
|
* Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
|
||||||
*/
|
*/
|
||||||
private String _virtualHost;
|
protected String _virtualHost;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique row in db for session
|
* Unique row in db for session
|
||||||
*/
|
*/
|
||||||
private String _rowId;
|
protected String _rowId;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
|
* Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
|
||||||
*/
|
*/
|
||||||
private String _canonicalContext;
|
protected String _canonicalContext;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -246,7 +246,8 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
@Override
|
@Override
|
||||||
public void setAttribute (String name, Object value)
|
public void setAttribute (String name, Object value)
|
||||||
{
|
{
|
||||||
_dirty = (updateAttribute(name, value) || _dirty);
|
updateAttribute(name, value);
|
||||||
|
_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -494,6 +495,7 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
" interval="+(_saveIntervalSec * 1000L));
|
" interval="+(_saveIntervalSec * 1000L));
|
||||||
else
|
else
|
||||||
LOG.debug("getSession("+idInCluster+"): in session map, "+
|
LOG.debug("getSession("+idInCluster+"): in session map, "+
|
||||||
|
" hashcode="+memSession.hashCode()+
|
||||||
" now="+now+
|
" now="+now+
|
||||||
" lastSaved="+(memSession==null?0:memSession._lastSaved)+
|
" lastSaved="+(memSession==null?0:memSession._lastSaved)+
|
||||||
" interval="+(_saveIntervalSec * 1000L)+
|
" interval="+(_saveIntervalSec * 1000L)+
|
||||||
|
@ -565,8 +567,12 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
//the session loaded from the db and the one in memory are the same, so keep using the one in memory
|
||||||
|
session = memSession;
|
||||||
LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
|
LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//No session in db with matching id and context path.
|
//No session in db with matching id and context path.
|
||||||
|
@ -770,6 +776,20 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
return new Session(request);
|
return new Session(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sessionId
|
||||||
|
* @param rowId
|
||||||
|
* @param created
|
||||||
|
* @param accessed
|
||||||
|
* @param maxInterval
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected AbstractSession newSession (String sessionId, String rowId, long created, long accessed, long maxInterval)
|
||||||
|
{
|
||||||
|
return new Session(sessionId, rowId, created, accessed, maxInterval);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Remove session from manager
|
/** Remove session from manager
|
||||||
* @param session The session to remove
|
* @param session The session to remove
|
||||||
|
@ -892,7 +912,7 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
{
|
{
|
||||||
maxInterval = getMaxInactiveInterval(); //if value not saved for maxInactiveInterval, use current value from sessionmanager
|
maxInterval = getMaxInactiveInterval(); //if value not saved for maxInactiveInterval, use current value from sessionmanager
|
||||||
}
|
}
|
||||||
session = new Session(id, result.getString(_sessionTableSchema.getRowIdColumn()),
|
session = (Session)newSession(id, result.getString(_sessionTableSchema.getRowIdColumn()),
|
||||||
result.getLong(_sessionTableSchema.getCreateTimeColumn()),
|
result.getLong(_sessionTableSchema.getCreateTimeColumn()),
|
||||||
result.getLong(_sessionTableSchema.getAccessTimeColumn()),
|
result.getLong(_sessionTableSchema.getAccessTimeColumn()),
|
||||||
maxInterval);
|
maxInterval);
|
||||||
|
@ -963,7 +983,7 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
connection.setAutoCommit(true);
|
connection.setAutoCommit(true);
|
||||||
statement.setString(1, rowId); //rowId
|
statement.setString(1, rowId); //rowId
|
||||||
statement.setString(2, session.getId()); //session id
|
statement.setString(2, session.getClusterId()); //session id
|
||||||
statement.setString(3, session.getCanonicalContext()); //context path
|
statement.setString(3, session.getCanonicalContext()); //context path
|
||||||
statement.setString(4, session.getVirtualHost()); //first vhost
|
statement.setString(4, session.getVirtualHost()); //first vhost
|
||||||
statement.setString(5, getSessionIdManager().getWorkerName());//my node id
|
statement.setString(5, getSessionIdManager().getWorkerName());//my node id
|
||||||
|
@ -1011,7 +1031,7 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
{
|
{
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
connection.setAutoCommit(true);
|
connection.setAutoCommit(true);
|
||||||
statement.setString(1, data.getId());
|
statement.setString(1, data.getClusterId());
|
||||||
statement.setString(2, getSessionIdManager().getWorkerName());//my node id
|
statement.setString(2, getSessionIdManager().getWorkerName());//my node id
|
||||||
statement.setLong(3, data.getAccessed());//accessTime
|
statement.setLong(3, data.getAccessed());//accessTime
|
||||||
statement.setLong(4, data.getLastAccessedTime()); //lastAccessTime
|
statement.setLong(4, data.getLastAccessedTime()); //lastAccessTime
|
||||||
|
@ -1085,7 +1105,7 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
data.setLastSaved(now);
|
data.setLastSaved(now);
|
||||||
}
|
}
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Updated access time session id="+data.getId());
|
LOG.debug("Updated access time session id="+data.getId()+" with lastsaved="+data.getLastSaved());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -547,6 +547,22 @@ public class HttpOutputTest
|
||||||
assertThat(response,containsString("400\tThis is a big file"));
|
assertThat(response,containsString("400\tThis is a big file"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncWriteSimpleKnown() throws Exception
|
||||||
|
{
|
||||||
|
final Resource big = Resource.newClassPathResource("simple/simple.txt");
|
||||||
|
|
||||||
|
_handler._async=true;
|
||||||
|
_handler._writeLengthIfKnown=true;
|
||||||
|
_handler._content=BufferUtil.toBuffer(big,false);
|
||||||
|
_handler._arrayBuffer=new byte[4000];
|
||||||
|
|
||||||
|
String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n");
|
||||||
|
assertThat(response,containsString("HTTP/1.1 200 OK"));
|
||||||
|
assertThat(response,containsString("Content-Length: 11"));
|
||||||
|
assertThat(response,containsString("simple text"));
|
||||||
|
}
|
||||||
|
|
||||||
static class ContentHandler extends AbstractHandler
|
static class ContentHandler extends AbstractHandler
|
||||||
{
|
{
|
||||||
boolean _writeLengthIfKnown=true;
|
boolean _writeLengthIfKnown=true;
|
||||||
|
@ -664,7 +680,6 @@ public class HttpOutputTest
|
||||||
BufferUtil.flipToFlush(_byteBuffer,0);
|
BufferUtil.flipToFlush(_byteBuffer,0);
|
||||||
out.write(_byteBuffer);
|
out.write(_byteBuffer);
|
||||||
}
|
}
|
||||||
Assert.assertFalse(out.isReady());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,9 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.server;
|
package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -31,7 +28,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -39,25 +35,27 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.NetworkTrafficListener;
|
import org.eclipse.jetty.io.NetworkTrafficListener;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector;
|
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
public class NetworkTrafficListenerTest
|
public class NetworkTrafficListenerTest
|
||||||
{
|
{
|
||||||
private static final byte END_OF_CONTENT = '~';
|
private static final byte END_OF_CONTENT = '~';
|
||||||
|
|
||||||
private Server server;
|
private Server server;
|
||||||
private NetworkTrafficSelectChannelConnector connector;
|
private NetworkTrafficServerConnector connector;
|
||||||
|
|
||||||
public void initConnector(Handler handler) throws Exception
|
public void initConnector(Handler handler) throws Exception
|
||||||
{
|
{
|
||||||
server = new Server();
|
server = new Server();
|
||||||
|
|
||||||
connector = new NetworkTrafficSelectChannelConnector(server);
|
connector = new NetworkTrafficServerConnector(server);
|
||||||
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
|
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
|
||||||
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
|
connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
|
@ -121,9 +119,9 @@ public class NetworkTrafficListenerTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final AtomicReference<String> incomingData = new AtomicReference<String>();
|
final AtomicReference<String> incomingData = new AtomicReference<>();
|
||||||
final CountDownLatch incomingLatch = new CountDownLatch(1);
|
final CountDownLatch incomingLatch = new CountDownLatch(1);
|
||||||
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
|
final AtomicReference<String> outgoingData = new AtomicReference<>("");
|
||||||
final CountDownLatch outgoingLatch = new CountDownLatch(1);
|
final CountDownLatch outgoingLatch = new CountDownLatch(1);
|
||||||
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
||||||
{
|
{
|
||||||
|
@ -186,9 +184,9 @@ public class NetworkTrafficListenerTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final AtomicReference<String> incomingData = new AtomicReference<String>();
|
final AtomicReference<String> incomingData = new AtomicReference<>();
|
||||||
final CountDownLatch incomingLatch = new CountDownLatch(1);
|
final CountDownLatch incomingLatch = new CountDownLatch(1);
|
||||||
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
|
final AtomicReference<String> outgoingData = new AtomicReference<>("");
|
||||||
final CountDownLatch outgoingLatch = new CountDownLatch(2);
|
final CountDownLatch outgoingLatch = new CountDownLatch(2);
|
||||||
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
||||||
{
|
{
|
||||||
|
@ -253,9 +251,9 @@ public class NetworkTrafficListenerTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final AtomicReference<String> incomingData = new AtomicReference<String>();
|
final AtomicReference<String> incomingData = new AtomicReference<>();
|
||||||
final CountDownLatch incomingLatch = new CountDownLatch(1);
|
final CountDownLatch incomingLatch = new CountDownLatch(1);
|
||||||
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
|
final AtomicReference<String> outgoingData = new AtomicReference<>("");
|
||||||
final CountDownLatch outgoingLatch = new CountDownLatch(4);
|
final CountDownLatch outgoingLatch = new CountDownLatch(4);
|
||||||
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
||||||
{
|
{
|
||||||
|
@ -319,9 +317,9 @@ public class NetworkTrafficListenerTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final AtomicReference<String> incomingData = new AtomicReference<String>();
|
final AtomicReference<String> incomingData = new AtomicReference<>();
|
||||||
final CountDownLatch incomingLatch = new CountDownLatch(1);
|
final CountDownLatch incomingLatch = new CountDownLatch(1);
|
||||||
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
|
final AtomicReference<String> outgoingData = new AtomicReference<>("");
|
||||||
final CountDownLatch outgoingLatch = new CountDownLatch(1);
|
final CountDownLatch outgoingLatch = new CountDownLatch(1);
|
||||||
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
||||||
{
|
{
|
||||||
|
@ -393,8 +391,8 @@ public class NetworkTrafficListenerTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final AtomicReference<String> incomingData = new AtomicReference<String>("");
|
final AtomicReference<String> incomingData = new AtomicReference<>("");
|
||||||
final AtomicReference<String> outgoingData = new AtomicReference<String>("");
|
final AtomicReference<String> outgoingData = new AtomicReference<>("");
|
||||||
final CountDownLatch outgoingLatch = new CountDownLatch(1);
|
final CountDownLatch outgoingLatch = new CountDownLatch(1);
|
||||||
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
connector.addNetworkTrafficListener(new NetworkTrafficListener.Adapter()
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,11 +16,18 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.mux;
|
package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class MuxResponse extends UpgradeResponse
|
public class QueuedHttpInputTest
|
||||||
{
|
{
|
||||||
|
@Test
|
||||||
|
public void testNoContentMessageComplete() throws Exception
|
||||||
|
{
|
||||||
|
ByteBufferQueuedHttpInput input = new ByteBufferQueuedHttpInput();
|
||||||
|
input.messageComplete();
|
||||||
|
|
||||||
|
input.getNextContent();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -319,7 +319,7 @@ public class ServletHandler extends ScopedHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
IdentityService getIdentityService()
|
protected IdentityService getIdentityService()
|
||||||
{
|
{
|
||||||
return _identityService;
|
return _identityService;
|
||||||
}
|
}
|
||||||
|
@ -647,7 +647,7 @@ public class ServletHandler extends ScopedHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
private FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
|
protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
|
||||||
{
|
{
|
||||||
String key=pathInContext==null?servletHolder.getName():pathInContext;
|
String key=pathInContext==null?servletHolder.getName():pathInContext;
|
||||||
int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
|
int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
|
||||||
|
@ -735,7 +735,7 @@ public class ServletHandler extends ScopedHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
private void invalidateChainsCache()
|
protected void invalidateChainsCache()
|
||||||
{
|
{
|
||||||
if (_chainLRU[FilterMapping.REQUEST]!=null)
|
if (_chainLRU[FilterMapping.REQUEST]!=null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.servlet;
|
package org.eclipse.jetty.servlet;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -28,14 +26,18 @@ import java.net.Socket;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import javax.servlet.AsyncContext;
|
import javax.servlet.AsyncContext;
|
||||||
|
import javax.servlet.AsyncEvent;
|
||||||
|
import javax.servlet.AsyncListener;
|
||||||
import javax.servlet.ReadListener;
|
import javax.servlet.ReadListener;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletInputStream;
|
import javax.servlet.ServletInputStream;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.WriteListener;
|
import javax.servlet.WriteListener;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -52,12 +54,14 @@ import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
// TODO need these on SPDY as well!
|
// TODO need these on SPDY as well!
|
||||||
public class AsyncServletIOTest
|
public class AsyncServletIOTest
|
||||||
{
|
{
|
||||||
protected AsyncIOServlet _servlet=new AsyncIOServlet();
|
protected AsyncIOServlet _servlet0=new AsyncIOServlet();
|
||||||
|
protected AsyncIOServlet2 _servlet2=new AsyncIOServlet2();
|
||||||
protected int _port;
|
protected int _port;
|
||||||
|
|
||||||
protected Server _server = new Server();
|
protected Server _server = new Server();
|
||||||
protected ServletHandler _servletHandler;
|
protected ServletHandler _servletHandler;
|
||||||
protected ServerConnector _connector;
|
protected ServerConnector _connector;
|
||||||
|
@ -70,13 +74,20 @@ public class AsyncServletIOTest
|
||||||
_connector = new ServerConnector(_server,new HttpConnectionFactory(http_config));
|
_connector = new ServerConnector(_server,new HttpConnectionFactory(http_config));
|
||||||
|
|
||||||
_server.setConnectors(new Connector[]{ _connector });
|
_server.setConnectors(new Connector[]{ _connector });
|
||||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
|
ServletContextHandler context = new ServletContextHandler();
|
||||||
context.setContextPath("/ctx");
|
context.setContextPath("/ctx");
|
||||||
_server.setHandler(context);
|
_server.setHandler(context);
|
||||||
_servletHandler=context.getServletHandler();
|
_servletHandler=context.getServletHandler();
|
||||||
ServletHolder holder=new ServletHolder(_servlet);
|
|
||||||
|
|
||||||
|
ServletHolder holder=new ServletHolder(_servlet0);
|
||||||
holder.setAsyncSupported(true);
|
holder.setAsyncSupported(true);
|
||||||
_servletHandler.addServletWithMapping(holder,"/path/*");
|
_servletHandler.addServletWithMapping(holder,"/path/*");
|
||||||
|
|
||||||
|
ServletHolder holder2=new ServletHolder(_servlet2);
|
||||||
|
holder.setAsyncSupported(true);
|
||||||
|
_servletHandler.addServletWithMapping(holder2,"/path2/*");
|
||||||
|
|
||||||
_server.start();
|
_server.start();
|
||||||
_port=_connector.getLocalPort();
|
_port=_connector.getLocalPort();
|
||||||
|
|
||||||
|
@ -146,15 +157,52 @@ public class AsyncServletIOTest
|
||||||
process("Hello!!!\r\n",10);
|
process("Hello!!!\r\n",10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
protected void assertContains(String content,String response)
|
public void testAsync2() throws Exception
|
||||||
{
|
{
|
||||||
Assert.assertThat(response,Matchers.containsString(content));
|
StringBuilder request = new StringBuilder(512);
|
||||||
|
request.append("GET /ctx/path2/info HTTP/1.1\r\n")
|
||||||
|
.append("Host: localhost\r\n")
|
||||||
|
.append("Connection: close\r\n")
|
||||||
|
.append("\r\n");
|
||||||
|
|
||||||
|
int port=_port;
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
try (Socket socket = new Socket("localhost",port))
|
||||||
|
{
|
||||||
|
socket.setSoTimeout(1000000);
|
||||||
|
OutputStream out = socket.getOutputStream();
|
||||||
|
out.write(request.toString().getBytes("ISO-8859-1"));
|
||||||
|
|
||||||
|
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()),102400);
|
||||||
|
|
||||||
|
// response line
|
||||||
|
String line = in.readLine();
|
||||||
|
// System.err.println("resp: "+line);
|
||||||
|
Assert.assertThat(line,Matchers.startsWith("HTTP/1.1 200 OK"));
|
||||||
|
|
||||||
|
// Skip headers
|
||||||
|
while (line!=null)
|
||||||
|
{
|
||||||
|
line = in.readLine();
|
||||||
|
// System.err.println("line: "+line);
|
||||||
|
if (line.length()==0)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertNotContains(String content,String response)
|
// Get body slowly
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
|
line = in.readLine();
|
||||||
|
// System.err.println("body: "+line);
|
||||||
|
if (line==null)
|
||||||
|
break;
|
||||||
|
list.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(list.get(0),"data");
|
||||||
|
Assert.assertTrue(_servlet2.completed.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized List<String> process(String content,int... writes) throws Exception
|
public synchronized List<String> process(String content,int... writes) throws Exception
|
||||||
|
@ -183,14 +231,14 @@ public class AsyncServletIOTest
|
||||||
.append("Connection: close\r\n");
|
.append("Connection: close\r\n");
|
||||||
|
|
||||||
if (content!=null)
|
if (content!=null)
|
||||||
request.append("Content-Length: "+content.length+"\r\n")
|
request.append("Content-Length: ").append(content.length).append("\r\n")
|
||||||
.append("Content-Type: text/plain\r\n");
|
.append("Content-Type: text/plain\r\n");
|
||||||
|
|
||||||
request.append("\r\n");
|
request.append("\r\n");
|
||||||
|
|
||||||
int port=_port;
|
int port=_port;
|
||||||
List<String> list = new ArrayList<>();
|
List<String> list = new ArrayList<>();
|
||||||
try (Socket socket = new Socket("localhost",port);)
|
try (Socket socket = new Socket("localhost",port))
|
||||||
{
|
{
|
||||||
socket.setSoTimeout(1000000);
|
socket.setSoTimeout(1000000);
|
||||||
OutputStream out = socket.getOutputStream();
|
OutputStream out = socket.getOutputStream();
|
||||||
|
@ -266,9 +314,9 @@ public class AsyncServletIOTest
|
||||||
private static final long serialVersionUID = -8161977157098646562L;
|
private static final long serialVersionUID = -8161977157098646562L;
|
||||||
|
|
||||||
public AsyncIOServlet()
|
public AsyncIOServlet()
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
@Override
|
@Override
|
||||||
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
||||||
{
|
{
|
||||||
|
@ -298,7 +346,7 @@ public class AsyncServletIOTest
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
||||||
// System.err.println("ODA");
|
// System.err.println("ODA");
|
||||||
while (in.isReady())
|
while (in.isReady() && !in.isFinished())
|
||||||
{
|
{
|
||||||
_oda.incrementAndGet();
|
_oda.incrementAndGet();
|
||||||
int len=in.read(_buf);
|
int len=in.read(_buf);
|
||||||
|
@ -374,4 +422,80 @@ public class AsyncServletIOTest
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AsyncIOServlet2 extends HttpServlet
|
||||||
|
{
|
||||||
|
public CountDownLatch completed = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
new SampleAsycListener(request,response);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SampleAsycListener implements WriteListener, AsyncListener
|
||||||
|
{
|
||||||
|
final ServletResponse response;
|
||||||
|
final ServletOutputStream servletOutputStream;
|
||||||
|
final AsyncContext asyncContext;
|
||||||
|
volatile boolean written=false;
|
||||||
|
|
||||||
|
SampleAsycListener(HttpServletRequest request,HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
asyncContext = request.startAsync();
|
||||||
|
asyncContext.setTimeout(10000L);
|
||||||
|
asyncContext.addListener(this);
|
||||||
|
servletOutputStream = response.getOutputStream();
|
||||||
|
servletOutputStream.setWriteListener(this);
|
||||||
|
this.response=response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWritePossible() throws IOException
|
||||||
|
{
|
||||||
|
if (!written)
|
||||||
|
{
|
||||||
|
written=true;
|
||||||
|
response.setContentLength(5);
|
||||||
|
servletOutputStream.write("data\n".getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (servletOutputStream.isReady())
|
||||||
|
{
|
||||||
|
asyncContext.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(final Throwable t)
|
||||||
|
{
|
||||||
|
t.printStackTrace();
|
||||||
|
asyncContext.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(final AsyncEvent event) throws IOException
|
||||||
|
{
|
||||||
|
completed.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimeout(final AsyncEvent event) throws IOException
|
||||||
|
{
|
||||||
|
asyncContext.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(final AsyncEvent event) throws IOException
|
||||||
|
{
|
||||||
|
asyncContext.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartAsync(AsyncEvent event) throws IOException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -392,6 +392,13 @@ public class SPDYClient
|
||||||
return Collections.unmodifiableCollection(sessions);
|
return Collections.unmodifiableCollection(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dumpThis(Appendable out) throws IOException
|
||||||
|
{
|
||||||
|
super.dumpThis(out);
|
||||||
|
dump(out, "", sessions);
|
||||||
|
}
|
||||||
|
|
||||||
private class ClientSelectorManager extends SelectorManager
|
private class ClientSelectorManager extends SelectorManager
|
||||||
{
|
{
|
||||||
private ClientSelectorManager(Executor executor, Scheduler scheduler)
|
private ClientSelectorManager(Executor executor, Scheduler scheduler)
|
||||||
|
|
|
@ -180,6 +180,7 @@ public class Flusher
|
||||||
// Has the stream been reset for this data frame ?
|
// Has the stream been reset for this data frame ?
|
||||||
if (stream != null && stream.isReset() && frameBytes instanceof StandardSession.DataFrameBytes)
|
if (stream != null && stream.isReset() && frameBytes instanceof StandardSession.DataFrameBytes)
|
||||||
{
|
{
|
||||||
|
// TODO: notify from within sync block !
|
||||||
frameBytes.failed(new StreamException(frameBytes.getStream().getId(),
|
frameBytes.failed(new StreamException(frameBytes.getStream().getId(),
|
||||||
StreamStatus.INVALID_STREAM, "Stream: " + frameBytes.getStream() + " is reset!"));
|
StreamStatus.INVALID_STREAM, "Stream: " + frameBytes.getStream() + " is reset!"));
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[name]
|
||||||
|
npn-boot
|
||||||
|
|
||||||
|
[files]
|
||||||
|
http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar:lib/npn/npn-boot-1.1.6.v20130911.jar
|
||||||
|
|
||||||
|
[ini-template]
|
||||||
|
--exec
|
||||||
|
-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.spdy.server;
|
package org.eclipse.jetty.spdy.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
@ -182,6 +183,13 @@ public class SPDYServerConnectionFactory extends AbstractConnectionFactory
|
||||||
return Collections.unmodifiableCollection(sessions);
|
return Collections.unmodifiableCollection(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dumpThis(Appendable out) throws IOException
|
||||||
|
{
|
||||||
|
super.dumpThis(out);
|
||||||
|
dump(out, "", sessions);
|
||||||
|
}
|
||||||
|
|
||||||
private class ServerSPDYConnection extends SPDYConnection implements Runnable
|
private class ServerSPDYConnection extends SPDYConnection implements Runnable
|
||||||
{
|
{
|
||||||
private final ServerSessionFrameListener listener;
|
private final ServerSessionFrameListener listener;
|
||||||
|
|
|
@ -57,12 +57,12 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.eclipse.jetty.util.thread.Scheduler;
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
public class SynDataReplyDataLoadTest extends AbstractTest
|
public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
{
|
{
|
||||||
private static final int TIMEOUT = 60000;
|
private static final int TIMEOUT = 60 * 1000;
|
||||||
private static final Logger logger = Log.getLogger(SynDataReplyDataLoadTest.class);
|
private static final Logger logger = Log.getLogger(SynDataReplyDataLoadTest.class);
|
||||||
|
|
||||||
@Test(timeout = TIMEOUT)
|
@Test(timeout = TIMEOUT)
|
||||||
|
@ -104,14 +104,20 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
short spdyVersion = SPDY.V2;
|
||||||
|
long idleTimeout = 2 * TIMEOUT;
|
||||||
|
|
||||||
server = newServer();
|
server = newServer();
|
||||||
connector = new ServerConnector(server, null, null, serverBufferPool, 1,
|
connector = new ServerConnector(server, null, null, serverBufferPool, 1,
|
||||||
Runtime.getRuntime().availableProcessors() / 2, new SPDYServerConnectionFactory(SPDY.V3, listener));
|
Math.max(1, Runtime.getRuntime().availableProcessors() / 2),
|
||||||
|
new SPDYServerConnectionFactory(spdyVersion, listener));
|
||||||
|
connector.setIdleTimeout(idleTimeout);
|
||||||
|
|
||||||
QueuedThreadPool clientExecutor = new QueuedThreadPool();
|
QueuedThreadPool clientExecutor = new QueuedThreadPool();
|
||||||
clientExecutor.setName(clientExecutor.getName() + "-client");
|
clientExecutor.setName(clientExecutor.getName() + "-client");
|
||||||
clientFactory = new SPDYClient.Factory(clientExecutor, null, clientBufferPool, null, 30000);
|
clientFactory = new SPDYClient.Factory(clientExecutor, null, clientBufferPool, null, idleTimeout);
|
||||||
final Session session = startClient(SPDY.V3, startServer(SPDY.V3, listener), null);
|
final Session session = startClient(spdyVersion, startServer(spdyVersion, listener), null);
|
||||||
|
|
||||||
final Thread testThread = Thread.currentThread();
|
final Thread testThread = Thread.currentThread();
|
||||||
Runnable timeout = new Runnable()
|
Runnable timeout = new Runnable()
|
||||||
|
@ -162,7 +168,7 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Scheduler.Task timeoutTask = clientFactory.getScheduler().schedule(timeout, TIMEOUT / 2, TimeUnit.MILLISECONDS);
|
Scheduler.Task syncTimeoutTask = clientFactory.getScheduler().schedule(timeout, TIMEOUT / 2, TimeUnit.MILLISECONDS);
|
||||||
{
|
{
|
||||||
long begin = System.nanoTime();
|
long begin = System.nanoTime();
|
||||||
List<Future<Object>> futures = threadPool.invokeAll(tasks);
|
List<Future<Object>> futures = threadPool.invokeAll(tasks);
|
||||||
|
@ -172,7 +178,7 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
long end = System.nanoTime();
|
long end = System.nanoTime();
|
||||||
System.err.printf("SYN+GET+DATA+GET completed in %d ms%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
|
System.err.printf("SYN+GET+DATA+GET completed in %d ms%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
|
||||||
}
|
}
|
||||||
timeoutTask.cancel();
|
syncTimeoutTask.cancel();
|
||||||
|
|
||||||
tasks.clear();
|
tasks.clear();
|
||||||
for (int i = 0; i < count; ++i)
|
for (int i = 0; i < count; ++i)
|
||||||
|
@ -187,7 +193,7 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
timeoutTask = clientFactory.getScheduler().schedule(timeout, TIMEOUT / 2, TimeUnit.MILLISECONDS);
|
Scheduler.Task asyncTimeoutTask = clientFactory.getScheduler().schedule(timeout, TIMEOUT / 2, TimeUnit.MILLISECONDS);
|
||||||
{
|
{
|
||||||
long begin = System.nanoTime();
|
long begin = System.nanoTime();
|
||||||
List<Future<Object>> futures = threadPool.invokeAll(tasks);
|
List<Future<Object>> futures = threadPool.invokeAll(tasks);
|
||||||
|
@ -197,7 +203,8 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
long end = System.nanoTime();
|
long end = System.nanoTime();
|
||||||
System.err.printf("SYN+COMPLETED+DATA completed in %d ms%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
|
System.err.printf("SYN+COMPLETED+DATA completed in %d ms%n", TimeUnit.NANOSECONDS.toMillis(end - begin));
|
||||||
}
|
}
|
||||||
timeoutTask.cancel();
|
asyncTimeoutTask.cancel();
|
||||||
|
|
||||||
threadPool.shutdown();
|
threadPool.shutdown();
|
||||||
|
|
||||||
Assert.assertEquals(0, leaks.get());
|
Assert.assertEquals(0, leaks.get());
|
||||||
|
@ -206,7 +213,7 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
private void synCompletedData(Session session, Fields headers, int iterations) throws Exception
|
private void synCompletedData(Session session, Fields headers, int iterations) throws Exception
|
||||||
{
|
{
|
||||||
final Map<Integer, Integer> counter = new ConcurrentHashMap<>(iterations);
|
final Map<Integer, Integer> counter = new ConcurrentHashMap<>(iterations);
|
||||||
final CountDownLatch latch = new CountDownLatch(2 * iterations);
|
final CountDownLatch requestsLatch = new CountDownLatch(2 * iterations);
|
||||||
for (int i = 0; i < iterations; ++i)
|
for (int i = 0; i < iterations; ++i)
|
||||||
{
|
{
|
||||||
final AtomicInteger count = new AtomicInteger(2);
|
final AtomicInteger count = new AtomicInteger(2);
|
||||||
|
@ -218,7 +225,7 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
public void onReply(Stream stream, ReplyInfo replyInfo)
|
public void onReply(Stream stream, ReplyInfo replyInfo)
|
||||||
{
|
{
|
||||||
Assert.assertEquals(2, count.getAndDecrement());
|
Assert.assertEquals(2, count.getAndDecrement());
|
||||||
latch.countDown();
|
requestsLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -230,7 +237,7 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
{
|
{
|
||||||
Assert.assertEquals(1, count.getAndDecrement());
|
Assert.assertEquals(1, count.getAndDecrement());
|
||||||
counter.remove(index);
|
counter.remove(index);
|
||||||
latch.countDown();
|
requestsLatch.countDown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, new Promise.Adapter<Stream>()
|
}, new Promise.Adapter<Stream>()
|
||||||
|
@ -244,7 +251,7 @@ public class SynDataReplyDataLoadTest extends AbstractTest
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS));
|
Assert.assertTrue(requestsLatch.await(iterations, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(counter.toString(), counter.isEmpty());
|
Assert.assertTrue(counter.toString(), counter.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
org.eclipse.jetty.spdy.LEVEL=WARN
|
#org.eclipse.jetty.spdy.LEVEL=DEBUG
|
||||||
|
|
|
@ -39,6 +39,13 @@ import java.util.regex.Pattern;
|
||||||
public class Modules implements Iterable<Module>
|
public class Modules implements Iterable<Module>
|
||||||
{
|
{
|
||||||
private Map<String, Module> modules = new HashMap<>();
|
private Map<String, Module> modules = new HashMap<>();
|
||||||
|
/*
|
||||||
|
* modules that may appear in the resolved graph but are undefined in the module system
|
||||||
|
*
|
||||||
|
* ex: modules/npn/npn-1.7.0_01.mod (property expansion resolves to non-existent file)
|
||||||
|
*/
|
||||||
|
private Set<String> missingModules = new HashSet<String>();
|
||||||
|
|
||||||
private int maxDepth = -1;
|
private int maxDepth = -1;
|
||||||
|
|
||||||
private Set<String> asNameSet(Set<Module> moduleSet)
|
private Set<String> asNameSet(Set<Module> moduleSet)
|
||||||
|
@ -110,7 +117,7 @@ public class Modules implements Iterable<Module>
|
||||||
|
|
||||||
if (parent == null)
|
if (parent == null)
|
||||||
{
|
{
|
||||||
System.err.printf("WARNING: module not found [%s]%n",parentName);
|
StartLog.debug("module not found [%s]%n",parentName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -124,7 +131,7 @@ public class Modules implements Iterable<Module>
|
||||||
Module optional = get(optionalParentName);
|
Module optional = get(optionalParentName);
|
||||||
if (optional == null)
|
if (optional == null)
|
||||||
{
|
{
|
||||||
System.err.printf("WARNING: module not found [%s]%n",optionalParentName);
|
StartLog.debug("optional module not found [%s]%n",optionalParentName);
|
||||||
}
|
}
|
||||||
else if (optional.isEnabled())
|
else if (optional.isEnabled())
|
||||||
{
|
{
|
||||||
|
@ -285,12 +292,12 @@ public class Modules implements Iterable<Module>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findParents(Module module, Set<Module> ret)
|
private void findParents(Module module, Map<String, Module> ret)
|
||||||
{
|
{
|
||||||
ret.add(module);
|
ret.put(module.getName(), module);
|
||||||
for (Module parent : module.getParentEdges())
|
for (Module parent : module.getParentEdges())
|
||||||
{
|
{
|
||||||
ret.add(parent);
|
ret.put(parent.getName(), parent);
|
||||||
findParents(parent,ret);
|
findParents(parent,ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,7 +387,7 @@ public class Modules implements Iterable<Module>
|
||||||
{
|
{
|
||||||
for (String parent : m.getParentNames())
|
for (String parent : m.getParentNames())
|
||||||
{
|
{
|
||||||
if (modules.containsKey(parent))
|
if (modules.containsKey(parent) || missingModules.contains(parent))
|
||||||
{
|
{
|
||||||
continue; // found. skip it.
|
continue; // found. skip it.
|
||||||
}
|
}
|
||||||
|
@ -392,9 +399,17 @@ public class Modules implements Iterable<Module>
|
||||||
for (String missingParent : missingParents)
|
for (String missingParent : missingParents)
|
||||||
{
|
{
|
||||||
File file = basehome.getFile("modules/" + missingParent + ".mod");
|
File file = basehome.getFile("modules/" + missingParent + ".mod");
|
||||||
|
if ( FS.canReadFile(file) )
|
||||||
|
{
|
||||||
Module module = registerModule(basehome,args,file);
|
Module module = registerModule(basehome,args,file);
|
||||||
updateParentReferencesTo(module);
|
updateParentReferencesTo(module);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartLog.debug("Missing module definition: [ Mod: %s | File: %s]", missingParent, file);
|
||||||
|
missingModules.add(missingParent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,7 +440,7 @@ public class Modules implements Iterable<Module>
|
||||||
*/
|
*/
|
||||||
public List<Module> resolveEnabled()
|
public List<Module> resolveEnabled()
|
||||||
{
|
{
|
||||||
Set<Module> active = new HashSet<Module>();
|
Map<String, Module> active = new HashMap<String,Module>();
|
||||||
|
|
||||||
for (Module module : modules.values())
|
for (Module module : modules.values())
|
||||||
{
|
{
|
||||||
|
@ -435,18 +450,37 @@ public class Modules implements Iterable<Module>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check against the missing modules
|
||||||
|
*
|
||||||
|
* Ex: npn should match anything under npn/
|
||||||
|
*/
|
||||||
|
for ( String missing : missingModules )
|
||||||
|
{
|
||||||
|
for (String activeModule: active.keySet())
|
||||||
|
{
|
||||||
|
if ( missing.startsWith(activeModule) )
|
||||||
|
{
|
||||||
|
StartLog.warn("** Unable to continue, required dependency missing. [%s]", missing);
|
||||||
|
StartLog.warn("** As configured, Jetty is unable to start due to a missing enabled module dependency.");
|
||||||
|
StartLog.warn("** This may be due to a transitive dependency akin to spdy on npn, which resolves based on the JDK in use.");
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Module> ordered = new ArrayList<>();
|
List<Module> ordered = new ArrayList<>();
|
||||||
ordered.addAll(active);
|
ordered.addAll(active.values());
|
||||||
Collections.sort(ordered,new Module.DepthComparator());
|
Collections.sort(ordered,new Module.DepthComparator());
|
||||||
return ordered;
|
return ordered;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> resolveParentModulesOf(String moduleName)
|
public Set<String> resolveParentModulesOf(String moduleName)
|
||||||
{
|
{
|
||||||
Set<Module> ret = new HashSet<>();
|
Map<String,Module> ret = new HashMap<>();
|
||||||
Module module = get(moduleName);
|
Module module = get(moduleName);
|
||||||
findParents(module,ret);
|
findParents(module,ret);
|
||||||
return asNameSet(ret);
|
return ret.keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toIndent(int depth)
|
private String toIndent(int depth)
|
||||||
|
|
|
@ -98,8 +98,9 @@ public final class Props implements Iterable<Prop>
|
||||||
|
|
||||||
if (props.isEmpty())
|
if (props.isEmpty())
|
||||||
{
|
{
|
||||||
// This is a stupid programming error, we should have something, even system properties
|
// nothing to expand
|
||||||
throw new PropsException("Props is empty: no properties declared!?");
|
// this situation can occur from --add-to-startd on a new blank base directory
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pattern pat = Pattern.compile("(?<=[^$]|^)(\\$\\{[^}]*\\})");
|
Pattern pat = Pattern.compile("(?<=[^$]|^)(\\$\\{[^}]*\\})");
|
||||||
|
|
|
@ -18,10 +18,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.start;
|
package org.eclipse.jetty.start;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -45,6 +42,15 @@ public class BaseHomeTest
|
||||||
Assert.assertThat(message + ": " + Main.join(actual,", "),actual,containsInAnyOrder(expected.toArray()));
|
Assert.assertThat(message + ": " + Main.join(actual,", "),actual,containsInAnyOrder(expected.toArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void toOsSeparators(List<String> expected)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < expected.size(); i++)
|
||||||
|
{
|
||||||
|
String fixed = FS.separators(expected.get(i));
|
||||||
|
expected.set(i,fixed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetFile_OnlyHome() throws IOException
|
public void testGetFile_OnlyHome() throws IOException
|
||||||
{
|
{
|
||||||
|
@ -76,6 +82,7 @@ public class BaseHomeTest
|
||||||
expected.add("${jetty.home}/start.d/jsp.ini");
|
expected.add("${jetty.home}/start.d/jsp.ini");
|
||||||
expected.add("${jetty.home}/start.d/logging.ini");
|
expected.add("${jetty.home}/start.d/logging.ini");
|
||||||
expected.add("${jetty.home}/start.d/ssl.ini");
|
expected.add("${jetty.home}/start.d/ssl.ini");
|
||||||
|
toOsSeparators(expected);
|
||||||
|
|
||||||
assertFileList(hb,"Files found",expected,files);
|
assertFileList(hb,"Files found",expected,files);
|
||||||
}
|
}
|
||||||
|
@ -95,6 +102,7 @@ public class BaseHomeTest
|
||||||
expected.add("${jetty.home}/start.d/jsp.ini");
|
expected.add("${jetty.home}/start.d/jsp.ini");
|
||||||
expected.add("${jetty.home}/start.d/logging.ini");
|
expected.add("${jetty.home}/start.d/logging.ini");
|
||||||
expected.add("${jetty.home}/start.d/ssl.ini");
|
expected.add("${jetty.home}/start.d/ssl.ini");
|
||||||
|
toOsSeparators(expected);
|
||||||
|
|
||||||
assertFileList(hb,"Files found",expected,files);
|
assertFileList(hb,"Files found",expected,files);
|
||||||
}
|
}
|
||||||
|
@ -115,6 +123,7 @@ public class BaseHomeTest
|
||||||
expected.add("${jetty.base}/start.d/logging.ini");
|
expected.add("${jetty.base}/start.d/logging.ini");
|
||||||
expected.add("${jetty.home}/start.d/ssl.ini");
|
expected.add("${jetty.home}/start.d/ssl.ini");
|
||||||
expected.add("${jetty.base}/start.d/myapp.ini");
|
expected.add("${jetty.base}/start.d/myapp.ini");
|
||||||
|
toOsSeparators(expected);
|
||||||
|
|
||||||
assertFileList(hb,"Files found",expected,files);
|
assertFileList(hb,"Files found",expected,files);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class ConfigurationAssert
|
||||||
{
|
{
|
||||||
if (line.startsWith("XML|"))
|
if (line.startsWith("XML|"))
|
||||||
{
|
{
|
||||||
expectedXmls.add(getValue(line));
|
expectedXmls.add(FS.separators(getValue(line)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<String> actualXmls = new ArrayList<>();
|
List<String> actualXmls = new ArrayList<>();
|
||||||
|
@ -76,7 +76,7 @@ public class ConfigurationAssert
|
||||||
{
|
{
|
||||||
if (line.startsWith("LIB|"))
|
if (line.startsWith("LIB|"))
|
||||||
{
|
{
|
||||||
expectedLibs.add(getValue(line));
|
expectedLibs.add(FS.separators(getValue(line)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<String> actualLibs = new ArrayList<>();
|
List<String> actualLibs = new ArrayList<>();
|
||||||
|
|
|
@ -61,12 +61,25 @@ public class TestUseCases
|
||||||
assertUseCase("home","base.jmx","assert-jmx.txt");
|
assertUseCase("home","base.jmx","assert-jmx.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithMissingNpnVersion() throws Exception
|
||||||
|
{
|
||||||
|
assertUseCase("home","base.missing.npn.version","assert-missing-npn-version.txt","java.version=1.7.0_01");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWithSpdy() throws Exception
|
public void testWithSpdy() throws Exception
|
||||||
{
|
{
|
||||||
assertUseCase("home","base.enable.spdy","assert-enable-spdy.txt","java.version=1.7.0_21");
|
assertUseCase("home","base.enable.spdy","assert-enable-spdy.txt","java.version=1.7.0_21");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithSpdyBadNpnVersion() throws Exception
|
||||||
|
{
|
||||||
|
//StartLog.enableDebug();
|
||||||
|
assertUseCase("home","base.enable.spdy.bad.npn.version","assert-enable-spdy-bad-npn-version.txt","java.version=1.7.0_01");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWithDatabase() throws Exception
|
public void testWithDatabase() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# The XMLs we expect (order is important)
|
||||||
|
|
||||||
|
# The LIBs we expect (order is irrelevant)
|
||||||
|
|
||||||
|
# The Properties we expect (order is irrelevant)
|
||||||
|
PROP|jetty.port=9090
|
||||||
|
PROP|jetty.keystore=etc/keystore
|
||||||
|
PROP|jetty.keystore.password=friendly
|
||||||
|
PROP|jetty.keymanager.password=icecream
|
||||||
|
PROP|jetty.truststore=etc/keystore
|
||||||
|
PROP|jetty.truststore.password=sundae
|
||||||
|
PROP|java.version=1.7.0_01
|
||||||
|
|
||||||
|
# The Downloads
|
||||||
|
|
||||||
|
# The Bootlib
|
|
@ -0,0 +1,28 @@
|
||||||
|
# The XMLs we expect (order is important)
|
||||||
|
XML|${jetty.home}/etc/jetty-jmx.xml
|
||||||
|
XML|${jetty.home}/etc/jetty.xml
|
||||||
|
XML|${jetty.home}/etc/jetty-http.xml
|
||||||
|
|
||||||
|
# The LIBs we expect (order is irrelevant)
|
||||||
|
LIB|${jetty.home}/lib/jetty-continuation-TEST.jar
|
||||||
|
LIB|${jetty.home}/lib/jetty-http-TEST.jar
|
||||||
|
LIB|${jetty.home}/lib/jetty-io-TEST.jar
|
||||||
|
LIB|${jetty.home}/lib/jetty-jmx-TEST.jar
|
||||||
|
LIB|${jetty.home}/lib/jetty-schemas-3.1.jar
|
||||||
|
LIB|${jetty.home}/lib/jetty-server-TEST.jar
|
||||||
|
LIB|${jetty.home}/lib/jetty-util-TEST.jar
|
||||||
|
LIB|${jetty.home}/lib/jetty-xml-TEST.jar
|
||||||
|
LIB|${jetty.home}/lib/servlet-api-3.1.jar
|
||||||
|
|
||||||
|
# The Properties we expect (order is irrelevant)
|
||||||
|
PROP|jetty.port=9090
|
||||||
|
PROP|jetty.keystore=etc/keystore
|
||||||
|
PROP|jetty.keystore.password=friendly
|
||||||
|
PROP|jetty.keymanager.password=icecream
|
||||||
|
PROP|jetty.truststore=etc/keystore
|
||||||
|
PROP|jetty.truststore.password=sundae
|
||||||
|
PROP|java.version=1.7.0_01
|
||||||
|
|
||||||
|
# The Downloads
|
||||||
|
|
||||||
|
# The Bootlib
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
--module=server,http,jmx,spdy
|
||||||
|
|
||||||
|
jetty.port=9090
|
||||||
|
|
||||||
|
# Some SSL keystore configuration
|
||||||
|
jetty.keystore=etc/keystore
|
||||||
|
jetty.keystore.password=friendly
|
||||||
|
jetty.keymanager.password=icecream
|
||||||
|
jetty.truststore=etc/keystore
|
||||||
|
jetty.truststore.password=sundae
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
--module=server,http,jmx
|
||||||
|
|
||||||
|
jetty.port=9090
|
||||||
|
|
||||||
|
# Some SSL keystore configuration
|
||||||
|
jetty.keystore=etc/keystore
|
||||||
|
jetty.keystore.password=friendly
|
||||||
|
jetty.keymanager.password=icecream
|
||||||
|
jetty.truststore=etc/keystore
|
||||||
|
jetty.truststore.password=sundae
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[name]
|
||||||
|
npn-boot
|
||||||
|
|
||||||
|
[files]
|
||||||
|
http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar:lib/npn/npn-boot-1.1.6.v20130911.jar
|
||||||
|
|
||||||
|
[ini-template]
|
||||||
|
--exec
|
||||||
|
-Xbootclasspath/p:lib/npn/npn-boot-1.1.6.v20130911.jar
|
0
jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
Executable file → Normal file
0
jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
Executable file → Normal file
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.util;
|
||||||
|
|
||||||
import java.lang.ref.PhantomReference;
|
import java.lang.ref.PhantomReference;
|
||||||
import java.lang.ref.ReferenceQueue;
|
import java.lang.ref.ReferenceQueue;
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
|
0
jetty-util/src/test/resources/TestData/test/subdir/subsubdir/alphabet
Executable file → Normal file
0
jetty-util/src/test/resources/TestData/test/subdir/subsubdir/alphabet
Executable file → Normal file
0
jetty-util/src/test/resources/TestData/test/subdir/subsubdir/numbers
Executable file → Normal file
0
jetty-util/src/test/resources/TestData/test/subdir/subsubdir/numbers
Executable file → Normal file
|
@ -398,6 +398,9 @@ public class WebInfConfiguration extends AbstractConfiguration
|
||||||
else
|
else
|
||||||
web_app=context.getBaseResource();
|
web_app=context.getBaseResource();
|
||||||
|
|
||||||
|
if (web_app == null)
|
||||||
|
throw new IllegalStateException("No resourceBase or war set for context");
|
||||||
|
|
||||||
// Accept aliases for WAR files
|
// Accept aliases for WAR files
|
||||||
if (web_app.getAlias() != null)
|
if (web_app.getAlias() != null)
|
||||||
{
|
{
|
||||||
|
@ -603,7 +606,7 @@ public class WebInfConfiguration extends AbstractConfiguration
|
||||||
if (resource == null)
|
if (resource == null)
|
||||||
{
|
{
|
||||||
if (context.getWar()==null || context.getWar().length()==0)
|
if (context.getWar()==null || context.getWar().length()==0)
|
||||||
resource=context.newResource(context.getResourceBase());
|
throw new IllegalStateException("No resourceBase or war set for context");
|
||||||
|
|
||||||
// Set dir or WAR
|
// Set dir or WAR
|
||||||
resource = context.newResource(context.getWar());
|
resource = context.newResource(context.getWar());
|
||||||
|
@ -621,7 +624,8 @@ public class WebInfConfiguration extends AbstractConfiguration
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOG.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
|
LOG.warn("Can't generate resourceBase as part of webapp tmp dir name: " + e);
|
||||||
|
LOG.debug(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Context name
|
//Context name
|
||||||
|
|
|
@ -30,7 +30,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
import javax.websocket.ClientEndpoint;
|
import javax.websocket.ClientEndpoint;
|
||||||
import javax.websocket.ClientEndpointConfig;
|
import javax.websocket.ClientEndpointConfig;
|
||||||
import javax.websocket.DeploymentException;
|
import javax.websocket.DeploymentException;
|
||||||
|
@ -187,6 +186,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
|
||||||
@Override
|
@Override
|
||||||
protected void doStop() throws Exception
|
protected void doStop() throws Exception
|
||||||
{
|
{
|
||||||
|
ShutdownThread.deregister(this);
|
||||||
endpointClientMetadataCache.clear();
|
endpointClientMetadataCache.clear();
|
||||||
super.doStop();
|
super.doStop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.jsr356;
|
package org.eclipse.jetty.websocket.jsr356;
|
||||||
|
|
||||||
|
import java.net.HttpCookie;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.websocket.ClientEndpointConfig.Configurator;
|
import javax.websocket.ClientEndpointConfig.Configurator;
|
||||||
|
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
|
@ -46,7 +46,23 @@ public class JsrUpgradeListener implements UpgradeListener
|
||||||
|
|
||||||
Map<String, List<String>> headers = request.getHeaders();
|
Map<String, List<String>> headers = request.getHeaders();
|
||||||
configurator.beforeRequest(headers);
|
configurator.beforeRequest(headers);
|
||||||
request.setHeaders(headers);
|
|
||||||
|
// Handle cookies
|
||||||
|
for (String name : headers.keySet())
|
||||||
|
{
|
||||||
|
if ("cookie".equalsIgnoreCase(name))
|
||||||
|
{
|
||||||
|
List<String> values = headers.get(name);
|
||||||
|
if (values != null)
|
||||||
|
{
|
||||||
|
for (String cookie : values)
|
||||||
|
{
|
||||||
|
List<HttpCookie> cookies = HttpCookie.parse(cookie);
|
||||||
|
request.getCookies().addAll(cookies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.websocket.jsr356;
|
||||||
|
|
||||||
|
import java.net.HttpCookie;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.websocket.ClientEndpointConfig;
|
||||||
|
import javax.websocket.ContainerProvider;
|
||||||
|
import javax.websocket.Endpoint;
|
||||||
|
import javax.websocket.EndpointConfig;
|
||||||
|
import javax.websocket.HandshakeResponse;
|
||||||
|
import javax.websocket.Session;
|
||||||
|
import javax.websocket.WebSocketContainer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||||
|
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class CookiesTest
|
||||||
|
{
|
||||||
|
private Server server;
|
||||||
|
private ServerConnector connector;
|
||||||
|
|
||||||
|
protected void startServer(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
connector = new ServerConnector(server);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
ContextHandler context = new ContextHandler();
|
||||||
|
context.setContextPath("/");
|
||||||
|
context.setHandler(handler);
|
||||||
|
server.setHandler(context);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void stopServer() throws Exception
|
||||||
|
{
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCookiesAreSentToServer() throws Exception
|
||||||
|
{
|
||||||
|
final String cookieName = "name";
|
||||||
|
final String cookieValue = "value";
|
||||||
|
final String cookieString = cookieName + "=" + cookieValue;
|
||||||
|
startServer(new EchoHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response)
|
||||||
|
{
|
||||||
|
List<HttpCookie> cookies = request.getCookies();
|
||||||
|
Assert.assertNotNull(cookies);
|
||||||
|
Assert.assertEquals(1, cookies.size());
|
||||||
|
HttpCookie cookie = cookies.get(0);
|
||||||
|
Assert.assertEquals(cookieName, cookie.getName());
|
||||||
|
Assert.assertEquals(cookieValue, cookie.getValue());
|
||||||
|
|
||||||
|
Map<String, List<String>> headers = request.getHeaders();
|
||||||
|
// Test case insensitivity
|
||||||
|
Assert.assertTrue(headers.containsKey("cookie"));
|
||||||
|
List<String> values = headers.get("Cookie");
|
||||||
|
Assert.assertNotNull(values);
|
||||||
|
Assert.assertEquals(1, values.size());
|
||||||
|
Assert.assertEquals(cookieString, values.get(0));
|
||||||
|
|
||||||
|
return super.createWebSocket(request, response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
|
||||||
|
|
||||||
|
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
|
||||||
|
builder.configurator(new ClientEndpointConfig.Configurator()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void beforeRequest(Map<String, List<String>> headers)
|
||||||
|
{
|
||||||
|
headers.put("Cookie", Collections.singletonList(cookieString));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ClientEndpointConfig config = builder.build();
|
||||||
|
|
||||||
|
Endpoint endPoint = new Endpoint()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onOpen(Session session, EndpointConfig config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Session session = container.connectToServer(endPoint, config, URI.create("ws://localhost:" + connector.getLocalPort()));
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCookiesAreSentToClient() throws Exception
|
||||||
|
{
|
||||||
|
final String cookieName = "name";
|
||||||
|
final String cookieValue = "value";
|
||||||
|
final String cookieDomain = "domain";
|
||||||
|
final String cookiePath = "/path";
|
||||||
|
startServer(new EchoHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response)
|
||||||
|
{
|
||||||
|
String cookieString = cookieName + "=" + cookieValue + ";Domain=" + cookieDomain + ";Path=" + cookiePath;
|
||||||
|
response.getHeaders().put("Set-Cookie", Collections.singletonList(cookieString));
|
||||||
|
return super.createWebSocket(request, response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
|
||||||
|
|
||||||
|
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
|
||||||
|
builder.configurator(new ClientEndpointConfig.Configurator()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void afterResponse(HandshakeResponse response)
|
||||||
|
{
|
||||||
|
Map<String, List<String>> headers = response.getHeaders();
|
||||||
|
// Test case insensitivity
|
||||||
|
Assert.assertTrue(headers.containsKey("set-cookie"));
|
||||||
|
List<String> values = headers.get("Set-Cookie");
|
||||||
|
Assert.assertNotNull(values);
|
||||||
|
Assert.assertEquals(1, values.size());
|
||||||
|
|
||||||
|
List<HttpCookie> cookies = HttpCookie.parse(values.get(0));
|
||||||
|
Assert.assertEquals(1, cookies.size());
|
||||||
|
HttpCookie cookie = cookies.get(0);
|
||||||
|
Assert.assertEquals(cookieName, cookie.getName());
|
||||||
|
Assert.assertEquals(cookieValue, cookie.getValue());
|
||||||
|
Assert.assertEquals(cookieDomain, cookie.getDomain());
|
||||||
|
Assert.assertEquals(cookiePath, cookie.getPath());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ClientEndpointConfig config = builder.build();
|
||||||
|
|
||||||
|
Endpoint endPoint = new Endpoint()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onOpen(Session session, EndpointConfig config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Session session = container.connectToServer(endPoint, config, URI.create("ws://localhost:" + connector.getLocalPort()));
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,11 +26,13 @@ import java.util.Queue;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.beans.DateDecoder;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.beans.DateDecoder;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.beans.TimeEncoder;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.beans.TimeEncoder;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.ConfiguredEchoSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.ConfiguredEchoSocket;
|
||||||
|
@ -38,6 +40,7 @@ import org.eclipse.jetty.websocket.jsr356.server.samples.echo.EchoSocketConfigur
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,6 +48,9 @@ import org.junit.Test;
|
||||||
*/
|
*/
|
||||||
public class AnnotatedServerEndpointTest
|
public class AnnotatedServerEndpointTest
|
||||||
{
|
{
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
private static WSServer server;
|
private static WSServer server;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
@ -72,7 +78,7 @@ public class AnnotatedServerEndpointTest
|
||||||
|
|
||||||
private void assertResponse(String message, String... expectedTexts) throws Exception
|
private void assertResponse(String message, String... expectedTexts) throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient(bufferPool);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.start();
|
client.start();
|
||||||
|
|
|
@ -23,10 +23,12 @@ import java.util.Queue;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpointConfigContextListener;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpointConfigContextListener;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -42,6 +44,9 @@ public class BasicEndpointTest
|
||||||
@Rule
|
@Rule
|
||||||
public TestingDir testdir = new TestingDir();
|
public TestingDir testdir = new TestingDir();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEcho() throws Exception
|
public void testEcho() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -61,7 +66,7 @@ public class BasicEndpointTest
|
||||||
wsb.deployWebapp(webapp);
|
wsb.deployWebapp(webapp);
|
||||||
// wsb.dump();
|
// wsb.dump();
|
||||||
|
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient(bufferPool);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.start();
|
client.start();
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
@ -36,6 +37,7 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.IdleTimeoutContextListener;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.IdleTimeoutContextListener;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.OnOpenIdleTimeoutEndpoint;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.OnOpenIdleTimeoutEndpoint;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.OnOpenIdleTimeoutSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.idletimeout.OnOpenIdleTimeoutSocket;
|
||||||
|
@ -51,6 +53,9 @@ public class IdleTimeoutTest
|
||||||
@Rule
|
@Rule
|
||||||
public TestingDir testdir = new TestingDir();
|
public TestingDir testdir = new TestingDir();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
private static WSServer server;
|
private static WSServer server;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
@ -80,7 +85,7 @@ public class IdleTimeoutTest
|
||||||
|
|
||||||
private void assertConnectionTimeout(URI uri) throws Exception, IOException, InterruptedException, ExecutionException, TimeoutException
|
private void assertConnectionTimeout(URI uri) throws Exception, IOException, InterruptedException, ExecutionException, TimeoutException
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient(bufferPool);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.start();
|
client.start();
|
||||||
|
|
|
@ -25,10 +25,12 @@ import java.util.Queue;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoConfiguredSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoConfiguredSocket;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -42,6 +44,9 @@ public class LargeAnnotatedTest
|
||||||
@Rule
|
@Rule
|
||||||
public TestingDir testdir = new TestingDir();
|
public TestingDir testdir = new TestingDir();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEcho() throws Exception
|
public void testEcho() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -58,7 +63,7 @@ public class LargeAnnotatedTest
|
||||||
wsb.deployWebapp(webapp);
|
wsb.deployWebapp(webapp);
|
||||||
// wsb.dump();
|
// wsb.dump();
|
||||||
|
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient(bufferPool);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.getPolicy().setMaxTextMessageSize(128*1024);
|
client.getPolicy().setMaxTextMessageSize(128*1024);
|
||||||
|
|
|
@ -25,10 +25,12 @@ import java.util.Queue;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoDefaultSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.LargeEchoDefaultSocket;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -42,6 +44,9 @@ public class LargeContainerTest
|
||||||
@Rule
|
@Rule
|
||||||
public TestingDir testdir = new TestingDir();
|
public TestingDir testdir = new TestingDir();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEcho() throws Exception
|
public void testEcho() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -58,7 +63,7 @@ public class LargeContainerTest
|
||||||
wsb.deployWebapp(webapp);
|
wsb.deployWebapp(webapp);
|
||||||
// wsb.dump();
|
// wsb.dump();
|
||||||
|
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient(bufferPool);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.getPolicy().setMaxTextMessageSize(128*1024);
|
client.getPolicy().setMaxTextMessageSize(128*1024);
|
||||||
|
|
|
@ -23,10 +23,12 @@ import java.util.Queue;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.EchoReturnEndpoint;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.EchoReturnEndpoint;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -37,6 +39,9 @@ public class OnMessageReturnTest
|
||||||
@Rule
|
@Rule
|
||||||
public TestingDir testdir = new TestingDir();
|
public TestingDir testdir = new TestingDir();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEchoReturn() throws Exception
|
public void testEchoReturn() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -53,7 +58,7 @@ public class OnMessageReturnTest
|
||||||
wsb.deployWebapp(webapp);
|
wsb.deployWebapp(webapp);
|
||||||
wsb.dump();
|
wsb.dump();
|
||||||
|
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient(bufferPool);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.start();
|
client.start();
|
||||||
|
|
|
@ -27,15 +27,19 @@ import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Parameterized;
|
import org.junit.runners.Parameterized;
|
||||||
|
@ -95,7 +99,11 @@ public class SessionTest
|
||||||
return cases;
|
return cases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
private final Case testcase;
|
private final Case testcase;
|
||||||
|
private final static AtomicInteger ID = new AtomicInteger(0);
|
||||||
private WSServer server;
|
private WSServer server;
|
||||||
private URI serverUri;
|
private URI serverUri;
|
||||||
|
|
||||||
|
@ -107,7 +115,7 @@ public class SessionTest
|
||||||
@Before
|
@Before
|
||||||
public void startServer() throws Exception
|
public void startServer() throws Exception
|
||||||
{
|
{
|
||||||
server = new WSServer(MavenTestingUtils.getTargetTestingDir(SessionTest.class.getSimpleName()),"app");
|
server = new WSServer(MavenTestingUtils.getTargetTestingDir(SessionTest.class.getSimpleName() + "-" + ID.incrementAndGet()),"app");
|
||||||
server.copyWebInf("empty-web.xml");
|
server.copyWebInf("empty-web.xml");
|
||||||
server.copyClass(SessionInfoSocket.class);
|
server.copyClass(SessionInfoSocket.class);
|
||||||
server.copyClass(SessionAltConfig.class);
|
server.copyClass(SessionAltConfig.class);
|
||||||
|
@ -127,7 +135,7 @@ public class SessionTest
|
||||||
|
|
||||||
private void assertResponse(String requestPath, String requestMessage, String expectedResponse) throws Exception
|
private void assertResponse(String requestPath, String requestMessage, String expectedResponse) throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient(bufferPool);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.start();
|
client.start();
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
<module>websocket-client</module>
|
<module>websocket-client</module>
|
||||||
<module>websocket-server</module>
|
<module>websocket-server</module>
|
||||||
<module>websocket-servlet</module>
|
<module>websocket-servlet</module>
|
||||||
<module>websocket-mux-extension</module>
|
|
||||||
<module>javax-websocket-client-impl</module>
|
<module>javax-websocket-client-impl</module>
|
||||||
<module>javax-websocket-server-impl</module>
|
<module>javax-websocket-server-impl</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
|
@ -262,7 +262,8 @@ public class UpgradeRequest
|
||||||
|
|
||||||
public void setCookies(List<HttpCookie> cookies)
|
public void setCookies(List<HttpCookie> cookies)
|
||||||
{
|
{
|
||||||
this.cookies = cookies;
|
this.cookies.clear();
|
||||||
|
this.cookies.addAll(cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExtensions(List<ExtensionConfig> configs)
|
public void setExtensions(List<ExtensionConfig> configs)
|
||||||
|
|
|
@ -24,10 +24,10 @@ import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.B64Code;
|
import org.eclipse.jetty.util.B64Code;
|
||||||
|
@ -44,15 +44,17 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
*/
|
*/
|
||||||
public class ClientUpgradeRequest extends UpgradeRequest
|
public class ClientUpgradeRequest extends UpgradeRequest
|
||||||
{
|
{
|
||||||
private final static Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
|
private static final Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
|
||||||
private final static int MAX_KEYS = -1; // maximum number of parameter keys to decode
|
private static final int MAX_KEYS = -1; // maximum number of parameter keys to decode
|
||||||
private static final Set<String> FORBIDDEN_HEADERS;
|
private static final Set<String> FORBIDDEN_HEADERS;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
// headers not allowed to be set in ClientUpgradeRequest.headers
|
// Headers not allowed to be set in ClientUpgradeRequest.headers.
|
||||||
FORBIDDEN_HEADERS = new HashSet<>();
|
FORBIDDEN_HEADERS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
// Cookies are handled explicitly, avoid to add them twice.
|
||||||
FORBIDDEN_HEADERS.add("cookie");
|
FORBIDDEN_HEADERS.add("cookie");
|
||||||
|
// Headers that cannot be set by applications.
|
||||||
FORBIDDEN_HEADERS.add("upgrade");
|
FORBIDDEN_HEADERS.add("upgrade");
|
||||||
FORBIDDEN_HEADERS.add("host");
|
FORBIDDEN_HEADERS.add("host");
|
||||||
FORBIDDEN_HEADERS.add("connection");
|
FORBIDDEN_HEADERS.add("connection");
|
||||||
|
@ -176,7 +178,7 @@ public class ClientUpgradeRequest extends UpgradeRequest
|
||||||
{
|
{
|
||||||
if (FORBIDDEN_HEADERS.contains(key))
|
if (FORBIDDEN_HEADERS.contains(key))
|
||||||
{
|
{
|
||||||
LOG.warn("Skipping forbidden header - {}",key);
|
LOG.debug("Skipping forbidden header - {}",key);
|
||||||
continue; // skip
|
continue; // skip
|
||||||
}
|
}
|
||||||
request.append(key).append(": ");
|
request.append(key).append(": ");
|
||||||
|
|
|
@ -95,17 +95,27 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen
|
||||||
this(null,executor);
|
this(null,executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WebSocketClient(ByteBufferPool bufferPool)
|
||||||
|
{
|
||||||
|
this(null,null,bufferPool);
|
||||||
|
}
|
||||||
|
|
||||||
public WebSocketClient(SslContextFactory sslContextFactory)
|
public WebSocketClient(SslContextFactory sslContextFactory)
|
||||||
{
|
{
|
||||||
this(sslContextFactory,null);
|
this(sslContextFactory,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor)
|
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor)
|
||||||
|
{
|
||||||
|
this(sslContextFactory,executor,new MappedByteBufferPool());
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSocketClient(SslContextFactory sslContextFactory, Executor executor, ByteBufferPool bufferPool)
|
||||||
{
|
{
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.sslContextFactory = sslContextFactory;
|
this.sslContextFactory = sslContextFactory;
|
||||||
this.policy = WebSocketPolicy.newClientPolicy();
|
this.policy = WebSocketPolicy.newClientPolicy();
|
||||||
this.bufferPool = new MappedByteBufferPool();
|
this.bufferPool = bufferPool;
|
||||||
this.extensionRegistry = new WebSocketExtensionFactory(policy,bufferPool);
|
this.extensionRegistry = new WebSocketExtensionFactory(policy,bufferPool);
|
||||||
this.masker = new RandomMasker();
|
this.masker = new RandomMasker();
|
||||||
this.eventDriverFactory = new EventDriverFactory(policy);
|
this.eventDriverFactory = new EventDriverFactory(policy);
|
||||||
|
@ -254,7 +264,7 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen
|
||||||
}
|
}
|
||||||
|
|
||||||
super.doStop();
|
super.doStop();
|
||||||
LOG.info("Stopped {}",this);
|
LOG.debug("Stopped {}",this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,10 +22,12 @@ import java.net.URI;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
|
import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -41,13 +43,16 @@ public class BadNetworkTest
|
||||||
@Rule
|
@Rule
|
||||||
public TestTracker tt = new TestTracker();
|
public TestTracker tt = new TestTracker();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
private BlockheadServer server;
|
private BlockheadServer server;
|
||||||
private WebSocketClient client;
|
private WebSocketClient client;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void startClient() throws Exception
|
public void startClient() throws Exception
|
||||||
{
|
{
|
||||||
client = new WebSocketClient();
|
client = new WebSocketClient(bufferPool);
|
||||||
client.getPolicy().setIdleTimeout(250);
|
client.getPolicy().setIdleTimeout(250);
|
||||||
client.start();
|
client.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,12 +33,14 @@ import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.toolchain.test.OS;
|
import org.eclipse.jetty.toolchain.test.OS;
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.UpgradeException;
|
import org.eclipse.jetty.websocket.api.UpgradeException;
|
||||||
import org.eclipse.jetty.websocket.common.AcceptHash;
|
import org.eclipse.jetty.websocket.common.AcceptHash;
|
||||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
import org.eclipse.jetty.websocket.common.test.BlockheadServer;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
|
import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -55,6 +57,9 @@ public class ClientConnectTest
|
||||||
@Rule
|
@Rule
|
||||||
public TestTracker tt = new TestTracker();
|
public TestTracker tt = new TestTracker();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("Test",new MappedByteBufferPool());
|
||||||
|
|
||||||
private final int timeout = 500;
|
private final int timeout = 500;
|
||||||
private BlockheadServer server;
|
private BlockheadServer server;
|
||||||
private WebSocketClient client;
|
private WebSocketClient client;
|
||||||
|
@ -86,7 +91,7 @@ public class ClientConnectTest
|
||||||
@Before
|
@Before
|
||||||
public void startClient() throws Exception
|
public void startClient() throws Exception
|
||||||
{
|
{
|
||||||
client = new WebSocketClient();
|
client = new WebSocketClient(bufferPool);
|
||||||
client.setConnectTimeout(timeout);
|
client.setConnectTimeout(timeout);
|
||||||
client.start();
|
client.start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,13 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
import org.eclipse.jetty.websocket.common.OpCode;
|
import org.eclipse.jetty.websocket.common.OpCode;
|
||||||
|
import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is not a general purpose websocket client. It's only for testing the websocket server and is hardwired to a specific draft version of the protocol.
|
* This is not a general purpose websocket client. It's only for testing the websocket server and is hardwired to a specific draft version of the protocol.
|
||||||
|
@ -95,6 +97,8 @@ public class TestClient
|
||||||
|
|
||||||
private static final Random __random = new Random();
|
private static final Random __random = new Random();
|
||||||
|
|
||||||
|
private static LeakTrackingBufferPool bufferPool = new LeakTrackingBufferPool("TestClient",new MappedByteBufferPool());
|
||||||
|
|
||||||
private final String _host;
|
private final String _host;
|
||||||
private final int _port;
|
private final int _port;
|
||||||
private final String _protocol;
|
private final String _protocol;
|
||||||
|
@ -172,7 +176,7 @@ public class TestClient
|
||||||
}
|
}
|
||||||
|
|
||||||
TestClient[] client = new TestClient[clients];
|
TestClient[] client = new TestClient[clients];
|
||||||
WebSocketClient wsclient = new WebSocketClient();
|
WebSocketClient wsclient = new WebSocketClient(bufferPool);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
wsclient.start();
|
wsclient.start();
|
||||||
|
@ -250,6 +254,7 @@ public class TestClient
|
||||||
|
|
||||||
wsclient.stop();
|
wsclient.stop();
|
||||||
}
|
}
|
||||||
|
bufferPool.assertNoLeaks();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void usage(String[] args)
|
private static void usage(String[] args)
|
||||||
|
|
|
@ -67,8 +67,7 @@ public class Parser
|
||||||
private int cursor = 0;
|
private int cursor = 0;
|
||||||
// Frame
|
// Frame
|
||||||
private WebSocketFrame frame;
|
private WebSocketFrame frame;
|
||||||
private Frame priorDataFrame;
|
private boolean priorDataFrame;
|
||||||
private byte lastDataOpcode;
|
|
||||||
// payload specific
|
// payload specific
|
||||||
private ByteBuffer payload;
|
private ByteBuffer payload;
|
||||||
private int payloadLength;
|
private int payloadLength;
|
||||||
|
@ -186,7 +185,7 @@ public class Parser
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
{
|
{
|
||||||
LOG.debug("{} Notify {}",policy.getBehavior(),incomingFramesHandler);
|
LOG.debug("{} Notify {}",policy.getBehavior(),getIncomingFramesHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (policy.getBehavior() == WebSocketBehavior.SERVER)
|
if (policy.getBehavior() == WebSocketBehavior.SERVER)
|
||||||
|
@ -201,14 +200,15 @@ public class Parser
|
||||||
* But we can't trust the client at this point, so Jetty opts to close
|
* But we can't trust the client at this point, so Jetty opts to close
|
||||||
* the connection as a Protocol error.
|
* the connection as a Protocol error.
|
||||||
*/
|
*/
|
||||||
if (f.isMasked() == false)
|
if (!f.isMasked())
|
||||||
{
|
{
|
||||||
throw new ProtocolException("Client MUST mask all frames (RFC-6455: Section 5.1)");
|
throw new ProtocolException("Client MUST mask all frames (RFC-6455: Section 5.1)");
|
||||||
}
|
}
|
||||||
} else if(policy.getBehavior() == WebSocketBehavior.CLIENT)
|
}
|
||||||
|
else if(policy.getBehavior() == WebSocketBehavior.CLIENT)
|
||||||
{
|
{
|
||||||
// Required by RFC-6455 / Section 5.1
|
// Required by RFC-6455 / Section 5.1
|
||||||
if (f.isMasked() == true)
|
if (f.isMasked())
|
||||||
{
|
{
|
||||||
throw new ProtocolException("Server MUST NOT mask any frames (RFC-6455: Section 5.1)");
|
throw new ProtocolException("Server MUST NOT mask any frames (RFC-6455: Section 5.1)");
|
||||||
}
|
}
|
||||||
|
@ -258,30 +258,36 @@ public class Parser
|
||||||
{
|
{
|
||||||
LOG.debug("{} Parsed Frame: {}",policy.getBehavior(),frame);
|
LOG.debug("{} Parsed Frame: {}",policy.getBehavior(),frame);
|
||||||
notifyFrame(frame);
|
notifyFrame(frame);
|
||||||
if (frame.isDataFrame() && frame.isFin())
|
if (frame.isDataFrame())
|
||||||
{
|
{
|
||||||
priorDataFrame = null;
|
priorDataFrame = !frame.isFin();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
priorDataFrame = frame;
|
|
||||||
}
|
}
|
||||||
|
reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (WebSocketException e)
|
catch (WebSocketException e)
|
||||||
{
|
{
|
||||||
buffer.position(buffer.limit()); // consume remaining
|
buffer.position(buffer.limit()); // consume remaining
|
||||||
this.payload = null; // reset
|
reset();
|
||||||
notifyWebSocketException(e);
|
notifyWebSocketException(e);
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
buffer.position(buffer.limit()); // consume remaining
|
buffer.position(buffer.limit()); // consume remaining
|
||||||
this.payload = null; // reset
|
reset();
|
||||||
notifyWebSocketException(new WebSocketException(t));
|
notifyWebSocketException(new WebSocketException(t));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
if (frame != null)
|
||||||
|
frame.reset();
|
||||||
|
frame = null;
|
||||||
|
bufferPool.release(payload);
|
||||||
|
payload = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the base framing protocol buffer.
|
* Parse the base framing protocol buffer.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -295,11 +301,6 @@ public class Parser
|
||||||
*/
|
*/
|
||||||
private boolean parseFrame(ByteBuffer buffer)
|
private boolean parseFrame(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
if (buffer.remaining() <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug("{} Parsing {} bytes",policy.getBehavior(),buffer.remaining());
|
LOG.debug("{} Parsing {} bytes",policy.getBehavior(),buffer.remaining());
|
||||||
while (buffer.hasRemaining())
|
while (buffer.hasRemaining())
|
||||||
{
|
{
|
||||||
|
@ -307,21 +308,15 @@ public class Parser
|
||||||
{
|
{
|
||||||
case START:
|
case START:
|
||||||
{
|
{
|
||||||
if ((frame != null) && (frame.isFin()))
|
|
||||||
{
|
|
||||||
frame.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// peek at byte
|
// peek at byte
|
||||||
byte b = buffer.get();
|
byte b = buffer.get();
|
||||||
boolean fin = ((b & 0x80) != 0);
|
boolean fin = ((b & 0x80) != 0);
|
||||||
|
|
||||||
byte opc = (byte)(b & 0x0F);
|
byte opcode = (byte)(b & 0x0F);
|
||||||
byte opcode = opc;
|
|
||||||
|
|
||||||
if (!OpCode.isKnown(opcode))
|
if (!OpCode.isKnown(opcode))
|
||||||
{
|
{
|
||||||
throw new ProtocolException("Unknown opcode: " + opc);
|
throw new ProtocolException("Unknown opcode: " + opcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
@ -339,32 +334,28 @@ public class Parser
|
||||||
{
|
{
|
||||||
case OpCode.TEXT:
|
case OpCode.TEXT:
|
||||||
frame = new TextFrame();
|
frame = new TextFrame();
|
||||||
lastDataOpcode = opcode;
|
|
||||||
// data validation
|
// data validation
|
||||||
if ((priorDataFrame != null) && (!priorDataFrame.isFin()))
|
if (priorDataFrame)
|
||||||
{
|
{
|
||||||
throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
|
throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OpCode.BINARY:
|
case OpCode.BINARY:
|
||||||
frame = new BinaryFrame();
|
frame = new BinaryFrame();
|
||||||
lastDataOpcode = opcode;
|
|
||||||
// data validation
|
// data validation
|
||||||
if ((priorDataFrame != null) && (!priorDataFrame.isFin()))
|
if (priorDataFrame)
|
||||||
{
|
{
|
||||||
throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
|
throw new ProtocolException("Unexpected " + OpCode.name(opcode) + " frame, was expecting CONTINUATION");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OpCode.CONTINUATION:
|
case OpCode.CONTINUATION:
|
||||||
frame = new ContinuationFrame();
|
frame = new ContinuationFrame();
|
||||||
lastDataOpcode = opcode;
|
|
||||||
// continuation validation
|
// continuation validation
|
||||||
if (priorDataFrame == null)
|
if (!priorDataFrame)
|
||||||
{
|
{
|
||||||
throw new ProtocolException("CONTINUATION frame without prior !FIN");
|
throw new ProtocolException("CONTINUATION frame without prior !FIN");
|
||||||
}
|
}
|
||||||
// Be careful to use the original opcode
|
// Be careful to use the original opcode
|
||||||
opcode = lastDataOpcode;
|
|
||||||
break;
|
break;
|
||||||
case OpCode.CLOSE:
|
case OpCode.CLOSE:
|
||||||
frame = new CloseFrame();
|
frame = new CloseFrame();
|
||||||
|
@ -556,11 +547,13 @@ public class Parser
|
||||||
|
|
||||||
case PAYLOAD:
|
case PAYLOAD:
|
||||||
{
|
{
|
||||||
|
frame.assertValid();
|
||||||
if (parsePayload(buffer))
|
if (parsePayload(buffer))
|
||||||
{
|
{
|
||||||
// special check for close
|
// special check for close
|
||||||
if (frame.getOpCode() == OpCode.CLOSE)
|
if (frame.getOpCode() == OpCode.CLOSE)
|
||||||
{
|
{
|
||||||
|
// TODO: yuck. Don't create an object to do validation checks!
|
||||||
new CloseInfo(frame);
|
new CloseInfo(frame);
|
||||||
}
|
}
|
||||||
state = State.START;
|
state = State.START;
|
||||||
|
@ -591,21 +584,18 @@ public class Parser
|
||||||
|
|
||||||
if (buffer.hasRemaining())
|
if (buffer.hasRemaining())
|
||||||
{
|
{
|
||||||
if (payload == null)
|
|
||||||
{
|
|
||||||
frame.assertValid();
|
|
||||||
payload = bufferPool.acquire(payloadLength,false);
|
|
||||||
BufferUtil.clearToFill(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a small window of the incoming buffer to work with.
|
// Create a small window of the incoming buffer to work with.
|
||||||
// this should only show the payload itself, and not any more
|
// this should only show the payload itself, and not any more
|
||||||
// bytes that could belong to the start of the next frame.
|
// bytes that could belong to the start of the next frame.
|
||||||
ByteBuffer window = buffer.slice();
|
int bytesSoFar = payload == null ? 0 : payload.position();
|
||||||
int bytesExpected = payloadLength - payload.position();
|
int bytesExpected = payloadLength - bytesSoFar;
|
||||||
int bytesAvailable = buffer.remaining();
|
int bytesAvailable = buffer.remaining();
|
||||||
int windowBytes = Math.min(bytesAvailable, bytesExpected);
|
int windowBytes = Math.min(bytesAvailable, bytesExpected);
|
||||||
window.limit(window.position() + windowBytes);
|
int limit = buffer.limit();
|
||||||
|
buffer.limit(buffer.position() + windowBytes);
|
||||||
|
ByteBuffer window = buffer.slice();
|
||||||
|
buffer.limit(limit);
|
||||||
|
buffer.position(buffer.position() + window.remaining());
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
{
|
{
|
||||||
|
@ -613,18 +603,31 @@ public class Parser
|
||||||
}
|
}
|
||||||
|
|
||||||
maskProcessor.process(window);
|
maskProcessor.process(window);
|
||||||
int len = BufferUtil.put(window,payload);
|
|
||||||
|
|
||||||
buffer.position(buffer.position() + len); // update incoming buffer position
|
if (window.remaining() == payloadLength)
|
||||||
|
{
|
||||||
|
// We have the whole content, no need to copy.
|
||||||
|
frame.setPayload(window);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (payload == null)
|
||||||
|
{
|
||||||
|
payload = bufferPool.acquire(payloadLength,false);
|
||||||
|
BufferUtil.clearToFill(payload);
|
||||||
|
}
|
||||||
|
// Copy the payload.
|
||||||
|
payload.put(window);
|
||||||
|
|
||||||
if (payload.position() >= payloadLength)
|
if (payload.position() == payloadLength)
|
||||||
{
|
{
|
||||||
BufferUtil.flipToFlush(payload, 0);
|
BufferUtil.flipToFlush(payload, 0);
|
||||||
frame.setPayload(payload);
|
frame.setPayload(payload);
|
||||||
this.payload = null;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,37 +56,42 @@ import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||||
*/
|
*/
|
||||||
public abstract class WebSocketFrame implements Frame
|
public abstract class WebSocketFrame implements Frame
|
||||||
{
|
{
|
||||||
public static WebSocketFrame copy(Frame copy)
|
public static WebSocketFrame copy(Frame original)
|
||||||
{
|
{
|
||||||
WebSocketFrame frame = null;
|
WebSocketFrame copy;
|
||||||
switch (copy.getOpCode())
|
switch (original.getOpCode())
|
||||||
{
|
{
|
||||||
case OpCode.BINARY:
|
case OpCode.BINARY:
|
||||||
frame = new BinaryFrame();
|
copy = new BinaryFrame();
|
||||||
break;
|
break;
|
||||||
case OpCode.TEXT:
|
case OpCode.TEXT:
|
||||||
frame = new TextFrame();
|
copy = new TextFrame();
|
||||||
break;
|
break;
|
||||||
case OpCode.CLOSE:
|
case OpCode.CLOSE:
|
||||||
frame = new CloseFrame();
|
copy = new CloseFrame();
|
||||||
break;
|
break;
|
||||||
case OpCode.CONTINUATION:
|
case OpCode.CONTINUATION:
|
||||||
frame = new ContinuationFrame();
|
copy = new ContinuationFrame();
|
||||||
break;
|
break;
|
||||||
case OpCode.PING:
|
case OpCode.PING:
|
||||||
frame = new PingFrame();
|
copy = new PingFrame();
|
||||||
break;
|
break;
|
||||||
case OpCode.PONG:
|
case OpCode.PONG:
|
||||||
frame = new PongFrame();
|
copy = new PongFrame();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Cannot copy frame with opcode " + copy.getOpCode() + " - " + copy);
|
throw new IllegalArgumentException("Cannot copy frame with opcode " + original.getOpCode() + " - " + original);
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.copyHeaders(copy);
|
copy.copyHeaders(original);
|
||||||
frame.setPayload(copy.getPayload());
|
ByteBuffer payload = original.getPayload();
|
||||||
|
if (payload != null)
|
||||||
return frame;
|
{
|
||||||
|
ByteBuffer payloadCopy = ByteBuffer.allocate(payload.remaining());
|
||||||
|
payloadCopy.put(payload.slice()).flip();
|
||||||
|
copy.setPayload(payloadCopy);
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,12 +128,6 @@ public abstract class WebSocketFrame implements Frame
|
||||||
|
|
||||||
public abstract void assertValid();
|
public abstract void assertValid();
|
||||||
|
|
||||||
protected void copy(WebSocketFrame copy, ByteBuffer payload)
|
|
||||||
{
|
|
||||||
copyHeaders(copy);
|
|
||||||
setPayload(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void copyHeaders(Frame frame)
|
protected void copyHeaders(Frame frame)
|
||||||
{
|
{
|
||||||
finRsvOp = 0x00;
|
finRsvOp = 0x00;
|
||||||
|
@ -348,13 +347,7 @@ public abstract class WebSocketFrame implements Frame
|
||||||
*/
|
*/
|
||||||
public WebSocketFrame setPayload(ByteBuffer buf)
|
public WebSocketFrame setPayload(ByteBuffer buf)
|
||||||
{
|
{
|
||||||
if (buf == null)
|
data = buf;
|
||||||
{
|
|
||||||
data = null;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = buf.slice();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,15 +122,9 @@ public abstract class ControlFrame extends WebSocketFrame
|
||||||
@Override
|
@Override
|
||||||
public WebSocketFrame setPayload(ByteBuffer buf)
|
public WebSocketFrame setPayload(ByteBuffer buf)
|
||||||
{
|
{
|
||||||
if (buf == null)
|
if (buf != null && buf.remaining() > MAX_CONTROL_PAYLOAD)
|
||||||
{
|
{
|
||||||
data = null;
|
throw new ProtocolException("Control Payloads can not exceed " + MAX_CONTROL_PAYLOAD + " bytes in length.");
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf.remaining() > ControlFrame.MAX_CONTROL_PAYLOAD)
|
|
||||||
{
|
|
||||||
throw new ProtocolException("Control Payloads can not exceed 125 bytes in length.");
|
|
||||||
}
|
}
|
||||||
return super.setPayload(buf);
|
return super.setPayload(buf);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,13 +78,20 @@ public class DataFrame extends WebSocketFrame
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void releaseBuffer()
|
public void reset()
|
||||||
{
|
{
|
||||||
|
// TODO: this is rather ugly.
|
||||||
|
// The ByteBufferPool is set only from extensions that
|
||||||
|
// compress the payload. It would be better to wrap the
|
||||||
|
// callback associated with this DataFrame into one that
|
||||||
|
// releases the buffer and then call the nested callback,
|
||||||
|
// rather than null-checking whether the pool exists and
|
||||||
|
// if so then release the buffer.
|
||||||
if (pool!=null)
|
if (pool!=null)
|
||||||
{
|
{
|
||||||
pool.release(this.data);
|
pool.release(this.data);
|
||||||
this.data=null;
|
|
||||||
}
|
}
|
||||||
|
super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -358,16 +358,9 @@ public class FrameFlusher
|
||||||
headerBuffer = null;
|
headerBuffer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!frame.hasPayload())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame instanceof DataFrame)
|
if (frame instanceof DataFrame)
|
||||||
{
|
{
|
||||||
// TODO null payload within frame
|
((DataFrame)frame).reset();
|
||||||
DataFrame data = (DataFrame)frame;
|
|
||||||
data.releaseBuffer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,25 +41,19 @@ public class MessageInputStream extends InputStream implements MessageAppender
|
||||||
private static final Logger LOG = Log.getLogger(MessageInputStream.class);
|
private static final Logger LOG = Log.getLogger(MessageInputStream.class);
|
||||||
// EOF (End of Buffers)
|
// EOF (End of Buffers)
|
||||||
private final static ByteBuffer EOF = ByteBuffer.allocate(0).asReadOnlyBuffer();
|
private final static ByteBuffer EOF = ByteBuffer.allocate(0).asReadOnlyBuffer();
|
||||||
/**
|
|
||||||
* Used for controlling read suspend/resume behavior if the queue is full, but the read operations haven't caught up yet.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private final LogicalConnection connection;
|
|
||||||
private final BlockingDeque<ByteBuffer> buffers = new LinkedBlockingDeque<>();
|
private final BlockingDeque<ByteBuffer> buffers = new LinkedBlockingDeque<>();
|
||||||
private AtomicBoolean closed = new AtomicBoolean(false);
|
private AtomicBoolean closed = new AtomicBoolean(false);
|
||||||
|
private final long timeoutMs;
|
||||||
private ByteBuffer activeBuffer = null;
|
private ByteBuffer activeBuffer = null;
|
||||||
private long timeoutMs = -1;
|
|
||||||
|
|
||||||
public MessageInputStream(LogicalConnection connection)
|
public MessageInputStream(LogicalConnection connection)
|
||||||
{
|
{
|
||||||
this.connection = connection;
|
this(connection, -1);
|
||||||
this.timeoutMs = -1; // disabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageInputStream(LogicalConnection connection, int timeoutMs)
|
public MessageInputStream(LogicalConnection connection, int timeoutMs)
|
||||||
{
|
{
|
||||||
this.connection = connection;
|
|
||||||
this.timeoutMs = timeoutMs;
|
this.timeoutMs = timeoutMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,16 +65,22 @@ public class MessageInputStream extends InputStream implements MessageAppender
|
||||||
LOG.debug("appendMessage(ByteBuffer,{}): {}",fin,BufferUtil.toDetailString(framePayload));
|
LOG.debug("appendMessage(ByteBuffer,{}): {}",fin,BufferUtil.toDetailString(framePayload));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if closed, we should just toss incoming payloads into the bit bucket.
|
// If closed, we should just toss incoming payloads into the bit bucket.
|
||||||
if (closed.get())
|
if (closed.get())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put the payload into the queue
|
// Put the payload into the queue, by copying it.
|
||||||
|
// Copying is necessary because the payload will
|
||||||
|
// be processed after this method returns.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
buffers.put(framePayload);
|
int capacity = framePayload.remaining();
|
||||||
|
// TODO: the copy buffer should be pooled too, but no buffer pool available from here.
|
||||||
|
ByteBuffer copy = framePayload.isDirect() ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
|
||||||
|
copy.put(framePayload).flip();
|
||||||
|
buffers.put(copy);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e)
|
catch (InterruptedException e)
|
||||||
{
|
{
|
||||||
|
@ -141,7 +141,7 @@ public class MessageInputStream extends InputStream implements MessageAppender
|
||||||
// grab a fresh buffer
|
// grab a fresh buffer
|
||||||
while (activeBuffer == null || !activeBuffer.hasRemaining())
|
while (activeBuffer == null || !activeBuffer.hasRemaining())
|
||||||
{
|
{
|
||||||
if (timeoutMs == -1)
|
if (timeoutMs < 0)
|
||||||
{
|
{
|
||||||
// infinite take
|
// infinite take
|
||||||
activeBuffer = buffers.take();
|
activeBuffer = buffers.take();
|
||||||
|
|
|
@ -143,6 +143,7 @@ public class MessageOutputStream extends OutputStream
|
||||||
// block success
|
// block success
|
||||||
frameCount++;
|
frameCount++;
|
||||||
frame.setIsContinuation();
|
frame.setIsContinuation();
|
||||||
|
BufferUtil.flipToFill(buffer);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue