Merged branch 'jetty-9.4.x' into 'master'.

This commit is contained in:
Simone Bordet 2017-10-12 12:26:09 +02:00
commit 81c3fe1507
3 changed files with 223 additions and 173 deletions

View File

@ -23,11 +23,14 @@ import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -35,11 +38,13 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
@ -181,6 +186,63 @@ public class AsyncServletTest extends AbstractTest
Assert.assertTrue(clientLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Test
public void testStartAsyncThenClientResetWithoutRemoteErrorNotification() throws Exception
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setNotifyRemoteAsyncErrors(false);
prepareServer(new HTTP2ServerConnectionFactory(httpConfiguration));
ServletContextHandler context = new ServletContextHandler(server, "/");
AtomicReference<AsyncContext> asyncContextRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
context.addServlet(new ServletHolder(new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(0);
asyncContextRef.set(asyncContext);
latch.countDown();
}
}), servletPath + "/*");
server.start();
prepareClient();
client.start();
Session session = newClient(new Session.Listener.Adapter());
HttpFields fields = new HttpFields();
MetaData.Request metaData = newRequest("GET", fields);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, new Stream.Listener.Adapter());
Stream stream = promise.get(5, TimeUnit.SECONDS);
// Wait for the server to be in ASYNC_WAIT.
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
sleep(500);
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
// Wait for the reset to be processed by the server.
sleep(500);
AsyncContext asyncContext = asyncContextRef.get();
ServletResponse response = asyncContext.getResponse();
ServletOutputStream output = response.getOutputStream();
try
{
// Large writes or explicit flush() must
// fail because the stream has been reset.
output.flush();
Assert.fail();
}
catch (IOException x)
{
// Expected
}
}
@Test
public void testStartAsyncThenServerSessionIdleTimeout() throws Exception
{

View File

@ -409,7 +409,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
{
if (handle)
handleWithContext();
else
else if (getHttpConfiguration().isNotifyRemoteAsyncErrors())
getState().asyncError(failure);
callback.succeeded();
}

View File

@ -32,9 +32,8 @@ import org.eclipse.jetty.util.Trie;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
/* ------------------------------------------------------------ */
/** HTTP Configuration.
/**
* HTTP Configuration.
* <p>This class is a holder of HTTP configuration for use by the
* {@link HttpChannel} class. Typically a HTTPConfiguration instance
* is instantiated and passed to a {@link ConnectionFactory} that can
@ -69,22 +68,23 @@ public class HttpConfiguration
private boolean _useDirectByteBuffers = false;
private long _minRequestDataRate;
private CookieCompliance _cookieCompliance = CookieCompliance.RFC6265;
private boolean _notifyRemoteAsyncErrors = true;
/* ------------------------------------------------------------ */
/**
/**
* <p>An interface that allows a request object to be customized
* for a particular HTTP connector configuration. Unlike Filters, customizer are
* applied before the request is submitted for processing and can be specific to the
* connector on which the request was received.
* connector on which the request was received.</p>
*
* <p>Typically Customizers perform tasks such as: <ul>
* <p>Typically Customizers perform tasks such as:</p>
* <ul>
* <li>process header fields that may be injected by a proxy or load balancer.
* <li>setup attributes that may come from the connection/connector such as SSL Session IDs
* <li>Allow a request to be marked as secure or authenticated if those have been offloaded
* and communicated by header, cookie or other out-of-band mechanism
* <li>Set request attributes/fields that are determined by the connector on which the
* request was received
* </ul>
* </ul>
*/
public interface Customizer
{
@ -102,8 +102,9 @@ public class HttpConfiguration
_formEncodedMethods.put(HttpMethod.PUT.asString(),Boolean.TRUE);
}
/* ------------------------------------------------------------ */
/** Create a configuration from another.
/**
* Creates a configuration from another.
*
* @param config The configuration to copy.
*/
public HttpConfiguration(HttpConfiguration config)
@ -129,13 +130,13 @@ public class HttpConfiguration
_useDirectByteBuffers=config._useDirectByteBuffers;
_minRequestDataRate=config._minRequestDataRate;
_cookieCompliance=config._cookieCompliance;
_notifyRemoteAsyncErrors=config._notifyRemoteAsyncErrors;
}
/* ------------------------------------------------------------ */
/**
* <p>Add a {@link Customizer} that is invoked for every
/**
* <p>Adds a {@link Customizer} that is invoked for every
* request received.</p>
* <p>Customiser are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or
* <p>Customizers are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or
* optional protocol semantics (eg {@link SecureRequestCustomizer}).
* @param customizer A request customizer
*/
@ -144,13 +145,11 @@ public class HttpConfiguration
_customizers.add(customizer);
}
/* ------------------------------------------------------------ */
public List<Customizer> getCustomizers()
{
return _customizers;
}
/* ------------------------------------------------------------ */
public <T> T getCustomizer(Class<T> type)
{
for (Customizer c : _customizers)
@ -159,66 +158,58 @@ public class HttpConfiguration
return null;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("The size in bytes of the output buffer used to aggregate HTTP output")
public int getOutputBufferSize()
{
return _outputBufferSize;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("The maximum size in bytes for HTTP output to be aggregated")
public int getOutputAggregationSize()
{
return _outputAggregationSize;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("The maximum allowed size in bytes for a HTTP request header")
public int getRequestHeaderSize()
{
return _requestHeaderSize;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("The maximum allowed size in bytes for a HTTP response header")
public int getResponseHeaderSize()
{
return _responseHeaderSize;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("The maximum allowed size in bytes for a HTTP header field cache")
public int getHeaderCacheSize()
{
return _headerCacheSize;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("The port to which Integral or Confidential security constraints are redirected")
public int getSecurePort()
{
return _securePort;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("The scheme with which Integral or Confidential security constraints are redirected")
public String getSecureScheme()
{
return _secureScheme;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("Whether persistent connections are enabled")
public boolean isPersistentConnectionsEnabled()
{
return _persistentConnectionsEnabled;
}
/* ------------------------------------------------------------ */
/** Get the max idle time in ms.
/**
* <p>The max idle time is applied to a HTTP request for IO operations and
* delayed dispatch.
* 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.
*/
@ -228,10 +219,10 @@ public class HttpConfiguration
return _idleTimeout;
}
/* ------------------------------------------------------------ */
/** Set the max idle time in ms.
/**
* <p>The max idle time is applied to a HTTP request for IO operations and
* delayed dispatch.
* delayed dispatch.</p>
*
* @param timeoutMs 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.
*/
@ -240,11 +231,11 @@ public class HttpConfiguration
_idleTimeout=timeoutMs;
}
/* ------------------------------------------------------------ */
/** Get the timeout applied to blocking operations.
/**
* <p>This timeout is in addition to the {@link Connector#getIdleTimeout()}, and applies
* to the total operation (as opposed to the idle timeout that applies to the time no
* data is being sent).
* data is being sent).</p>
*
* @return -1, for no blocking timeout (default), 0 for a blocking timeout equal to the
* idle timeout; &gt;0 for a timeout in ms applied to the total blocking operation.
*/
@ -255,10 +246,10 @@ public class HttpConfiguration
}
/**
* Set the timeout applied to blocking operations.
* <p>This timeout is in addition to the {@link Connector#getIdleTimeout()}, and applies
* to the total operation (as opposed to the idle timeout that applies to the time no
* data is being sent).
* data is being sent).</p>
*
* @param blockingTimeout -1, for no blocking timeout (default), 0 for a blocking timeout equal to the
* idle timeout; &gt;0 for a timeout in ms applied to the total blocking operation.
*/
@ -267,26 +258,22 @@ public class HttpConfiguration
_blockingTimeout = blockingTimeout;
}
/* ------------------------------------------------------------ */
public void setPersistentConnectionsEnabled(boolean persistentConnectionsEnabled)
{
_persistentConnectionsEnabled = persistentConnectionsEnabled;
}
/* ------------------------------------------------------------ */
public void setSendServerVersion (boolean sendServerVersion)
{
_sendServerVersion = sendServerVersion;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("Whether to send the Server header in responses")
public boolean getSendServerVersion()
{
return _sendServerVersion;
}
/* ------------------------------------------------------------ */
public void writePoweredBy(Appendable out,String preamble,String postamble) throws IOException
{
if (getSendServerVersion())
@ -299,33 +286,28 @@ public class HttpConfiguration
}
}
/* ------------------------------------------------------------ */
public void setSendXPoweredBy (boolean sendXPoweredBy)
{
_sendXPoweredBy=sendXPoweredBy;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("Whether to send the X-Powered-By header in responses")
public boolean getSendXPoweredBy()
{
return _sendXPoweredBy;
}
/* ------------------------------------------------------------ */
public void setSendDateHeader(boolean sendDateHeader)
{
_sendDateHeader = sendDateHeader;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("Whether to send the Date header in responses")
public boolean getSendDateHeader()
{
return _sendDateHeader;
}
/* ------------------------------------------------------------ */
/**
* @param delay if true, delay the application dispatch until content is available (default false)
*/
@ -334,14 +316,12 @@ public class HttpConfiguration
_delayDispatchUntilContent = delay;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("Whether to delay the application dispatch until content is available")
public boolean isDelayDispatchUntilContent()
{
return _delayDispatchUntilContent;
}
/* ------------------------------------------------------------ */
/**
* @param useDirectByteBuffers if true, use direct byte buffers for requests
*/
@ -350,19 +330,18 @@ public class HttpConfiguration
_useDirectByteBuffers = useDirectByteBuffers;
}
/* ------------------------------------------------------------ */
@ManagedAttribute("Whether to use direct byte buffers for requests")
public boolean isUseDirectByteBuffers()
{
return _useDirectByteBuffers;
}
/* ------------------------------------------------------------ */
/**
* <p>Set the {@link Customizer}s that are invoked for every
* <p>Sets the {@link Customizer}s that are invoked for every
* request received.</p>
* <p>Customizers are often used to interpret optional headers (eg {@link ForwardedRequestCustomizer}) or
* optional protocol semantics (eg {@link SecureRequestCustomizer}).
* optional protocol semantics (eg {@link SecureRequestCustomizer}).</p>
*
* @param customizers the list of customizers
*/
public void setCustomizers(List<Customizer> customizers)
@ -371,12 +350,12 @@ public class HttpConfiguration
_customizers.addAll(customizers);
}
/* ------------------------------------------------------------ */
/**
* Set the size of the buffer into which response content is aggregated
* before being sent to the client. A larger buffer can improve performance by allowing
* a content producer to run without blocking, however larger buffers consume more memory and
* may induce some latency before a client starts processing the content.
*
* @param outputBufferSize buffer size in bytes.
*/
public void setOutputBufferSize(int outputBufferSize)
@ -385,12 +364,12 @@ public class HttpConfiguration
setOutputAggregationSize(outputBufferSize / 4);
}
/* ------------------------------------------------------------ */
/**
* Set the max size of the response content write that is copied into the aggregate buffer.
* Writes that are smaller of this size are copied into the aggregate buffer, while
* writes that are larger of this size will cause the aggregate buffer to be flushed
* and the write to be executed without being copied.
*
* @param outputAggregationSize the max write size that is aggregated
*/
public void setOutputAggregationSize(int outputAggregationSize)
@ -398,32 +377,30 @@ public class HttpConfiguration
_outputAggregationSize = outputAggregationSize;
}
/* ------------------------------------------------------------ */
/** Set the maximum size of a request header.
/**
* <p>Larger headers will allow for more and/or larger cookies plus larger form content encoded
* in a URL. However, larger headers consume more memory and can make a server more vulnerable to denial of service
* attacks.</p>
* @param requestHeaderSize Max header size in bytes
*
* @param requestHeaderSize the maximum size in bytes of the request header
*/
public void setRequestHeaderSize(int requestHeaderSize)
{
_requestHeaderSize = requestHeaderSize;
}
/* ------------------------------------------------------------ */
/** Set the maximum size of a response header.
*
* <p>Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection).
/**
* <p>Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection).
* However, larger headers will also consume more memory.</p>
* @param responseHeaderSize Response header size in bytes.
*
* @param responseHeaderSize the maximum size in bytes of the response header
*/
public void setResponseHeaderSize(int responseHeaderSize)
{
_responseHeaderSize = responseHeaderSize;
}
/* ------------------------------------------------------------ */
/** Set the header field cache size.
/**
* @param headerCacheSize The size in bytes of the header field cache.
*/
public void setHeaderCacheSize(int headerCacheSize)
@ -431,8 +408,9 @@ public class HttpConfiguration
_headerCacheSize = headerCacheSize;
}
/* ------------------------------------------------------------ */
/** Set the TCP/IP port used for CONFIDENTIAL and INTEGRAL redirections.
/**
* <p>Sets the TCP/IP port used for CONFIDENTIAL and INTEGRAL redirections.</p>
*
* @param securePort the secure port to redirect to.
*/
public void setSecurePort(int securePort)
@ -440,8 +418,9 @@ public class HttpConfiguration
_securePort = securePort;
}
/* ------------------------------------------------------------ */
/** Set the URI scheme used for CONFIDENTIAL and INTEGRAL redirections.
/**
* <p>Set the URI scheme used for CONFIDENTIAL and INTEGRAL redirections.</p>
*
* @param secureScheme A scheme string like "https"
*/
public void setSecureScheme(String secureScheme)
@ -449,7 +428,121 @@ public class HttpConfiguration
_secureScheme = secureScheme;
}
/* ------------------------------------------------------------ */
/**
* Sets the form encoded HTTP methods.
*
* @param methods the HTTP methods of requests that can be decoded as
* {@code x-www-form-urlencoded} content to be made available via the
* {@link Request#getParameter(String)} and associated APIs
*/
public void setFormEncodedMethods(String... methods)
{
_formEncodedMethods.clear();
for (String method:methods)
addFormEncodedMethod(method);
}
/**
* @return the set of HTTP methods of requests that can be decoded as
* {@code x-www-form-urlencoded} content to be made available via the
* {@link Request#getParameter(String)} and associated APIs
*/
public Set<String> getFormEncodedMethods()
{
return _formEncodedMethods.keySet();
}
/**
* Adds a form encoded HTTP Method
*
* @param method the HTTP method of requests that can be decoded as
* {@code x-www-form-urlencoded} content to be made available via the
* {@link Request#getParameter(String)} and associated APIs
*/
public void addFormEncodedMethod(String method)
{
_formEncodedMethods.put(method,Boolean.TRUE);
}
/**
* Tests whether the HTTP method supports {@code x-www-form-urlencoded} content
*
* @param method the HTTP method
* @return true if requests with this method can be
* decoded as {@code x-www-form-urlencoded} content to be made available via the
* {@link Request#getParameter(String)} and associated APIs
*/
public boolean isFormEncodedMethod(String method)
{
return Boolean.TRUE.equals(_formEncodedMethods.get(method));
}
/**
* @return The maximum error dispatches for a request to prevent looping on an error
*/
@ManagedAttribute("The maximum ERROR dispatches for a request for loop prevention (default 10)")
public int getMaxErrorDispatches()
{
return _maxErrorDispatches;
}
/**
* @param max The maximum error dispatches for a request to prevent looping on an error
*/
public void setMaxErrorDispatches(int max)
{
_maxErrorDispatches=max;
}
/**
* @return The minimum request data rate in bytes per second; or &lt;=0 for no limit
*/
@ManagedAttribute("The minimum request content data rate in bytes per second")
public long getMinRequestDataRate()
{
return _minRequestDataRate;
}
/**
* @param bytesPerSecond The minimum request data rate in bytes per second; or &lt;=0 for no limit
*/
public void setMinRequestDataRate(long bytesPerSecond)
{
_minRequestDataRate=bytesPerSecond;
}
public CookieCompliance getCookieCompliance()
{
return _cookieCompliance;
}
public void setCookieCompliance(CookieCompliance cookieCompliance)
{
_cookieCompliance = cookieCompliance==null?CookieCompliance.RFC6265:cookieCompliance;
}
public boolean isCookieCompliance(CookieCompliance compliance)
{
return _cookieCompliance.equals(compliance);
}
/**
* @param notifyRemoteAsyncErrors whether remote errors, when detected, are notified to async applications
*/
public void setNotifyRemoteAsyncErrors(boolean notifyRemoteAsyncErrors)
{
this._notifyRemoteAsyncErrors = notifyRemoteAsyncErrors;
}
/**
* @return whether remote errors, when detected, are notified to async applications
*/
@ManagedAttribute("Whether remote errors, when detected, are notified to async applications")
public boolean isNotifyRemoteAsyncErrors()
{
return _notifyRemoteAsyncErrors;
}
@Override
public String toString()
{
@ -461,109 +554,4 @@ public class HttpConfiguration
_secureScheme,_securePort,
_customizers);
}
/* ------------------------------------------------------------ */
/** Set the form encoded methods.
* @param methods HTTP Methods of requests that can be decoded as
* x-www-form-urlencoded content to be made available via the
* {@link Request#getParameter(String)} and associated APIs
*/
public void setFormEncodedMethods(String... methods)
{
_formEncodedMethods.clear();
for (String method:methods)
addFormEncodedMethod(method);
}
/* ------------------------------------------------------------ */
/**
* @return Set of HTTP Methods of requests that can be decoded as
* x-www-form-urlencoded content to be made available via the
* {@link Request#getParameter(String)} and associated APIs
*/
public Set<String> getFormEncodedMethods()
{
return _formEncodedMethods.keySet();
}
/* ------------------------------------------------------------ */
/** Add a form encoded HTTP Method
* @param method HTTP Method of requests that can be decoded as
* x-www-form-urlencoded content to be made available via the
* {@link Request#getParameter(String)} and associated APIs
*/
public void addFormEncodedMethod(String method)
{
_formEncodedMethods.put(method,Boolean.TRUE);
}
/* ------------------------------------------------------------ */
/**
* Test if the method type supports <code>x-www-form-urlencoded</code> content
*
* @param method the method type
* @return True of the requests of this method type can be
* decoded as <code>x-www-form-urlencoded</code> content to be made available via the
* {@link Request#getParameter(String)} and associated APIs
*/
public boolean isFormEncodedMethod(String method)
{
return Boolean.TRUE.equals(_formEncodedMethods.get(method));
}
/* ------------------------------------------------------------ */
/**
* @return The maximum error dispatches for a request to prevent looping on an error
*/
@ManagedAttribute("The maximum ERROR dispatches for a request for loop prevention (default 10)")
public int getMaxErrorDispatches()
{
return _maxErrorDispatches;
}
/* ------------------------------------------------------------ */
/**
* @param max The maximum error dispatches for a request to prevent looping on an error
*/
public void setMaxErrorDispatches(int max)
{
_maxErrorDispatches=max;
}
/* ------------------------------------------------------------ */
/**
* @return The minimum request data rate in bytes per second; or &lt;=0 for no limit
*/
@ManagedAttribute("The minimum request content data rate in bytes per second")
public long getMinRequestDataRate()
{
return _minRequestDataRate;
}
/* ------------------------------------------------------------ */
/**
* @param bytesPerSecond The minimum request data rate in bytes per second; or &lt;=0 for no limit
*/
public void setMinRequestDataRate(long bytesPerSecond)
{
_minRequestDataRate=bytesPerSecond;
}
/* ------------------------------------------------------------ */
public CookieCompliance getCookieCompliance()
{
return _cookieCompliance;
}
/* ------------------------------------------------------------ */
public void setCookieCompliance(CookieCompliance cookieCompliance)
{
_cookieCompliance = cookieCompliance==null?CookieCompliance.RFC6265:cookieCompliance;
}
/* ------------------------------------------------------------ */
public boolean isCookieCompliance(CookieCompliance compliance)
{
return _cookieCompliance.equals(compliance);
}
}