Merged branch 'jetty-10.0.x' into 'jetty-10.0.x-3951-http2_demand'.

This commit is contained in:
Simone Bordet 2019-11-14 09:14:49 +01:00
commit 13e40c8b33
34 changed files with 831 additions and 323 deletions

19
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,19 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 365
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 30
# Issues with these labels will never be considered stale
exemptLabels:
- Pinned
- Security
- Specification
# Label to use when marking an issue as stale
staleLabel: Stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has been a
full year without activit. It will be closed if no further activity occurs.
Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
This issue has been closed due to it having no activity.

View File

@ -55,13 +55,13 @@ public class JDK9ClientALPNProcessor implements ALPNProcessor.Client
ALPNClientConnection alpn = (ALPNClientConnection)connection;
SSLParameters sslParameters = sslEngine.getSSLParameters();
List<String> protocols = alpn.getProtocols();
sslParameters.setApplicationProtocols(protocols.toArray(new String[protocols.size()]));
sslParameters.setApplicationProtocols(protocols.toArray(new String[0]));
sslEngine.setSSLParameters(sslParameters);
((DecryptedEndPoint)connection.getEndPoint()).getSslConnection()
.addHandshakeListener(new ALPNListener(alpn));
}
private final class ALPNListener implements SslHandshakeListener
private static final class ALPNListener implements SslHandshakeListener
{
private final ALPNClientConnection alpnConnection;

View File

@ -55,7 +55,7 @@ public class JDK9ServerALPNProcessor implements ALPNProcessor.Server, SslHandsha
sslEngine.setHandshakeApplicationProtocolSelector(new ALPNCallback((ALPNServerConnection)connection));
}
private final class ALPNCallback implements BiFunction<SSLEngine, List<String>, String>, SslHandshakeListener
private static final class ALPNCallback implements BiFunction<SSLEngine, List<String>, String>, SslHandshakeListener
{
private final ALPNServerConnection alpnConnection;
@ -68,10 +68,19 @@ public class JDK9ServerALPNProcessor implements ALPNProcessor.Server, SslHandsha
@Override
public String apply(SSLEngine engine, List<String> protocols)
{
if (LOG.isDebugEnabled())
LOG.debug("apply {} {}", alpnConnection, protocols);
alpnConnection.select(protocols);
return alpnConnection.getProtocol();
try
{
if (LOG.isDebugEnabled())
LOG.debug("apply {} {}", alpnConnection, protocols);
alpnConnection.select(protocols);
return alpnConnection.getProtocol();
}
catch (Throwable x)
{
// Cannot negotiate the protocol, return null to have
// JSSE send Alert.NO_APPLICATION_PROTOCOL to the client.
return null;
}
}
@Override

View File

@ -19,15 +19,18 @@
package org.eclipse.jetty.alpn.java.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -40,12 +43,16 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
public class JDK9ALPNTest
{
@ -90,7 +97,7 @@ public class JDK9ALPNTest
startServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
}
@ -132,7 +139,7 @@ public class JDK9ALPNTest
startServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
}
@ -170,4 +177,57 @@ public class JDK9ALPNTest
}
}
}
@Test
public void testClientSupportingALPNCannotNegotiateProtocol() throws Exception
{
startServer(new AbstractHandler() {
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
jettyRequest.setHandled(true);
}
});
SslContextFactory sslContextFactory = new SslContextFactory.Client(true);
sslContextFactory.start();
String host = "localhost";
int port = connector.getLocalPort();
try (SocketChannel client = SocketChannel.open(new InetSocketAddress(host, port)))
{
client.socket().setSoTimeout(5000);
SSLEngine sslEngine = sslContextFactory.newSSLEngine(host, port);
sslEngine.setUseClientMode(true);
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setApplicationProtocols(new String[]{"unknown/1.0"});
sslEngine.setSSLParameters(sslParameters);
sslEngine.beginHandshake();
assertSame(SSLEngineResult.HandshakeStatus.NEED_WRAP, sslEngine.getHandshakeStatus());
ByteBuffer sslBuffer = ByteBuffer.allocate(sslEngine.getSession().getPacketBufferSize());
SSLEngineResult result = sslEngine.wrap(BufferUtil.EMPTY_BUFFER, sslBuffer);
assertSame(SSLEngineResult.Status.OK, result.getStatus());
sslBuffer.flip();
client.write(sslBuffer);
assertSame(SSLEngineResult.HandshakeStatus.NEED_UNWRAP, sslEngine.getHandshakeStatus());
sslBuffer.clear();
int read = client.read(sslBuffer);
assertThat(read, greaterThan(0));
sslBuffer.flip();
// TLS frame layout: record_type, major_version, minor_version, hi_length, lo_length
int recordTypeAlert = 21;
assertEquals(recordTypeAlert, sslBuffer.get(0) & 0xFF);
// Alert record layout: alert_level, alert_code
int alertLevelFatal = 2;
assertEquals(alertLevelFatal, sslBuffer.get(5) & 0xFF);
int alertCodeNoApplicationProtocol = 120;
assertEquals(alertCodeNoApplicationProtocol, sslBuffer.get(6) & 0xFF);
}
}
}

View File

@ -119,7 +119,7 @@ public abstract class HttpReceiver
}
}
private long demand()
protected long demand()
{
return demand(LongUnaryOperator.identity());
}

View File

