Merged branch 'jetty-10.0.x' into 'jetty-10.0.x-3951-http2_demand'.
This commit is contained in:
commit
0485fb5dde
|
@ -89,7 +89,6 @@ pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
def slackNotif() {
|
||||
script {
|
||||
try {
|
||||
|
@ -108,7 +107,6 @@ def slackNotif() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To other developers, if you are using this method above, please use the following syntax.
|
||||
*
|
||||
|
@ -135,4 +133,5 @@ def mavenBuild(jdk, cmdline, mvnName, junitPublishDisabled) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// vim: et:ts=2:sw=2:ft=groovy
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer;
|
||||
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
|
||||
|
||||
/**
|
||||
* Example of setting up a javax.websocket server with Jetty embedded
|
||||
|
|
|
@ -173,7 +173,7 @@ public class TestSecurityAnnotationConversions
|
|||
public void testMethodAnnotation() throws Exception
|
||||
{
|
||||
//ServletSecurity annotation with HttpConstraint of TransportGuarantee.CONFIDENTIAL, and a list of rolesAllowed, and
|
||||
//a HttpMethodConstraint for GET method that permits all and has TransportGuarantee.NONE (ie is default)
|
||||
//an HttpMethodConstraint for GET method that permits all and has TransportGuarantee.NONE (ie is default)
|
||||
|
||||
WebAppContext wac = makeWebAppContext(Method1Servlet.class.getCanonicalName(), "method1Servlet", new String[]{
|
||||
"/foo/*", "*.foo"
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.eclipse.jetty.io.ClientConnectionFactory;
|
|||
* in order to plug-in a different transport for {@link HttpClient}.
|
||||
* <p>
|
||||
* While the {@link HttpClient} APIs define the HTTP semantic (request, response, headers, etc.)
|
||||
* <em>how</em> a HTTP exchange is carried over the network depends on implementations of this class.
|
||||
* <em>how</em> an HTTP exchange is carried over the network depends on implementations of this class.
|
||||
* <p>
|
||||
* The default implementation uses the HTTP protocol to carry over the network the HTTP exchange,
|
||||
* but the HTTP exchange may also be carried using the FCGI protocol, the HTTP/2 protocol or,
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
/**
|
||||
* {@link HttpContent} is a stateful, linear representation of the request content provided
|
||||
* by a {@link ContentProvider} that can be traversed one-way to obtain content buffers to
|
||||
* send to a HTTP server.
|
||||
* send to an HTTP server.
|
||||
* <p>
|
||||
* {@link HttpContent} offers the notion of a one-way cursor to traverse the content.
|
||||
* The cursor starts in a virtual "before" position and can be advanced using {@link #advance()}
|
||||
|
|
|
@ -50,7 +50,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
* <ol>
|
||||
* <li>{@link #responseBegin(HttpExchange)}, when the HTTP response data containing the HTTP status code
|
||||
* is available</li>
|
||||
* <li>{@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available</li>
|
||||
* <li>{@link #responseHeader(HttpExchange, HttpField)}, when an HTTP field is available</li>
|
||||
* <li>{@link #responseHeaders(HttpExchange)}, when all HTTP headers are available</li>
|
||||
* <li>{@link #responseContent(HttpExchange, ByteBuffer, Callback)}, when HTTP content is available</li>
|
||||
* <li>{@link #responseSuccess(HttpExchange)}, when the response is successful</li>
|
||||
|
|
|
@ -82,7 +82,7 @@ public class HttpRedirector
|
|||
|
||||
/**
|
||||
* @param response the response to check for redirects
|
||||
* @return whether the response code is a HTTP redirect code
|
||||
* @return whether the response code is an HTTP redirect code
|
||||
*/
|
||||
public boolean isRedirect(Response response)
|
||||
{
|
||||
|
|
|
@ -876,7 +876,7 @@ public class HttpRequest implements Request
|
|||
}
|
||||
catch (URISyntaxException x)
|
||||
{
|
||||
// The "path" of a HTTP request may not be a URI,
|
||||
// The "path" of an HTTP request may not be a URI,
|
||||
// for example for CONNECT 127.0.0.1:8080.
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ import org.eclipse.jetty.http.HttpVersion;
|
|||
import org.eclipse.jetty.util.Fields;
|
||||
|
||||
/**
|
||||
* <p>{@link Request} represents a HTTP request, and offers a fluent interface to customize
|
||||
* <p>{@link Request} represents an HTTP request, and offers a fluent interface to customize
|
||||
* various attributes such as the path, the headers, the content, etc.</p>
|
||||
* <p>You can create {@link Request} objects via {@link HttpClient#newRequest(String)} and
|
||||
* you can send them using either {@link #send()} for a blocking semantic, or
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.eclipse.jetty.http.HttpVersion;
|
|||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
/**
|
||||
* <p>{@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version
|
||||
* <p>{@link Response} represents an HTTP response and offers methods to retrieve status code, HTTP version
|
||||
* and headers.</p>
|
||||
* <p>{@link Response} objects are passed as parameters to {@link Response.Listener} callbacks, or as
|
||||
* future result of {@link Request#send()}.</p>
|
||||
|
|
|
@ -161,7 +161,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses a HTTP response in the receivers buffer.
|
||||
* Parses an HTTP response in the receivers buffer.
|
||||
*
|
||||
* @return true to indicate that parsing should be interrupted (and will be resumed by another thread).
|
||||
*/
|
||||
|
|
|
@ -485,7 +485,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
ContentResponse response = request
|
||||
.onResponseBegin(response1 ->
|
||||
{
|
||||
// Simulate a HTTP 1.0 response has been received.
|
||||
// Simulate an HTTP 1.0 response has been received.
|
||||
((HttpResponse)response1).version(HttpVersion.HTTP_1_0);
|
||||
})
|
||||
.send();
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -44,7 +44,7 @@ import org.eclipse.jetty.util.ProcessorUtils;
|
|||
/**
|
||||
* Specific implementation of {@link org.eclipse.jetty.proxy.AsyncProxyServlet.Transparent} for FastCGI.
|
||||
* <p>
|
||||
* This servlet accepts a HTTP request and transforms it into a FastCGI request
|
||||
* This servlet accepts an HTTP request and transforms it into a FastCGI request
|
||||
* that is sent to the FastCGI server specified in the {@code proxyTo}
|
||||
* init-param.
|
||||
* <p>
|
||||
|
|
|
@ -64,15 +64,14 @@ public abstract class CookieCutter
|
|||
boolean inQuoted = false;
|
||||
boolean quoted = false;
|
||||
boolean escaped = false;
|
||||
boolean reject = false;
|
||||
int tokenstart = -1;
|
||||
int tokenend = -1;
|
||||
for (int i = 0, length = hdr.length(); i <= length; i++)
|
||||
{
|
||||
char c = i == length ? 0 : hdr.charAt(i);
|
||||
|
||||
// System.err.printf("i=%d/%d c=%s v=%b q=%b/%b e=%b u=%s s=%d e=%d \t%s=%s%n" ,i,length,c==0?"|":(""+c),invalue,inQuoted,quoted,escaped,unquoted,tokenstart,tokenend,name,value);
|
||||
|
||||
// Handle quoted values for name or value
|
||||
// Handle quoted values for value
|
||||
if (inQuoted)
|
||||
{
|
||||
if (escaped)
|
||||
|
@ -119,7 +118,7 @@ public abstract class CookieCutter
|
|||
// Handle name and value state machines
|
||||
if (invalue)
|
||||
{
|
||||
// parse the value
|
||||
// parse the cookie-value
|
||||
switch (c)
|
||||
{
|
||||
case ' ':
|
||||
|
@ -193,7 +192,11 @@ public abstract class CookieCutter
|
|||
// This is a new cookie, so add the completed last cookie if we have one
|
||||
if (cookieName != null)
|
||||
{
|
||||
addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment);
|
||||
if (!reject)
|
||||
{
|
||||
addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment);
|
||||
reject = false;
|
||||
}
|
||||
cookieDomain = null;
|
||||
cookiePath = null;
|
||||
cookieComment = null;
|
||||
|
@ -234,6 +237,15 @@ public abstract class CookieCutter
|
|||
quoted = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_complianceMode == CookieCompliance.RFC6265)
|
||||
{
|
||||
if (isRFC6265RejectedCharacter(inQuoted, c))
|
||||
{
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenstart < 0)
|
||||
tokenstart = i;
|
||||
tokenend = i;
|
||||
|
@ -242,13 +254,26 @@ public abstract class CookieCutter
|
|||
}
|
||||
else
|
||||
{
|
||||
// parse the name
|
||||
// parse the cookie-name
|
||||
switch (c)
|
||||
{
|
||||
case 0:
|
||||
case ' ':
|
||||
case '\t':
|
||||
continue;
|
||||
|
||||
case '"':
|
||||
// Quoted name is not allowed in any version of the Cookie spec
|
||||
reject = true;
|
||||
break;
|
||||
|
||||
case ';':
|
||||
// a cookie terminated with no '=' sign.
|
||||
tokenstart = -1;
|
||||
invalue = false;
|
||||
reject = false;
|
||||
continue;
|
||||
|
||||
case '=':
|
||||
if (quoted)
|
||||
{
|
||||
|
@ -272,6 +297,15 @@ public abstract class CookieCutter
|
|||
quoted = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_complianceMode == CookieCompliance.RFC6265)
|
||||
{
|
||||
if (isRFC6265RejectedCharacter(inQuoted, c))
|
||||
{
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenstart < 0)
|
||||
tokenstart = i;
|
||||
tokenend = i;
|
||||
|
@ -281,7 +315,7 @@ public abstract class CookieCutter
|
|||
}
|
||||
}
|
||||
|
||||
if (cookieName != null)
|
||||
if (cookieName != null && !reject)
|
||||
addCookie(cookieName, cookieValue, cookieDomain, cookiePath, cookieVersion, cookieComment);
|
||||
}
|
||||
}
|
||||
|
@ -295,4 +329,31 @@ public abstract class CookieCutter
|
|||
}
|
||||
|
||||
protected abstract void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment);
|
||||
|
||||
protected boolean isRFC6265RejectedCharacter(boolean inQuoted, char c)
|
||||
{
|
||||
if (inQuoted)
|
||||
{
|
||||
// We only reject if a Control Character is encountered
|
||||
if (Character.isISOControl(c))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* From RFC6265 - Section 4.1.1 - Syntax
|
||||
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||
* ; US-ASCII characters excluding CTLs,
|
||||
* ; whitespace DQUOTE, comma, semicolon,
|
||||
* ; and backslash
|
||||
*/
|
||||
return Character.isISOControl(c) || // control characters
|
||||
c > 127 || // 8-bit characters
|
||||
c == ',' || // comma
|
||||
c == ';'; // semicolon
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ package org.eclipse.jetty.http;
|
|||
import org.eclipse.jetty.util.HostPort;
|
||||
|
||||
/**
|
||||
* A HttpField holding a preparsed Host and port number
|
||||
* An HttpField holding a preparsed Host and port number
|
||||
*
|
||||
* @see HostPort
|
||||
*/
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.util.Objects;
|
|||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A HTTP Field
|
||||
* An HTTP Field
|
||||
*/
|
||||
public class HttpField
|
||||
{
|
||||
|
|
|
@ -48,7 +48,7 @@ public enum HttpMethod
|
|||
* @param bytes Array containing ISO-8859-1 characters
|
||||
* @param position The first valid index
|
||||
* @param limit The first non valid index
|
||||
* @return A HttpMethod if a match or null if no easy match.
|
||||
* @return An HttpMethod if a match or null if no easy match.
|
||||
*/
|
||||
public static HttpMethod lookAheadGet(byte[] bytes, final int position, int limit)
|
||||
{
|
||||
|
@ -110,7 +110,7 @@ public enum HttpMethod
|
|||
* Optimized lookup to find a method name and trailing space in a byte array.
|
||||
*
|
||||
* @param buffer buffer containing ISO-8859-1 characters, it is not modified.
|
||||
* @return A HttpMethod if a match or null if no easy match.
|
||||
* @return An HttpMethod if a match or null if no easy match.
|
||||
*/
|
||||
public static HttpMethod lookAheadGet(ByteBuffer buffer)
|
||||
{
|
||||
|
|
|
@ -442,7 +442,7 @@ public class HttpParser
|
|||
return t;
|
||||
}
|
||||
|
||||
/* Quick lookahead for the start state looking for a request method or a HTTP version,
|
||||
/* Quick lookahead for the start state looking for a request method or an HTTP version,
|
||||
* otherwise skip white space until something else to parse.
|
||||
*/
|
||||
private boolean quickStart(ByteBuffer buffer)
|
||||
|
@ -1834,14 +1834,14 @@ public class HttpParser
|
|||
boolean messageComplete();
|
||||
|
||||
/**
|
||||
* This is the method called by parser when a HTTP Header name and value is found
|
||||
* This is the method called by parser when an HTTP Header name and value is found
|
||||
*
|
||||
* @param field The field parsed
|
||||
*/
|
||||
void parsedHeader(HttpField field);
|
||||
|
||||
/**
|
||||
* This is the method called by parser when a HTTP Trailer name and value is found
|
||||
* This is the method called by parser when an HTTP Trailer name and value is found
|
||||
*
|
||||
* @param field The field parsed
|
||||
*/
|
||||
|
@ -1851,7 +1851,7 @@ public class HttpParser
|
|||
|
||||
/**
|
||||
* Called to signal that an EOF was received unexpectedly
|
||||
* during the parsing of a HTTP message
|
||||
* during the parsing of an HTTP message
|
||||
*/
|
||||
void earlyEOF();
|
||||
|
||||
|
|
|
@ -314,6 +314,20 @@ public class HttpStatus
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean hasNoBody(int status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case NO_CONTENT_204:
|
||||
case NOT_MODIFIED_304:
|
||||
case PARTIAL_CONTENT_206:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return status < OK_200;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test against an code to determine if it falls into the
|
||||
* <code>Informational</code> message category as defined in the <a
|
||||
|
|
|
@ -31,7 +31,7 @@ import org.eclipse.jetty.util.UrlEncoded;
|
|||
|
||||
/**
|
||||
* Http URI.
|
||||
* Parse a HTTP URI from a string or byte array. Given a URI
|
||||
* Parse an HTTP URI from a string or byte array. Given a URI
|
||||
* <code>http://user@host:port/path/info;param?query#fragment</code>
|
||||
* this class will split it into the following undecoded optional elements:<ul>
|
||||
* <li>{@link #getScheme()} - http:</li>
|
||||
|
|
|
@ -42,12 +42,12 @@ public enum HttpVersion
|
|||
}
|
||||
|
||||
/**
|
||||
* Optimised lookup to find a Http Version and whitespace in a byte array.
|
||||
* Optimised lookup to find an Http Version and whitespace in a byte array.
|
||||
*
|
||||
* @param bytes Array containing ISO-8859-1 characters
|
||||
* @param position The first valid index
|
||||
* @param limit The first non valid index
|
||||
* @return A HttpMethod if a match or null if no easy match.
|
||||
* @return An HttpMethod if a match or null if no easy match.
|
||||
*/
|
||||
public static HttpVersion lookAheadGet(byte[] bytes, int position, int limit)
|
||||
{
|
||||
|
@ -88,10 +88,10 @@ public enum HttpVersion
|
|||
}
|
||||
|
||||
/**
|
||||
* Optimised lookup to find a HTTP Version and trailing white space in a byte array.
|
||||
* Optimised lookup to find an HTTP Version and trailing white space in a byte array.
|
||||
*
|
||||
* @param buffer buffer containing ISO-8859-1 characters
|
||||
* @return A HttpVersion if a match or null if no easy match.
|
||||
* @return An HttpVersion if a match or null if no easy match.
|
||||
*/
|
||||
public static HttpVersion lookAheadGet(ByteBuffer buffer)
|
||||
{
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
|
||||
/**
|
||||
* Pre encoded HttpField.
|
||||
* <p>A HttpField that will be cached and used many times can be created as
|
||||
* <p>An HttpField that will be cached and used many times can be created as
|
||||
* a {@link PreEncodedHttpField}, which will use the {@link HttpFieldPreEncoder}
|
||||
* instances discovered by the {@link ServiceLoader} to pre-encode the header
|
||||
* for each version of HTTP in use. This will save garbage
|
||||
|
|
|
@ -40,6 +40,7 @@ public class CookieCutterLenientTest
|
|||
{
|
||||
return Stream.of(
|
||||
// Simple test to verify behavior
|
||||
Arguments.of("x=y", "x", "y"),
|
||||
Arguments.of("key=value", "key", "value"),
|
||||
|
||||
// Tests that conform to RFC2109
|
||||
|
@ -62,12 +63,17 @@ public class CookieCutterLenientTest
|
|||
// quoted-string = ( <"> *(qdtext) <"> )
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
// rejected, as name cannot be DQUOTED
|
||||
Arguments.of("\"a\"=bcd", null, null),
|
||||
Arguments.of("\"a\"=\"b c d\"", null, null),
|
||||
|
||||
// lenient with spaces and EOF
|
||||
Arguments.of("abc=", "abc", ""),
|
||||
Arguments.of("abc = ", "abc", ""),
|
||||
Arguments.of("abc = ;", "abc", ""),
|
||||
Arguments.of("abc = ; ", "abc", ""),
|
||||
Arguments.of("abc = x ", "abc", "x"),
|
||||
Arguments.of("abc = e f g ", "abc", "e f g"),
|
||||
Arguments.of("abc=\"\"", "abc", ""),
|
||||
Arguments.of("abc= \"\" ", "abc", ""),
|
||||
Arguments.of("abc= \"x\" ", "abc", "x"),
|
||||
|
@ -112,28 +118,29 @@ public class CookieCutterLenientTest
|
|||
// Unterminated Quotes with trailing escape
|
||||
Arguments.of("x=\"abc\\", "x", "\"abc\\"),
|
||||
|
||||
// UTF-8 values
|
||||
Arguments.of("2sides=\u262F", "2sides", "\u262f"), // 2 byte
|
||||
Arguments.of("currency=\"\u20AC\"", "currency", "\u20AC"), // 3 byte
|
||||
Arguments.of("gothic=\"\uD800\uDF48\"", "gothic", "\uD800\uDF48"), // 4 byte
|
||||
// UTF-8 raw values (not encoded) - VIOLATION of RFC6265
|
||||
Arguments.of("2sides=\u262F", null, null), // 2 byte (YIN YANG) - rejected due to not being DQUOTED
|
||||
Arguments.of("currency=\"\u20AC\"", "currency", "\u20AC"), // 3 byte (EURO SIGN)
|
||||
Arguments.of("gothic=\"\uD800\uDF48\"", "gothic", "\uD800\uDF48"), // 4 byte (GOTHIC LETTER HWAIR)
|
||||
|
||||
// Spaces
|
||||
Arguments.of("foo=bar baz", "foo", "bar baz"),
|
||||
Arguments.of("foo=\"bar baz\"", "foo", "bar baz"),
|
||||
Arguments.of("z=a b c d e f g", "z", "a b c d e f g"),
|
||||
|
||||
// Bad tspecials usage
|
||||
// Bad tspecials usage - VIOLATION of RFC6265
|
||||
Arguments.of("foo=bar;baz", "foo", "bar"),
|
||||
Arguments.of("foo=\"bar;baz\"", "foo", "bar;baz"),
|
||||
Arguments.of("z=a;b,c:d;e/f[g]", "z", "a"),
|
||||
Arguments.of("z=\"a;b,c:d;e/f[g]\"", "z", "a;b,c:d;e/f[g]"),
|
||||
Arguments.of("name=quoted=\"\\\"badly\\\"\"", "name", "quoted=\"\\\"badly\\\"\""), // someone attempting to escape a DQUOTE from within a DQUOTED pair)
|
||||
|
||||
// Quoted with other Cookie keywords
|
||||
Arguments.of("x=\"$Version=0\"", "x", "$Version=0"),
|
||||
Arguments.of("x=\"$Path=/\"", "x", "$Path=/"),
|
||||
Arguments.of("x=\"$Path=/ $Domain=.foo.com\"", "x", "$Path=/ $Domain=.foo.com"),
|
||||
Arguments.of("x=\" $Path=/ $Domain=.foo.com \"", "x", " $Path=/ $Domain=.foo.com "),
|
||||
Arguments.of("a=\"b; $Path=/a; c=d; $PATH=/c; e=f\"; $Path=/e/", "a", "b; $Path=/a; c=d; $PATH=/c; e=f"),
|
||||
Arguments.of("a=\"b; $Path=/a; c=d; $PATH=/c; e=f\"; $Path=/e/", "a", "b; $Path=/a; c=d; $PATH=/c; e=f"), // VIOLATES RFC6265
|
||||
|
||||
// Lots of equals signs
|
||||
Arguments.of("query=b=c&d=e", "query", "b=c&d=e"),
|
||||
|
@ -144,7 +151,7 @@ public class CookieCutterLenientTest
|
|||
// Google cookies (seen in wild, has `tspecials` of ':' in value)
|
||||
Arguments.of("GAPS=1:A1aaaAaAA1aaAAAaa1a11a:aAaaAa-aaA1-", "GAPS", "1:A1aaaAaAA1aaAAAaa1a11a:aAaaAa-aaA1-"),
|
||||
|
||||
// Strong abuse of cookie spec (lots of tspecials)
|
||||
// Strong abuse of cookie spec (lots of tspecials) - VIOLATION of RFC6265
|
||||
Arguments.of("$Version=0; rToken=F_TOKEN''!--\"</a>=&{()}", "rToken", "F_TOKEN''!--\"</a>=&{()}"),
|
||||
|
||||
// Commas that were not commas
|
||||
|
|
|
@ -212,6 +212,34 @@ public class CookieCutterTest
|
|||
assertThat("Cookies.length", cookies.length, is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleCookies()
|
||||
{
|
||||
String rawCookie = "testcookie; server.id=abcd; server.detail=cfg";
|
||||
|
||||
// The first cookie "testcookie" should be ignored, per RFC6265, as it's missing the "=" sign.
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "server.id", "abcd", 0, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "server.detail", "cfg", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExcessiveSemicolons()
|
||||
{
|
||||
char[] excessive = new char[65535];
|
||||
Arrays.fill(excessive, ';');
|
||||
String rawCookie = "foo=bar; " + excessive + "; xyz=pdq";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(2));
|
||||
assertCookie("Cookies[0]", cookies[0], "foo", "bar", 0, null);
|
||||
assertCookie("Cookies[1]", cookies[1], "xyz", "pdq", 0, null);
|
||||
}
|
||||
|
||||
static class Cookie
|
||||
{
|
||||
String name;
|
||||
|
@ -282,6 +310,4 @@ public class CookieCutterTest
|
|||
super.parseFields(Arrays.asList(fields));
|
||||
}
|
||||
}
|
||||
|
||||
;
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy
|
|||
// and here we keep track of its max value.
|
||||
|
||||
// Updating the max session recv window is done here
|
||||
// so that if a peer decides to send an unilateral
|
||||
// so that if a peer decides to send a unilateral
|
||||
// window update to enlarge the session window,
|
||||
// without the corresponding data consumption, here
|
||||
// we can track it.
|
||||
|
|
|
@ -40,7 +40,7 @@ public enum ErrorCode
|
|||
*/
|
||||
INTERNAL_ERROR(2),
|
||||
/**
|
||||
* Indicates a HTTP/2 flow control violation.
|
||||
* Indicates an HTTP/2 flow control violation.
|
||||
*/
|
||||
FLOW_CONTROL_ERROR(3),
|
||||
/**
|
||||
|
@ -68,7 +68,7 @@ public enum ErrorCode
|
|||
*/
|
||||
COMPRESSION_ERROR(9),
|
||||
/**
|
||||
* Indicates that the connection established by a HTTP CONNECT was abnormally closed.
|
||||
* Indicates that the connection established by an HTTP CONNECT was abnormally closed.
|
||||
*/
|
||||
HTTP_CONNECT_ERROR(10),
|
||||
/**
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.eclipse.jetty.util.Callback;
|
|||
import org.eclipse.jetty.util.Promise;
|
||||
|
||||
/**
|
||||
* <p>The SPI interface for implementing a HTTP/2 session.</p>
|
||||
* <p>The SPI interface for implementing an HTTP/2 session.</p>
|
||||
* <p>This class extends {@link Session} by adding the methods required to
|
||||
* implement the HTTP/2 session functionalities.</p>
|
||||
*/
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.eclipse.jetty.http2.frames.Frame;
|
|||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
/**
|
||||
* <p>The SPI interface for implementing a HTTP/2 stream.</p>
|
||||
* <p>The SPI interface for implementing an HTTP/2 stream.</p>
|
||||
* <p>This class extends {@link Stream} by adding the methods required to
|
||||
* implement the HTTP/2 stream functionalities.</p>
|
||||
*/
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.eclipse.jetty.util.Callback;
|
|||
import org.eclipse.jetty.util.Promise;
|
||||
|
||||
/**
|
||||
* <p>A {@link Session} represents the client-side endpoint of a HTTP/2 connection to a single origin server.</p>
|
||||
* <p>A {@link Session} represents the client-side endpoint of an HTTP/2 connection to a single origin server.</p>
|
||||
* <p>Once a {@link Session} has been obtained, it can be used to open HTTP/2 streams:</p>
|
||||
* <pre>
|
||||
* Session session = ...;
|
||||
|
@ -140,7 +140,7 @@ public interface Session
|
|||
|
||||
/**
|
||||
* <p>A {@link Listener} is the passive counterpart of a {@link Session} and
|
||||
* receives events happening on a HTTP/2 connection.</p>
|
||||
* receives events happening on an HTTP/2 connection.</p>
|
||||
*
|
||||
* @see Session
|
||||
*/
|
||||
|
@ -164,9 +164,9 @@ public interface Session
|
|||
|
||||
/**
|
||||
* <p>Callback method invoked when a new stream is being created upon
|
||||
* receiving a HEADERS frame representing a HTTP request.</p>
|
||||
* receiving a HEADERS frame representing an HTTP request.</p>
|
||||
* <p>Applications should implement this method to process HTTP requests,
|
||||
* typically providing a HTTP response via
|
||||
* typically providing an HTTP response via
|
||||
* {@link Stream#headers(HeadersFrame, Callback)}.</p>
|
||||
* <p>Applications can detect whether request DATA frames will be arriving
|
||||
* by testing {@link HeadersFrame#isEndStream()}. If the application is
|
||||
|
|
|
@ -29,8 +29,8 @@ import org.eclipse.jetty.util.Promise;
|
|||
* <p>A {@link Stream} represents a bidirectional exchange of data on top of a {@link Session}.</p>
|
||||
* <p>Differently from socket streams, where the input and output streams are permanently associated
|
||||
* with the socket (and hence with the connection that the socket represents), there can be multiple
|
||||
* HTTP/2 streams present concurrently for a HTTP/2 session.</p>
|
||||
* <p>A {@link Stream} maps to a HTTP request/response cycle, and after the request/response cycle is
|
||||
* HTTP/2 streams present concurrently for an HTTP/2 session.</p>
|
||||
* <p>A {@link Stream} maps to an HTTP request/response cycle, and after the request/response cycle is
|
||||
* completed, the stream is closed and removed from the session.</p>
|
||||
* <p>Like {@link Session}, {@link Stream} is the active part and by calling its API applications
|
||||
* can generate events on the stream; conversely, {@link Stream.Listener} is the passive part, and
|
||||
|
@ -51,7 +51,7 @@ public interface Stream
|
|||
public Session getSession();
|
||||
|
||||
/**
|
||||
* <p>Sends the given HEADERS {@code frame} representing a HTTP response.</p>
|
||||
* <p>Sends the given HEADERS {@code frame} representing an HTTP response.</p>
|
||||
*
|
||||
* @param frame the HEADERS frame to send
|
||||
* @param callback the callback that gets notified when the frame has been sent
|
||||
|
@ -139,7 +139,7 @@ public interface Stream
|
|||
|
||||
/**
|
||||
* <p>A {@link Stream.Listener} is the passive counterpart of a {@link Stream} and receives
|
||||
* events happening on a HTTP/2 stream.</p>
|
||||
* events happening on an HTTP/2 stream.</p>
|
||||
* <p>HTTP/2 data is flow controlled - this means that only a finite number of data events
|
||||
* are delivered, until the flow control window is exhausted.</p>
|
||||
* <p>Applications control the delivery of data events by requesting them via
|
||||
|
@ -147,7 +147,7 @@ public interface Stream
|
|||
* events must be explicitly demanded.</p>
|
||||
* <p>Applications control the HTTP/2 flow control by completing the callback associated
|
||||
* with data events - this allows the implementation to recycle the data buffer and
|
||||
* eventually enlarges the flow control window so that the sender can send more data.</p>
|
||||
* eventually to enlarge the flow control window so that the sender can send more data.</p>
|
||||
*
|
||||
* @see Stream
|
||||
*/
|
||||
|
@ -182,8 +182,6 @@ public interface Stream
|
|||
|
||||
/**
|
||||
* <p>Callback method invoked when a DATA frame has been received.</p>
|
||||
* <p>When this method is called, the {@link #demand(long) demand} has
|
||||
* already been incremented by 1.</p>
|
||||
*
|
||||
* @param stream the stream
|
||||
* @param frame the DATA frame received
|
||||
|
@ -203,8 +201,8 @@ public interface Stream
|
|||
*/
|
||||
public default void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
stream.demand(1);
|
||||
onData(stream, frame, callback);
|
||||
stream.demand(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,7 +42,7 @@ public class PrefaceParser
|
|||
* <p>Advances this parser after the {@link PrefaceFrame#PREFACE_PREAMBLE_BYTES}.</p>
|
||||
* <p>This allows the HTTP/1.1 parser to parse the preamble of the preface,
|
||||
* which is a legal HTTP/1.1 request, and this parser will parse the remaining
|
||||
* bytes, that are not parseable by a HTTP/1.1 parser.</p>
|
||||
* bytes, that are not parseable by an HTTP/1.1 parser.</p>
|
||||
*/
|
||||
protected void directUpgrade()
|
||||
{
|
||||
|
|
|
@ -195,7 +195,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
.onRequestBegin(request ->
|
||||
{
|
||||
if (request.getVersion() != HttpVersion.HTTP_2)
|
||||
request.abort(new Exception("Not a HTTP/2 request"));
|
||||
request.abort(new Exception("Not an HTTP/2 request"));
|
||||
})
|
||||
.send();
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure a HTTP2 on the ssl connector. -->
|
||||
<!-- Configure an HTTP2 on the ssl connector. -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Call name="addConnectionFactory">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure a HTTP2 on the ssl connector. -->
|
||||
<!-- Configure an HTTP2 on the ssl connector. -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="httpConnector" class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Call name="addConnectionFactory">
|
||||
|
|
|
@ -39,8 +39,8 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
* </p>
|
||||
* <p>If used in combination with a {@link HttpConnectionFactory} as the
|
||||
* default protocol, this factory can support the non-standard direct
|
||||
* update mechanism, where a HTTP1 request of the form "PRI * HTTP/2.0"
|
||||
* is used to trigger a switch to a HTTP2 connection. This approach
|
||||
* update mechanism, where an HTTP1 request of the form "PRI * HTTP/2.0"
|
||||
* is used to trigger a switch to an HTTP2 connection. This approach
|
||||
* allows a single port to accept either HTTP/1 or HTTP/2 direct
|
||||
* connections.
|
||||
*/
|
||||
|
|
|
@ -66,7 +66,7 @@ import org.eclipse.jetty.util.TypeUtil;
|
|||
public class HTTP2ServerConnection extends HTTP2Connection implements Connection.UpgradeTo
|
||||
{
|
||||
/**
|
||||
* @param protocol A HTTP2 protocol variant
|
||||
* @param protocol An HTTP2 protocol variant
|
||||
* @return True if the protocol version is supported
|
||||
*/
|
||||
public static boolean isSupportedProtocol(String protocol)
|
||||
|
@ -376,10 +376,9 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void checkAndPrepareUpgrade()
|
||||
protected boolean checkAndPrepareUpgrade()
|
||||
{
|
||||
if (isTunnel())
|
||||
getHttpTransport().prepareUpgrade();
|
||||
return isTunnel() && getHttpTransport().prepareUpgrade();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -322,7 +322,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
return transportCallback.onIdleTimeout(failure);
|
||||
}
|
||||
|
||||
void prepareUpgrade()
|
||||
boolean prepareUpgrade()
|
||||
{
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment();
|
||||
Request request = channel.getRequest();
|
||||
|
@ -331,7 +331,8 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
endPoint.upgrade(connection);
|
||||
stream.setAttachment(endPoint);
|
||||
if (request.getHttpInput().hasContent())
|
||||
channel.sendErrorOrAbort("Unexpected content in CONNECT request");
|
||||
return channel.sendErrorOrAbort("Unexpected content in CONNECT request");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -188,7 +188,7 @@ public class HTTP2CServerTest extends AbstractServerTest
|
|||
assertThat(content, containsString("Hello from Jetty using HTTP/1.1"));
|
||||
assertThat(content, containsString("uri=/one"));
|
||||
|
||||
// Send a HTTP/2 request.
|
||||
// Send an HTTP/2 request.
|
||||
headersRef.set(null);
|
||||
dataRef.set(null);
|
||||
latchRef.set(new CountDownLatch(2));
|
||||
|
@ -319,7 +319,7 @@ public class HTTP2CServerTest extends AbstractServerTest
|
|||
connector.setDefaultProtocol(connectionFactory.getProtocol());
|
||||
connector.start();
|
||||
|
||||
// Now send a HTTP/2 direct request, which
|
||||
// Now send an HTTP/2 direct request, which
|
||||
// will have the PRI * HTTP/2.0 preface.
|
||||
|
||||
byteBufferPool = new MappedByteBufferPool();
|
||||
|
@ -336,7 +336,7 @@ public class HTTP2CServerTest extends AbstractServerTest
|
|||
output.write(BufferUtil.toArray(buffer));
|
||||
}
|
||||
|
||||
// We sent a HTTP/2 preface, but the server has no "h2c" connection
|
||||
// We sent an HTTP/2 preface, but the server has no "h2c" connection
|
||||
// factory so it does not know how to handle this request.
|
||||
|
||||
InputStream input = client.getInputStream();
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.io;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
||||
/**
|
||||
* Simple wrapper of a ByteBuffer as an OutputStream.
|
||||
* The buffer does not grow and this class will throw an
|
||||
* {@link java.nio.BufferOverflowException} if the buffer capacity is exceeded.
|
||||
*/
|
||||
public class ByteBufferOutputStream extends OutputStream
|
||||
{
|
||||
final ByteBuffer _buffer;
|
||||
|
||||
public ByteBufferOutputStream(ByteBuffer buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
}
|
||||
|
||||
public void flush()
|
||||
{
|
||||
}
|
||||
|
||||
public void write(byte[] b)
|
||||
{
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
public void write(byte[] b, int off, int len)
|
||||
{
|
||||
BufferUtil.append(_buffer, b, off, len);
|
||||
}
|
||||
|
||||
public void write(int b)
|
||||
{
|
||||
BufferUtil.append(_buffer, (byte)b);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
.classpath
|
||||
.project
|
||||
.settings
|
||||
target
|
||||
*.swp
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
<Item>org.eclipse.jetty.webapp.JmxConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.annotations.AnnotationConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.server.config.JettyWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.javax.server.JavaxWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.boot.OSGiMetaInfConfiguration</Item>
|
||||
</Array>
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure a HTTP2 on the ssl connector. -->
|
||||
<!-- Configure an HTTP2 on the ssl connector. -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Call name="addConnectionFactory">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure a HTTP2 on the ssl connector. -->
|
||||
<!-- Configure an HTTP2 on the ssl connector. -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Call name="addConnectionFactory">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure a HTTPS connector. -->
|
||||
<!-- Configure an HTTPS connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- and jetty-ssl.xml. -->
|
||||
<!-- ============================================================= -->
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
<Item>org.eclipse.jetty.plus.webapp.EnvConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.webapp.JmxConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.server.config.JettyWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.javax.server.JavaxWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.annotations.AnnotationConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.boot.OSGiMetaInfConfiguration</Item>
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
<Item>org.eclipse.jetty.webapp.JmxConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.annotations.AnnotationConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.server.config.JettyWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.javax.server.JavaxWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.boot.OSGiWebInfConfiguration</Item>
|
||||
<Item>org.eclipse.jetty.osgi.boot.OSGiMetaInfConfiguration</Item>
|
||||
</Array>
|
||||
|
|
|
@ -693,6 +693,14 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
catch (Exception e)
|
||||
{
|
||||
_log.ignore(e);
|
||||
try
|
||||
{
|
||||
proxyResponse.sendError(-1);
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
_log.ignore(e2);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.io.IOException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.annotation.Name;
|
||||
|
||||
/**
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</Call>
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- To add a HTTPS SSL listener -->
|
||||
<!-- To add an HTTPS SSL listener -->
|
||||
<!-- see jetty-ssl.xml to add an ssl connector. use -->
|
||||
<!-- java -jar start.jar etc/jetty-ssl.xml -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
|
|
|
@ -51,7 +51,7 @@ import org.ietf.jgss.Oid;
|
|||
* of the {@link #getServiceName() service name} and the {@link #getHostName() host name},
|
||||
* for example {@code HTTP/wonder.com}, using a {@code keyTab} file as the service principal
|
||||
* credentials.</p>
|
||||
* <p>Upon receiving a HTTP request, the server tries to authenticate the client
|
||||
* <p>Upon receiving an HTTP request, the server tries to authenticate the client
|
||||
* calling {@link #login(String, Object, ServletRequest)} where the GSS APIs are used to
|
||||
* verify client tokens and (perhaps after a few round-trips) a {@code GSSContext} is
|
||||
* established.</p>
|
||||
|
|
|
@ -814,7 +814,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
|
|||
{
|
||||
//an exact method name
|
||||
if (!hasOmissions)
|
||||
//a http-method does not have http-method-omission to cover the other method names
|
||||
//an http-method does not have http-method-omission to cover the other method names
|
||||
uncoveredPaths.add(path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
*
|
||||
* When a user has been successfully authenticated with some types
|
||||
* of Authenticator, the Authenticator stashes a SessionAuthentication
|
||||
* into a HttpSession to remember that the user is authenticated.
|
||||
* into an HttpSession to remember that the user is authenticated.
|
||||
*/
|
||||
public class SessionAuthentication extends AbstractUserAuthentication
|
||||
implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
|
||||
|
|
|
@ -423,7 +423,7 @@ public class ConstraintTest
|
|||
assertEquals(1, uncoveredPaths.size());
|
||||
assertThat("/user/*", is(in(uncoveredPaths)));
|
||||
|
||||
//Test an explicitly named method with a http-method-omission to cover all other methods
|
||||
//Test an explicitly named method with an http-method-omission to cover all other methods
|
||||
Constraint constraint2a = new Constraint();
|
||||
constraint2a.setAuthenticate(true);
|
||||
constraint2a.setName("forbid constraint");
|
||||
|
@ -437,7 +437,7 @@ public class ConstraintTest
|
|||
assertNotNull(uncoveredPaths);
|
||||
assertEquals(0, uncoveredPaths.size());
|
||||
|
||||
//Test a http-method-omission only
|
||||
//Test an http-method-omission only
|
||||
Constraint constraint3 = new Constraint();
|
||||
constraint3.setAuthenticate(true);
|
||||
constraint3.setName("omit constraint");
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -322,7 +323,8 @@ public class SpecExampleConstraintTest
|
|||
response = _connector.getResponse("POST /ctx/acme/wholesale/index.html HTTP/1.0\r\n" +
|
||||
"Authorization: Basic " + encodedChris + "\r\n" +
|
||||
"\r\n");
|
||||
assertThat(response, startsWith("HTTP/1.1 403 "));
|
||||
assertThat(response, startsWith("HTTP/1.1 403 Forbidden"));
|
||||
assertThat(response, containsString("!Secure"));
|
||||
|
||||
//a user in role HOMEOWNER can do a GET
|
||||
response = _connector.getResponse("GET /ctx/acme/retail/index.html HTTP/1.0\r\n" +
|
||||
|
|
|
@ -18,14 +18,17 @@
|
|||
|
||||
package org.eclipse.jetty.security.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.server.AbstractConnector;
|
||||
import org.eclipse.jetty.server.Authentication;
|
||||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.HttpChannelState;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpOutput;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
@ -34,6 +37,8 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class SpnegoAuthenticatorTest
|
||||
|
@ -49,27 +54,34 @@ public class SpnegoAuthenticatorTest
|
|||
@Test
|
||||
public void testChallengeSentWithNoAuthorization() throws Exception
|
||||
{
|
||||
HttpChannel channel = new HttpChannel(null, new HttpConfiguration(), null, null)
|
||||
HttpChannel channel = new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null)
|
||||
{
|
||||
@Override
|
||||
public Server getServer()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Request req = new Request(channel, null);
|
||||
HttpOutput out = new HttpOutput(channel)
|
||||
{
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
protected HttpOutput newHttpOutput()
|
||||
{
|
||||
return new HttpOutput(this)
|
||||
{
|
||||
@Override
|
||||
public void close() {}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {}
|
||||
};
|
||||
}
|
||||
};
|
||||
Response res = new Response(channel, out);
|
||||
Request req = channel.getRequest();
|
||||
Response res = channel.getResponse();
|
||||
MetaData.Request metadata = new MetaData.Request(new HttpFields());
|
||||
metadata.setURI(new HttpURI("http://localhost"));
|
||||
req.setMetaData(metadata);
|
||||
|
||||
assertThat(channel.getState().handling(), is(HttpChannelState.Action.DISPATCH));
|
||||
assertEquals(Authentication.SEND_CONTINUE, _authenticator.validateRequest(req, res, true));
|
||||
assertEquals(HttpHeader.NEGOTIATE.asString(), res.getHeader(HttpHeader.WWW_AUTHENTICATE.asString()));
|
||||
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, res.getStatus());
|
||||
|
@ -78,23 +90,29 @@ public class SpnegoAuthenticatorTest
|
|||
@Test
|
||||
public void testChallengeSentWithUnhandledAuthorization() throws Exception
|
||||
{
|
||||
HttpChannel channel = new HttpChannel(null, new HttpConfiguration(), null, null)
|
||||
HttpChannel channel = new HttpChannel(new MockConnector(), new HttpConfiguration(), null, null)
|
||||
{
|
||||
@Override
|
||||
public Server getServer()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Request req = new Request(channel, null);
|
||||
HttpOutput out = new HttpOutput(channel)
|
||||
{
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
protected HttpOutput newHttpOutput()
|
||||
{
|
||||
return new HttpOutput(this)
|
||||
{
|
||||
@Override
|
||||
public void close() {}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {}
|
||||
};
|
||||
}
|
||||
};
|
||||
Response res = new Response(channel, out);
|
||||
Request req = channel.getRequest();
|
||||
Response res = channel.getResponse();
|
||||
HttpFields http_fields = new HttpFields();
|
||||
// Create a bogus Authorization header. We don't care about the actual credentials.
|
||||
http_fields.add(HttpHeader.AUTHORIZATION, "Basic asdf");
|
||||
|
@ -102,8 +120,34 @@ public class SpnegoAuthenticatorTest
|
|||
metadata.setURI(new HttpURI("http://localhost"));
|
||||
req.setMetaData(metadata);
|
||||
|
||||
assertThat(channel.getState().handling(), is(HttpChannelState.Action.DISPATCH));
|
||||
assertEquals(Authentication.SEND_CONTINUE, _authenticator.validateRequest(req, res, true));
|
||||
assertEquals(HttpHeader.NEGOTIATE.asString(), res.getHeader(HttpHeader.WWW_AUTHENTICATE.asString()));
|
||||
assertEquals(HttpServletResponse.SC_UNAUTHORIZED, res.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
class MockConnector extends AbstractConnector
|
||||
{
|
||||
public MockConnector()
|
||||
{
|
||||
super(new Server() , null, null, null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void accept(int acceptorID) throws IOException, InterruptedException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTransport()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dumpSelf()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Server" -->
|
||||
<!-- by adding a HTTP connector. -->
|
||||
<!-- by adding an HTTP connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add a HTTP Connector. -->
|
||||
<!-- Add an HTTP Connector. -->
|
||||
<!-- Configure an o.e.j.server.ServerConnector with a single -->
|
||||
<!-- HttpConnectionFactory instance using the common httpConfig -->
|
||||
<!-- instance defined in jetty.xml -->
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure a HTTPS connector. -->
|
||||
<!-- Configure an HTTPS connector. -->
|
||||
<!-- This configuration must be used in conjunction with jetty.xml -->
|
||||
<!-- and jetty-ssl.xml. -->
|
||||
<!-- ============================================================= -->
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
<New class="org.eclipse.jetty.server.session.NullSessionCacheFactory">
|
||||
<Set name="saveOnCreate" property="jetty.session.saveOnCreate"/>
|
||||
<Set name="removeUnloadableSessions" property="jetty.session.removeUnloadableSessions"/>
|
||||
<Set name="writeThroughMode">
|
||||
<Call class="org.eclipse.jetty.server.session.NullSessionCache$WriteThroughMode" name="valueOf">
|
||||
<Arg><Property name="jetty.session.writeThroughMode" default="ON_EXIT"/></Arg>
|
||||
</Call>
|
||||
</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[description]
|
||||
Enables a HTTP connector on the server.
|
||||
Enables an HTTP connector on the server.
|
||||
By default HTTP/1 is support, but HTTP2C can
|
||||
be added to the connector with the http2c module.
|
||||
|
||||
|
|
|
@ -18,3 +18,4 @@ etc/sessions/session-cache-null.xml
|
|||
[ini-template]
|
||||
#jetty.session.saveOnCreate=false
|
||||
#jetty.session.removeUnloadableSessions=false
|
||||
#jetty.session.writeThroughMode=ON_EXIT
|
||||
|
|
|
@ -45,6 +45,7 @@ import org.eclipse.jetty.util.ProcessorUtils;
|
|||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.component.Container;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.component.Graceful;
|
||||
|
@ -115,7 +116,7 @@ import org.eclipse.jetty.util.thread.ThreadPoolBudget;
|
|||
* {@link ConnectionFactory}s may also create temporary {@link org.eclipse.jetty.io.Connection} instances that will exchange bytes
|
||||
* over the connection to determine what is the next protocol to use. For example the ALPN protocol is an extension
|
||||
* of SSL to allow a protocol to be specified during the SSL handshake. ALPN is used by the HTTP/2 protocol to
|
||||
* negotiate the protocol that the client and server will speak. Thus to accept a HTTP/2 connection, the
|
||||
* negotiate the protocol that the client and server will speak. Thus to accept an HTTP/2 connection, the
|
||||
* connector will be configured with {@link ConnectionFactory}s for "SSL-ALPN", "h2", "http/1.1"
|
||||
* with the default protocol being "SSL-ALPN". Thus a newly accepted connection uses "SSL-ALPN", which specifies a
|
||||
* SSLConnectionFactory with "ALPN" as the next protocol. Thus an SSL connection instance is created chained to an ALPN
|
||||
|
@ -154,6 +155,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
|||
private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
|
||||
private final Graceful.Shutdown _shutdown = new Graceful.Shutdown();
|
||||
private HttpChannel.Listener _httpChannelListeners = HttpChannel.NOOP_LISTENER;
|
||||
private CountDownLatch _stopping;
|
||||
private long _idleTimeout = 30000;
|
||||
private String _defaultProtocol;
|
||||
|
@ -188,6 +190,23 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
|||
pool = _server.getBean(ByteBufferPool.class);
|
||||
_byteBufferPool = pool != null ? pool : new ArrayByteBufferPool();
|
||||
|
||||
addEventListener(new Container.Listener()
|
||||
{
|
||||
@Override
|
||||
public void beanAdded(Container parent, Object bean)
|
||||
{
|
||||
if (bean instanceof HttpChannel.Listener)
|
||||
_httpChannelListeners = new HttpChannelListeners(getBeans(HttpChannel.Listener.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beanRemoved(Container parent, Object bean)
|
||||
{
|
||||
if (bean instanceof HttpChannel.Listener)
|
||||
_httpChannelListeners = new HttpChannelListeners(getBeans(HttpChannel.Listener.class));
|
||||
}
|
||||
});
|
||||
|
||||
addBean(_server, false);
|
||||
addBean(_executor);
|
||||
if (executor == null)
|
||||
|
@ -208,6 +227,24 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
|||
_acceptors = new Thread[acceptors];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link HttpChannel.Listener}s added to the connector
|
||||
* as a single combined Listener.
|
||||
* This is equivalent to a listener that iterates over the individual
|
||||
* listeners returned from <code>getBeans(HttpChannel.Listener.class);</code>,
|
||||
* except that: <ul>
|
||||
* <li>The result is precomputed, so it is more efficient</li>
|
||||
* <li>The result is ordered by the order added.</li>
|
||||
* <li>The result is immutable.</li>
|
||||
* </ul>
|
||||
* @see #getBeans(Class)
|
||||
* @return An unmodifiable list of EventListener beans
|
||||
*/
|
||||
public HttpChannel.Listener getHttpChannelListeners()
|
||||
{
|
||||
return _httpChannelListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Server getServer()
|
||||
{
|
||||
|
|
|
@ -160,7 +160,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable
|
|||
Scheduler.Task task = _timeoutTask;
|
||||
_timeoutTask = null;
|
||||
if (task != null)
|
||||
_state.getHttpChannel().execute(() -> _state.onTimeout());
|
||||
_state.timeout();
|
||||
}
|
||||
|
||||
public void addThrowable(Throwable e)
|
||||
|
|
|
@ -35,11 +35,11 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
* A ConnectionFactory has a protocol name that represents the protocol of the Connections
|
||||
* created. Example of protocol names include:
|
||||
* <dl>
|
||||
* <dt>http</dt><dd>Creates a HTTP connection that can handle multiple versions of HTTP from 0.9 to 1.1</dd>
|
||||
* <dt>h2</dt><dd>Creates a HTTP/2 connection that handles the HTTP/2 protocol</dd>
|
||||
* <dt>http</dt><dd>Creates an HTTP connection that can handle multiple versions of HTTP from 0.9 to 1.1</dd>
|
||||
* <dt>h2</dt><dd>Creates an HTTP/2 connection that handles the HTTP/2 protocol</dd>
|
||||
* <dt>SSL-XYZ</dt><dd>Create an SSL connection chained to a connection obtained from a connection factory
|
||||
* with a protocol "XYZ".</dd>
|
||||
* <dt>SSL-http</dt><dd>Create an SSL connection chained to a HTTP connection (aka https)</dd>
|
||||
* <dt>SSL-http</dt><dd>Create an SSL connection chained to an HTTP connection (aka https)</dd>
|
||||
* <dt>SSL-ALPN</dt><dd>Create an SSL connection chained to a ALPN connection, that uses a negotiation with
|
||||
* the client to determine the next protocol.</dd>
|
||||
* </dl>
|
||||
|
|
|
@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.handler.DebugHandler;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -42,8 +43,6 @@ public class Dispatcher implements RequestDispatcher
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(Dispatcher.class);
|
||||
|
||||
public static final String __ERROR_DISPATCH = "org.eclipse.jetty.server.Dispatcher.ERROR";
|
||||
|
||||
/**
|
||||
* Dispatch include attribute names
|
||||
*/
|
||||
|
@ -77,15 +76,7 @@ public class Dispatcher implements RequestDispatcher
|
|||
|
||||
public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
request.setAttribute(__ERROR_DISPATCH, Boolean.TRUE);
|
||||
forward(request, response, DispatcherType.ERROR);
|
||||
}
|
||||
finally
|
||||
{
|
||||
request.setAttribute(__ERROR_DISPATCH, null);
|
||||
}
|
||||
forward(request, response, DispatcherType.ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,286 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* A {@link HttpChannel.Listener} that holds a collection of
|
||||
* other {@link HttpChannel.Listener} instances that are efficiently
|
||||
* invoked without iteration.
|
||||
* @see AbstractConnector
|
||||
*/
|
||||
public class HttpChannelListeners implements HttpChannel.Listener
|
||||
{
|
||||
static final Logger LOG = Log.getLogger(HttpChannel.class);
|
||||
public static HttpChannel.Listener NOOP = new HttpChannel.Listener() {};
|
||||
|
||||
private final NotifyRequest onRequestBegin;
|
||||
private final NotifyRequest onBeforeDispatch;
|
||||
private final NotifyFailure onDispatchFailure;
|
||||
private final NotifyRequest onAfterDispatch;
|
||||
private final NotifyContent onRequestContent;
|
||||
private final NotifyRequest onRequestContentEnd;
|
||||
private final NotifyRequest onRequestTrailers;
|
||||
private final NotifyRequest onRequestEnd;
|
||||
private final NotifyFailure onRequestFailure;
|
||||
private final NotifyRequest onResponseBegin;
|
||||
private final NotifyRequest onResponseCommit;
|
||||
private final NotifyContent onResponseContent;
|
||||
private final NotifyRequest onResponseEnd;
|
||||
private final NotifyFailure onResponseFailure;
|
||||
private final NotifyRequest onComplete;
|
||||
|
||||
public HttpChannelListeners(Collection<HttpChannel.Listener> listeners)
|
||||
{
|
||||
try
|
||||
{
|
||||
NotifyRequest onRequestBegin = NotifyRequest.NOOP;
|
||||
NotifyRequest onBeforeDispatch = NotifyRequest.NOOP;
|
||||
NotifyFailure onDispatchFailure = NotifyFailure.NOOP;
|
||||
NotifyRequest onAfterDispatch = NotifyRequest.NOOP;
|
||||
NotifyContent onRequestContent = NotifyContent.NOOP;
|
||||
NotifyRequest onRequestContentEnd = NotifyRequest.NOOP;
|
||||
NotifyRequest onRequestTrailers = NotifyRequest.NOOP;
|
||||
NotifyRequest onRequestEnd = NotifyRequest.NOOP;
|
||||
NotifyFailure onRequestFailure = NotifyFailure.NOOP;
|
||||
NotifyRequest onResponseBegin = NotifyRequest.NOOP;
|
||||
NotifyRequest onResponseCommit = NotifyRequest.NOOP;
|
||||
NotifyContent onResponseContent = NotifyContent.NOOP;
|
||||
NotifyRequest onResponseEnd = NotifyRequest.NOOP;
|
||||
NotifyFailure onResponseFailure = NotifyFailure.NOOP;
|
||||
NotifyRequest onComplete = NotifyRequest.NOOP;
|
||||
|
||||
for (HttpChannel.Listener listener : listeners)
|
||||
{
|
||||
if (!listener.getClass().getMethod("onRequestBegin", Request.class).isDefault())
|
||||
onRequestBegin = combine(onRequestBegin, listener::onRequestBegin);
|
||||
if (!listener.getClass().getMethod("onBeforeDispatch", Request.class).isDefault())
|
||||
onBeforeDispatch = combine(onBeforeDispatch, listener::onBeforeDispatch);
|
||||
if (!listener.getClass().getMethod("onDispatchFailure", Request.class, Throwable.class).isDefault())
|
||||
onDispatchFailure = combine(onDispatchFailure, listener::onDispatchFailure);
|
||||
if (!listener.getClass().getMethod("onAfterDispatch", Request.class).isDefault())
|
||||
onAfterDispatch = combine(onAfterDispatch, listener::onAfterDispatch);
|
||||
if (!listener.getClass().getMethod("onRequestContent", Request.class, ByteBuffer.class).isDefault())
|
||||
onRequestContent = combine(onRequestContent, listener::onRequestContent);
|
||||
if (!listener.getClass().getMethod("onRequestContentEnd", Request.class).isDefault())
|
||||
onRequestContentEnd = combine(onRequestContentEnd, listener::onRequestContentEnd);
|
||||
if (!listener.getClass().getMethod("onRequestTrailers", Request.class).isDefault())
|
||||
onRequestTrailers = combine(onRequestTrailers, listener::onRequestTrailers);
|
||||
if (!listener.getClass().getMethod("onRequestEnd", Request.class).isDefault())
|
||||
onRequestEnd = combine(onRequestEnd, listener::onRequestEnd);
|
||||
if (!listener.getClass().getMethod("onRequestFailure", Request.class, Throwable.class).isDefault())
|
||||
onRequestFailure = combine(onRequestFailure, listener::onRequestFailure);
|
||||
if (!listener.getClass().getMethod("onResponseBegin", Request.class).isDefault())
|
||||
onResponseBegin = combine(onResponseBegin, listener::onResponseBegin);
|
||||
if (!listener.getClass().getMethod("onResponseCommit", Request.class).isDefault())
|
||||
onResponseCommit = combine(onResponseCommit, listener::onResponseCommit);
|
||||
if (!listener.getClass().getMethod("onResponseContent", Request.class, ByteBuffer.class).isDefault())
|
||||
onResponseContent = combine(onResponseContent, listener::onResponseContent);
|
||||
if (!listener.getClass().getMethod("onResponseEnd", Request.class).isDefault())
|
||||
onResponseEnd = combine(onResponseEnd, listener::onResponseEnd);
|
||||
if (!listener.getClass().getMethod("onResponseFailure", Request.class, Throwable.class).isDefault())
|
||||
onResponseFailure = combine(onResponseFailure, listener::onResponseFailure);
|
||||
if (!listener.getClass().getMethod("onComplete", Request.class).isDefault())
|
||||
onComplete = combine(onComplete, listener::onComplete);
|
||||
}
|
||||
|
||||
this.onRequestBegin = onRequestBegin;
|
||||
this.onBeforeDispatch = onBeforeDispatch;
|
||||
this.onDispatchFailure = onDispatchFailure;
|
||||
this.onAfterDispatch = onAfterDispatch;
|
||||
this.onRequestContent = onRequestContent;
|
||||
this.onRequestContentEnd = onRequestContentEnd;
|
||||
this.onRequestTrailers = onRequestTrailers;
|
||||
this.onRequestEnd = onRequestEnd;
|
||||
this.onRequestFailure = onRequestFailure;
|
||||
this.onResponseBegin = onResponseBegin;
|
||||
this.onResponseCommit = onResponseCommit;
|
||||
this.onResponseContent = onResponseContent;
|
||||
this.onResponseEnd = onResponseEnd;
|
||||
this.onResponseFailure = onResponseFailure;
|
||||
this.onComplete = onComplete;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestBegin(Request request)
|
||||
{
|
||||
onRequestBegin.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeforeDispatch(Request request)
|
||||
{
|
||||
onBeforeDispatch.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDispatchFailure(Request request, Throwable failure)
|
||||
{
|
||||
onDispatchFailure.onFailure(request, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterDispatch(Request request)
|
||||
{
|
||||
onAfterDispatch.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestContent(Request request, ByteBuffer content)
|
||||
{
|
||||
onRequestContent.onContent(request, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestContentEnd(Request request)
|
||||
{
|
||||
onRequestContentEnd.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestTrailers(Request request)
|
||||
{
|
||||
onRequestTrailers.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestEnd(Request request)
|
||||
{
|
||||
onRequestEnd.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestFailure(Request request, Throwable failure)
|
||||
{
|
||||
onRequestFailure.onFailure(request, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseBegin(Request request)
|
||||
{
|
||||
onResponseBegin.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseCommit(Request request)
|
||||
{
|
||||
onResponseCommit.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseContent(Request request, ByteBuffer content)
|
||||
{
|
||||
onResponseContent.onContent(request, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseEnd(Request request)
|
||||
{
|
||||
onResponseEnd.onRequest(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseFailure(Request request, Throwable failure)
|
||||
{
|
||||
onResponseFailure.onFailure(request, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Request request)
|
||||
{
|
||||
onComplete.onRequest(request);
|
||||
}
|
||||
|
||||
private interface NotifyRequest
|
||||
{
|
||||
void onRequest(Request request);
|
||||
|
||||
NotifyRequest NOOP = request ->
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
private interface NotifyFailure
|
||||
{
|
||||
void onFailure(Request request, Throwable failure);
|
||||
|
||||
NotifyFailure NOOP = (request, failure) ->
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
private interface NotifyContent
|
||||
{
|
||||
void onContent(Request request, ByteBuffer content);
|
||||
|
||||
NotifyContent NOOP = (request, content) ->
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
private static NotifyRequest combine(NotifyRequest first, NotifyRequest second)
|
||||
{
|
||||
if (first == NotifyRequest.NOOP)
|
||||
return second;
|
||||
if (second == NotifyRequest.NOOP)
|
||||
return first;
|
||||
return request ->
|
||||
{
|
||||
first.onRequest(request);
|
||||
second.onRequest(request);
|
||||
};
|
||||
}
|
||||
|
||||
private static NotifyFailure combine(NotifyFailure first, NotifyFailure second)
|
||||
{
|
||||
if (first == NotifyFailure.NOOP)
|
||||
return second;
|
||||
if (second == NotifyFailure.NOOP)
|
||||
return first;
|
||||
return (request, throwable) ->
|
||||
{
|
||||
first.onFailure(request, throwable);
|
||||
second.onFailure(request, throwable);
|
||||
};
|
||||
}
|
||||
|
||||
private static NotifyContent combine(NotifyContent first, NotifyContent second)
|
||||
{
|
||||
if (first == NotifyContent.NOOP)
|
||||
return (request, content) -> second.onContent(request, content.slice());
|
||||
if (second == NotifyContent.NOOP)
|
||||
return (request, content) -> first.onContent(request, content.slice());
|
||||
return (request, content) ->
|
||||
{
|
||||
content = content.slice();
|
||||
first.onContent(request, content);
|
||||
second.onContent(request, content);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ import org.eclipse.jetty.util.log.Log;
|
|||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* A HttpChannel customized to be transported over the HTTP/1 protocol
|
||||
* An HttpChannel customized to be transported over the HTTP/1 protocol
|
||||
*/
|
||||
public class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandler, ComplianceViolation.Listener
|
||||
{
|
||||
|
@ -408,7 +408,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>Attempts to perform a HTTP/1.1 upgrade.</p>
|
||||
* <p>Attempts to perform an HTTP/1.1 upgrade.</p>
|
||||
* <p>The upgrade looks up a {@link ConnectionFactory.Upgrading} from the connector
|
||||
* matching the protocol specified in the {@code Upgrade} header.</p>
|
||||
* <p>The upgrade may succeed, be ignored (which can allow a later handler to implement)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -38,7 +38,7 @@ import org.eclipse.jetty.util.component.DumpableCollection;
|
|||
/**
|
||||
* HTTP Configuration.
|
||||
* <p>This class is a holder of HTTP configuration for use by the
|
||||
* {@link HttpChannel} class. Typically a HTTPConfiguration instance
|
||||
* {@link HttpChannel} class. Typically an HTTPConfiguration instance
|
||||
* is instantiated and passed to a {@link ConnectionFactory} that can
|
||||
* create HTTP channels (e.g. HTTP, AJP or FCGI).</p>
|
||||
* <p>The configuration held by this class is not for the wire protocol,
|
||||
|
@ -183,19 +183,19 @@ public class HttpConfiguration implements Dumpable
|
|||
return _outputAggregationSize;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The maximum allowed size in bytes for a HTTP request header")
|
||||
@ManagedAttribute("The maximum allowed size in bytes for an HTTP request header")
|
||||
public int getRequestHeaderSize()
|
||||
{
|
||||
return _requestHeaderSize;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The maximum allowed size in bytes for a HTTP response header")
|
||||
@ManagedAttribute("The maximum allowed size in bytes for an HTTP response header")
|
||||
public int getResponseHeaderSize()
|
||||
{
|
||||
return _responseHeaderSize;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The maximum allowed size in bytes for a HTTP header field cache")
|
||||
@ManagedAttribute("The maximum allowed size in bytes for an HTTP header field cache")
|
||||
public int getHeaderCacheSize()
|
||||
{
|
||||
return _headerCacheSize;
|
||||
|
@ -226,20 +226,20 @@ public class HttpConfiguration implements Dumpable
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>The max idle time is applied to a HTTP request for IO operations and
|
||||
* <p>The max idle time is applied to an HTTP request for IO operations and
|
||||
* delayed dispatch.</p>
|
||||
*
|
||||
* @return the max idle time in ms or if == 0 implies an infinite timeout, <0
|
||||
* implies no HTTP channel timeout and the connection timeout is used instead.
|
||||
*/
|
||||
@ManagedAttribute("The idle timeout in ms for I/O operations during the handling of a HTTP request")
|
||||
@ManagedAttribute("The idle timeout in ms for I/O operations during the handling of an HTTP request")
|
||||
public long getIdleTimeout()
|
||||
{
|
||||
return _idleTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The max idle time is applied to a HTTP request for IO operations and
|
||||
* <p>The max idle time is applied to an HTTP request for IO operations and
|
||||
* delayed dispatch.</p>
|
||||
*
|
||||
* @param timeoutMs the max idle time in ms or if == 0 implies an infinite timeout, <0
|
||||
|
|
|
@ -274,18 +274,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
}
|
||||
else if (filled < 0)
|
||||
{
|
||||
switch (_channel.getState().getState())
|
||||
{
|
||||
case COMPLETING:
|
||||
case COMPLETED:
|
||||
case IDLE:
|
||||
case THROWN:
|
||||
case ASYNC_ERROR:
|
||||
getEndPoint().shutdownOutput();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (_channel.getState().isIdle())
|
||||
getEndPoint().shutdownOutput();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,7 +274,8 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
{
|
||||
BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,
|
||||
String.format("Request content data rate < %d B/s", minRequestDataRate));
|
||||
_channelState.getHttpChannel().abort(bad);
|
||||
if (_channelState.isResponseCommitted())
|
||||
_channelState.getHttpChannel().abort(bad);
|
||||
throw bad;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
@ -62,8 +63,31 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
public class HttpOutput extends ServletOutputStream implements Runnable
|
||||
{
|
||||
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
|
||||
private static final Callback BLOCKING_CLOSE_CALLBACK = new Callback() {};
|
||||
private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
|
||||
|
||||
/*
|
||||
ACTION OPEN ASYNC READY PENDING UNREADY CLOSING CLOSED
|
||||
--------------------------------------------------------------------------------------------------
|
||||
setWriteListener() READY->owp ise ise ise ise ise ise
|
||||
write() OPEN ise PENDING wpe wpe eof eof
|
||||
flush() OPEN ise PENDING wpe wpe eof eof
|
||||
close() CLOSING CLOSING CLOSING CLOSED CLOSED CLOSING CLOSED
|
||||
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true CLOSED:true
|
||||
write completed - - - ASYNC READY->owp CLOSED -
|
||||
*/
|
||||
enum State
|
||||
{
|
||||
OPEN, // Open in blocking mode
|
||||
ASYNC, // Open in async mode
|
||||
READY, // isReady() has returned true
|
||||
PENDING, // write operating in progress
|
||||
UNREADY, // write operating in progress, isReady has returned false
|
||||
ERROR, // An error has occured
|
||||
CLOSING, // Asynchronous close in progress
|
||||
CLOSED // Closed
|
||||
}
|
||||
|
||||
/**
|
||||
* The HttpOutput.Interceptor is a single intercept point for all
|
||||
* output written to the HttpOutput: via writer; via output stream;
|
||||
|
@ -129,6 +153,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
private static Logger LOG = Log.getLogger(HttpOutput.class);
|
||||
private static final ThreadLocal<CharsetEncoder> _encoder = new ThreadLocal<>();
|
||||
|
||||
private final AtomicReference<State> _state = new AtomicReference<>(State.OPEN);
|
||||
private final HttpChannel _channel;
|
||||
private final SharedBlockingCallback _writeBlocker;
|
||||
private Interceptor _interceptor;
|
||||
|
@ -140,23 +165,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
private int _commitSize;
|
||||
private WriteListener _writeListener;
|
||||
private volatile Throwable _onError;
|
||||
|
||||
/*
|
||||
ACTION OPEN ASYNC READY PENDING UNREADY CLOSED
|
||||
-------------------------------------------------------------------------------------------
|
||||
setWriteListener() READY->owp ise ise ise ise ise
|
||||
write() OPEN ise PENDING wpe wpe eof
|
||||
flush() OPEN ise PENDING wpe wpe eof
|
||||
close() CLOSED CLOSED CLOSED CLOSED CLOSED CLOSED
|
||||
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
|
||||
write completed - - - ASYNC READY->owp -
|
||||
*/
|
||||
private enum OutputState
|
||||
{
|
||||
OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED
|
||||
}
|
||||
|
||||
private final AtomicReference<OutputState> _state = new AtomicReference<>(OutputState.OPEN);
|
||||
private Callback _closeCallback;
|
||||
|
||||
public HttpOutput(HttpChannel channel)
|
||||
{
|
||||
|
@ -200,7 +209,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
|
||||
public void reopen()
|
||||
{
|
||||
_state.set(OutputState.OPEN);
|
||||
_state.set(State.OPEN);
|
||||
}
|
||||
|
||||
private boolean isLastContentToWrite(int len)
|
||||
|
@ -225,28 +234,78 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
_channel.abort(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
public void closedBySendError()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
OutputState state = _state.get();
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case OPEN:
|
||||
case READY:
|
||||
case ASYNC:
|
||||
if (!_state.compareAndSet(state, State.CLOSED))
|
||||
continue;
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close(Closeable wrapper, Callback callback)
|
||||
{
|
||||
_closeCallback = callback;
|
||||
try
|
||||
{
|
||||
if (wrapper != null)
|
||||
wrapper.close();
|
||||
if (!isClosed())
|
||||
close();
|
||||
}
|
||||
catch (Throwable th)
|
||||
{
|
||||
closed();
|
||||
if (_closeCallback == null)
|
||||
LOG.ignore(th);
|
||||
else
|
||||
callback.failed(th);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_closeCallback != null)
|
||||
callback.succeeded();
|
||||
_closeCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
Callback closeCallback = _closeCallback == null ? BLOCKING_CLOSE_CALLBACK : _closeCallback;
|
||||
|
||||
while (true)
|
||||
{
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
{
|
||||
_closeCallback = null;
|
||||
closeCallback.succeeded();
|
||||
return;
|
||||
}
|
||||
case ASYNC:
|
||||
{
|
||||
// A close call implies a write operation, thus in asynchronous mode
|
||||
// a call to isReady() that returned true should have been made.
|
||||
// However it is desirable to allow a close at any time, specially if
|
||||
// complete is called. Thus we simulate a call to isReady here, assuming
|
||||
// that we can transition to READY.
|
||||
if (!_state.compareAndSet(state, OutputState.READY))
|
||||
continue;
|
||||
break;
|
||||
// However it is desirable to allow a close at any time, specially if
|
||||
// complete is called. Thus we simulate a call to isReady here, by
|
||||
// trying to move to READY state. Either way we continue.
|
||||
_state.compareAndSet(state, State.READY);
|
||||
continue;
|
||||
}
|
||||
case UNREADY:
|
||||
case PENDING:
|
||||
|
@ -257,34 +316,45 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// complete is called. Because the prior write has not yet completed
|
||||
// and/or isReady has not been called, this close is allowed, but will
|
||||
// abort the response.
|
||||
if (!_state.compareAndSet(state, OutputState.CLOSED))
|
||||
if (!_state.compareAndSet(state, State.CLOSED))
|
||||
continue;
|
||||
IOException ex = new IOException("Closed while Pending/Unready");
|
||||
LOG.warn(ex.toString());
|
||||
LOG.debug(ex);
|
||||
abort(ex);
|
||||
_closeCallback = null;
|
||||
closeCallback.failed(ex);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!_state.compareAndSet(state, OutputState.CLOSED))
|
||||
if (!_state.compareAndSet(state, State.CLOSING))
|
||||
continue;
|
||||
|
||||
// Do a normal close by writing the aggregate buffer or an empty buffer. If we are
|
||||
// not including, then indicate this is the last write.
|
||||
try
|
||||
{
|
||||
write(BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER, !_channel.getResponse().isIncluding());
|
||||
ByteBuffer content = BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER;
|
||||
if (closeCallback == BLOCKING_CLOSE_CALLBACK)
|
||||
{
|
||||
// Do a blocking close
|
||||
write(content, !_channel.getResponse().isIncluding());
|
||||
_closeCallback = null;
|
||||
closeCallback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
_closeCallback = null;
|
||||
write(content, !_channel.getResponse().isIncluding(), closeCallback);
|
||||
}
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
LOG.ignore(x); // Ignore it, it's been already logged in write().
|
||||
_closeCallback = null;
|
||||
closeCallback.failed(x);
|
||||
}
|
||||
finally
|
||||
{
|
||||
releaseBuffer();
|
||||
}
|
||||
// Return even if an exception is thrown by write().
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -295,11 +365,11 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
* Called to indicate that the last write has been performed.
|
||||
* It updates the state and performs cleanup operations.
|
||||
*/
|
||||
void closed()
|
||||
public void closed()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
OutputState state = _state.get();
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case CLOSED:
|
||||
|
@ -308,15 +378,16 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
case UNREADY:
|
||||
{
|
||||
if (_state.compareAndSet(state, OutputState.ERROR))
|
||||
if (_state.compareAndSet(state, State.ERROR))
|
||||
_writeListener.onError(_onError == null ? new EofException("Async closed") : _onError);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!_state.compareAndSet(state, OutputState.CLOSED))
|
||||
if (!_state.compareAndSet(state, State.CLOSED))
|
||||
break;
|
||||
|
||||
// Just make sure write and output stream really are closed
|
||||
try
|
||||
{
|
||||
_channel.getResponse().closeOutput();
|
||||
|
@ -338,6 +409,18 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
}
|
||||
|
||||
public ByteBuffer getBuffer()
|
||||
{
|
||||
return _aggregate;
|
||||
}
|
||||
|
||||
public ByteBuffer acquireBuffer()
|
||||
{
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
|
||||
return _aggregate;
|
||||
}
|
||||
|
||||
private void releaseBuffer()
|
||||
{
|
||||
if (_aggregate != null)
|
||||
|
@ -349,7 +432,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
|
||||
public boolean isClosed()
|
||||
{
|
||||
return _state.get() == OutputState.CLOSED;
|
||||
switch (_state.get())
|
||||
{
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAsync()
|
||||
|
@ -371,7 +461,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
switch (_state.get())
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case OPEN:
|
||||
write(BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER, false);
|
||||
|
@ -381,25 +472,24 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
throw new IllegalStateException("isReady() not called");
|
||||
|
||||
case READY:
|
||||
if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(state, State.PENDING))
|
||||
continue;
|
||||
new AsyncFlush().iterate();
|
||||
return;
|
||||
|
||||
case PENDING:
|
||||
return;
|
||||
|
||||
case UNREADY:
|
||||
throw new WritePendingException();
|
||||
|
||||
case ERROR:
|
||||
throw new EofException(_onError);
|
||||
|
||||
case PENDING:
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -441,7 +531,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// Async or Blocking ?
|
||||
while (true)
|
||||
{
|
||||
switch (_state.get())
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case OPEN:
|
||||
// process blocking below
|
||||
|
@ -451,15 +542,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
throw new IllegalStateException("isReady() not called");
|
||||
|
||||
case READY:
|
||||
if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(state, State.PENDING))
|
||||
continue;
|
||||
|
||||
// Should we aggregate?
|
||||
boolean last = isLastContentToWrite(len);
|
||||
if (!last && len <= _commitSize)
|
||||
{
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
|
||||
acquireBuffer();
|
||||
|
||||
// YES - fill the aggregate with content from the buffer
|
||||
int filled = BufferUtil.fill(_aggregate, b, off, len);
|
||||
|
@ -467,8 +557,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// return if we are not complete, not full and filled all the content
|
||||
if (filled == len && !BufferUtil.isFull(_aggregate))
|
||||
{
|
||||
if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
|
||||
throw new IllegalStateException();
|
||||
if (!_state.compareAndSet(State.PENDING, State.ASYNC))
|
||||
throw new IllegalStateException(_state.get().toString());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -488,11 +578,12 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
case ERROR:
|
||||
throw new EofException(_onError);
|
||||
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
throw new EofException("Closed");
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -504,8 +595,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
boolean last = isLastContentToWrite(len);
|
||||
if (!last && len <= _commitSize)
|
||||
{
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(capacity, _interceptor.isOptimizedForDirectBuffers());
|
||||
acquireBuffer();
|
||||
|
||||
// YES - fill the aggregate with content from the buffer
|
||||
int filled = BufferUtil.fill(_aggregate, b, off, len);
|
||||
|
@ -554,9 +644,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
write(BufferUtil.EMPTY_BUFFER, true);
|
||||
}
|
||||
|
||||
if (last)
|
||||
closed();
|
||||
}
|
||||
|
||||
public void write(ByteBuffer buffer) throws IOException
|
||||
|
@ -566,7 +653,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
// Async or Blocking ?
|
||||
while (true)
|
||||
{
|
||||
switch (_state.get())
|
||||
State state = _state.get();
|
||||
switch (state)
|
||||
{
|
||||
case OPEN:
|
||||
// process blocking below
|
||||
|
@ -576,7 +664,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
throw new IllegalStateException("isReady() not called");
|
||||
|
||||
case READY:
|
||||
if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(state, State.PENDING))
|
||||
continue;
|
||||
|
||||
// Do the asynchronous writing from the callback
|
||||
|
@ -591,11 +679,12 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
case ERROR:
|
||||
throw new EofException(_onError);
|
||||
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
throw new EofException("Closed");
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -613,9 +702,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
write(buffer, last);
|
||||
else if (last)
|
||||
write(BufferUtil.EMPTY_BUFFER, true);
|
||||
|
||||
if (last)
|
||||
closed();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -630,34 +716,28 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
switch (_state.get())
|
||||
{
|
||||
case OPEN:
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
|
||||
acquireBuffer();
|
||||
BufferUtil.append(_aggregate, (byte)b);
|
||||
|
||||
// Check if all written or full
|
||||
if (complete || BufferUtil.isFull(_aggregate))
|
||||
{
|
||||
write(_aggregate, complete);
|
||||
if (complete)
|
||||
closed();
|
||||
}
|
||||
break;
|
||||
|
||||
case ASYNC:
|
||||
throw new IllegalStateException("isReady() not called");
|
||||
|
||||
case READY:
|
||||
if (!_state.compareAndSet(OutputState.READY, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(State.READY, State.PENDING))
|
||||
continue;
|
||||
|
||||
if (_aggregate == null)
|
||||
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), _interceptor.isOptimizedForDirectBuffers());
|
||||
acquireBuffer();
|
||||
BufferUtil.append(_aggregate, (byte)b);
|
||||
|
||||
// Check if all written or full
|
||||
if (!complete && !BufferUtil.isFull(_aggregate))
|
||||
{
|
||||
if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
|
||||
if (!_state.compareAndSet(State.PENDING, State.ASYNC))
|
||||
throw new IllegalStateException();
|
||||
return;
|
||||
}
|
||||
|
@ -673,6 +753,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
case ERROR:
|
||||
throw new EofException(_onError);
|
||||
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
throw new EofException("Closed");
|
||||
|
||||
|
@ -810,7 +891,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
|
||||
_written += content.remaining();
|
||||
write(content, true);
|
||||
closed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -966,7 +1046,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
switch (_state.get())
|
||||
{
|
||||
case OPEN:
|
||||
if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING))
|
||||
if (!_state.compareAndSet(State.OPEN, State.PENDING))
|
||||
continue;
|
||||
break;
|
||||
|
||||
|
@ -974,6 +1054,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
callback.failed(new EofException(_onError));
|
||||
return;
|
||||
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
callback.failed(new EofException("Closed"));
|
||||
return;
|
||||
|
@ -1073,6 +1154,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
_onError = null;
|
||||
_firstByteTimeStamp = -1;
|
||||
_flushed = 0;
|
||||
_closeCallback = null;
|
||||
reopen();
|
||||
}
|
||||
|
||||
|
@ -1082,7 +1164,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
if (BufferUtil.hasContent(_aggregate))
|
||||
BufferUtil.clear(_aggregate);
|
||||
_written = 0;
|
||||
reopen();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1091,7 +1172,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
if (!_channel.getState().isAsync())
|
||||
throw new IllegalStateException("!ASYNC");
|
||||
|
||||
if (_state.compareAndSet(OutputState.OPEN, OutputState.READY))
|
||||
if (_state.compareAndSet(State.OPEN, State.READY))
|
||||
{
|
||||
_writeListener = writeListener;
|
||||
if (_channel.getState().onWritePossible())
|
||||
|
@ -1109,30 +1190,25 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
switch (_state.get())
|
||||
{
|
||||
case OPEN:
|
||||
case READY:
|
||||
case ERROR:
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
return true;
|
||||
|
||||
case ASYNC:
|
||||
if (!_state.compareAndSet(OutputState.ASYNC, OutputState.READY))
|
||||
if (!_state.compareAndSet(State.ASYNC, State.READY))
|
||||
continue;
|
||||
return true;
|
||||
|
||||
case READY:
|
||||
return true;
|
||||
|
||||
case PENDING:
|
||||
if (!_state.compareAndSet(OutputState.PENDING, OutputState.UNREADY))
|
||||
if (!_state.compareAndSet(State.PENDING, State.UNREADY))
|
||||
continue;
|
||||
return false;
|
||||
|
||||
case UNREADY:
|
||||
return false;
|
||||
|
||||
case ERROR:
|
||||
return true;
|
||||
|
||||
case CLOSED:
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
@ -1144,12 +1220,13 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
OutputState state = _state.get();
|
||||
State state = _state.get();
|
||||
|
||||
if (_onError != null)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case CLOSING:
|
||||
case CLOSED:
|
||||
case ERROR:
|
||||
{
|
||||
|
@ -1158,7 +1235,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
}
|
||||
default:
|
||||
{
|
||||
if (_state.compareAndSet(state, OutputState.ERROR))
|
||||
if (_state.compareAndSet(state, State.ERROR))
|
||||
{
|
||||
Throwable th = _onError;
|
||||
_onError = null;
|
||||
|
@ -1234,16 +1311,16 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
while (true)
|
||||
{
|
||||
OutputState last = _state.get();
|
||||
State last = _state.get();
|
||||
switch (last)
|
||||
{
|
||||
case PENDING:
|
||||
if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC))
|
||||
if (!_state.compareAndSet(State.PENDING, State.ASYNC))
|
||||
continue;
|
||||
break;
|
||||
|
||||
case UNREADY:
|
||||
if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY))
|
||||
if (!_state.compareAndSet(State.UNREADY, State.READY))
|
||||
continue;
|
||||
if (_last)
|
||||
closed();
|
||||
|
|
|
@ -95,7 +95,7 @@ public class OptionalSslConnectionFactory extends AbstractConnectionFactory
|
|||
int byte2 = buffer.get(1) & 0xFF;
|
||||
if (byte1 == 'G' && byte2 == 'E')
|
||||
{
|
||||
// Plain text HTTP to a HTTPS port,
|
||||
// Plain text HTTP to an HTTPS port,
|
||||
// write a minimal response.
|
||||
String body =
|
||||
"<!DOCTYPE html>\r\n" +
|
||||
|
|
|
@ -212,6 +212,7 @@ public class Request implements HttpServletRequest
|
|||
private String _contentType;
|
||||
private String _characterEncoding;
|
||||
private ContextHandler.Context _context;
|
||||
private ContextHandler.Context _errorContext;
|
||||
private Cookies _cookies;
|
||||
private DispatcherType _dispatcherType;
|
||||
private int _inputState = INPUT_NONE;
|
||||
|
@ -759,6 +760,22 @@ public class Request implements HttpServletRequest
|
|||
return _context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current {@link Context context} used for this error handling for this request. If the request is asynchronous,
|
||||
* then it is the context that called async. Otherwise it is the last non-null context passed to #setContext
|
||||
*/
|
||||
public Context getErrorContext()
|
||||
{
|
||||
if (isAsyncStarted())
|
||||
{
|
||||
ContextHandler handler = _channel.getState().getContextHandler();
|
||||
if (handler != null)
|
||||
return handler.getServletContext();
|
||||
}
|
||||
|
||||
return _errorContext;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see javax.servlet.http.HttpServletRequest#getContextPath()
|
||||
*/
|
||||
|
@ -1846,6 +1863,7 @@ public class Request implements HttpServletRequest
|
|||
_remote = null;
|
||||
_sessions = null;
|
||||
_input.recycle();
|
||||
_requestAttributeListeners.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1983,6 +2001,7 @@ public class Request implements HttpServletRequest
|
|||
else
|
||||
{
|
||||
_context = context;
|
||||
_errorContext = context;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.eclipse.jetty.util.resource.Resource;
|
|||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
/**
|
||||
* A HttpContent.Factory for transient content (not cached). The HttpContent's created by
|
||||
* An HttpContent.Factory for transient content (not cached). The HttpContent's created by
|
||||
* this factory are not intended to be cached, so memory limits for individual
|
||||
* HttpOutput streams are enforced.
|
||||
*/
|
||||
|
|
|
@ -18,19 +18,19 @@
|
|||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.channels.IllegalSelectorException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletResponseWrapper;
|
||||
|
@ -58,9 +58,8 @@ import org.eclipse.jetty.http.MetaData;
|
|||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.io.RuntimeIOException;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -379,71 +378,40 @@ public class Response implements HttpServletResponse
|
|||
sendError(sc, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an error response.
|
||||
* <p>In addition to the servlet standard handling, this method supports some additional codes:</p>
|
||||
* <dl>
|
||||
* <dt>102</dt><dd>Send a partial PROCESSING response and allow additional responses</dd>
|
||||
* <dt>-1</dt><dd>Abort the HttpChannel and close the connection/stream</dd>
|
||||
* </dl>
|
||||
* @param code The error code
|
||||
* @param message The message
|
||||
* @throws IOException If an IO problem occurred sending the error response.
|
||||
*/
|
||||
@Override
|
||||
public void sendError(int code, String message) throws IOException
|
||||
{
|
||||
if (isIncluding())
|
||||
return;
|
||||
|
||||
if (isCommitted())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Aborting on sendError on committed response {} {}", code, message);
|
||||
code = -1;
|
||||
}
|
||||
else
|
||||
resetBuffer();
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case -1:
|
||||
_channel.abort(new IOException());
|
||||
return;
|
||||
case 102:
|
||||
_channel.abort(new IOException(message));
|
||||
break;
|
||||
case HttpStatus.PROCESSING_102:
|
||||
sendProcessing();
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
_channel.getState().sendError(code, message);
|
||||
break;
|
||||
}
|
||||
|
||||
_outputType = OutputType.NONE;
|
||||
setContentType(null);
|
||||
setCharacterEncoding(null);
|
||||
setHeader(HttpHeader.EXPIRES, null);
|
||||
setHeader(HttpHeader.LAST_MODIFIED, null);
|
||||
setHeader(HttpHeader.CACHE_CONTROL, null);
|
||||
setHeader(HttpHeader.CONTENT_TYPE, null);
|
||||
setHeader(HttpHeader.CONTENT_LENGTH, null);
|
||||
|
||||
setStatus(code);
|
||||
|
||||
Request request = _channel.getRequest();
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
_reason = HttpStatus.getMessage(code);
|
||||
if (message == null)
|
||||
message = cause == null ? _reason : cause.toString();
|
||||
|
||||
// If we are allowed to have a body, then produce the error page.
|
||||
if (code != SC_NO_CONTENT && code != SC_NOT_MODIFIED &&
|
||||
code != SC_PARTIAL_CONTENT && code >= SC_OK)
|
||||
{
|
||||
ContextHandler.Context context = request.getContext();
|
||||
ContextHandler contextHandler = context == null ? _channel.getState().getContextHandler() : context.getContextHandler();
|
||||
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, code);
|
||||
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
|
||||
request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
|
||||
request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, request.getServletName());
|
||||
ErrorHandler errorHandler = ErrorHandler.getErrorHandler(_channel.getServer(), contextHandler);
|
||||
if (errorHandler != null)
|
||||
errorHandler.handle(null, request, request, this);
|
||||
}
|
||||
if (!request.isAsyncStarted())
|
||||
closeOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 102-Processing response.
|
||||
* If the connection is a HTTP connection, the version is 1.1 and the
|
||||
* If the connection is an HTTP connection, the version is 1.1 and the
|
||||
* request has a Expect header starting with 102, then a 102 response is
|
||||
* sent. This indicates that the request still be processed and real response
|
||||
* can still be sent. This method is called by sendError if it is passed 102.
|
||||
|
@ -650,8 +618,11 @@ public class Response implements HttpServletResponse
|
|||
throw new IllegalArgumentException();
|
||||
if (!isIncluding())
|
||||
{
|
||||
// Null the reason only if the status is different. This allows
|
||||
// a specific reason to be sent with setStatusWithReason followed by sendError.
|
||||
if (_status != sc)
|
||||
_reason = null;
|
||||
_status = sc;
|
||||
_reason = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -714,6 +685,11 @@ public class Response implements HttpServletResponse
|
|||
return _outputType == OutputType.STREAM;
|
||||
}
|
||||
|
||||
public boolean isWritingOrStreaming()
|
||||
{
|
||||
return isWriting() || isStreaming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException
|
||||
{
|
||||
|
@ -822,21 +798,15 @@ public class Response implements HttpServletResponse
|
|||
|
||||
public void closeOutput() throws IOException
|
||||
{
|
||||
switch (_outputType)
|
||||
{
|
||||
case WRITER:
|
||||
_writer.close();
|
||||
if (!_out.isClosed())
|
||||
_out.close();
|
||||
break;
|
||||
case STREAM:
|
||||
if (!_out.isClosed())
|
||||
getOutputStream().close();
|
||||
break;
|
||||
default:
|
||||
if (!_out.isClosed())
|
||||
_out.close();
|
||||
}
|
||||
if (_outputType == OutputType.WRITER)
|
||||
_writer.close();
|
||||
if (!_out.isClosed())
|
||||
_out.close();
|
||||
}
|
||||
|
||||
public void closeOutput(Callback callback)
|
||||
{
|
||||
_out.close((_outputType == OutputType.WRITER) ? _writer : _out, callback);
|
||||
}
|
||||
|
||||
public long getLongContentLength()
|
||||
|
@ -1029,19 +999,20 @@ public class Response implements HttpServletResponse
|
|||
@Override
|
||||
public void reset()
|
||||
{
|
||||
reset(false);
|
||||
}
|
||||
|
||||
public void reset(boolean preserveCookies)
|
||||
{
|
||||
resetForForward();
|
||||
_status = 200;
|
||||
_reason = null;
|
||||
_out.resetBuffer();
|
||||
_outputType = OutputType.NONE;
|
||||
_contentLength = -1;
|
||||
_contentType = null;
|
||||
_mimeType = null;
|
||||
_characterEncoding = null;
|
||||
_encodingFrom = EncodingFrom.NOT_SET;
|
||||
|
||||
List<HttpField> cookies = preserveCookies ? _fields.getFields(HttpHeader.SET_COOKIE) : null;
|
||||
// Clear all response headers
|
||||
_fields.clear();
|
||||
|
||||
// recreate necessary connection related fields
|
||||
for (String value : _channel.getRequest().getHttpFields().getCSV(HttpHeader.CONNECTION, false))
|
||||
{
|
||||
HttpHeaderValue cb = HttpHeaderValue.CACHE.get(value);
|
||||
|
@ -1064,21 +1035,57 @@ public class Response implements HttpServletResponse
|
|||
}
|
||||
}
|
||||
|
||||
if (preserveCookies)
|
||||
cookies.forEach(_fields::add);
|
||||
else
|
||||
// recreate session cookies
|
||||
Request request = getHttpChannel().getRequest();
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null && session.isNew())
|
||||
{
|
||||
Request request = getHttpChannel().getRequest();
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null && session.isNew())
|
||||
SessionHandler sh = request.getSessionHandler();
|
||||
if (sh != null)
|
||||
{
|
||||
SessionHandler sh = request.getSessionHandler();
|
||||
if (sh != null)
|
||||
{
|
||||
HttpCookie c = sh.getSessionCookie(session, request.getContextPath(), request.isSecure());
|
||||
if (c != null)
|
||||
addCookie(c);
|
||||
}
|
||||
HttpCookie c = sh.getSessionCookie(session, request.getContextPath(), request.isSecure());
|
||||
if (c != null)
|
||||
addCookie(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resetContent()
|
||||
{
|
||||
_out.resetBuffer();
|
||||
_outputType = OutputType.NONE;
|
||||
_contentLength = -1;
|
||||
_contentType = null;
|
||||
_mimeType = null;
|
||||
_characterEncoding = null;
|
||||
_encodingFrom = EncodingFrom.NOT_SET;
|
||||
|
||||
// remove the content related response headers and keep all others
|
||||
for (Iterator<HttpField> i = getHttpFields().iterator(); i.hasNext(); )
|
||||
{
|
||||
HttpField field = i.next();
|
||||
if (field.getHeader() == null)
|
||||
continue;
|
||||
|
||||
switch (field.getHeader())
|
||||
{
|
||||
case CONTENT_TYPE:
|
||||
case CONTENT_LENGTH:
|
||||
case CONTENT_ENCODING:
|
||||
case CONTENT_LANGUAGE:
|
||||
case CONTENT_RANGE:
|
||||
case CONTENT_MD5:
|
||||
case CONTENT_LOCATION:
|
||||
case TRANSFER_ENCODING:
|
||||
case CACHE_CONTROL:
|
||||
case LAST_MODIFIED:
|
||||
case EXPIRES:
|
||||
case ETAG:
|
||||
case DATE:
|
||||
case VARY:
|
||||
i.remove();
|
||||
continue;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1093,6 +1100,7 @@ public class Response implements HttpServletResponse
|
|||
public void resetBuffer()
|
||||
{
|
||||
_out.resetBuffer();
|
||||
_out.reopen();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1143,6 +1151,9 @@ public class Response implements HttpServletResponse
|
|||
@Override
|
||||
public boolean isCommitted()
|
||||
{
|
||||
// If we are in sendError state, we pretend to be committed
|
||||
if (_channel.isSendError())
|
||||
return true;
|
||||
return _channel.isCommitted();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.eclipse.jetty.server.handler.ContextHandler.AliasCheck;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.PathResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
||||
/**
|
||||
* Alias checking for working with FileSystems that normalize access to the
|
||||
* File System.
|
||||
* <p>
|
||||
* The Java {@link Files#isSameFile(Path, Path)} method is used to determine
|
||||
* if the requested file is the same as the alias file.
|
||||
* </p>
|
||||
* <p>
|
||||
* For File Systems that are case insensitive (eg: Microsoft Windows FAT32 and NTFS),
|
||||
* the access to the file can be in any combination or style of upper and lowercase.
|
||||
* </p>
|
||||
* <p>
|
||||
* For File Systems that normalize UTF-8 access (eg: Mac OSX on HFS+ or APFS,
|
||||
* or Linux on XFS) the the actual file could be stored using UTF-16,
|
||||
* but be accessed using NFD UTF-8 or NFC UTF-8 for the same file.
|
||||
* </p>
|
||||
*/
|
||||
public class SameFileAliasChecker implements AliasCheck
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SameFileAliasChecker.class);
|
||||
|
||||
@Override
|
||||
public boolean check(String uri, Resource resource)
|
||||
{
|
||||
// Only support PathResource alias checking
|
||||
if (!(resource instanceof PathResource))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
PathResource pathResource = (PathResource)resource;
|
||||
Path path = pathResource.getPath();
|
||||
Path alias = pathResource.getAliasPath();
|
||||
|
||||
if (Files.isSameFile(path, alias))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Allow alias to same file {} --> {}", path, alias);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -514,10 +514,16 @@ public class Server extends HandlerWrapper implements Attributes
|
|||
if (HttpMethod.OPTIONS.is(request.getMethod()) || "*".equals(target))
|
||||
{
|
||||
if (!HttpMethod.OPTIONS.is(request.getMethod()))
|
||||
{
|
||||
request.setHandled(true);
|
||||
response.sendError(HttpStatus.BAD_REQUEST_400);
|
||||
handleOptions(request, response);
|
||||
if (!request.isHandled())
|
||||
handle(target, request, request, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleOptions(request, response);
|
||||
if (!request.isHandled())
|
||||
handle(target, request, request, response);
|
||||
}
|
||||
}
|
||||
else
|
||||
handle(target, request, request, response);
|
||||
|
|
|
@ -35,7 +35,7 @@ import javax.servlet.http.Part;
|
|||
/**
|
||||
* ServletRequestHttpWrapper
|
||||
*
|
||||
* Class to tunnel a ServletRequest via a HttpServletRequest
|
||||
* Class to tunnel a ServletRequest via an HttpServletRequest
|
||||
*/
|
||||
public class ServletRequestHttpWrapper extends ServletRequestWrapper implements HttpServletRequest
|
||||
{
|
||||
|
|
|
@ -28,7 +28,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
/**
|
||||
* ServletResponseHttpWrapper
|
||||
*
|
||||
* Wrapper to tunnel a ServletResponse via a HttpServletResponse
|
||||
* Wrapper to tunnel a ServletResponse via an HttpServletResponse
|
||||
*/
|
||||
public class ServletResponseHttpWrapper extends ServletResponseWrapper implements HttpServletResponse
|
||||
{
|
||||
|
|
|
@ -52,7 +52,7 @@ public class AllowSymLinkAliasChecker implements AliasCheck
|
|||
Path path = pathResource.getPath();
|
||||
Path alias = pathResource.getAliasPath();
|
||||
|
||||
if (path.equals(alias))
|
||||
if (PathResource.isSameName(alias, path))
|
||||
return false; // Unknown why this is an alias
|
||||
|
||||
if (hasSymbolicLink(path) && Files.isSameFile(path, alias))
|
||||
|
|
|
@ -864,7 +864,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
* insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
|
||||
*
|
||||
* @throws Exception if unable to start the context
|
||||
* @see org.eclipse.jetty.server.handler.ContextHandler.Context
|
||||
* @see ContextHandler.Context
|
||||
*/
|
||||
protected void startContext() throws Exception
|
||||
{
|
||||
|
@ -1103,7 +1103,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
case UNAVAILABLE:
|
||||
baseRequest.setHandled(true);
|
||||
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
|
||||
return true;
|
||||
return false;
|
||||
default:
|
||||
if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
|
||||
return false;
|
||||
|
@ -1138,8 +1138,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
if (oldContext != _scontext)
|
||||
{
|
||||
// check the target.
|
||||
if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch) ||
|
||||
DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isAsync())
|
||||
if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch))
|
||||
{
|
||||
if (_compactPath)
|
||||
target = URIUtil.compactPath(target);
|
||||
|
@ -1276,29 +1275,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
if (new_context)
|
||||
requestInitialized(baseRequest, request);
|
||||
|
||||
switch (dispatch)
|
||||
if (dispatch == DispatcherType.REQUEST && isProtectedTarget(target))
|
||||
{
|
||||
case REQUEST:
|
||||
if (isProtectedTarget(target))
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
// If this is already a dispatch to an error page, proceed normally
|
||||
if (Boolean.TRUE.equals(baseRequest.getAttribute(Dispatcher.__ERROR_DISPATCH)))
|
||||
break;
|
||||
|
||||
// We can just call doError here. If there is no error page, then one will
|
||||
// be generated. If there is an error page, then a RequestDispatcher will be
|
||||
// used to route the request through appropriate filters etc.
|
||||
doError(target, baseRequest, request, response);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
baseRequest.setHandled(true);
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
nextHandle(target, baseRequest, request, response);
|
||||
|
|
|
@ -19,28 +19,29 @@
|
|||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.http.QuotedQualityCSV;
|
||||
import org.eclipse.jetty.io.ByteBufferOutputStream;
|
||||
import org.eclipse.jetty.server.Dispatcher;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -49,15 +50,18 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
* Handler for Error pages
|
||||
* An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
|
||||
* {@link Server#setErrorHandler(ErrorHandler)}.
|
||||
* It is called by the HttpResponse.sendError method to write a error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
|
||||
* It is called by the HttpResponse.sendError method to write an error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
|
||||
* or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a dispatch cannot be done.
|
||||
*/
|
||||
public class ErrorHandler extends AbstractHandler
|
||||
{
|
||||
// TODO This classes API needs to be majorly refactored/cleanup in jetty-10
|
||||
private static final Logger LOG = Log.getLogger(ErrorHandler.class);
|
||||
public static final String ERROR_PAGE = "org.eclipse.jetty.server.error_page";
|
||||
public static final String ERROR_CONTEXT = "org.eclipse.jetty.server.error_context";
|
||||
|
||||
boolean _showStacks = true;
|
||||
boolean _disableStacks = false;
|
||||
boolean _showMessageInTitle = true;
|
||||
String _cacheControl = "must-revalidate,no-cache,no-store";
|
||||
|
||||
|
@ -65,6 +69,19 @@ public class ErrorHandler extends AbstractHandler
|
|||
{
|
||||
}
|
||||
|
||||
public boolean errorPageForMethod(String method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case "GET":
|
||||
case "POST":
|
||||
case "HEAD":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
|
||||
*/
|
||||
|
@ -77,71 +94,13 @@ public class ErrorHandler extends AbstractHandler
|
|||
@Override
|
||||
public void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
String method = request.getMethod();
|
||||
if (!HttpMethod.GET.is(method) && !HttpMethod.POST.is(method) && !HttpMethod.HEAD.is(method))
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
return;
|
||||
}
|
||||
String cacheControl = getCacheControl();
|
||||
if (cacheControl != null)
|
||||
response.setHeader(HttpHeader.CACHE_CONTROL.asString(), cacheControl);
|
||||
|
||||
if (this instanceof ErrorPageMapper)
|
||||
{
|
||||
String errorPage = ((ErrorPageMapper)this).getErrorPage(request);
|
||||
if (errorPage != null)
|
||||
{
|
||||
String oldErrorPage = (String)request.getAttribute(ERROR_PAGE);
|
||||
ContextHandler.Context context = baseRequest.getContext();
|
||||
if (context == null)
|
||||
context = ContextHandler.getCurrentContext();
|
||||
if (context == null)
|
||||
{
|
||||
LOG.warn("No ServletContext for error page {}", errorPage);
|
||||
}
|
||||
else if (oldErrorPage != null && oldErrorPage.equals(errorPage))
|
||||
{
|
||||
LOG.warn("Error page loop {}", errorPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.setAttribute(ERROR_PAGE, errorPage);
|
||||
|
||||
Dispatcher dispatcher = (Dispatcher)context.getRequestDispatcher(errorPage);
|
||||
try
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("error page dispatch {}->{}", errorPage, dispatcher);
|
||||
if (dispatcher != null)
|
||||
{
|
||||
dispatcher.error(request, response);
|
||||
return;
|
||||
}
|
||||
LOG.warn("No error page found " + errorPage);
|
||||
}
|
||||
catch (ServletException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("No Error Page mapping for request({} {}) (using default)", request.getMethod(), request.getRequestURI());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_cacheControl != null)
|
||||
response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl);
|
||||
|
||||
String message = (String)request.getAttribute(RequestDispatcher.ERROR_MESSAGE);
|
||||
String message = (String)request.getAttribute(Dispatcher.ERROR_MESSAGE);
|
||||
if (message == null)
|
||||
message = baseRequest.getResponse().getReason();
|
||||
if (message == null)
|
||||
message = HttpStatus.getMessage(response.getStatus());
|
||||
|
||||
generateAcceptableResponse(baseRequest, request, response, response.getStatus(), message);
|
||||
}
|
||||
|
||||
|
@ -151,7 +110,7 @@ public class ErrorHandler extends AbstractHandler
|
|||
* acceptable to the user-agent. The Accept header is evaluated in
|
||||
* quality order and the method
|
||||
* {@link #generateAcceptableResponse(Request, HttpServletRequest, HttpServletResponse, int, String, String)}
|
||||
* is called for each mimetype until {@link Request#isHandled()} is true.</p>
|
||||
* is called for each mimetype until the response is written to or committed.</p>
|
||||
*
|
||||
* @param baseRequest The base request
|
||||
* @param request The servlet request (may be wrapped)
|
||||
|
@ -174,48 +133,10 @@ public class ErrorHandler extends AbstractHandler
|
|||
for (String mimeType : acceptable)
|
||||
{
|
||||
generateAcceptableResponse(baseRequest, request, response, code, message, mimeType);
|
||||
if (response.isCommitted() || baseRequest.getResponse().isWriting() || baseRequest.getResponse().isStreaming())
|
||||
if (response.isCommitted() || baseRequest.getResponse().isWritingOrStreaming())
|
||||
break;
|
||||
}
|
||||
}
|
||||
baseRequest.getResponse().closeOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an acceptable error response for a mime type.
|
||||
* <p>This method is called for each mime type in the users agent's
|
||||
* <code>Accept</code> header, until {@link Request#isHandled()} is true and a
|
||||
* response of the appropriate type is generated.
|
||||
*
|
||||
* @param baseRequest The base request
|
||||
* @param request The servlet request (may be wrapped)
|
||||
* @param response The response (may be wrapped)
|
||||
* @param code the http error code
|
||||
* @param message the http error message
|
||||
* @param mimeType The mimetype to generate (may be */*or other wildcard)
|
||||
* @throws IOException if a response cannot be generated
|
||||
*/
|
||||
protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String mimeType)
|
||||
throws IOException
|
||||
{
|
||||
switch (mimeType)
|
||||
{
|
||||
case "text/html":
|
||||
case "text/*":
|
||||
case "*/*":
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
Writer writer = getAcceptableWriter(baseRequest, request, response);
|
||||
if (writer != null)
|
||||
{
|
||||
response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
|
||||
handleErrorPage(request, writer, code, message);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,6 +157,7 @@ public class ErrorHandler extends AbstractHandler
|
|||
* @return A {@link Writer} if there is a known acceptable charset or null
|
||||
* @throws IOException if a Writer cannot be returned
|
||||
*/
|
||||
@Deprecated
|
||||
protected Writer getAcceptableWriter(Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -264,6 +186,139 @@ public class ErrorHandler extends AbstractHandler
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an acceptable error response for a mime type.
|
||||
* <p>This method is called for each mime type in the users agent's
|
||||
* <code>Accept</code> header, until {@link Request#isHandled()} is true and a
|
||||
* response of the appropriate type is generated.
|
||||
* </p>
|
||||
* <p>The default implementation handles "text/html", "text/*" and "*/*".
|
||||
* The method can be overridden to handle other types. Implementations must
|
||||
* immediate produce a response and may not be async.
|
||||
* </p>
|
||||
*
|
||||
* @param baseRequest The base request
|
||||
* @param request The servlet request (may be wrapped)
|
||||
* @param response The response (may be wrapped)
|
||||
* @param code the http error code
|
||||
* @param message the http error message
|
||||
* @param contentType The mimetype to generate (may be */*or other wildcard)
|
||||
* @throws IOException if a response cannot be generated
|
||||
*/
|
||||
protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message, String contentType)
|
||||
throws IOException
|
||||
{
|
||||
// We can generate an acceptable contentType, but can we generate an acceptable charset?
|
||||
// TODO refactor this in jetty-10 to be done in the other calling loop
|
||||
Charset charset = null;
|
||||
List<String> acceptable = baseRequest.getHttpFields().getQualityCSV(HttpHeader.ACCEPT_CHARSET);
|
||||
if (!acceptable.isEmpty())
|
||||
{
|
||||
for (String name : acceptable)
|
||||
{
|
||||
if ("*".equals(name))
|
||||
{
|
||||
charset = StandardCharsets.UTF_8;
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
charset = Charset.forName(name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
}
|
||||
if (charset == null)
|
||||
return;
|
||||
}
|
||||
|
||||
MimeTypes.Type type;
|
||||
switch (contentType)
|
||||
{
|
||||
case "text/html":
|
||||
case "text/*":
|
||||
case "*/*":
|
||||
type = MimeTypes.Type.TEXT_HTML;
|
||||
if (charset == null)
|
||||
charset = StandardCharsets.ISO_8859_1;
|
||||
break;
|
||||
|
||||
case "text/json":
|
||||
case "application/json":
|
||||
type = MimeTypes.Type.TEXT_JSON;
|
||||
if (charset == null)
|
||||
charset = StandardCharsets.UTF_8;
|
||||
break;
|
||||
|
||||
case "text/plain":
|
||||
type = MimeTypes.Type.TEXT_PLAIN;
|
||||
if (charset == null)
|
||||
charset = StandardCharsets.ISO_8859_1;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// write into the response aggregate buffer and flush it asynchronously.
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO currently the writer used here is of fixed size, so a large
|
||||
// TODO error page may cause a BufferOverflow. In which case we try
|
||||
// TODO again with stacks disabled. If it still overflows, it is
|
||||
// TODO written without a body.
|
||||
ByteBuffer buffer = baseRequest.getResponse().getHttpOutput().acquireBuffer();
|
||||
ByteBufferOutputStream out = new ByteBufferOutputStream(buffer);
|
||||
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, charset));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case TEXT_HTML:
|
||||
response.setContentType(MimeTypes.Type.TEXT_HTML.asString());
|
||||
response.setCharacterEncoding(charset.name());
|
||||
handleErrorPage(request, writer, code, message);
|
||||
break;
|
||||
case TEXT_JSON:
|
||||
response.setContentType(contentType);
|
||||
writeErrorJson(request, writer, code, message);
|
||||
break;
|
||||
case TEXT_PLAIN:
|
||||
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
|
||||
response.setCharacterEncoding(charset.name());
|
||||
writeErrorPlain(request, writer, code, message);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
break;
|
||||
}
|
||||
catch (BufferOverflowException e)
|
||||
{
|
||||
LOG.warn("Error page too large: {} {} {}", code, message, request);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.warn(e);
|
||||
baseRequest.getResponse().resetContent();
|
||||
if (!_disableStacks)
|
||||
{
|
||||
LOG.info("Disabling showsStacks for " + this);
|
||||
_disableStacks = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Do an asynchronous completion.
|
||||
baseRequest.getHttpChannel().sendResponseAndComplete();
|
||||
}
|
||||
|
||||
protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -288,12 +343,13 @@ public class ErrorHandler extends AbstractHandler
|
|||
{
|
||||
writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n");
|
||||
writer.write("<title>Error ");
|
||||
writer.write(Integer.toString(code));
|
||||
|
||||
if (_showMessageInTitle)
|
||||
// TODO this code is duplicated in writeErrorPageMessage
|
||||
String status = Integer.toString(code);
|
||||
writer.write(status);
|
||||
if (message != null && !message.equals(status))
|
||||
{
|
||||
writer.write(' ');
|
||||
write(writer, message);
|
||||
writer.write(StringUtil.sanitizeXmlString(message));
|
||||
}
|
||||
writer.write("</title>\n");
|
||||
}
|
||||
|
@ -304,7 +360,7 @@ public class ErrorHandler extends AbstractHandler
|
|||
String uri = request.getRequestURI();
|
||||
|
||||
writeErrorPageMessage(request, writer, code, message, uri);
|
||||
if (showStacks)
|
||||
if (showStacks && !_disableStacks)
|
||||
writeErrorPageStacks(request, writer);
|
||||
|
||||
Request.getBaseRequest(request).getHttpChannel().getHttpConfiguration()
|
||||
|
@ -315,35 +371,103 @@ public class ErrorHandler extends AbstractHandler
|
|||
throws IOException
|
||||
{
|
||||
writer.write("<h2>HTTP ERROR ");
|
||||
String status = Integer.toString(code);
|
||||
writer.write(status);
|
||||
if (message != null && !message.equals(status))
|
||||
{
|
||||
writer.write(' ');
|
||||
writer.write(StringUtil.sanitizeXmlString(message));
|
||||
}
|
||||
writer.write("</h2>\n");
|
||||
writer.write("<table>\n");
|
||||
htmlRow(writer, "URI", uri);
|
||||
htmlRow(writer, "STATUS", status);
|
||||
htmlRow(writer, "MESSAGE", message);
|
||||
htmlRow(writer, "SERVLET", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
while (cause != null)
|
||||
{
|
||||
htmlRow(writer, "CAUSED BY", cause);
|
||||
cause = cause.getCause();
|
||||
}
|
||||
writer.write("</table>\n");
|
||||
}
|
||||
|
||||
private void htmlRow(Writer writer, String tag, Object value)
|
||||
throws IOException
|
||||
{
|
||||
writer.write("<tr><th>");
|
||||
writer.write(tag);
|
||||
writer.write(":</th><td>");
|
||||
if (value == null)
|
||||
writer.write("-");
|
||||
else
|
||||
writer.write(StringUtil.sanitizeXmlString(value.toString()));
|
||||
writer.write("</td></tr>\n");
|
||||
}
|
||||
|
||||
private void writeErrorPlain(HttpServletRequest request, PrintWriter writer, int code, String message)
|
||||
{
|
||||
writer.write("HTTP ERROR ");
|
||||
writer.write(Integer.toString(code));
|
||||
writer.write("</h2>\n<p>Problem accessing ");
|
||||
write(writer, uri);
|
||||
writer.write(". Reason:\n<pre> ");
|
||||
write(writer, message);
|
||||
writer.write("</pre></p>");
|
||||
writer.write(' ');
|
||||
writer.write(StringUtil.sanitizeXmlString(message));
|
||||
writer.write("\n");
|
||||
writer.printf("URI: %s%n", request.getRequestURI());
|
||||
writer.printf("STATUS: %s%n", code);
|
||||
writer.printf("MESSAGE: %s%n", message);
|
||||
writer.printf("SERVLET: %s%n", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
while (cause != null)
|
||||
{
|
||||
writer.printf("CAUSED BY %s%n", cause);
|
||||
if (_showStacks && !_disableStacks)
|
||||
cause.printStackTrace(writer);
|
||||
cause = cause.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeErrorJson(HttpServletRequest request, PrintWriter writer, int code, String message)
|
||||
{
|
||||
writer
|
||||
.append("{\n")
|
||||
.append(" url: \"").append(request.getRequestURI()).append("\",\n")
|
||||
.append(" status: \"").append(Integer.toString(code)).append("\",\n")
|
||||
.append(" message: ").append(QuotedStringTokenizer.quote(message)).append(",\n");
|
||||
Object servlet = request.getAttribute(Dispatcher.ERROR_SERVLET_NAME);
|
||||
if (servlet != null)
|
||||
writer.append("servlet: \"").append(servlet.toString()).append("\",\n");
|
||||
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
|
||||
int c = 0;
|
||||
while (cause != null)
|
||||
{
|
||||
writer.append(" cause").append(Integer.toString(c++)).append(": ")
|
||||
.append(QuotedStringTokenizer.quote(cause.toString())).append(",\n");
|
||||
cause = cause.getCause();
|
||||
}
|
||||
writer.append("}");
|
||||
}
|
||||
|
||||
protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
|
||||
throws IOException
|
||||
{
|
||||
Throwable th = (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
|
||||
while (th != null)
|
||||
if (_showStacks && th != null)
|
||||
{
|
||||
writer.write("<h3>Caused by:</h3><pre>");
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
th.printStackTrace(pw);
|
||||
pw.flush();
|
||||
write(writer, sw.getBuffer().toString());
|
||||
PrintWriter pw = writer instanceof PrintWriter ? (PrintWriter)writer : new PrintWriter(writer);
|
||||
pw.write("<pre>");
|
||||
while (th != null)
|
||||
{
|
||||
th.printStackTrace(pw);
|
||||
th = th.getCause();
|
||||
}
|
||||
writer.write("</pre>\n");
|
||||
|
||||
th = th.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bad Message Error body
|
||||
* <p>Generate a error response body to be sent for a bad message.
|
||||
* <p>Generate an error response body to be sent for a bad message.
|
||||
* In this case there is something wrong with the request, so either
|
||||
* a request cannot be built, or it is not safe to build a request.
|
||||
* This method allows for a simple error page body to be returned
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
* A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java.
|
||||
* If _exitJvm is set to true a hard System.exit() call is being made.
|
||||
* If _sendShutdownAtStart is set to true, starting the server will try to shut down an existing server at the same port.
|
||||
* If _sendShutdownAtStart is set to true, make a http call to
|
||||
* If _sendShutdownAtStart is set to true, make an http call to
|
||||
* "http://localhost:" + port + "/shutdown?token=" + shutdownCookie
|
||||
* in order to shut down the server.
|
||||
*
|
||||
|
@ -93,7 +93,7 @@ public class ShutdownHandler extends HandlerWrapper
|
|||
/**
|
||||
* @param shutdownToken a secret password to avoid unauthorized shutdown attempts
|
||||
* @param exitJVM If true, when the shutdown is executed, the handler class System.exit()
|
||||
* @param sendShutdownAtStart If true, a shutdown is sent as a HTTP post
|
||||
* @param sendShutdownAtStart If true, a shutdown is sent as an HTTP post
|
||||
* during startup, which will shutdown any previously running instances of
|
||||
* this server with an identically configured ShutdownHandler
|
||||
*/
|
||||
|
@ -190,8 +190,9 @@ public class ShutdownHandler extends HandlerWrapper
|
|||
connector.shutdown();
|
||||
}
|
||||
|
||||
response.sendError(200, "Connectors closed, commencing full shutdown");
|
||||
baseRequest.setHandled(true);
|
||||
response.setStatus(200);
|
||||
response.flushBuffer();
|
||||
|
||||
final Server server = getServer();
|
||||
new Thread()
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.eclipse.jetty.server.HttpInput.Content;
|
|||
import org.eclipse.jetty.util.component.Destroyable;
|
||||
|
||||
/**
|
||||
* A HttpInput Interceptor that inflates GZIP encoded request content.
|
||||
* An HttpInput Interceptor that inflates GZIP encoded request content.
|
||||
*/
|
||||
public class GzipHttpInputInterceptor implements HttpInput.Interceptor, Destroyable
|
||||
{
|
||||
|
|
|
@ -80,6 +80,9 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
|
|||
@Override
|
||||
public SessionData load(String id) throws Exception
|
||||
{
|
||||
if (!isStarted())
|
||||
throw new IllegalStateException ("Not started");
|
||||
|
||||
final AtomicReference<SessionData> reference = new AtomicReference<SessionData>();
|
||||
final AtomicReference<Exception> exception = new AtomicReference<Exception>();
|
||||
|
||||
|
@ -109,6 +112,9 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
|
|||
@Override
|
||||
public void store(String id, SessionData data) throws Exception
|
||||
{
|
||||
if (!isStarted())
|
||||
throw new IllegalStateException("Not started");
|
||||
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
|
@ -126,7 +132,7 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
|
|||
LOG.debug("Store: id={}, dirty={}, lsave={}, period={}, elapsed={}", id, data.isDirty(), data.getLastSaved(), savePeriodMs, (System.currentTimeMillis() - lastSave));
|
||||
|
||||
//save session if attribute changed or never been saved or time between saves exceeds threshold
|
||||
if (data.isDirty() || (lastSave <= 0) || ((System.currentTimeMillis() - lastSave) > savePeriodMs))
|
||||
if (data.isDirty() || (lastSave <= 0) || ((System.currentTimeMillis() - lastSave) >= savePeriodMs))
|
||||
{
|
||||
//set the last saved time to now
|
||||
data.setLastSaved(System.currentTimeMillis());
|
||||
|
@ -156,6 +162,9 @@ public abstract class AbstractSessionDataStore extends ContainerLifeCycle implem
|
|||
@Override
|
||||
public Set<String> getExpired(Set<String> candidates)
|
||||
{
|
||||
if (!isStarted())
|
||||
throw new IllegalStateException ("Not started");
|
||||
|
||||
try
|
||||
{
|
||||
return doGetExpired(candidates);
|
||||
|
|
|
@ -467,9 +467,9 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
|
|||
}
|
||||
|
||||
/**
|
||||
* Get SessionManager for every context.
|
||||
* Get SessionHandler for every context.
|
||||
*
|
||||
* @return all session managers
|
||||
* @return all SessionHandlers that are running
|
||||
*/
|
||||
@Override
|
||||
public Set<SessionHandler> getSessionHandlers()
|
||||
|
@ -480,7 +480,8 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
|
|||
{
|
||||
for (Handler h : tmp)
|
||||
{
|
||||
handlers.add((SessionHandler)h);
|
||||
if (h.isStarted())
|
||||
handlers.add((SessionHandler)h);
|
||||
}
|
||||
}
|
||||
return handlers;
|
||||
|
|
|
@ -18,7 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.server.session;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSessionAttributeListener;
|
||||
import javax.servlet.http.HttpSessionBindingEvent;
|
||||
|
||||
/**
|
||||
* NullSessionCache
|
||||
|
@ -30,6 +35,150 @@ import javax.servlet.http.HttpServletRequest;
|
|||
*/
|
||||
public class NullSessionCache extends AbstractSessionCache
|
||||
{
|
||||
/**
|
||||
* If the writethrough mode is ALWAYS or NEW, then use an
|
||||
* attribute listener to ascertain when the attribute has changed.
|
||||
*
|
||||
*/
|
||||
public class WriteThroughAttributeListener implements HttpSessionAttributeListener
|
||||
{
|
||||
Set<Session> _sessionsBeingWritten = ConcurrentHashMap.newKeySet();
|
||||
|
||||
@Override
|
||||
public void attributeAdded(HttpSessionBindingEvent event)
|
||||
{
|
||||
doAttributeChanged(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributeRemoved(HttpSessionBindingEvent event)
|
||||
{
|
||||
doAttributeChanged(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributeReplaced(HttpSessionBindingEvent event)
|
||||
{
|
||||
doAttributeChanged(event);
|
||||
}
|
||||
|
||||
private void doAttributeChanged(HttpSessionBindingEvent event)
|
||||
{
|
||||
if (_writeThroughMode == WriteThroughMode.ON_EXIT)
|
||||
return;
|
||||
|
||||
Session session = (Session)event.getSession();
|
||||
|
||||
SessionDataStore store = getSessionDataStore();
|
||||
|
||||
if (store == null)
|
||||
return;
|
||||
|
||||
if (_writeThroughMode == WriteThroughMode.ALWAYS || (_writeThroughMode == WriteThroughMode.NEW && session.isNew()))
|
||||
{
|
||||
//ensure that a call to willPassivate doesn't result in a passivation
|
||||
//listener removing an attribute, which would cause this listener to
|
||||
//be called again
|
||||
if (_sessionsBeingWritten.add(session))
|
||||
{
|
||||
try
|
||||
{
|
||||
//should hold the lock on the session, but as sessions are never shared
|
||||
//with the NullSessionCache, there can be no other thread modifying the
|
||||
//same session at the same time (although of course there can be another
|
||||
//request modifying its copy of the session data, so it is impossible
|
||||
//to guarantee the order of writes).
|
||||
if (store.isPassivating())
|
||||
session.willPassivate();
|
||||
store.store(session.getId(), session.getSessionData());
|
||||
if (store.isPassivating())
|
||||
session.didActivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Write through of {} failed", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsBeingWritten.remove(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the circumstances a session will be written to the backing store.
|
||||
*/
|
||||
public enum WriteThroughMode
|
||||
{
|
||||
/**
|
||||
* ALWAYS means write through every attribute change.
|
||||
*/
|
||||
ALWAYS,
|
||||
/**
|
||||
* NEW means to write through every attribute change only
|
||||
* while the session is freshly created, ie its id has not yet been returned to the client
|
||||
*/
|
||||
NEW,
|
||||
/**
|
||||
* ON_EXIT means write the session only when the request exits
|
||||
* (which is the default behaviour of AbstractSessionCache)
|
||||
*/
|
||||
ON_EXIT
|
||||
}
|
||||
|
||||
private WriteThroughMode _writeThroughMode = WriteThroughMode.ON_EXIT;
|
||||
protected WriteThroughAttributeListener _listener = null;
|
||||
|
||||
/**
|
||||
* @return the writeThroughMode
|
||||
*/
|
||||
public WriteThroughMode getWriteThroughMode()
|
||||
{
|
||||
return _writeThroughMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param writeThroughMode the writeThroughMode to set
|
||||
*/
|
||||
public void setWriteThroughMode(WriteThroughMode writeThroughMode)
|
||||
{
|
||||
if (getSessionHandler() == null)
|
||||
throw new IllegalStateException("No SessionHandler");
|
||||
|
||||
//assume setting null is the same as ON_EXIT
|
||||
if (writeThroughMode == null)
|
||||
{
|
||||
if (_listener != null)
|
||||
getSessionHandler().removeEventListener(_listener);
|
||||
_listener = null;
|
||||
_writeThroughMode = WriteThroughMode.ON_EXIT;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (writeThroughMode)
|
||||
{
|
||||
case ON_EXIT:
|
||||
{
|
||||
if (_listener != null)
|
||||
getSessionHandler().removeEventListener(_listener);
|
||||
_listener = null;
|
||||
break;
|
||||
}
|
||||
case NEW:
|
||||
case ALWAYS:
|
||||
{
|
||||
if (_listener == null)
|
||||
{
|
||||
_listener = new WriteThroughAttributeListener();
|
||||
getSessionHandler().addEventListener(_listener);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
_writeThroughMode = writeThroughMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param handler The SessionHandler related to this SessionCache
|
||||
|
|
|
@ -27,6 +27,23 @@ public class NullSessionCacheFactory implements SessionCacheFactory
|
|||
{
|
||||
boolean _saveOnCreate;
|
||||
boolean _removeUnloadableSessions;
|
||||
NullSessionCache.WriteThroughMode _writeThroughMode;
|
||||
|
||||
/**
|
||||
* @return the writeThroughMode
|
||||
*/
|
||||
public NullSessionCache.WriteThroughMode getWriteThroughMode()
|
||||
{
|
||||
return _writeThroughMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param writeThroughMode the writeThroughMode to set
|
||||
*/
|
||||
public void setWriteThroughMode(NullSessionCache.WriteThroughMode writeThroughMode)
|
||||
{
|
||||
_writeThroughMode = writeThroughMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the saveOnCreate
|
||||
|
@ -69,6 +86,7 @@ public class NullSessionCacheFactory implements SessionCacheFactory
|
|||
NullSessionCache cache = new NullSessionCache(handler);
|
||||
cache.setSaveOnCreate(isSaveOnCreate());
|
||||
cache.setRemoveUnloadableSessions(isRemoveUnloadableSessions());
|
||||
cache.setWriteThroughMode(_writeThroughMode);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ import org.eclipse.jetty.util.thread.Locker.Lock;
|
|||
/**
|
||||
* Session
|
||||
*
|
||||
* A heavy-weight Session object representing a HttpSession. Session objects
|
||||
* A heavy-weight Session object representing an HttpSession. Session objects
|
||||
* relating to a context are kept in a {@link SessionCache}. The purpose of the
|
||||
* SessionCache is to keep the working set of Session objects in memory so that
|
||||
* they may be accessed quickly, and facilitate the sharing of a Session object
|
||||
|
@ -476,6 +476,10 @@ public class Session implements SessionHandler.SessionIf
|
|||
{
|
||||
try (Lock lock = _lock.lock())
|
||||
{
|
||||
if (isInvalid())
|
||||
{
|
||||
throw new IllegalStateException("Session not valid");
|
||||
}
|
||||
return _sessionData.getLastAccessed();
|
||||
}
|
||||
}
|
||||
|
@ -690,6 +694,7 @@ public class Session implements SessionHandler.SessionIf
|
|||
{
|
||||
try (Lock lock = _lock.lock())
|
||||
{
|
||||
checkValidForRead();
|
||||
return _sessionData.getAttribute(name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -554,7 +554,7 @@ public class SessionHandler extends ScopedHandler
|
|||
/**
|
||||
* @return same as SessionCookieConfig.getSecure(). If true, session
|
||||
* cookies are ALWAYS marked as secure. If false, a session cookie is
|
||||
* ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
|
||||
* ONLY marked as secure if _secureRequestOnly == true and it is an HTTPS request.
|
||||
*/
|
||||
@ManagedAttribute("if true, secure cookie flag is set on session cookies")
|
||||
public boolean getSecureCookies()
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue