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

This commit is contained in:
Simone Bordet 2019-09-05 18:16:04 +02:00
commit 0485fb5dde
238 changed files with 7078 additions and 3149 deletions

3
Jenkinsfile vendored
View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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,

View File

@ -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()}

View File

@ -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>

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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

View File

@ -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>

View File

@ -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).
*/

View File

@ -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();

View File

@ -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 -->

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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
*/

View File

@ -23,7 +23,7 @@ import java.util.Objects;
import org.eclipse.jetty.util.StringUtil;
/**
* A HTTP Field
* An HTTP Field
*/
public class HttpField
{

View File

@ -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)
{

View File

@ -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();

View File

@ -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

View File

@ -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>

View File

@ -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)
{

View File

@ -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

View File

@ -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

View File

@ -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));
}
}
;
}

View File

@ -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.

View File

@ -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),
/**

View File

@ -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>
*/

View File

@ -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>
*/

View File

@ -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

View File

@ -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);
}
/**

View File

@ -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()
{

View File

@ -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();

View File

@ -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">

View File

@ -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">

View File

@ -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.
*/

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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);
}
}

View File

@ -1,5 +0,0 @@
.classpath
.project
.settings
target
*.swp

View File

@ -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 -->

View File

@ -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>

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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">

View File

@ -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">

View File

@ -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. -->
<!-- ============================================================= -->

View File

@ -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>

View File

@ -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>

View File

@ -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
{

View File

@ -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;
/**

View File

@ -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 -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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

View File

@ -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");

View File

@ -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" +

View File

@ -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;
}
}

View File

@ -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 -->

View File

@ -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. -->
<!-- ============================================================= -->

View File

@ -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>

View File

@ -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.

View File

@ -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

View File

@ -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()
{

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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);
};
}
}

View File

@ -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)

View File

@ -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, &lt;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, &lt;0

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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" +

View File

@ -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;
}
}

View File

@ -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.
*/

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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
{

View File

@ -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
{

View File

@ -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))

View File

@ -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);

View File

@ -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 *&#47;*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 "*&#47;*".
* 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 *&#47;*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

View File

@ -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()

View File

@ -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
{

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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