@ -552,6 +552,12 @@ public class HttpRequest implements Request
{
this.responseListeners.add(new Response.DemandedContentListener()
{
@Override
public void onBeforeContent(Response response, LongConsumer demand)
{
listener.onBeforeContent(response, demand);
}
@Override
public void onContent(Response response, LongConsumer demand, ByteBuffer content, Callback callback)
{

View File

@ -416,13 +416,13 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
}
@Override
public void onHeaders(int request)
public boolean onHeaders(int request)
{
HttpChannelOverFCGI channel = activeChannels.get(request);
if (channel != null)
channel.responseHeaders();
else
noChannel(request);
return !channel.responseHeaders();
noChannel(request);
return false;
}
@Override

View File

@ -80,9 +80,9 @@ public class ClientParser extends Parser
}
@Override
public void onHeaders(int request)
public boolean onHeaders(int request)
{
listener.onHeaders(request);
return listener.onHeaders(request);
}
@Override

View File

@ -135,7 +135,11 @@ public abstract class Parser
{
public void onHeader(int request, HttpField field);
public void onHeaders(int request);
/**
* @param request the request id
* @return true to signal to the parser to stop parsing, false to continue parsing
*/
public boolean onHeaders(int request);
/**
* @param request the request id
@ -158,8 +162,9 @@ public abstract class Parser
}
@Override
public void onHeaders(int request)
public boolean onHeaders(int request)
{
return false;
}
@Override

View File

@ -82,7 +82,7 @@ public class ResponseContentParser extends StreamContentParser
parsers.remove(request);
}
private class ResponseParser implements HttpParser.ResponseHandler
private static class ResponseParser implements HttpParser.ResponseHandler
{
private final HttpFields fields = new HttpFields();
private ClientParser.Listener listener;
@ -90,6 +90,7 @@ public class ResponseContentParser extends StreamContentParser
private final FCGIHttpParser httpParser;
private State state = State.HEADERS;
private boolean seenResponseCode;
private boolean stalled;
private ResponseParser(ClientParser.Listener listener, int request)
{
@ -111,7 +112,11 @@ public class ResponseContentParser extends StreamContentParser
case HEADERS:
{
if (httpParser.parseNext(buffer))
{
state = State.CONTENT_MODE;
if (stalled)
return true;
}
remaining = buffer.remaining();
break;
}
@ -233,16 +238,17 @@ public class ResponseContentParser extends StreamContentParser
}
}
private void notifyHeaders()
private boolean notifyHeaders()
{
try
{
listener.onHeaders(request);
return listener.onHeaders(request);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Exception while invoking listener " + listener, x);
return false;
}
}
@ -255,8 +261,10 @@ public class ResponseContentParser extends StreamContentParser
notifyBegin(200, "OK");
notifyHeaders(fields);
}
notifyHeaders();
// Return from HTTP parsing so that we can parse the content.
// Remember whether we have demand.
stalled = notifyHeaders();
// Always return from HTTP parsing so that we
// can parse the content with the FCGI parser.
return true;
}

View File

@ -109,10 +109,11 @@ public class ClientGeneratorTest
}
@Override
public void onHeaders(int request)
public boolean onHeaders(int request)
{
assertEquals(id, request);
params.set(params.get() * primes[4]);
return false;
}
});

View File

@ -91,10 +91,11 @@ public class ClientParserTest
}
@Override
public void onHeaders(int request)
public boolean onHeaders(int request)
{
assertEquals(id, request);
params.set(params.get() * primes[2]);
return false;
}
});

View File

@ -144,7 +144,7 @@ public class ServerFCGIConnection extends AbstractConnection
}
@Override
public void onHeaders(int request)
public boolean onHeaders(int request)
{
HttpChannelOverFCGI channel = channels.get(request);
if (LOG.isDebugEnabled())
@ -154,6 +154,7 @@ public class ServerFCGIConnection extends AbstractConnection
channel.onRequest();
channel.dispatch();
}
return false;
}
@Override

View File

@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
// TODO consider replacing this with java.net.HttpCookie
// TODO consider replacing this with java.net.HttpCookie (once it supports RFC6265)
public class HttpCookie
{
private static final String __COOKIE_DELIM = "\",;\\ \t";
@ -33,14 +33,14 @@ public class HttpCookie
/**
*If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true
**/
private static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
public static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
/**
*These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment
**/
private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
private static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
private static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__";
private static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__";
public static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
public static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__";
public static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__";
public enum SameSite
{
@ -428,17 +428,17 @@ public class HttpCookie
{
if (comment != null)
{
if (comment.contains(SAME_SITE_NONE_COMMENT))
if (comment.contains(SAME_SITE_STRICT_COMMENT))
{
return SameSite.NONE;
return SameSite.STRICT;
}
if (comment.contains(SAME_SITE_LAX_COMMENT))
{
return SameSite.LAX;
}
if (comment.contains(SAME_SITE_STRICT_COMMENT))
if (comment.contains(SAME_SITE_NONE_COMMENT))
{
return SameSite.STRICT;
return SameSite.NONE;
}
}
@ -462,6 +462,44 @@ public class HttpCookie
return strippedComment.length() == 0 ? null : strippedComment;
}
public static String getCommentWithAttributes(String comment, boolean httpOnly, SameSite sameSite)
{
if (comment == null && sameSite == null)
return null;
StringBuilder builder = new StringBuilder();
if (StringUtil.isNotBlank(comment))
{
comment = getCommentWithoutAttributes(comment);
if (StringUtil.isNotBlank(comment))
builder.append(comment);
}
if (httpOnly)
builder.append(HTTP_ONLY_COMMENT);
if (sameSite != null)
{
switch (sameSite)
{
case NONE:
builder.append(SAME_SITE_NONE_COMMENT);
break;
case STRICT:
builder.append(SAME_SITE_STRICT_COMMENT);
break;
case LAX:
builder.append(SAME_SITE_LAX_COMMENT);
break;
default:
throw new IllegalArgumentException(sameSite.toString());
}
}
if (builder.length() == 0)
return null;
return builder.toString();
}
public static class SetCookieHttpField extends HttpField
{
final HttpCookie _cookie;

View File

@ -18,17 +18,25 @@
package org.eclipse.jetty.http;
import java.util.stream.Stream;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class HttpCookieTest
{
@ -93,7 +101,6 @@ public class HttpCookieTest
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly", httpCookie.getRFC6265SetCookie());
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, HttpCookie.SameSite.NONE);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=None", httpCookie.getRFC6265SetCookie());
@ -102,8 +109,11 @@ public class HttpCookieTest
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, HttpCookie.SameSite.STRICT);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict", httpCookie.getRFC6265SetCookie());
}
String[] badNameExamples = {
public static Stream<String> rfc6265BadNameSource()
{
return Stream.of(
"\"name\"",
"name\t",
"na me",
@ -113,25 +123,32 @@ public class HttpCookieTest
"{name}",
"[name]",
"\""
};
);
}
for (String badNameExample : badNameExamples)
{
try
@ParameterizedTest
@MethodSource("rfc6265BadNameSource")
public void testSetRFC6265CookieBadName(String badNameExample)
{
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
() ->
{
httpCookie = new HttpCookie(badNameExample, "value", null, "/", 1, true, true, null, -1);
HttpCookie httpCookie = new HttpCookie(badNameExample, "value", null, "/", 1, true, true, null, -1);
httpCookie.getRFC6265SetCookie();
fail(badNameExample);
}
catch (IllegalArgumentException ex)
{
// System.err.printf("%s: %s%n", ex.getClass().getSimpleName(), ex.getMessage());
assertThat("Testing bad name: [" + badNameExample + "]", ex.getMessage(),
allOf(containsString("RFC6265"), containsString("RFC2616")));
}
}
});
// make sure that exception mentions just how mad of a name it truly is
assertThat("message", ex.getMessage(),
allOf(
// violation of Cookie spec
containsString("RFC6265"),
// violation of HTTP spec
containsString("RFC2616")
));
}
String[] badValueExamples = {
public static Stream<String> rfc6265BadValueSource()
{
return Stream.of(
"va\tlue",
"\t",
"value\u0000",
@ -143,39 +160,44 @@ public class HttpCookieTest
"val\\ue",
"val\"ue",
"\""
};
);
}
for (String badValueExample : badValueExamples)
{
try
@ParameterizedTest
@MethodSource("rfc6265BadValueSource")
public void testSetRFC6265CookieBadValue(String badValueExample)
{
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
() ->
{
httpCookie = new HttpCookie("name", badValueExample, null, "/", 1, true, true, null, -1);
HttpCookie httpCookie = new HttpCookie("name", badValueExample, null, "/", 1, true, true, null, -1);
httpCookie.getRFC6265SetCookie();
fail();
}
catch (IllegalArgumentException ex)
{
// System.err.printf("%s: %s%n", ex.getClass().getSimpleName(), ex.getMessage());
assertThat("Testing bad value [" + badValueExample + "]", ex.getMessage(), Matchers.containsString("RFC6265"));
}
}
});
assertThat("message", ex.getMessage(), containsString("RFC6265"));
}
String[] goodNameExamples = {
public static Stream<String> rfc6265GoodNameSource()
{
return Stream.of(
"name",
"n.a.m.e",
"na-me",
"+name",
"na*me",
"na$me",
"#name"
};
"#name");
}
for (String goodNameExample : goodNameExamples)
{
httpCookie = new HttpCookie(goodNameExample, "value", null, "/", 1, true, true, null, -1);
// should not throw an exception
}
@ParameterizedTest
@MethodSource("rfc6265GoodNameSource")
public void testSetRFC6265CookieGoodName(String goodNameExample)
{
new HttpCookie(goodNameExample, "value", null, "/", 1, true, true, null, -1);
// should not throw an exception
}
public static Stream<String> rfc6265GoodValueSource()
{
String[] goodValueExamples = {
"value",
"",
@ -185,37 +207,150 @@ public class HttpCookieTest
"val/ue",
"v.a.l.u.e"
};
return Stream.of(goodValueExamples);
}
for (String goodValueExample : goodValueExamples)
@ParameterizedTest
@MethodSource("rfc6265GoodValueSource")
public void testSetRFC6265CookieGoodValue(String goodValueExample)
{
new HttpCookie("name", goodValueExample, null, "/", 1, true, true, null, -1);
// should not throw an exception
}
@ParameterizedTest
@ValueSource(strings = {
"__HTTP_ONLY__",
"__HTTP_ONLY__comment",
"comment__HTTP_ONLY__"
})
public void testIsHttpOnlyInCommentTrue(String comment)
{
assertTrue(HttpCookie.isHttpOnlyInComment(comment), "Comment \"" + comment + "\"");
}
@ParameterizedTest
@ValueSource(strings = {
"comment",
"",
"__",
"__HTTP__ONLY__",
"__http_only__",
"HTTP_ONLY",
"__HTTP__comment__ONLY__"
})
public void testIsHttpOnlyInCommentFalse(String comment)
{
assertFalse(HttpCookie.isHttpOnlyInComment(comment), "Comment \"" + comment + "\"");
}
@ParameterizedTest
@ValueSource(strings = {
"__SAME_SITE_NONE__",
"__SAME_SITE_NONE____SAME_SITE_NONE__"
})
public void testGetSameSiteFromCommentNONE(String comment)
{
assertEquals(HttpCookie.getSameSiteFromComment(comment), HttpCookie.SameSite.NONE, "Comment \"" + comment + "\"");
}
@ParameterizedTest
@ValueSource(strings = {
"__SAME_SITE_LAX__",
"__SAME_SITE_LAX____SAME_SITE_NONE__",
"__SAME_SITE_NONE____SAME_SITE_LAX__",
"__SAME_SITE_LAX____SAME_SITE_NONE__"
})
public void testGetSameSiteFromCommentLAX(String comment)
{
assertEquals(HttpCookie.getSameSiteFromComment(comment), HttpCookie.SameSite.LAX, "Comment \"" + comment + "\"");
}
@ParameterizedTest
@ValueSource(strings = {
"__SAME_SITE_STRICT__",
"__SAME_SITE_NONE____SAME_SITE_STRICT____SAME_SITE_LAX__",
"__SAME_SITE_STRICT____SAME_SITE_LAX____SAME_SITE_NONE__",
"__SAME_SITE_STRICT____SAME_SITE_STRICT__"
})
public void testGetSameSiteFromCommentSTRICT(String comment)
{
assertEquals(HttpCookie.getSameSiteFromComment(comment), HttpCookie.SameSite.STRICT, "Comment \"" + comment + "\"");
}
/**
* A comment that does not have a declared SamesSite attribute defined
*/
@ParameterizedTest
@ValueSource(strings = {
"__HTTP_ONLY__",
"comment",
// not jetty attributes
"SameSite=None",
"SameSite=Lax",
"SameSite=Strict",
// incomplete jetty attributes
"SAME_SITE_NONE",
"SAME_SITE_LAX",
"SAME_SITE_STRICT",
})
public void testGetSameSiteFromCommentUndefined(String comment)
{
assertNull(HttpCookie.getSameSiteFromComment(comment), "Comment \"" + comment + "\"");
}
public static Stream<Arguments> getCommentWithoutAttributesSource()
{
return Stream.of(
// normal - only attribute comment
Arguments.of("__SAME_SITE_LAX__", null),
// normal - no attribute comment
Arguments.of("comment", "comment"),
// mixed - attributes at end
Arguments.of("comment__SAME_SITE_NONE__", "comment"),
Arguments.of("comment__HTTP_ONLY____SAME_SITE_NONE__", "comment"),
// mixed - attributes at start
Arguments.of("__SAME_SITE_NONE__comment", "comment"),
Arguments.of("__HTTP_ONLY____SAME_SITE_NONE__comment", "comment"),
// mixed - attributes at start and end
Arguments.of("__SAME_SITE_NONE__comment__HTTP_ONLY__", "comment"),
Arguments.of("__HTTP_ONLY__comment__SAME_SITE_NONE__", "comment")
);
}
@ParameterizedTest
@MethodSource("getCommentWithoutAttributesSource")
public void testGetCommentWithoutAttributes(String rawComment, String expectedComment)
{
String actualComment = HttpCookie.getCommentWithoutAttributes(rawComment);
if (expectedComment == null)
{
httpCookie = new HttpCookie("name", goodValueExample, null, "/", 1, true, true, null, -1);
// should not throw an exception
assertNull(actualComment);
}
else
{
assertEquals(actualComment, expectedComment);
}
}
@Test
public void testGetHttpOnlyFromComment()
public void testGetCommentWithAttributes()
{
assertTrue(HttpCookie.isHttpOnlyInComment("__HTTP_ONLY__"));
assertTrue(HttpCookie.isHttpOnlyInComment("__HTTP_ONLY__comment"));
assertFalse(HttpCookie.isHttpOnlyInComment("comment"));
}
assertThat(HttpCookie.getCommentWithAttributes(null, false, null), nullValue());
assertThat(HttpCookie.getCommentWithAttributes("", false, null), nullValue());
assertThat(HttpCookie.getCommentWithAttributes("hello", false, null), is("hello"));
@Test
public void testGetSameSiteFromComment()
{
assertEquals(HttpCookie.getSameSiteFromComment("__SAME_SITE_NONE__"), HttpCookie.SameSite.NONE);
assertEquals(HttpCookie.getSameSiteFromComment("__SAME_SITE_LAX__"), HttpCookie.SameSite.LAX);
assertEquals(HttpCookie.getSameSiteFromComment("__SAME_SITE_STRICT__"), HttpCookie.SameSite.STRICT);
assertEquals(HttpCookie.getSameSiteFromComment("__SAME_SITE_NONE____SAME_SITE_STRICT__"), HttpCookie.SameSite.NONE);
assertNull(HttpCookie.getSameSiteFromComment("comment"));
}
assertThat(HttpCookie.getCommentWithAttributes(null, true, HttpCookie.SameSite.STRICT),
is("__HTTP_ONLY____SAME_SITE_STRICT__"));
assertThat(HttpCookie.getCommentWithAttributes("", true, HttpCookie.SameSite.NONE),
is("__HTTP_ONLY____SAME_SITE_NONE__"));
assertThat(HttpCookie.getCommentWithAttributes("hello", true, HttpCookie.SameSite.LAX),
is("hello__HTTP_ONLY____SAME_SITE_LAX__"));
@Test
public void getCommentWithoutAttributes()
{
assertEquals(HttpCookie.getCommentWithoutAttributes("comment__SAME_SITE_NONE__"), "comment");
assertEquals(HttpCookie.getCommentWithoutAttributes("comment__HTTP_ONLY____SAME_SITE_NONE__"), "comment");
assertNull(HttpCookie.getCommentWithoutAttributes("__SAME_SITE_LAX__"));
assertThat(HttpCookie.getCommentWithAttributes("__HTTP_ONLY____SAME_SITE_LAX__", false, null), nullValue());
assertThat(HttpCookie.getCommentWithAttributes("__HTTP_ONLY____SAME_SITE_LAX__", true, HttpCookie.SameSite.NONE),
is("__HTTP_ONLY____SAME_SITE_NONE__"));
assertThat(HttpCookie.getCommentWithAttributes("__HTTP_ONLY____SAME_SITE_LAX__hello", true, HttpCookie.SameSite.LAX),
is("hello__HTTP_ONLY____SAME_SITE_LAX__"));
}
}

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.http2.parser;
import org.eclipse.jetty.io.EndPoint;
/**
* Controls rate of events via {@link #onEvent(Object)}.
*/
@ -36,4 +38,19 @@ public interface RateControl
* @return true IFF the rate is within limits
*/
public boolean onEvent(Object event);
/**
* Factory to create RateControl instances.
*/
public interface Factory
{
/**
* @return a new RateControl instance for the given EndPoint
* @param endPoint the EndPoint for which the RateControl is created
*/
public default RateControl newRateControl(EndPoint endPoint)
{
return NO_RATE_CONTROL;
}
}
}

View File

@ -23,6 +23,8 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.io.EndPoint;
/**
* <p>An implementation of {@link RateControl} that limits the number of
* events within a time period.</p>
@ -48,6 +50,21 @@ public class WindowRateControl implements RateControl
{
this.maxEvents = maxEvents;
this.window = window.toNanos();
if (this.window == 0)
throw new IllegalArgumentException("Invalid duration " + window);
}
public int getEventsPerSecond()
{
try
{
long rate = maxEvents * 1_000_000_000L / window;
return Math.toIntExact(rate);
}
catch (ArithmeticException x)
{
return Integer.MAX_VALUE;
}
}
@Override
@ -67,4 +84,20 @@ public class WindowRateControl implements RateControl
events.add(now + window);
return size.incrementAndGet() <= maxEvents;
}
public static class Factory implements RateControl.Factory
{
private final int maxEventRate;
public Factory(int maxEventRate)
{
this.maxEventRate = maxEventRate;
}
@Override
public RateControl newRateControl(EndPoint endPoint)
{
return WindowRateControl.fromEventsPerSecond(maxEventRate);
}
}
}

View File

@ -120,12 +120,24 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
if (frame.isEndStream() || informational)
responseSuccess(exchange);
}
else
{
if (frame.isEndStream())
{
// There is no demand to trigger response success, so add
// a poison pill to trigger it when there will be demand.
notifyContent(exchange, new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
}
}
}
}
else // Response trailers.
{
HttpFields trailers = metaData.getFields();
trailers.forEach(httpResponse::trailer);
// Previous DataFrames had endStream=false, so
// add a poison pill to trigger response success
// after all normal DataFrames have been consumed.
notifyContent(exchange, new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.NOOP);
}
}
@ -212,7 +224,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
contentNotifier.offer(exchange, frame, callback);
}
private static class ContentNotifier
private class ContentNotifier
{
private final Queue<DataInfo> queue = new ArrayDeque<>();
private final HttpReceiverOverHTTP2 receiver;
@ -246,9 +258,25 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
private void process(boolean resume)
{
// Allow only one thread at a time.
if (active(resume))
boolean busy = active(resume);
if (LOG.isDebugEnabled())
LOG.debug("Resuming({}) processing({}) of content", resume, !busy);
if (busy)
return;
// Process only if there is demand.
synchronized (this)
{
if (!resume && demand() <= 0)
{
if (LOG.isDebugEnabled())
LOG.debug("Stalling processing, content available but no demand");
active = false;
stalled = true;
return;
}
}
while (true)
{
if (dataInfo != null)
@ -265,7 +293,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
{
dataInfo = queue.poll();
if (LOG.isDebugEnabled())
LOG.debug("Dequeued content {}", dataInfo);
LOG.debug("Processing content {}", dataInfo);
if (dataInfo == null)
{
active = false;
@ -281,8 +309,12 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
boolean proceed = receiver.responseContent(dataInfo.exchange, buffer, Callback.from(callback::succeeded, x -> fail(callback, x)));
if (!proceed)
{
// Should stall, unless just resumed.
if (stall())
// The call to responseContent() said we should
// stall, but another thread may have just resumed.
boolean stall = stall();
if (LOG.isDebugEnabled())
LOG.debug("Stalling({}) processing", stall);
if (stall)
return;
}
}
@ -299,27 +331,46 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
{
if (active)
{
// There is a thread in process(),
// but it may be about to exit, so
// remember "resume" to signal the
// processing thread to continue.
if (resume)
this.resume = true;
return true;
}
// If there is no demand (i.e. stalled
// and not resuming) then don't process.
if (stalled && !resume)
return true;
// Start processing.
active = true;
stalled = false;
return false;
}
}
/**
* Called when there is no demand, this method checks whether
* the processing should really stop or it should continue.
*
* @return true to stop processing, false to continue processing
*/
private boolean stall()
{
synchronized (this)
{
if (resume)
{
// There was no demand, but another thread
// just demanded, continue processing.
resume = false;
return false;
}
// There is no demand, stop processing.
active = false;
stalled = true;
return true;
@ -344,7 +395,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
receiver.responseFailure(failure);
}
private static class DataInfo
private class DataInfo
{
private final HttpExchange exchange;
private final DataFrame frame;

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<!-- ============================================================= -->
<!-- Configure an HTTP2 on the ssl connector. -->
<!-- ============================================================= -->
<Configure id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
<Call name="addConnectionFactory">
<Arg>
@ -13,10 +10,10 @@
<Set name="initialStreamRecvWindow" property="jetty.http2.initialStreamRecvWindow"/>
<Set name="initialSessionRecvWindow" property="jetty.http2.initialSessionRecvWindow"/>
<Set name="maxSettingsKeys"><Property name="jetty.http2.maxSettingsKeys" default="64"/></Set>
<Set name="rateControl">
<Call class="org.eclipse.jetty.http2.parser.WindowRateControl" name="fromEventsPerSecond">
<Set name="rateControlFactory">
<New class="org.eclipse.jetty.http2.parser.WindowRateControl$Factory">
<Arg type="int"><Property name="jetty.http2.rateControl.maxEventsPerSecond" default="20"/></Arg>
</Call>
</New>
</Set>
</New>
</Arg>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<!-- ============================================================= -->
<!-- Configure an HTTP2 on the ssl connector. -->
<!-- ============================================================= -->
<Configure id="httpConnector" class="org.eclipse.jetty.server.ServerConnector">
<Call name="addConnectionFactory">
<Arg>
@ -12,10 +9,10 @@
<Set name="maxConcurrentStreams" property="jetty.http2c.maxConcurrentStreams"/>
<Set name="initialStreamRecvWindow" property="jetty.http2c.initialStreamRecvWindow"/>
<Set name="maxSettingsKeys"><Property name="jetty.http2.maxSettingsKeys" default="64"/></Set>
<Set name="rateControl">
<Call class="org.eclipse.jetty.http2.parser.WindowRateControl" name="fromEventsPerSecond">
<Set name="rateControlFactory">
<New class="org.eclipse.jetty.http2.parser.WindowRateControl$Factory">
<Arg type="int"><Property name="jetty.http2.rateControl.maxEventsPerSecond" default="20"/></Arg>
</Call>
</New>
</Set>
</New>
</Arg>

View File

@ -19,7 +19,6 @@
package org.eclipse.jetty.http2.server;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -61,7 +60,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
private int maxHeaderBlockFragment = 0;
private int maxFrameLength = Frame.DEFAULT_MAX_LENGTH;
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
private RateControl rateControl = new WindowRateControl(20, Duration.ofSeconds(1));
private RateControl.Factory rateControlFactory = new WindowRateControl.Factory(20);
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
private long streamIdleTimeout;
private boolean _useInputDirectByteBuffers;
@ -186,14 +185,22 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
this.maxSettingsKeys = maxSettingsKeys;
}
public RateControl getRateControl()
/**
* @return the factory that creates RateControl objects
*/
public RateControl.Factory getRateControlFactory()
{
return rateControl;
return rateControlFactory;
}
public void setRateControl(RateControl rateControl)
/**
* <p>Sets the factory that creates a per-connection RateControl object.</p>
*
* @param rateControlFactory the factory that creates RateControl objects
*/
public void setRateControlFactory(RateControl.Factory rateControlFactory)
{
this.rateControl = rateControl;
this.rateControlFactory = Objects.requireNonNull(rateControlFactory);
}
public boolean isUseInputDirectByteBuffers()
@ -253,7 +260,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
session.setInitialSessionRecvWindow(getInitialSessionRecvWindow());
session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize());
ServerParser parser = newServerParser(connector, session, getRateControl());
ServerParser parser = newServerParser(connector, session, getRateControlFactory().newRateControl(endPoint));
parser.setMaxFrameLength(getMaxFrameLength());
parser.setMaxSettingsKeys(getMaxSettingsKeys());

View File

@ -251,8 +251,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
if (LOG.isDebugEnabled())
LOG.debug("Session ID should be cookie for OpenID authentication to work");
int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
baseResponse.sendRedirect(redirectCode, URIUtil.addPaths(request.getContextPath(), _errorPage));
baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), URIUtil.addPaths(request.getContextPath(), _errorPage));
return Authentication.SEND_FAILURE;
}
@ -297,8 +296,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
LOG.debug("authenticated {}->{}", openIdAuth, nuri);
response.setContentLength(0);
int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
baseResponse.sendRedirect(redirectCode, nuri);
baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), nuri);
return openIdAuth;
}
}
@ -317,8 +315,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
{
if (LOG.isDebugEnabled())
LOG.debug("auth failed {}", _errorPage);
int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
baseResponse.sendRedirect(redirectCode, URIUtil.addPaths(request.getContextPath(), _errorPage));
baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), URIUtil.addPaths(request.getContextPath(), _errorPage));
}
return Authentication.SEND_FAILURE;
@ -408,8 +405,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
String challengeUri = getChallengeUri(request);
if (LOG.isDebugEnabled())
LOG.debug("challenge {}->{}", session.getId(), challengeUri);
int redirectCode = (baseRequest.getHttpVersion().getVersion() < HttpVersion.HTTP_1_1.getVersion() ? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
baseResponse.sendRedirect(redirectCode, challengeUri);
baseResponse.sendRedirect(getRedirectCode(baseRequest.getHttpVersion()), challengeUri);
return Authentication.SEND_CONTINUE;
}
@ -437,6 +433,12 @@ public class OpenIdAuthenticator extends LoginAuthenticator
return pathInContext != null && (pathInContext.equals(_errorPath));
}
private static int getRedirectCode(HttpVersion httpVersion)
{
return (httpVersion.getVersion() < HttpVersion.HTTP_1_1.getVersion()
? HttpServletResponse.SC_MOVED_TEMPORARILY : HttpServletResponse.SC_SEE_OTHER);
}
private String getRedirectUri(HttpServletRequest request)
{
final StringBuffer redirectUri = new StringBuffer(128);

View File

@ -124,13 +124,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
};
public static final int DEFAULT_LISTENER_TYPE_INDEX = 1;
public static final int EXTENDED_LISTENER_TYPE_INDEX = 0;
private static final String UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER = "Unimplemented {} - use org.eclipse.jetty.servlet.ServletContextHandler";
private static final Logger LOG = Log.getLogger(ContextHandler.class);
private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
private static final ThreadLocal<Context> __context = new ThreadLocal<>();
private static String __serverInfo = "jetty/" + Server.getVersion();
@ -207,7 +208,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private final List<ContextScopeListener> _contextListeners = new CopyOnWriteArrayList<>();
private final Set<EventListener> _durableListeners = new HashSet<>();
private String[] _protectedTargets;
private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<>();
public enum Availability
{
@ -241,7 +242,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
_scontext = context == null ? new Context() : context;
_attributes = new AttributesMap();
_initParams = new HashMap<String, String>();
_initParams = new HashMap<>();
addAliasCheck(new ApproveNonExistentDirectoryAliases());
if (File.separatorChar == '/')
addAliasCheck(new AllowSymLinkAliasChecker());
@ -350,7 +351,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (connectorIndex == 0)
{
if (connectorOnlyIndexes == null)
connectorOnlyIndexes = new ArrayList<Integer>();
connectorOnlyIndexes = new ArrayList<>();
connectorOnlyIndexes.add(i);
}
}
@ -408,7 +409,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
else
{
Set<String> currentVirtualHosts = new HashSet<String>(Arrays.asList(getVirtualHosts()));
Set<String> currentVirtualHosts = new HashSet<>(Arrays.asList(getVirtualHosts()));
for (String vh : virtualHosts)
{
currentVirtualHosts.add(normalizeHostname(vh));
@ -433,7 +434,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (virtualHosts == null || virtualHosts.length == 0 || _vhosts == null || _vhosts.length == 0)
return; // do nothing
Set<String> existingVirtualHosts = new HashSet<String>(Arrays.asList(getVirtualHosts()));
Set<String> existingVirtualHosts = new HashSet<>(Arrays.asList(getVirtualHosts()));
for (String vh : virtualHosts)
{
existingVirtualHosts.remove(normalizeHostname(vh));
@ -1101,7 +1102,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (LOG.isDebugEnabled())
LOG.debug("scope {}|{}|{} @ {}", baseRequest.getContextPath(), baseRequest.getServletPath(), baseRequest.getPathInfo(), this);
Context oldContext = null;
Context oldContext;
String oldContextPath = null;
String oldServletPath = null;
String oldPathInfo = null;
@ -1119,7 +1120,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
// check the target.
if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch))
{
if (_compactPath)
if (isCompactPath())
target = URIUtil.compactPath(target);
if (!checkContext(target, baseRequest, response))
return;
@ -1165,7 +1166,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (_contextPath.length() == 1)
baseRequest.setContextPath("");
else
baseRequest.setContextPath(_contextPathEncoded);
baseRequest.setContextPath(getContextPathEncoded());
baseRequest.setServletPath(null);
baseRequest.setPathInfo(pathInfo);
}
@ -1741,7 +1742,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
public void addLocaleEncoding(String locale, String encoding)
{
if (_localeEncodingMap == null)
_localeEncodingMap = new HashMap<String, String>();
_localeEncodingMap = new HashMap<>();
_localeEncodingMap.put(locale, encoding);
}
@ -1821,7 +1822,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
// alias checks
for (Iterator<AliasCheck> i = _aliasChecks.iterator(); i.hasNext(); )
for (Iterator<AliasCheck> i = getAliasChecks().iterator(); i.hasNext(); )
{
AliasCheck check = i.next();
if (check.check(path, resource))
@ -1887,7 +1888,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
String[] l = resource.list();
if (l != null)
{
HashSet<String> set = new HashSet<String>();
HashSet<String> set = new HashSet<>();
for (int i = 0; i < l.length; i++)
{
set.add(path + l[i]);
@ -1930,7 +1931,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
*/
public void addAliasCheck(AliasCheck check)
{
_aliasChecks.add(check);
getAliasChecks().add(check);
}
/**
@ -1946,8 +1947,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
*/
public void setAliasChecks(List<AliasCheck> checks)
{
_aliasChecks.clear();
_aliasChecks.addAll(checks);
getAliasChecks().clear();
getAliasChecks().addAll(checks);
}
/**
@ -1955,13 +1956,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
*/
public void clearAliasChecks()
{
_aliasChecks.clear();
getAliasChecks().clear();
}
/**
* Context.
* <p>
* A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
* A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the
* derived {@link ContextHandler} implementations.
* </p>
*/
public class Context extends StaticContext
@ -1984,7 +1986,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
public ServletContext getContext(String uripath)
{
List<ContextHandler> contexts = new ArrayList<ContextHandler>();
List<ContextHandler> contexts = new ArrayList<>();
Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
String matchedPath = null;
@ -2057,7 +2059,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
matchedPath = contextPath;
}
if (matchedPath != null && matchedPath.equals(contextPath))
if (matchedPath.equals(contextPath))
contexts.add(ch);
}
}
@ -2252,7 +2254,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
public synchronized Enumeration<String> getAttributeNames()
{
HashSet<String> set = new HashSet<String>();
HashSet<String> set = new HashSet<>();
Enumeration<String> e = super.getAttributeNames();
while (e.hasMoreElements())
{
@ -2399,19 +2401,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
@Override
public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
{
try
{
return createInstance(clazz);
}
catch (Exception e)
{
throw new ServletException(e);
}
}
public void checkListener(Class<? extends EventListener> listener) throws IllegalStateException
{
boolean ok = false;
@ -2445,7 +2434,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
throw new UnsupportedOperationException();
// no security manager just return the classloader
if (!_usingSecurityManager)
if (!isUsingSecurityManager())
{
return _classLoader;
}
@ -2501,12 +2490,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return _enabled;
}
public <T> T createInstance(Class<T> clazz) throws Exception
{
T o = clazz.getDeclaredConstructor().newInstance();
return o;
}
@Override
public String getVirtualServerName()
{
@ -2517,15 +2500,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
/**
* A simple implementation of ServletContext that is used when there is no
* ContextHandler. This is also used as the base for all other ServletContext
* implementations.
*/
public static class StaticContext extends AttributesMap implements ServletContext
{
private int _effectiveMajorVersion = SERVLET_MAJOR_VERSION;
private int _effectiveMinorVersion = SERVLET_MINOR_VERSION;
public StaticContext()
{
}
@Override
public ServletContext getContext(String uripath)
{
@ -2589,7 +2573,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
public String getServerInfo()
{
return __serverInfo;
return ContextHandler.getServerInfo();
}
@Override
@ -2716,20 +2700,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return null;
}
@Override
public <T extends Filter> T createFilter(Class<T> c) throws ServletException
{
LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "createFilter(Class)");
return null;
}
@Override
public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
{
LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "createServlet(Class)");
return null;
}
@Override
public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
{
@ -2803,8 +2773,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
LOG.warn(UNIMPLEMENTED_USE_SERVLET_CONTEXT_HANDLER, "addListener(Class)");
}
@Override
public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
protected <T> T createInstance(Class<T> clazz) throws ServletException
{
try
{
@ -2816,6 +2785,24 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
@Override
public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
{
return createInstance(clazz);
}
@Override
public <T extends Servlet> T createServlet(Class<T> clazz) throws ServletException
{
return createInstance(clazz);
}
@Override
public <T extends Filter> T createFilter(Class<T> clazz) throws ServletException
{
return createInstance(clazz);
}
@Override
public ClassLoader getClassLoader()
{

View File

@ -522,6 +522,16 @@ public class SessionHandler extends ScopedHandler
return _httpOnly;
}
/**
* @return The sameSite setting for session cookies or null for no setting
* @see HttpCookie#getSameSite()
*/
@ManagedAttribute("SameSite setting for session cookies")
public HttpCookie.SameSite getSameSite()
{
return HttpCookie.getSameSiteFromComment(_sessionComment);
}
/**
* Returns the <code>HttpSession</code> with the given session id
*
@ -642,30 +652,18 @@ public class SessionHandler extends ScopedHandler
sessionPath = (StringUtil.isEmpty(sessionPath)) ? "/" : sessionPath;
String id = getExtendedId(session);
HttpCookie cookie = null;
if (_sessionComment == null)
{
cookie = new HttpCookie(
_cookieConfig.getName(),
id,
_cookieConfig.getDomain(),
sessionPath,
_cookieConfig.getMaxAge(),
_cookieConfig.isHttpOnly(),
_cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
}
else
{
cookie = new HttpCookie(
_cookieConfig.getName(),
id,
_cookieConfig.getDomain(),
sessionPath,
_cookieConfig.getMaxAge(),
_cookieConfig.isHttpOnly(),
_cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
_sessionComment,
1);
}
cookie = new HttpCookie(
_cookieConfig.getName(),
id,
_cookieConfig.getDomain(),
sessionPath,
_cookieConfig.getMaxAge(),
_cookieConfig.isHttpOnly(),
_cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
HttpCookie.getCommentWithoutAttributes(_cookieConfig.getComment()),
0,
HttpCookie.getSameSiteFromComment(_cookieConfig.getComment()));
return cookie;
}
@ -805,13 +803,28 @@ public class SessionHandler extends ScopedHandler
}
/**
* @param httpOnly The httpOnly to set.
* Set if Session cookies should use HTTP Only
* @param httpOnly True if cookies should be HttpOnly.
* @see HttpCookie
*/
public void setHttpOnly(boolean httpOnly)
{
_httpOnly = httpOnly;
}
/**
* Set Session cookie sameSite mode.
* Currently this is encoded in the session comment until sameSite is supported by {@link SessionCookieConfig}
* @param sameSite The sameSite setting for Session cookies (or null for no sameSite setting)
*/
public void setSameSite(HttpCookie.SameSite sameSite)
{
// Encode in comment whilst not supported by SessionConfig, so that it can be set/saved in
// web.xml and quickstart.
// Always pass false for httpOnly as it has it's own setter.
_sessionComment = HttpCookie.getCommentWithAttributes(_sessionComment, false, sameSite);
}
/**
* @param metaManager The metaManager used for cross context session management.
*/
@ -1345,6 +1358,8 @@ public class SessionHandler extends ScopedHandler
* CookieConfig
*
* Implementation of the javax.servlet.SessionCookieConfig.
* SameSite configuration can be achieved by using setComment
* @see HttpCookie
*/
public final class CookieConfig implements SessionCookieConfig
{

View File

@ -114,7 +114,7 @@ public class FilterHolder extends Holder<Filter>
try
{
ServletContext context = getServletHandler().getServletContext();
_filter = (context instanceof ServletContextHandler.Context)
_filter = (context != null)
? context.createFilter(getHeldClass())
: getHeldClass().getDeclaredConstructor().newInstance();
}
@ -234,7 +234,7 @@ public class FilterHolder extends Holder<Filter>
public Collection<String> getServletNameMappings()
{
FilterMapping[] mappings = getServletHandler().getFilterMappings();
List<String> names = new ArrayList<String>();
List<String> names = new ArrayList<>();
for (FilterMapping mapping : mappings)
{
if (mapping.getFilterHolder() != FilterHolder.this)
@ -250,7 +250,7 @@ public class FilterHolder extends Holder<Filter>
public Collection<String> getUrlPatternMappings()
{
FilterMapping[] mappings = getServletHandler().getFilterMappings();
List<String> patterns = new ArrayList<String>();
List<String> patterns = new ArrayList<>();
for (FilterMapping mapping : mappings)
{
if (mapping.getFilterHolder() != FilterHolder.this)

View File

@ -88,9 +88,9 @@ public class ListenerHolder extends BaseHolder<EventListener>
//create an instance of the listener and decorate it
try
{
ServletContext scontext = contextHandler.getServletContext();
_listener = (scontext instanceof ServletContextHandler.Context)
? scontext.createListener(getHeldClass())
ServletContext context = contextHandler.getServletContext();
_listener = (context != null)
? context.createListener(getHeldClass())
: getHeldClass().getDeclaredConstructor().newInstance();
}
catch (ServletException ex)

View File

@ -305,7 +305,6 @@ public class ServletContextHandler extends ContextHandler
if (handler.getHandler() != _servletHandler)
doSetHandler(handler, _servletHandler);
handler = _servletHandler;
}
}
@ -356,7 +355,7 @@ public class ServletContextHandler extends ContextHandler
{
try
{
return _defaultSecurityHandlerClass.getDeclaredConstructor().newInstance();
return getDefaultSecurityHandlerClass().getDeclaredConstructor().newInstance();
}
catch (Exception e)
{
@ -547,7 +546,7 @@ public class ServletContextHandler extends ContextHandler
//Get a reference to the SecurityHandler, which must be ConstraintAware
if (_securityHandler != null && _securityHandler instanceof ConstraintAware)
{
HashSet<String> union = new HashSet<String>();
HashSet<String> union = new HashSet<>();
Set<String> existing = ((ConstraintAware)_securityHandler).getRoles();
if (existing != null)
union.addAll(existing);
@ -745,13 +744,13 @@ public class ServletContextHandler extends ContextHandler
public static class JspPropertyGroup implements JspPropertyGroupDescriptor
{
private List<String> _urlPatterns = new ArrayList<String>();
private List<String> _urlPatterns = new ArrayList<>();
private String _elIgnored;
private String _pageEncoding;
private String _scriptingInvalid;
private String _isXml;
private List<String> _includePreludes = new ArrayList<String>();
private List<String> _includeCodas = new ArrayList<String>();
private List<String> _includePreludes = new ArrayList<>();
private List<String> _includeCodas = new ArrayList<>();
private String _deferredSyntaxAllowedAsLiteral;
private String _trimDirectiveWhitespaces;
private String _defaultContentType;
@ -764,7 +763,7 @@ public class ServletContextHandler extends ContextHandler
@Override
public Collection<String> getUrlPatterns()
{
return new ArrayList<String>(_urlPatterns); // spec says must be a copy
return new ArrayList<>(_urlPatterns); // spec says must be a copy
}
public void addUrlPattern(String s)
@ -860,7 +859,7 @@ public class ServletContextHandler extends ContextHandler
@Override
public Collection<String> getIncludePreludes()
{
return new ArrayList<String>(_includePreludes); //must be a copy
return new ArrayList<>(_includePreludes); //must be a copy
}
public void addIncludePrelude(String prelude)
@ -875,7 +874,7 @@ public class ServletContextHandler extends ContextHandler
@Override
public Collection<String> getIncludeCodas()
{
return new ArrayList<String>(_includeCodas); //must be a copy
return new ArrayList<>(_includeCodas); //must be a copy
}
public void addIncludeCoda(String coda)
@ -932,24 +931,24 @@ public class ServletContextHandler extends ContextHandler
@Override
public String toString()
{
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
sb.append("JspPropertyGroupDescriptor:");
sb.append(" el-ignored=" + _elIgnored);
sb.append(" is-xml=" + _isXml);
sb.append(" page-encoding=" + _pageEncoding);
sb.append(" scripting-invalid=" + _scriptingInvalid);
sb.append(" deferred-syntax-allowed-as-literal=" + _deferredSyntaxAllowedAsLiteral);
sb.append(" trim-directive-whitespaces" + _trimDirectiveWhitespaces);
sb.append(" default-content-type=" + _defaultContentType);
sb.append(" buffer=" + _buffer);
sb.append(" error-on-undeclared-namespace=" + _errorOnUndeclaredNamespace);
sb.append(" el-ignored=").append(_elIgnored);
sb.append(" is-xml=").append(_isXml);
sb.append(" page-encoding=").append(_pageEncoding);
sb.append(" scripting-invalid=").append(_scriptingInvalid);
sb.append(" deferred-syntax-allowed-as-literal=").append(_deferredSyntaxAllowedAsLiteral);
sb.append(" trim-directive-whitespaces").append(_trimDirectiveWhitespaces);
sb.append(" default-content-type=").append(_defaultContentType);
sb.append(" buffer=").append(_buffer);
sb.append(" error-on-undeclared-namespace=").append(_errorOnUndeclaredNamespace);
for (String prelude : _includePreludes)
{
sb.append(" include-prelude=" + prelude);
sb.append(" include-prelude=").append(prelude);
}
for (String coda : _includeCodas)
{
sb.append(" include-coda=" + coda);
sb.append(" include-coda=").append(coda);
}
return sb.toString();
}
@ -997,8 +996,8 @@ public class ServletContextHandler extends ContextHandler
public static class JspConfig implements JspConfigDescriptor
{
private List<TaglibDescriptor> _taglibs = new ArrayList<TaglibDescriptor>();
private List<JspPropertyGroupDescriptor> _jspPropertyGroups = new ArrayList<JspPropertyGroupDescriptor>();
private List<TaglibDescriptor> _taglibs = new ArrayList<>();
private List<JspPropertyGroupDescriptor> _jspPropertyGroups = new ArrayList<>();
public JspConfig()
{
@ -1010,7 +1009,7 @@ public class ServletContextHandler extends ContextHandler
@Override
public Collection<TaglibDescriptor> getTaglibs()
{
return new ArrayList<TaglibDescriptor>(_taglibs);
return new ArrayList<>(_taglibs);
}
public void addTaglibDescriptor(TaglibDescriptor d)
@ -1024,7 +1023,7 @@ public class ServletContextHandler extends ContextHandler
@Override
public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups()
{
return new ArrayList<JspPropertyGroupDescriptor>(_jspPropertyGroups);
return new ArrayList<>(_jspPropertyGroups);
}
public void addJspPropertyGroup(JspPropertyGroupDescriptor g)
@ -1035,15 +1034,15 @@ public class ServletContextHandler extends ContextHandler
@Override
public String toString()
{
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
sb.append("JspConfigDescriptor: \n");
for (TaglibDescriptor taglib : _taglibs)
{
sb.append(taglib + "\n");
sb.append(taglib).append("\n");
}
for (JspPropertyGroupDescriptor jpg : _jspPropertyGroups)
{
sb.append(jpg + "\n");
sb.append(jpg).append("\n");
}
return sb.toString();
}
@ -1051,7 +1050,6 @@ public class ServletContextHandler extends ContextHandler
public class Context extends ContextHandler.Context
{
/*
* @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
*/
@ -1272,18 +1270,9 @@ public class ServletContextHandler extends ContextHandler
}
@Override
public <T extends Filter> T createFilter(Class<T> c) throws ServletException
protected <T> T createInstance(Class<T> clazz) throws ServletException
{
try
{
T f = createInstance(c);
f = _objFactory.decorate(f);
return f;
}
catch (Exception e)
{
throw new ServletException(e);
}
return _objFactory.decorate(super.createInstance(clazz));
}
public <T extends Filter> void destroyFilter(T f)
@ -1291,21 +1280,6 @@ public class ServletContextHandler extends ContextHandler
_objFactory.destroy(f);
}
@Override
public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
{
try
{
T s = createInstance(c);
s = _objFactory.decorate(s);
return s;
}
catch (Exception e)
{
throw new ServletException(e);
}
}
public <T extends Servlet> void destroyServlet(T s)
{
_objFactory.destroy(s);
@ -1343,7 +1317,7 @@ public class ServletContextHandler extends ContextHandler
if (!_enabled)
throw new UnsupportedOperationException();
HashMap<String, FilterRegistration> registrations = new HashMap<String, FilterRegistration>();
HashMap<String, FilterRegistration> registrations = new HashMap<>();
ServletHandler handler = ServletContextHandler.this.getServletHandler();
FilterHolder[] holders = handler.getFilters();
if (holders != null)
@ -1372,7 +1346,7 @@ public class ServletContextHandler extends ContextHandler
if (!_enabled)
throw new UnsupportedOperationException();
HashMap<String, ServletRegistration> registrations = new HashMap<String, ServletRegistration>();
HashMap<String, ServletRegistration> registrations = new HashMap<>();
ServletHandler handler = ServletContextHandler.this.getServletHandler();
ServletHolder[] holders = handler.getServlets();
if (holders != null)
@ -1455,21 +1429,6 @@ public class ServletContextHandler extends ContextHandler
super.addListener(listenerClass);
}
@Override
public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
{
try
{
T l = createInstance(clazz);
l = _objFactory.decorate(l);
return l;
}
catch (Exception e)
{
throw new ServletException(e);
}
}
@Override
public JspConfigDescriptor getJspConfigDescriptor()
{

View File

@ -258,7 +258,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
public synchronized void setUserRoleLink(String name, String link)
{
if (_roleMap == null)
_roleMap = new HashMap<String, String>();
_roleMap = new HashMap<>();
_roleMap.put(name, link);
}
@ -639,7 +639,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
}
/* ensure scratch dir */
File scratch = null;
File scratch;
if (getInitParameter("scratchdir") == null)
{
File tmp = (File)getServletHandler().getServletContext().getAttribute(ServletContext.TEMPDIR);
@ -848,8 +848,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
{
Class<?> jspUtil = Loader.loadClass("org.apache.jasper.compiler.JspUtil");
Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class);
String p = (String)makeJavaPackage.invoke(null, jsp.substring(0, i));
return p;
return (String)makeJavaPackage.invoke(null, jsp.substring(0, i));
}
catch (Exception e)
{
@ -953,7 +952,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
if (!mapping.isDefault())
{
if (clash == null)
clash = new HashSet<String>();
clash = new HashSet<>();
clash.add(pattern);
}
}
@ -976,7 +975,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
public Collection<String> getMappings()
{
ServletMapping[] mappings = getServletHandler().getServletMappings();
List<String> patterns = new ArrayList<String>();
List<String> patterns = new ArrayList<>();
if (mappings != null)
{
for (ServletMapping mapping : mappings)
@ -1042,7 +1041,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
private class SingleThreadedWrapper implements Servlet
{
Stack<Servlet> _stack = new Stack<Servlet>();
Stack<Servlet> _stack = new Stack<>();
@Override
public void destroy()
@ -1154,7 +1153,7 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
try
{
ServletContext ctx = getServletHandler().getServletContext();
if (ctx instanceof ServletContextHandler.Context)
if (ctx != null)
return ctx.createServlet(getHeldClass());
return getHeldClass().getDeclaredConstructor().newInstance();

View File

@ -318,7 +318,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
{
super.setDisplayName(servletContextName);
ClassLoader cl = getClassLoader();
if (cl != null && cl instanceof WebAppClassLoader && servletContextName != null)
if (cl instanceof WebAppClassLoader && servletContextName != null)
((WebAppClassLoader)cl).setName(servletContextName);
}
@ -344,7 +344,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
public void setResourceAlias(String alias, String uri)
{
if (_resourceAliases == null)
_resourceAliases = new HashMap<String, String>(5);
_resourceAliases = new HashMap<>(5);
_resourceAliases.put(alias, uri);
}
@ -398,7 +398,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
if (name == null)
name = getContextPath();
if (classLoader != null && classLoader instanceof WebAppClassLoader && getDisplayName() != null)
if (classLoader instanceof WebAppClassLoader && getDisplayName() != null)
((WebAppClassLoader)classLoader).setName(name);
}
@ -429,7 +429,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
}
}
if (ioe != null && ioe instanceof MalformedURLException)
if (ioe instanceof MalformedURLException)
throw (MalformedURLException)ioe;
return resource;
@ -537,7 +537,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
{
_metadata.setAllowDuplicateFragmentNames(isAllowDuplicateFragmentNames());
Boolean validate = (Boolean)getAttribute(MetaData.VALIDATE_XML);
_metadata.setValidateXml((validate != null && validate.booleanValue()));
_metadata.setValidateXml((validate != null && validate));
preConfigure();
super.doStart();
postConfigure();
@ -922,16 +922,18 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
{
if (_war != null)
{
if (_war.indexOf("/webapps/") >= 0)
name = _war.substring(_war.indexOf("/webapps/") + 8);
int webapps = _war.indexOf("/webapps/");
if (webapps >= 0)
name = _war.substring(webapps + 8);
else
name = _war;
}
else if (getResourceBase() != null)
{
name = getResourceBase();
if (name.indexOf("/webapps/") >= 0)
name = name.substring(name.indexOf("/webapps/") + 8);
int webapps = name.indexOf("/webapps/");
if (webapps >= 0)
name = name.substring(webapps + 8);
}
else
{
@ -966,7 +968,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
public void setConfigurationClasses(List<String> configurations)
{
setConfigurationClasses(configurations.toArray(new String[configurations.size()]));
setConfigurationClasses(configurations.toArray(new String[0]));
}
/**
@ -1313,7 +1315,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
if (_ownClassLoader)
{
ClassLoader loader = getClassLoader();
if (loader != null && loader instanceof URLClassLoader)
if (loader instanceof URLClassLoader)
((URLClassLoader)loader).close();
setClassLoader(null);
}
@ -1326,7 +1328,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
@Override
public Set<String> setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement)
{
Set<String> unchangedURLMappings = new HashSet<String>();
Set<String> unchangedURLMappings = new HashSet<>();
//From javadoc for ServletSecurityElement:
/*
If a URL pattern of this ServletRegistration is an exact target of a security-constraint that
@ -1400,7 +1402,6 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
public class Context extends ServletContextHandler.Context
{
@Override
public void checkListener(Class<? extends EventListener> listener) throws IllegalStateException
{

View File

@ -269,6 +269,7 @@ public class HugeResourceTest
multipart.addFilePart(name, filename, new PathContentProvider(inputFile), null);
URI destUri = server.getURI().resolve("/multipart");
client.setIdleTimeout(90_000);
Request request = client.newRequest(destUri).method(HttpMethod.POST).content(multipart);
ContentResponse response = request.send();
assertThat("HTTP Response Code", response.getStatus(), is(200));

View File

@ -26,6 +26,8 @@ import java.net.SocketTimeoutException;
import java.net.URI;
import java.time.Duration;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@ -182,6 +184,27 @@ public class ClientConnectTest
}
}
@Test
public void testUpgradeRequest_PercentEncodedQuery() throws Exception
{
CloseTrackingEndpoint cliSock = new CloseTrackingEndpoint();
client.setIdleTimeout(Duration.ofSeconds(10));
URI wsUri = WSURI.toWebsocket(server.getURI().resolve("/echo?name=%25foo"));
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("echo");
Future<Session> future = client.connect(cliSock, wsUri);
try (Session sess = future.get(30, TimeUnit.SECONDS))
{
assertThat("Connect.UpgradeRequest", sess.getUpgradeRequest(), notNullValue());
Map<String, List<String>> paramMap = sess.getUpgradeRequest().getParameterMap();
List<String> values = paramMap.get("name");
assertThat("Params[name]", values.get(0), is("%foo"));
assertThat("Connect.UpgradeResponse", sess.getUpgradeResponse(), notNullValue());
}
}
@Test
public void testUpgradeWithAuthorizationHeader() throws Exception
{

View File

@ -28,6 +28,7 @@ import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
@ -50,14 +51,17 @@ public class Negotiated
this.extensions = extensions;
this.protocolVersion = protocolVersion;
String rawQuery = requestURI.getRawQuery();
Map<String, List<String>> map;
if (requestURI.getQuery() == null)
if (StringUtil.isBlank(rawQuery))
{
map = Collections.emptyMap();
}
else
{
map = new HashMap<>();
MultiMap<String> params = new MultiMap<>();
UrlEncoded.decodeUtf8To(requestURI.getQuery(), params);
UrlEncoded.decodeUtf8To(rawQuery, params);
for (String p : params.keySet())
{
map.put(p, Collections.unmodifiableList(params.getValues(p)));

View File

@ -33,10 +33,12 @@ import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.unixsocket.client.HttpClientTransportOverUnixSockets;
import org.eclipse.jetty.unixsocket.server.UnixSocketConnector;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE;
@ -211,6 +213,18 @@ public class DistributionTests extends AbstractDistributionTest
@Test
@DisabledOnJre(JRE.JAVA_8)
public void testSimpleWebAppWithJSPOverH2C() throws Exception
{
testSimpleWebAppWithJSPOverHTTP2(false);
}
@Test
@DisabledOnJre(JRE.JAVA_8)
public void testSimpleWebAppWithJSPOverH2() throws Exception
{
testSimpleWebAppWithJSPOverHTTP2(true);
}
private void testSimpleWebAppWithJSPOverHTTP2(boolean ssl) throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
DistributionTester distribution = DistributionTester.Builder.newInstance()
@ -220,7 +234,7 @@ public class DistributionTests extends AbstractDistributionTest
String[] args1 = {
"--create-startd",
"--add-to-start=http2c,jsp,deploy"
"--add-to-start=jsp,deploy," + (ssl ? "http2,test-keystore" : "http2c")
};
try (DistributionTester.Run run1 = distribution.start(args1))
{
@ -231,13 +245,16 @@ public class DistributionTests extends AbstractDistributionTest
distribution.installWarFile(war, "test");
int port = distribution.freePort();
try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port))
String portProp = ssl ? "jetty.ssl.port" : "jetty.http.port";
try (DistributionTester.Run run2 = distribution.start(portProp + "=" + port))
{
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
HTTP2Client h2Client = new HTTP2Client();
ClientConnector connector = new ClientConnector();
connector.setSslContextFactory(new SslContextFactory.Client(true));
HTTP2Client h2Client = new HTTP2Client(connector);
startHttpClient(() -> new HttpClient(new HttpClientTransportOverHTTP2(h2Client)));
ContentResponse response = client.GET("http://localhost:" + port + "/test/index.jsp");
ContentResponse response = client.GET((ssl ? "https" : "http") + "://localhost:" + port + "/test/index.jsp");
assertEquals(HttpStatus.OK_200, response.getStatus());
assertThat(response.getContentAsString(), containsString("Hello"));
assertThat(response.getContentAsString(), not(containsString("<%")));

View File

@ -414,4 +414,119 @@ public class HttpClientDemandTest extends AbstractTest<TransportScenario>
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
assertArrayEquals(content, bytes);
}
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
public void testDelayedBeforeContentDemand(Transport transport) throws Exception
{
init(transport);
byte[] content = new byte[1024];
new Random().nextBytes(content);
scenario.start(new EmptyServerHandler()
{
@Override
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.setContentLength(content.length);
response.getOutputStream().write(content);
}
});
byte[] bytes = new byte[content.length];
ByteBuffer received = ByteBuffer.wrap(bytes);
AtomicReference<LongConsumer> beforeContentDemandRef = new AtomicReference<>();
CountDownLatch beforeContentLatch = new CountDownLatch(1);
CountDownLatch contentLatch = new CountDownLatch(1);
CountDownLatch resultLatch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.onResponseContentDemanded(new Response.DemandedContentListener()
{
@Override
public void onBeforeContent(Response response, LongConsumer demand)
{
// Do not demand now.
beforeContentDemandRef.set(demand);
beforeContentLatch.countDown();
}
@Override
public void onContent(Response response, LongConsumer demand, ByteBuffer buffer, Callback callback)
{
contentLatch.countDown();
received.put(buffer);
callback.succeeded();
demand.accept(1);
}
})
.send(result ->
{
assertTrue(result.isSucceeded());
assertEquals(HttpStatus.OK_200, result.getResponse().getStatus());
resultLatch.countDown();
});
assertTrue(beforeContentLatch.await(5, TimeUnit.SECONDS));
LongConsumer demand = beforeContentDemandRef.get();
// Content must not be notified until we demand.
assertFalse(contentLatch.await(1, TimeUnit.SECONDS));
demand.accept(1);
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
assertArrayEquals(content, bytes);
}
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
public void testDelayedBeforeContentDemandWithNoResponseContent(Transport transport) throws Exception
{
init(transport);
scenario.start(new EmptyServerHandler());
AtomicReference<LongConsumer> beforeContentDemandRef = new AtomicReference<>();
CountDownLatch beforeContentLatch = new CountDownLatch(1);
CountDownLatch contentLatch = new CountDownLatch(1);
CountDownLatch resultLatch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.onResponseContentDemanded(new Response.DemandedContentListener()
{
@Override
public void onBeforeContent(Response response, LongConsumer demand)
{
// Do not demand now.
beforeContentDemandRef.set(demand);
beforeContentLatch.countDown();
}
@Override
public void onContent(Response response, LongConsumer demand, ByteBuffer buffer, Callback callback)
{
contentLatch.countDown();
callback.succeeded();
demand.accept(1);
}
})
.send(result ->
{
assertTrue(result.isSucceeded());
assertEquals(HttpStatus.OK_200, result.getResponse().getStatus());
resultLatch.countDown();
});
assertTrue(beforeContentLatch.await(5, TimeUnit.SECONDS));
LongConsumer demand = beforeContentDemandRef.get();
// Content must not be notified until we demand.
assertFalse(contentLatch.await(1, TimeUnit.SECONDS));
demand.accept(1);
// Content must not be notified as there is no content.
assertFalse(contentLatch.await(1, TimeUnit.SECONDS));
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
}
}