Jetty 12 inserted handler in ee10 servlet context (#9927)

This PR refactors the ee10 handing of servlet API request and response objects:

 + The ServletContextHandler matches the request to a servlet and creates a one time only ServletContextRequest and a ServletContextResponse
 + A reusable ServletChannel object with all the heavy weight HttpInput and HttpOutput object is associated with the ServletContextRequest and ServletContextResponse.
 + Once the handling reaches the ServletHandler, the possibly wrapped request, response and callback are associated with the ServletChannel before handling.
 + Were possible the ServletApiRequest and ServletApiResponse use the possibly wrapped request/response

Added tests to check that GzipHandler can now be nested inside of an EE10 context.

---------

Signed-off-by: Ludovic Orban <lorban@bitronix.be>
Signed-off-by: gregw <gregw@webtide.com>
Co-authored-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
Greg Wilkins 2023-06-22 17:04:49 +02:00 committed by GitHub
parent 715535ab95
commit 0b1c28a888
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1314 additions and 941 deletions

View File

@ -1204,6 +1204,167 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
// return a merged header with missing ensured values added
return new HttpField(ensure.getHeader(), ensure.getName(), v.toString());
}
/**
* A wrapper of {@link HttpFields}.
*/
class Wrapper implements Mutable
{
private final Mutable _fields;
public Wrapper(Mutable fields)
{
_fields = fields;
}
/**
* Called when a field is added (including as part of a put).
* @param field The field being added.
* @return The field to add, or null if the add is to be ignored.
*/
public HttpField onAddField(HttpField field)
{
return field;
}
/**
* Called when a field is removed (including as part of a put).
* @param field The field being removed.
* @return True if the field should be removed, false otherwise.
*/
public boolean onRemoveField(HttpField field)
{
return true;
}
@Override
public HttpFields takeAsImmutable()
{
return Mutable.super.takeAsImmutable();
}
@Override
public int size()
{
// This impl needed only as an optimization
return _fields.size();
}
@Override
public Stream<HttpField> stream()
{
// This impl needed only as an optimization
return _fields.stream();
}
@Override
public Mutable add(HttpField field)
{
// This impl needed only as an optimization
if (field != null)
{
field = onAddField(field);
if (field != null)
return _fields.add(field);
}
return this;
}
@Override
public ListIterator<HttpField> listIterator()
{
ListIterator<HttpField> i = _fields.listIterator();
return new ListIterator<>()
{
HttpField last;
@Override
public boolean hasNext()
{
return i.hasNext();
}
@Override
public HttpField next()
{
return last = i.next();
}
@Override
public boolean hasPrevious()
{
return i.hasPrevious();
}
@Override
public HttpField previous()
{
return last = i.previous();
}
@Override
public int nextIndex()
{
return i.nextIndex();
}
@Override
public int previousIndex()
{
return i.previousIndex();
}
@Override
public void remove()
{
if (last != null && onRemoveField(last))
{
last = null;
i.remove();
}
}
@Override
public void set(HttpField field)
{
if (field == null)
{
if (last != null && onRemoveField(last))
{
last = null;
i.remove();
}
}
else
{
if (last != null && onRemoveField(last))
{
field = onAddField(field);
if (field != null)
{
last = null;
i.set(field);
}
}
}
}
@Override
public void add(HttpField field)
{
if (field != null)
{
field = onAddField(field);
if (field != null)
{
last = null;
i.add(field);
}
}
}
};
}
}
}
/**
@ -1391,8 +1552,12 @@ public interface HttpFields extends Iterable<HttpField>, Supplier<HttpFields>
public int hashCode()
{
int hash = 0;
for (int i = _fields.length; i-- > 0; )
hash ^= _fields[i].hashCode();
for (int i = _size; i-- > 0; )
{
HttpField field = _fields[i];
if (field != null)
hash ^= field.hashCode();
}
return hash;
}

View File

@ -54,6 +54,7 @@ public class HttpFieldsTest
return Stream.of(
HttpFields.build(),
HttpFields.build(0),
new HttpFields.Mutable.Wrapper(HttpFields.build()),
new HttpFields.Mutable()
{
private final HttpFields.Mutable fields = HttpFields.build();

View File

@ -589,11 +589,8 @@ public class ResourceService
return null;
String contextPath = request.getContext().getContextPath();
if (LOG.isDebugEnabled())
LOG.debug("welcome={}", welcomeTarget);
WelcomeMode welcomeMode = getWelcomeMode();
welcomeTarget = switch (welcomeMode)
{
case REDIRECT, REHANDLE -> HttpURI.build(request.getHttpURI())
@ -602,6 +599,9 @@ public class ResourceService
case SERVE -> welcomeTarget;
};
if (LOG.isDebugEnabled())
LOG.debug("welcome {} {}", welcomeMode, welcomeTarget);
return new WelcomeAction(welcomeTarget, welcomeMode);
}

View File

@ -526,9 +526,9 @@ public class GzipHandler extends Handler.Wrapper implements GzipFactory
if (Request.as(request, GzipRequest.class) != null)
return next.handle(request, response, callback);
String path = Request.getPathInContext(request);
boolean tryInflate = getInflateBufferSize() >= 0 && isPathInflatable(path);
boolean tryDeflate = _methods.test(request.getMethod()) && isPathDeflatable(path) && isMimeTypeDeflatable(request.getContext().getMimeTypes(), path);
String pathInContext = Request.getPathInContext(request);
boolean tryInflate = getInflateBufferSize() >= 0 && isPathInflatable(pathInContext);
boolean tryDeflate = _methods.test(request.getMethod()) && isPathDeflatable(pathInContext) && isMimeTypeDeflatable(request.getContext().getMimeTypes(), pathInContext);
// Can we skip looking at the request and wrapping request or response?
if (!tryInflate && !tryDeflate)
@ -624,15 +624,15 @@ public class GzipHandler extends Handler.Wrapper implements GzipFactory
/**
* Test if the provided Request URI is allowed to be inflated based on the Path Specs filters.
*
* @param requestURI the request uri
* @param pathInContext the request path in context
* @return whether decompressing is allowed for the given the path.
*/
protected boolean isPathInflatable(String requestURI)
protected boolean isPathInflatable(String pathInContext)
{
if (requestURI == null)
if (pathInContext == null)
return true;
return _inflatePaths.test(requestURI);
return _inflatePaths.test(pathInContext);
}
/**

View File

@ -104,7 +104,6 @@ public class HttpChannelState implements HttpChannel, Components
private final HandlerInvoker _handlerInvoker = new HandlerInvoker();
private final ConnectionMetaData _connectionMetaData;
private final SerializedInvoker _serializedInvoker;
private final Attributes _requestAttributes = new Attributes.Lazy();
private final ResponseHttpFields _responseHeaders = new ResponseHttpFields();
private Thread _handling;
private boolean _handled;
@ -157,7 +156,6 @@ public class HttpChannelState implements HttpChannel, Components
_streamSendState = StreamSendState.SENDING;
// Recycle.
_requestAttributes.clearAttributes();
_responseHeaders.reset();
_handling = null;
_handled = false;
@ -741,6 +739,7 @@ public class HttpChannelState implements HttpChannel, Components
private final MetaData.Request _metaData;
private final AutoLock _lock;
private final LongAdder _contentBytesRead = new LongAdder();
private final Attributes _attributes = new Attributes.Lazy();
private HttpChannelState _httpChannelState;
private Request _loggedRequest;
private HttpFields _trailers;
@ -777,26 +776,25 @@ public class HttpChannelState implements HttpChannel, Components
@Override
public Object getAttribute(String name)
{
HttpChannelState httpChannel = getHttpChannelState();
if (name.startsWith("org.eclipse.jetty"))
{
if (Server.class.getName().equals(name))
return httpChannel.getConnectionMetaData().getConnector().getServer();
return getConnectionMetaData().getConnector().getServer();
if (HttpChannelState.class.getName().equals(name))
return httpChannel;
return getHttpChannelState();
// TODO: is the instanceof needed?
// TODO: possibly remove this if statement or move to Servlet.
if (HttpConnection.class.getName().equals(name) &&
getConnectionMetaData().getConnection() instanceof HttpConnection)
return getConnectionMetaData().getConnection();
}
return httpChannel._requestAttributes.getAttribute(name);
return _attributes.getAttribute(name);
}
@Override
public Object removeAttribute(String name)
{
return getHttpChannelState()._requestAttributes.removeAttribute(name);
return _attributes.removeAttribute(name);
}
@Override
@ -804,19 +802,19 @@ public class HttpChannelState implements HttpChannel, Components
{
if (Server.class.getName().equals(name) || HttpChannelState.class.getName().equals(name) || HttpConnection.class.getName().equals(name))
return null;
return getHttpChannelState()._requestAttributes.setAttribute(name, attribute);
return _attributes.setAttribute(name, attribute);
}
@Override
public Set<String> getAttributeNameSet()
{
return getHttpChannelState()._requestAttributes.getAttributeNameSet();
return _attributes.getAttributeNameSet();
}
@Override
public void clearAttributes()
{
getHttpChannelState()._requestAttributes.clearAttributes();
_attributes.clearAttributes();
}
@Override
@ -837,7 +835,7 @@ public class HttpChannelState implements HttpChannel, Components
return _connectionMetaData;
}
HttpChannelState getHttpChannelState()
private HttpChannelState getHttpChannelState()
{
try (AutoLock ignore = _lock.lock())
{

View File

@ -208,7 +208,6 @@ public class Blocker
/**
* A shared reusable Blocking source.
* TODO Review need for this, as it is currently unused.
*/
public static class Shared
{

View File

@ -28,5 +28,7 @@ public interface ServerUpgradeResponse extends Response
void addExtensions(List<ExtensionConfig> configs);
void removeExtensions(List<ExtensionConfig> configs);
void setExtensions(List<ExtensionConfig> configs);
}

View File

@ -1,166 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.core.server.internal;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.ListIterator;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
public class HttpFieldsWrapper implements HttpFields.Mutable
{
private final HttpFields.Mutable _fields;
public HttpFieldsWrapper(HttpFields.Mutable fields)
{
_fields = fields;
}
public boolean onPutField(String name, String value)
{
return true;
}
public boolean onAddField(String name, String value)
{
return true;
}
public boolean onRemoveField(String name)
{
return true;
}
@Override
public Mutable add(String name, String value)
{
if (onAddField(name, value))
return _fields.add(name, value);
return this;
}
@Override
public Mutable add(HttpHeader header, HttpHeaderValue value)
{
if (onAddField(header.asString(), value.asString()))
return _fields.add(header, value);
return this;
}
@Override
public Mutable add(HttpHeader header, String value)
{
if (onAddField(header.asString(), value))
return _fields.add(header, value);
return this;
}
@Override
public Mutable add(HttpField field)
{
if (onAddField(field.getName(), field.getValue()))
return _fields.add(field);
return this;
}
@Override
public Mutable add(HttpFields fields)
{
for (HttpField field : fields)
{
add(field);
}
return this;
}
@Override
public Mutable clear()
{
return _fields.clear();
}
@Override
public Iterator<HttpField> iterator()
{
return _fields.iterator();
}
@Override
public ListIterator<HttpField> listIterator()
{
return _fields.listIterator();
}
@Override
public Mutable put(HttpField field)
{
if (onPutField(field.getName(), field.getValue()))
return _fields.put(field);
return this;
}
@Override
public Mutable put(String name, String value)
{
if (onPutField(name, value))
return _fields.put(name, value);
return this;
}
@Override
public Mutable put(HttpHeader header, HttpHeaderValue value)
{
if (onPutField(header.asString(), value.asString()))
return _fields.put(header, value);
return this;
}
@Override
public Mutable put(HttpHeader header, String value)
{
if (onPutField(header.asString(), value))
return _fields.put(header, value);
return this;
}
@Override
public Mutable remove(HttpHeader header)
{
if (onRemoveField(header.asString()))
return _fields.remove(header);
return this;
}
@Override
public Mutable remove(EnumSet<HttpHeader> fields)
{
for (HttpHeader header : fields)
{
remove(header);
}
return this;
}
@Override
public Mutable remove(String name)
{
if (onRemoveField(name))
return _fields.remove(name);
return this;
}
}

View File

@ -69,12 +69,19 @@ public class ServerUpgradeResponseImpl extends Response.Wrapper implements Serve
@Override
public void addExtensions(List<ExtensionConfig> configs)
{
ArrayList<ExtensionConfig> combinedConfig = new ArrayList<>();
combinedConfig.addAll(getExtensions());
ArrayList<ExtensionConfig> combinedConfig = new ArrayList<>(getExtensions());
combinedConfig.addAll(configs);
setExtensions(combinedConfig);
}
@Override
public void removeExtensions(List<ExtensionConfig> configs)
{
ArrayList<ExtensionConfig> trimmedExtensions = new ArrayList<>(getExtensions());
trimmedExtensions.removeAll(configs);
setExtensions(trimmedExtensions);
}
@Override
public void setExtensions(List<ExtensionConfig> configs)
{

View File

@ -14,75 +14,117 @@
package org.eclipse.jetty.websocket.core.server.internal;
import java.util.Collections;
import java.util.List;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
public class WebSocketHttpFieldsWrapper extends HttpFieldsWrapper
public class WebSocketHttpFieldsWrapper extends HttpFields.Mutable.Wrapper
{
private final WebSocketNegotiation _negotiation;
private final ServerUpgradeResponse _response;
public WebSocketHttpFieldsWrapper(Mutable fields, ServerUpgradeResponse response, WebSocketNegotiation negotiation)
{
super(fields);
_negotiation = negotiation;
_response = response;
}
@Override
public boolean onPutField(String name, String value)
public HttpField onAddField(HttpField field)
{
if (HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL.is(name))
if (field.getHeader() != null)
{
_response.setAcceptedSubProtocol(value);
return false;
}
return switch (field.getHeader())
{
case SEC_WEBSOCKET_SUBPROTOCOL ->
{
_response.setAcceptedSubProtocol(field.getValue());
yield null;
}
if (HttpHeader.SEC_WEBSOCKET_EXTENSIONS.is(name))
{
_response.setExtensions(ExtensionConfig.parseList(value));
return false;
}
case SEC_WEBSOCKET_EXTENSIONS ->
{
_response.addExtensions(ExtensionConfig.parseList(field.getValue()));
yield null;
}
return super.onPutField(name, value);
default -> super.onAddField(field);
};
}
return super.onAddField(field);
}
@Override
public boolean onAddField(String name, String value)
public boolean onRemoveField(HttpField field)
{
if (HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL.is(name))
if (field.getHeader() != null)
{
_response.setAcceptedSubProtocol(value);
return false;
}
return switch (field.getHeader())
{
case SEC_WEBSOCKET_SUBPROTOCOL ->
{
_response.setAcceptedSubProtocol(null);
yield true;
}
if (HttpHeader.SEC_WEBSOCKET_EXTENSIONS.is(name))
{
_response.addExtensions(ExtensionConfig.parseList(value));
return false;
}
case SEC_WEBSOCKET_EXTENSIONS ->
{
_response.removeExtensions(ExtensionConfig.parseList(field.getValue()));
yield true;
}
return super.onAddField(name, value);
default -> super.onRemoveField(field);
};
}
return super.onRemoveField(field);
}
@Override
public boolean onRemoveField(String name)
public Mutable put(HttpField field)
{
if (HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL.is(name))
{
_response.setAcceptedSubProtocol(null);
return false;
}
// Need to override put methods as putting extensions clears them, even if field does not exist.
if (field.getHeader() == HttpHeader.SEC_WEBSOCKET_EXTENSIONS)
_response.setExtensions(Collections.emptyList());
return super.put(field);
}
@Override
public Mutable put(String name, String value)
{
// Need to override put methods as putting extensions clears them, even if field does not exist.
if (HttpHeader.SEC_WEBSOCKET_EXTENSIONS.is(name))
{
// TODO: why add extensions??
_response.addExtensions(Collections.emptyList());
return false;
}
_response.setExtensions(Collections.emptyList());
return super.put(name, value);
}
return super.onRemoveField(name);
@Override
public Mutable put(HttpHeader header, HttpHeaderValue value)
{
// Need to override put methods as putting extensions clears them, even if field does not exist.
if (header == HttpHeader.SEC_WEBSOCKET_EXTENSIONS)
_response.setExtensions(Collections.emptyList());
return super.put(header, value);
}
@Override
public Mutable put(HttpHeader header, String value)
{
// Need to override put methods as putting extensions clears them, even if field does not exist.
if (header == HttpHeader.SEC_WEBSOCKET_EXTENSIONS)
_response.setExtensions(Collections.emptyList());
return super.put(header, value);
}
@Override
public Mutable put(String name, List<String> list)
{
// Need to override put methods as putting extensions clears them, even if field does not exist.
if (HttpHeader.SEC_WEBSOCKET_EXTENSIONS.is(name))
_response.setExtensions(Collections.emptyList());
return super.put(name, list);
}
}

View File

@ -126,7 +126,7 @@ class AsyncContentProducer implements ContentProducer
LOG.debug("checkMinDataRate check failed {}", this);
BadMessageException bad = new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,
String.format("Request content data rate < %d B/s", minRequestDataRate));
if (_servletChannel.getState().isResponseCommitted())
if (_servletChannel.getServletRequestState().isResponseCommitted())
{
if (LOG.isDebugEnabled())
LOG.debug("checkMinDataRate aborting channel {}", this);
@ -180,7 +180,7 @@ class AsyncContentProducer implements ContentProducer
private boolean consumeAvailableChunks()
{
return _servletChannel.getServletContextRequest().consumeAvailable();
return _servletChannel.getRequest().consumeAvailable();
}
@Override
@ -189,7 +189,7 @@ class AsyncContentProducer implements ContentProducer
assertLocked();
if (LOG.isDebugEnabled())
LOG.debug("onContentProducible {}", this);
return _servletChannel.getState().onReadReady();
return _servletChannel.getServletRequestState().onReadReady();
}
@Override
@ -200,7 +200,7 @@ class AsyncContentProducer implements ContentProducer
if (LOG.isDebugEnabled())
LOG.debug("nextChunk = {} {}", chunk, this);
if (chunk != null)
_servletChannel.getState().onReadIdle();
_servletChannel.getServletRequestState().onReadIdle();
return chunk;
}
@ -227,8 +227,8 @@ class AsyncContentProducer implements ContentProducer
return true;
}
_servletChannel.getState().onReadUnready();
_servletChannel.getServletContextRequest().demand(() ->
_servletChannel.getServletRequestState().onReadUnready();
_servletChannel.getRequest().demand(() ->
{
if (_servletChannel.getHttpInput().onContentProducible())
_servletChannel.handle();
@ -241,7 +241,7 @@ class AsyncContentProducer implements ContentProducer
boolean isUnready()
{
return _servletChannel.getState().isInputUnready();
return _servletChannel.getServletRequestState().isInputUnready();
}
private Content.Chunk produceChunk()
@ -280,7 +280,7 @@ class AsyncContentProducer implements ContentProducer
}
else
{
_servletChannel.getState().onContentAdded();
_servletChannel.getServletRequestState().onContentAdded();
}
}
@ -297,7 +297,7 @@ class AsyncContentProducer implements ContentProducer
private Content.Chunk readChunk()
{
Content.Chunk chunk = _servletChannel.getServletContextRequest().read();
Content.Chunk chunk = _servletChannel.getRequest().read();
if (chunk != null)
{
_bytesArrived += chunk.remaining();
@ -305,7 +305,6 @@ class AsyncContentProducer implements ContentProducer
_firstByteNanoTime = NanoTime.now();
if (LOG.isDebugEnabled())
LOG.debug("readChunk() updated _bytesArrived to {} and _firstByteTimeStamp to {} {}", _bytesArrived, _firstByteNanoTime, this);
// TODO: notify channel listeners (see ee9)?
if (chunk instanceof Trailers trailers)
_servletChannel.onTrailers(trailers.getTrailers());
}

View File

@ -117,7 +117,7 @@ public class AsyncContextState implements AsyncContext
ServletResponse servletResponse = getResponse();
ServletChannel servletChannel = _state.getServletChannel();
HttpServletRequest originalHttpServletRequest = servletChannel.getServletContextRequest().getServletApiRequest();
HttpServletResponse originalHttpServletResponse = servletChannel.getResponse().getHttpServletResponse();
HttpServletResponse originalHttpServletResponse = servletChannel.getServletContextResponse().getServletApiResponse();
return (servletRequest == originalHttpServletRequest && servletResponse == originalHttpServletResponse);
}

View File

@ -162,18 +162,16 @@ public class DebugListener extends AbstractLifeCycle implements ServletContextLi
return n;
}
protected String findRequestName(ServletRequest request)
protected String findRequestName(ServletContextRequest request)
{
if (request == null)
return null;
HttpServletRequest r = (HttpServletRequest)request;
String n = (String)request.getAttribute(_attr);
if (n == null)
{
n = String.format("%s@%x", r.getRequestURI(), request.hashCode());
request.setAttribute(_attr, n);
}
return n;
return request.getId();
}
protected String findRequestName(ServletRequest request)
{
return findRequestName(ServletContextRequest.getServletContextRequest(request));
}
protected void log(String format, Object... arg)
@ -225,7 +223,7 @@ public class DebugListener extends AbstractLifeCycle implements ServletContextLi
String rname = findRequestName(ace.getAsyncContext().getRequest());
ServletContextRequest request = ServletContextRequest.getServletContextRequest(ace.getAsyncContext().getRequest());
Response response = request.getResponse();
Response response = request.getServletContextResponse();
String headers = _showHeaders ? ("\n" + response.getHeaders().toString()) : "";
log("! ctx=%s r=%s onComplete %s %d%s", cname, rname, ace.getServletRequestState(), response.getStatus(), headers);
@ -280,8 +278,8 @@ public class DebugListener extends AbstractLifeCycle implements ServletContextLi
else
{
ServletContextRequest request = ServletContextRequest.getServletContextRequest(r);
String headers = _showHeaders ? ("\n" + request.getResponse().getHeaders().toString()) : "";
log("<< %s ctx=%s r=%s async=false %d%s", d, cname, rname, request.getResponse().getStatus(), headers);
String headers = _showHeaders ? ("\n" + request.getServletContextResponse().getHeaders().toString()) : "";
log("<< %s ctx=%s r=%s async=false %d%s", d, cname, rname, request.getServletContextResponse().getStatus(), headers);
}
}
};
@ -296,7 +294,7 @@ public class DebugListener extends AbstractLifeCycle implements ServletContextLi
log("> ctx=%s", cname);
else
{
String rname = findRequestName(request.getServletApiRequest());
String rname = findRequestName(request);
if (_renameThread)
{
@ -316,7 +314,7 @@ public class DebugListener extends AbstractLifeCycle implements ServletContextLi
log("< ctx=%s", cname);
else
{
String rname = findRequestName(request.getServletApiRequest());
String rname = findRequestName(request);
log("< ctx=%s r=%s", cname, rname);
if (_renameThread)

View File

@ -458,7 +458,7 @@ public class DefaultServlet extends HttpServlet
else if (isPathInfoOnly())
encodedPathInContext = URIUtil.encodePath(req.getPathInfo());
else if (req instanceof ServletApiRequest apiRequest)
encodedPathInContext = Context.getPathInContext(req.getContextPath(), apiRequest.getServletContextRequest().getHttpURI().getCanonicalPath());
encodedPathInContext = Context.getPathInContext(req.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath());
else
encodedPathInContext = Context.getPathInContext(req.getContextPath(), URIUtil.canonicalPath(req.getRequestURI()));
@ -878,12 +878,7 @@ public class DefaultServlet extends HttpServlet
public ServletContextResponse getServletContextResponse()
{
if (_response instanceof ServletApiResponse)
{
ServletApiResponse apiResponse = (ServletApiResponse)_response;
return apiResponse.getResponse();
}
return null;
return ServletContextResponse.getServletContextResponse(_response);
}
@Override
@ -1003,6 +998,12 @@ public class DefaultServlet extends HttpServlet
{
return null;
}
@Override
public String toString()
{
return "%s@%x{%s,%s}".formatted(this.getClass().getSimpleName(), hashCode(), this._coreRequest, _response);
}
}
private class ServletResourceService extends ResourceService implements ResourceService.WelcomeFactory

View File

@ -104,7 +104,7 @@ public class Dispatcher implements RequestDispatcher
HttpServletResponse httpResponse = (response instanceof HttpServletResponse) ? (HttpServletResponse)response : new ServletResponseHttpWrapper(response);
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(request);
servletContextRequest.getResponse().resetForForward();
servletContextRequest.getServletContextResponse().resetForForward();
_mappedServlet.handle(_servletHandler, _decodedPathInContext, new ForwardRequest(httpRequest), httpResponse);
// If we are not async and not closed already, then close via the possibly wrapped response.

View File

@ -91,7 +91,7 @@ public class ErrorHandler implements Request.Handler
ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
HttpServletRequest httpServletRequest = servletContextRequest.getServletApiRequest();
HttpServletResponse httpServletResponse = servletContextRequest.getHttpServletResponse();
ServletContextHandler contextHandler = servletContextRequest.getContext().getServletContextHandler();
ServletContextHandler contextHandler = servletContextRequest.getServletContext().getServletContextHandler();
String cacheControl = getCacheControl();
if (cacheControl != null)
response.getHeaders().put(HttpHeader.CACHE_CONTROL.asString(), cacheControl);
@ -164,7 +164,7 @@ public class ErrorHandler implements Request.Handler
for (String mimeType : acceptable)
{
generateAcceptableResponse(baseRequest, request, response, code, message, mimeType);
if (response.isCommitted() || baseRequest.getResponse().isWritingOrStreaming())
if (response.isCommitted() || baseRequest.getServletContextResponse().isWritingOrStreaming())
break;
}
}
@ -300,7 +300,7 @@ public class ErrorHandler implements Request.Handler
// 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().getByteBuffer();
ByteBuffer buffer = baseRequest.getServletContextResponse().getHttpOutput().getByteBuffer();
ByteBufferOutputStream out = new ByteBufferOutputStream(buffer);
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, charset));
@ -334,7 +334,7 @@ public class ErrorHandler implements Request.Handler
LOG.warn("Error page too large: {} {} {}", code, message, request, e);
else
LOG.warn("Error page too large: {} {} {}", code, message, request);
baseRequest.getResponse().resetContent();
baseRequest.getServletContextResponse().resetContent();
if (!_disableStacks)
{
LOG.info("Disabling showsStacks for {}", this);
@ -395,7 +395,7 @@ public class ErrorHandler implements Request.Handler
if (showStacks && !_disableStacks)
writeErrorPageStacks(request, writer);
((ServletApiRequest)request).getServletContextRequest().getServletChannel().getHttpConfiguration()
((ServletApiRequest)request).getServletRequestInfo().getServletChannel().getHttpConfiguration()
.writePoweredBy(writer, "<hr/>", "<hr/>\n");
}

View File

@ -52,7 +52,7 @@ public class HttpInput extends ServletInputStream implements Runnable
public HttpInput(ServletChannel channel)
{
_servletChannel = channel;
_channelState = _servletChannel.getState();
_channelState = _servletChannel.getServletRequestState();
_asyncContentProducer = new AsyncContentProducer(_servletChannel, _lock);
_blockingContentProducer = new BlockingContentProducer(_asyncContentProducer);
_contentProducer = _blockingContentProducer;
@ -349,7 +349,7 @@ public class HttpInput extends ServletInputStream implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("running error={} {}", error, this);
// TODO is this necessary to add here?
_servletChannel.getResponse().getHeaders().add(HttpFields.CONNECTION_CLOSE);
_servletChannel.getServletContextResponse().getHeaders().add(HttpFields.CONNECTION_CLOSE);
_readListener.onError(error);
}
else if (chunk.isLast() && !chunk.hasRemaining())

View File

@ -35,17 +35,14 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.server.ConnectionMetaData;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.SharedBlockingCallback;
import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -127,12 +124,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private static final Logger LOG = LoggerFactory.getLogger(HttpOutput.class);
private static final ThreadLocal<CharsetEncoder> _encoder = new ThreadLocal<>();
private final ConnectionMetaData _connectionMetaData;
private final ServletChannel _servletChannel;
private final Response _response;
private final ByteBufferPool _bufferPool;
private final ServletRequestState _channelState;
private final SharedBlockingCallback _writeBlocker;
private final Blocker.Shared _writeBlocker;
private ApiState _apiState = ApiState.BLOCKING;
private State _state = State.OPEN;
private boolean _softClose = false;
@ -146,15 +140,11 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private volatile Throwable _onError;
private Callback _closedCallback;
public HttpOutput(Response response, ServletChannel channel)
public HttpOutput(ServletChannel channel)
{
_response = response;
_servletChannel = channel;
_connectionMetaData = _response.getRequest().getConnectionMetaData();
_bufferPool = _response.getRequest().getComponents().getByteBufferPool();
_channelState = _servletChannel.getState();
_writeBlocker = new WriteBlocker(_servletChannel);
_channelState = _servletChannel.getServletRequestState();
_writeBlocker = new Blocker.Shared();
HttpConfiguration config = _servletChannel.getHttpConfiguration();
_bufferSize = config.getOutputBufferSize();
_commitSize = config.getOutputAggregationSize();
@ -164,11 +154,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_commitSize = _bufferSize;
}
}
public Response getResponse()
{
return _response;
}
public boolean isWritten()
{
@ -188,14 +173,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
}
protected Blocker acquireWriteBlockingCallback() throws IOException
{
return _writeBlocker.acquire();
}
private void channelWrite(ByteBuffer content, boolean complete) throws IOException
{
try (Blocker blocker = _writeBlocker.acquire())
try (Blocker.Callback blocker = _writeBlocker.callback())
{
channelWrite(content, complete, blocker);
blocker.block();
@ -206,13 +186,13 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
if (_firstByteNanoTime == -1)
{
long minDataRate = _connectionMetaData.getHttpConfiguration().getMinResponseDataRate();
long minDataRate = _servletChannel.getConnectionMetaData().getHttpConfiguration().getMinResponseDataRate();
if (minDataRate > 0)
_firstByteNanoTime = NanoTime.now();
else
_firstByteNanoTime = Long.MAX_VALUE;
}
_response.write(last, content, callback);
_servletChannel.getResponse().write(last, content, callback);
}
private void onWriteComplete(boolean last, Throwable failure)
@ -353,7 +333,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case PENDING: // an async write is pending and may complete at any time
// If this is not the last write, then we must abort
if (!_servletChannel.getResponse().isContentComplete(_written))
if (_servletChannel.getServletContextResponse().isContentIncomplete(_written))
error = new CancellationException("Completed whilst write pending");
break;
@ -369,7 +349,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (error != null)
{
_servletChannel.abort(error);
_writeBlocker.fail(error);
_state = State.CLOSED;
}
else
@ -463,7 +442,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
public void close() throws IOException
{
ByteBuffer content = null;
Blocker blocker = null;
Blocker.Callback blocker = null;
try (AutoLock l = _channelState.lock())
{
if (_onError != null)
@ -489,7 +468,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case BLOCKING:
case BLOCKED:
// block until CLOSED state reached.
blocker = _writeBlocker.acquire();
blocker = _writeBlocker.callback();
_closedCallback = Callback.combine(_closedCallback, blocker);
break;
@ -506,7 +485,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// Output is idle blocking state, but we still do an async close
_apiState = ApiState.BLOCKED;
_state = State.CLOSING;
blocker = _writeBlocker.acquire();
blocker = _writeBlocker.callback();
content = _aggregate != null && _aggregate.hasRemaining() ? _aggregate.getByteBuffer() : BufferUtil.EMPTY_BUFFER;
break;
@ -516,7 +495,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
// then trigger a close from onWriteComplete
_state = State.CLOSE;
// and block until it is complete
blocker = _writeBlocker.acquire();
blocker = _writeBlocker.callback();
_closedCallback = Callback.combine(_closedCallback, blocker);
break;
@ -550,9 +529,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return;
// Just wait for some other close to finish.
try (Blocker b = blocker)
try (Blocker.Callback cb = blocker)
{
b.block();
cb.block();
}
}
else
@ -565,7 +544,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
else
{
// Do a blocking close
try (Blocker b = blocker)
try (Blocker.Callback b = blocker)
{
channelWrite(content, true, blocker);
b.block();
@ -590,9 +569,10 @@ public class HttpOutput extends ServletOutputStream implements Runnable
private RetainableByteBuffer acquireBuffer()
{
boolean useOutputDirectByteBuffers = _connectionMetaData.getHttpConfiguration().isUseOutputDirectByteBuffers();
boolean useOutputDirectByteBuffers = _servletChannel.getConnectionMetaData().getHttpConfiguration().isUseOutputDirectByteBuffers();
ByteBufferPool pool = _servletChannel.getRequest().getComponents().getByteBufferPool();
if (_aggregate == null)
_aggregate = _bufferPool.acquire(getBufferSize(), useOutputDirectByteBuffers);
_aggregate = pool.acquire(getBufferSize(), useOutputDirectByteBuffers);
return _aggregate;
}
@ -724,7 +704,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
checkWritable();
long written = _written + len;
int space = maximizeAggregateSpace();
last = _servletChannel.getResponse().isAllContentWritten(written);
last = _servletChannel.getServletContextResponse().isAllContentWritten(written);
// Write will be aggregated if:
// + it is smaller than the commitSize
// + is not the last one, or is last but will fit in an already allocated aggregate buffer.
@ -858,7 +838,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
checkWritable();
long written = _written + len;
last = _servletChannel.getResponse().isAllContentWritten(written);
last = _servletChannel.getServletContextResponse().isAllContentWritten(written);
flush = last || len > 0 || (_aggregate != null && _aggregate.hasRemaining());
if (last && _state == State.OPEN)
@ -938,7 +918,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
checkWritable();
long written = _written + 1;
int space = maximizeAggregateSpace();
last = _servletChannel.getResponse().isAllContentWritten(written);
last = _servletChannel.getServletContextResponse().isAllContentWritten(written);
flush = last || space == 1;
if (last && _state == State.OPEN)
@ -1012,7 +992,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
s = String.valueOf(s);
String charset = _servletChannel.getResponse().getCharacterEncoding(false);
String charset = _servletChannel.getServletContextResponse().getCharacterEncoding(false);
CharsetEncoder encoder = _encoder.get();
if (encoder == null || !encoder.charset().name().equalsIgnoreCase(charset))
{
@ -1025,8 +1005,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
encoder.reset();
}
RetainableByteBuffer out = _bufferPool.acquire((int)(1 + (s.length() + 2) * encoder.averageBytesPerChar()), false);
ByteBufferPool pool = _servletChannel.getRequest().getComponents().getByteBufferPool();
RetainableByteBuffer out = pool.acquire((int)(1 + (s.length() + 2) * encoder.averageBytesPerChar()), false);
try
{
CharBuffer in = CharBuffer.wrap(s);
@ -1062,7 +1042,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
if (result.isOverflow())
{
BufferUtil.flipToFlush(byteBuffer, 0);
RetainableByteBuffer bigger = _bufferPool.acquire(out.capacity() + s.length() + 2, out.isDirect());
RetainableByteBuffer bigger = pool.acquire(out.capacity() + s.length() + 2, out.isDirect());
BufferUtil.flipToFill(bigger.getByteBuffer());
bigger.getByteBuffer().put(byteBuffer);
out.release();
@ -1107,7 +1087,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
*/
public void sendContent(InputStream in) throws IOException
{
try (Blocker blocker = _writeBlocker.acquire())
try (Blocker.Callback blocker = _writeBlocker.callback())
{
new InputStreamWritingCB(in, blocker).iterate();
blocker.block();
@ -1122,7 +1102,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
*/
public void sendContent(ReadableByteChannel in) throws IOException
{
try (Blocker blocker = _writeBlocker.acquire())
try (Blocker.Callback blocker = _writeBlocker.callback())
{
new ReadableByteChannelWritingCB(in, blocker).iterate();
blocker.block();
@ -1256,7 +1236,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
if (_firstByteNanoTime == -1 || _firstByteNanoTime == Long.MAX_VALUE)
return;
long minDataRate = _connectionMetaData.getHttpConfiguration().getMinResponseDataRate();
long minDataRate = _servletChannel.getConnectionMetaData().getHttpConfiguration().getMinResponseDataRate();
_flushed += bytes;
long minFlushed = minDataRate * NanoTime.millisSince(_firstByteNanoTime) / TimeUnit.SECONDS.toMillis(1);
if (LOG.isDebugEnabled())
@ -1276,7 +1256,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_state = State.OPEN;
_apiState = ApiState.BLOCKING;
_softClose = true; // Stay closed until next request
HttpConfiguration config = _connectionMetaData.getHttpConfiguration();
HttpConfiguration config = _servletChannel.getConnectionMetaData().getHttpConfiguration();
_bufferSize = config.getOutputBufferSize();
_commitSize = config.getOutputAggregationSize();
if (_commitSize > _bufferSize)
@ -1304,7 +1284,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
@Override
public void setWriteListener(WriteListener writeListener)
{
if (!_servletChannel.getState().isAsync())
if (!_servletChannel.getServletRequestState().isAsync())
throw new IllegalStateException("!ASYNC: " + stateString());
boolean wake;
try (AutoLock l = _channelState.lock())
@ -1313,7 +1293,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
throw new IllegalStateException("!OPEN" + stateString());
_apiState = ApiState.READY;
_writeListener = writeListener;
wake = _servletChannel.getState().onWritePossible();
wake = _servletChannel.getServletRequestState().onWritePossible();
}
if (wake)
_servletChannel.execute(_servletChannel::handle);
@ -1626,7 +1606,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
super(callback, true);
_in = in;
// Reading from InputStream requires byte[], don't use direct buffers.
_buffer = _bufferPool.acquire(getBufferSize(), false);
ByteBufferPool pool = _servletChannel.getRequest().getComponents().getByteBufferPool();
_buffer = pool.acquire(getBufferSize(), false);
}
@Override
@ -1701,8 +1682,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
super(callback, true);
_in = in;
boolean useOutputDirectByteBuffers = _connectionMetaData.getHttpConfiguration().isUseOutputDirectByteBuffers();
_buffer = _bufferPool.acquire(getBufferSize(), useOutputDirectByteBuffers);
boolean useOutputDirectByteBuffers = _servletChannel.getConnectionMetaData().getHttpConfiguration().isUseOutputDirectByteBuffers();
ByteBufferPool pool = _servletChannel.getRequest().getComponents().getByteBufferPool();
_buffer = pool.acquire(getBufferSize(), useOutputDirectByteBuffers);
}
@Override
@ -1753,16 +1735,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
}
private static class WriteBlocker extends SharedBlockingCallback
{
private final ServletChannel _channel;
private WriteBlocker(ServletChannel channel)
{
_channel = channel;
}
}
private class WriteCompleteCB implements Callback
{
@Override

View File

@ -114,7 +114,7 @@ class PushBuilderImpl implements PushBuilder
}
if (!pushPath.startsWith("/"))
pushPath = URIUtil.addPaths(_request.getContext().getContextPath(), pushPath);
pushPath = URIUtil.addPaths(_request.getServletContext().getContextPath(), pushPath);
String pushParam = null;
if (_sessionId != null)

View File

@ -56,6 +56,7 @@ import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpUpgradeHandler;
import jakarta.servlet.http.Part;
import jakarta.servlet.http.PushBuilder;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler.ServletRequestInfo;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.CookieCache;
import org.eclipse.jetty.http.CookieCompliance;
@ -77,9 +78,11 @@ import org.eclipse.jetty.server.ConnectionMetaData;
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.HttpCookieUtils;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Session;
import org.eclipse.jetty.session.AbstractSessionManager;
import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.IO;
@ -89,16 +92,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Jetty low level implementation of the ee10 {@link HttpServletRequest} object.
*
* <p>
* This provides the bridges from Servlet {@link HttpServletRequest} to the Jetty Core {@link Request} concepts (provided by the {@link ServletContextRequest})
* </p>
* The Jetty implementation of the ee10 {@link HttpServletRequest} object.
* This provides the bridge from Servlet {@link HttpServletRequest} to the Jetty Core {@link Request}
* via the {@link ServletContextRequest}.
*/
public class ServletApiRequest implements HttpServletRequest
{
private static final Logger LOG = LoggerFactory.getLogger(ServletApiRequest.class);
private final ServletContextRequest _request;
private final ServletContextRequest _servletContextRequest;
private final ServletChannel _servletChannel;
//TODO review which fields should be in ServletContextRequest
private AsyncContextState _async;
private String _characterEncoding;
@ -110,30 +112,18 @@ public class ServletApiRequest implements HttpServletRequest
private Fields _contentParameters;
private Fields _parameters;
private Fields _queryParameters;
private String _method;
private ServletMultiPartFormData.Parts _parts;
private boolean _asyncSupported = true;
protected ServletApiRequest(ServletContextRequest servletContextRequest)
{
_request = servletContextRequest;
}
public Fields getQueryParams()
{
extractQueryParameters();
return _queryParameters;
}
public Fields getContentParams()
{
extractContentParameters();
return _contentParameters;
_servletContextRequest = servletContextRequest;
_servletChannel = _servletContextRequest.getServletChannel();
}
public AuthenticationState getAuthentication()
{
return AuthenticationState.getAuthenticationState(getServletContextRequest());
return AuthenticationState.getAuthenticationState(getRequest());
}
private AuthenticationState getUndeferredAuthentication()
@ -141,11 +131,11 @@ public class ServletApiRequest implements HttpServletRequest
AuthenticationState authenticationState = getAuthentication();
if (authenticationState instanceof AuthenticationState.Deferred deferred)
{
AuthenticationState undeferred = deferred.authenticate(getServletContextRequest());
AuthenticationState undeferred = deferred.authenticate(getRequest());
if (undeferred != null && undeferred != authenticationState)
{
authenticationState = undeferred;
AuthenticationState.setAuthenticationState(getServletContextRequest(), authenticationState);
AuthenticationState.setAuthenticationState(getRequest(), authenticationState);
}
}
return authenticationState;
@ -154,45 +144,55 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public String getMethod()
{
if (_method == null)
return getServletContextRequest().getMethod();
else
return _method;
return getRequest().getMethod();
}
//TODO shouldn't really be public?
public void setMethod(String method)
/**
* @return The {@link ServletRequestInfo} view of the {@link ServletContextRequest} as wrapped
* by the {@link ServletContextHandler}.
* @see #getRequest()
*/
public ServletRequestInfo getServletRequestInfo()
{
_method = method;
return _servletContextRequest;
}
public ServletContextRequest getServletContextRequest()
/**
* @return The core {@link Request} associated with the servlet API request. This may differ
* from {@link ServletContextRequest} as wrapped by the {@link ServletContextHandler} as it
* may have been further wrapped before being passed
* to {@link ServletChannel#associate(Request, Response, Callback)}.
* @see #getServletRequestInfo()
* @see ServletChannel#associate(Request, Response, Callback)
*/
public Request getRequest()
{
return _request;
ServletChannel servletChannel = _servletChannel;
return servletChannel == null ? _servletContextRequest : servletChannel.getRequest();
}
public HttpFields getFields()
{
return _request.getHeaders();
return getRequest().getHeaders();
}
@Override
public String getRequestId()
{
return _request.getConnectionMetaData().getId() + "#" + _request.getId();
return getRequest().getConnectionMetaData().getId() + "#" + getRequest().getId();
}
@Override
public String getProtocolRequestId()
{
return _request.getId();
return getRequest().getId();
}
@Override
public ServletConnection getServletConnection()
{
// TODO cache the results
final ConnectionMetaData connectionMetaData = _request.getConnectionMetaData();
final ConnectionMetaData connectionMetaData = getRequest().getConnectionMetaData();
return new ServletConnection()
{
@Override
@ -236,15 +236,15 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public Cookie[] getCookies()
{
List<HttpCookie> httpCookies = Request.getCookies(getServletContextRequest());
List<HttpCookie> httpCookies = Request.getCookies(getRequest());
if (httpCookies.isEmpty())
return null;
if (httpCookies instanceof ServletCookieList servletCookieList)
return servletCookieList.getServletCookies();
ServletCookieList servletCookieList = new ServletCookieList(httpCookies, getServletContextRequest().getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance());
_request.setAttribute(Request.COOKIE_ATTRIBUTE, servletCookieList);
if (_request.getComponents().getCache().getAttribute(Request.CACHE_ATTRIBUTE) instanceof CookieCache cookieCache)
ServletCookieList servletCookieList = new ServletCookieList(httpCookies, getRequest().getConnectionMetaData().getHttpConfiguration().getRequestCookieCompliance());
getRequest().setAttribute(Request.COOKIE_ATTRIBUTE, servletCookieList);
if (getRequest().getComponents().getCache().getAttribute(Request.CACHE_ATTRIBUTE) instanceof CookieCache cookieCache)
cookieCache.replaceCookieList(servletCookieList);
return servletCookieList.getServletCookies();
}
@ -279,7 +279,7 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public Enumeration<String> getHeaderNames()
{
return getFields().getFieldNames();
return Collections.enumeration(getFields().getFieldNamesCollection());
}
@Override
@ -292,28 +292,28 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public String getPathInfo()
{
return _request._matchedPath.getPathInfo();
return getServletRequestInfo().getMatchedResource().getMatchedPath().getPathInfo();
}
@Override
public String getPathTranslated()
{
String pathInfo = getPathInfo();
if (pathInfo == null || _request.getContext() == null)
if (pathInfo == null || getServletRequestInfo().getServletContext() == null)
return null;
return _request.getContext().getServletContext().getRealPath(pathInfo);
return getServletRequestInfo().getServletContext().getServletContext().getRealPath(pathInfo);
}
@Override
public String getContextPath()
{
return _request.getContext().getServletContextHandler().getRequestContextPath();
return getServletRequestInfo().getServletContext().getServletContextHandler().getRequestContextPath();
}
@Override
public String getQueryString()
{
return _request.getHttpURI().getQuery();
return getRequest().getHttpURI().getQuery();
}
@Override
@ -329,7 +329,7 @@ public class ServletApiRequest implements HttpServletRequest
public boolean isUserInRole(String role)
{
//obtain any substituted role name from the destination servlet
String linkedRole = _request._mappedServlet.getServletHolder().getUserRoleLink(role);
String linkedRole = getServletRequestInfo().getMatchedResource().getResource().getServletHolder().getUserRoleLink(role);
AuthenticationState authenticationState = getUndeferredAuthentication();
if (authenticationState instanceof AuthenticationState.Succeeded succeededAuthentication)
@ -354,33 +354,34 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public String getRequestedSessionId()
{
AbstractSessionManager.RequestedSession requestedSession = _request.getRequestedSession();
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
return requestedSession == null ? null : requestedSession.sessionId();
}
@Override
public String getRequestURI()
{
HttpURI uri = _request.getHttpURI();
HttpURI uri = getRequest().getHttpURI();
return uri == null ? null : uri.getPath();
}
@Override
public StringBuffer getRequestURL()
{
return new StringBuffer(HttpURI.build(_request.getHttpURI()).query(null).asString());
// Use the ServletContextRequest here as even if changed in the Request, it must match the servletPath and pathInfo
return new StringBuffer(HttpURI.build(getRequest().getHttpURI()).query(null).asString());
}
@Override
public String getServletPath()
{
return _request._matchedPath.getPathMatch();
return getServletRequestInfo().getMatchedResource().getMatchedPath().getPathMatch();
}
@Override
public HttpSession getSession(boolean create)
{
Session session = _request.getSession(create);
Session session = getRequest().getSession(create);
if (session == null)
return null;
if (session.isNew() && getAuthentication() instanceof AuthenticationState.Succeeded)
@ -397,11 +398,11 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public String changeSessionId()
{
Session session = _request.getSession(false);
Session session = getRequest().getSession(false);
if (session == null)
throw new IllegalStateException("No session");
session.renewId(_request, _request.getResponse());
session.renewId(getRequest(), _servletChannel.getResponse());
if (getRemoteUser() != null)
session.setAttribute(ManagedSession.SESSION_CREATED_SECURE, Boolean.TRUE);
@ -412,21 +413,21 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public boolean isRequestedSessionIdValid()
{
AbstractSessionManager.RequestedSession requestedSession = _request.getRequestedSession();
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
return requestedSession != null && requestedSession.sessionId() != null && !requestedSession.sessionIdFromCookie();
}
@Override
public boolean isRequestedSessionIdFromCookie()
{
AbstractSessionManager.RequestedSession requestedSession = _request.getRequestedSession();
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
return requestedSession != null && requestedSession.sessionId() != null && requestedSession.sessionIdFromCookie();
}
@Override
public boolean isRequestedSessionIdFromURL()
{
AbstractSessionManager.RequestedSession requestedSession = _request.getRequestedSession();
AbstractSessionManager.RequestedSession requestedSession = getServletRequestInfo().getRequestedSession();
return requestedSession != null && requestedSession.sessionId() != null && !requestedSession.sessionIdFromCookie();
}
@ -462,8 +463,9 @@ public class ServletApiRequest implements HttpServletRequest
{
try
{
ServletRequestInfo servletRequestInfo = getServletRequestInfo();
AuthenticationState.Succeeded succeededAuthentication = AuthenticationState.login(
username, password, getServletContextRequest(), getServletContextRequest().getResponse());
username, password, getRequest(), servletRequestInfo.getServletChannel().getServletContextResponse());
if (succeededAuthentication == null)
throw new QuietException.Exception("Authentication failed for username '" + username + "'");
@ -477,7 +479,8 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public void logout() throws ServletException
{
if (!AuthenticationState.logout(getServletContextRequest(), getServletContextRequest().getResponse()))
ServletRequestInfo servletRequestInfo = getServletRequestInfo();
if (!AuthenticationState.logout(getRequest(), servletRequestInfo.getServletChannel().getServletContextResponse()))
throw new ServletException("logout failed");
}
@ -494,7 +497,7 @@ public class ServletApiRequest implements HttpServletRequest
if (config == null)
throw new IllegalStateException("No multipart config for servlet");
ServletContextHandler contextHandler = _request.getContext().getServletContextHandler();
ServletContextHandler contextHandler = getServletRequestInfo().getServletContext().getServletContextHandler();
int maxFormContentSize = contextHandler.getMaxFormContentSize();
int maxFormKeys = contextHandler.getMaxFormKeys();
@ -574,10 +577,10 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public PushBuilder newPushBuilder()
{
if (!_request.getConnectionMetaData().isPushSupported())
if (!getRequest().getConnectionMetaData().isPushSupported())
return null;
HttpFields.Mutable pushHeaders = HttpFields.build(_request.getHeaders(), EnumSet.of(
HttpFields.Mutable pushHeaders = HttpFields.build(getRequest().getHeaders(), EnumSet.of(
HttpHeader.IF_MATCH,
HttpHeader.IF_RANGE,
HttpHeader.IF_UNMODIFIED_SINCE,
@ -594,7 +597,7 @@ public class ServletApiRequest implements HttpServletRequest
pushHeaders.put(HttpHeader.REFERER, referrer);
// Any Set-Cookie in the response should be present in the push.
HttpFields.Mutable responseHeaders = _request.getResponse().getHeaders();
HttpFields.Mutable responseHeaders = _servletChannel.getResponse().getHeaders();
List<String> setCookies = new ArrayList<>(responseHeaders.getValuesList(HttpHeader.SET_COOKIE));
setCookies.addAll(responseHeaders.getValuesList(HttpHeader.SET_COOKIE2));
String cookies = pushHeaders.get(HttpHeader.COOKIE);
@ -642,7 +645,7 @@ public class ServletApiRequest implements HttpServletRequest
sessionId = getRequestedSessionId();
}
return new PushBuilderImpl(_request, pushHeaders, sessionId);
return new PushBuilderImpl(ServletContextRequest.getServletContextRequest(this), pushHeaders, sessionId);
}
@Override
@ -659,17 +662,17 @@ public class ServletApiRequest implements HttpServletRequest
case AsyncContext.ASYNC_PATH_INFO -> getPathInfo();
case AsyncContext.ASYNC_QUERY_STRING -> getQueryString();
case AsyncContext.ASYNC_MAPPING -> getHttpServletMapping();
default -> _request.getAttribute(name);
default -> getRequest().getAttribute(name);
};
}
return _request.getAttribute(name);
return getRequest().getAttribute(name);
}
@Override
public Enumeration<String> getAttributeNames()
{
Set<String> set = _request.getAttributeNameSet();
Set<String> set = getRequest().getAttributeNameSet();
if (_async != null)
{
set = new HashSet<>(set);
@ -689,8 +692,8 @@ public class ServletApiRequest implements HttpServletRequest
{
if (_characterEncoding == null)
{
if (_request.getContext() != null)
_characterEncoding = _request.getContext().getServletContext().getRequestCharacterEncoding();
if (getRequest().getContext() != null)
_characterEncoding = getServletRequestInfo().getServletContext().getServletContext().getRequestCharacterEncoding();
if (_characterEncoding == null)
{
@ -765,10 +768,10 @@ public class ServletApiRequest implements HttpServletRequest
throw new IllegalStateException("READER");
_inputState = ServletContextRequest.INPUT_STREAM;
if (_request.getServletChannel().isExpecting100Continue())
_request.getServletChannel().continue100(_request.getHttpInput().available());
if (getServletRequestInfo().getServletChannel().isExpecting100Continue())
getServletRequestInfo().getServletChannel().continue100(getServletRequestInfo().getHttpInput().available());
return _request.getHttpInput();
return getServletRequestInfo().getHttpInput();
}
@Override
@ -798,20 +801,6 @@ public class ServletApiRequest implements HttpServletRequest
return Collections.unmodifiableMap(getParameters().toStringArrayMap());
}
public Fields getContentParameters()
{
getParameters(); // ensure extracted
return _contentParameters;
}
public void setContentParameters(Fields params)
{
if (params == null || params.getSize() == 0)
_contentParameters = ServletContextRequest.NO_PARAMS;
else
_contentParameters = params;
}
private Fields getParameters()
{
extractContentParameters();
@ -855,13 +844,14 @@ public class ServletApiRequest implements HttpServletRequest
{
String baseType = HttpField.valueParameters(getContentType(), null);
if (MimeTypes.Type.FORM_ENCODED.is(baseType) &&
_request.getConnectionMetaData().getHttpConfiguration().isFormEncodedMethod(getMethod()))
getRequest().getConnectionMetaData().getHttpConfiguration().isFormEncodedMethod(getMethod()))
{
try
{
int maxKeys = _request.getServletRequestState().getContextHandler().getMaxFormKeys();
int maxContentSize = _request.getServletRequestState().getContextHandler().getMaxFormContentSize();
_contentParameters = FormFields.from(getServletContextRequest(), maxKeys, maxContentSize).get();
ServletContextHandler contextHandler = getServletRequestInfo().getServletContextHandler();
int maxKeys = contextHandler.getMaxFormKeys();
int maxContentSize = contextHandler.getMaxFormContentSize();
_contentParameters = FormFields.from(getRequest(), maxKeys, maxContentSize).get();
}
catch (IllegalStateException | IllegalArgumentException | ExecutionException |
InterruptedException e)
@ -889,7 +879,7 @@ public class ServletApiRequest implements HttpServletRequest
{
try
{
_contentParameters = FormFields.get(getServletContextRequest()).get();
_contentParameters = FormFields.get(getRequest()).get();
}
catch (IllegalStateException | IllegalArgumentException | ExecutionException |
InterruptedException e)
@ -918,14 +908,14 @@ public class ServletApiRequest implements HttpServletRequest
// and may have already been extracted by mergeQueryParameters().
if (_queryParameters == null)
{
HttpURI httpURI = _request.getHttpURI();
HttpURI httpURI = getRequest().getHttpURI();
if (httpURI == null || StringUtil.isEmpty(httpURI.getQuery()))
_queryParameters = ServletContextRequest.NO_PARAMS;
else
{
try
{
_queryParameters = Request.extractQueryParameters(_request, _request.getQueryEncoding());
_queryParameters = Request.extractQueryParameters(getRequest(), getServletRequestInfo().getQueryEncoding());
}
catch (IllegalStateException | IllegalArgumentException e)
{
@ -939,19 +929,19 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public String getProtocol()
{
return _request.getConnectionMetaData().getProtocol();
return getRequest().getConnectionMetaData().getProtocol();
}
@Override
public String getScheme()
{
return _request.getHttpURI().getScheme();
return getRequest().getHttpURI().getScheme();
}
@Override
public String getServerName()
{
HttpURI uri = _request.getHttpURI();
HttpURI uri = getRequest().getHttpURI();
if ((uri != null) && StringUtil.isNotBlank(uri.getAuthority()))
return formatAddrOrHost(uri.getHost());
else
@ -960,13 +950,13 @@ public class ServletApiRequest implements HttpServletRequest
private String formatAddrOrHost(String name)
{
ServletChannel servletChannel = _request.getServletChannel();
ServletChannel servletChannel = _servletChannel;
return servletChannel == null ? HostPort.normalizeHost(name) : servletChannel.formatAddrOrHost(name);
}
private String findServerName()
{
ServletChannel servletChannel = _request.getServletChannel();
ServletChannel servletChannel = _servletChannel;
if (servletChannel != null)
{
HostPort serverAuthority = servletChannel.getServerAuthority();
@ -985,9 +975,9 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public int getServerPort()
{
int port = -1;
int port;
HttpURI uri = _request.getHttpURI();
HttpURI uri = getRequest().getHttpURI();
if ((uri != null) && StringUtil.isNotBlank(uri.getAuthority()))
port = uri.getPort();
else
@ -1003,7 +993,7 @@ public class ServletApiRequest implements HttpServletRequest
private int findServerPort()
{
ServletChannel servletChannel = _request.getServletChannel();
ServletChannel servletChannel = getServletRequestInfo().getServletChannel();
if (servletChannel != null)
{
HostPort serverAuthority = servletChannel.getServerAuthority();
@ -1043,9 +1033,9 @@ public class ServletApiRequest implements HttpServletRequest
}
};
}
else if (_request.getServletChannel().isExpecting100Continue())
else if (getServletRequestInfo().getServletChannel().isExpecting100Continue())
{
_request.getServletChannel().continue100(_request.getHttpInput().available());
getServletRequestInfo().getServletChannel().continue100(getServletRequestInfo().getHttpInput().available());
}
_inputState = ServletContextRequest.INPUT_READER;
return _reader;
@ -1054,28 +1044,28 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public String getRemoteAddr()
{
return Request.getRemoteAddr(_request);
return Request.getRemoteAddr(getRequest());
}
@Override
public String getRemoteHost()
{
// TODO: review.
return Request.getRemoteAddr(_request);
return Request.getRemoteAddr(getRequest());
}
@Override
public void setAttribute(String name, Object attribute)
{
Object oldValue = _request.setAttribute(name, attribute);
Object oldValue = getRequest().setAttribute(name, attribute);
if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
_request.setQueryEncoding(attribute == null ? null : attribute.toString());
getServletRequestInfo().setQueryEncoding(attribute == null ? null : attribute.toString());
if (!_request.getRequestAttributeListeners().isEmpty())
if (!getServletRequestInfo().getRequestAttributeListeners().isEmpty())
{
final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_request.getContext().getServletContext(), this, name, oldValue == null ? attribute : oldValue);
for (ServletRequestAttributeListener l : _request.getRequestAttributeListeners())
final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(getServletRequestInfo().getServletContext().getServletContext(), this, name, oldValue == null ? attribute : oldValue);
for (ServletRequestAttributeListener l : getServletRequestInfo().getRequestAttributeListeners())
{
if (oldValue == null)
l.attributeAdded(event);
@ -1090,12 +1080,12 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public void removeAttribute(String name)
{
Object oldValue = _request.removeAttribute(name);
Object oldValue = getRequest().removeAttribute(name);
if (oldValue != null && !_request.getRequestAttributeListeners().isEmpty())
if (oldValue != null && !getServletRequestInfo().getRequestAttributeListeners().isEmpty())
{
final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_request.getContext().getServletContext(), this, name, oldValue);
for (ServletRequestAttributeListener listener : _request.getRequestAttributeListeners())
final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(getServletRequestInfo().getServletContext().getServletContext(), this, name, oldValue);
for (ServletRequestAttributeListener listener : getServletRequestInfo().getRequestAttributeListeners())
{
listener.attributeRemoved(event);
}
@ -1105,32 +1095,32 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public Locale getLocale()
{
return Request.getLocales(_request).get(0);
return Request.getLocales(getRequest()).get(0);
}
@Override
public Enumeration<Locale> getLocales()
{
return Collections.enumeration(Request.getLocales(_request));
return Collections.enumeration(Request.getLocales(getRequest()));
}
@Override
public boolean isSecure()
{
return _request.getConnectionMetaData().isSecure();
return getRequest().getConnectionMetaData().isSecure();
}
@Override
public RequestDispatcher getRequestDispatcher(String path)
{
ServletContextHandler.ServletScopedContext context = _request.getContext();
ServletContextHandler.ServletScopedContext context = getServletRequestInfo().getServletContext();
if (path == null || context == null)
return null;
// handle relative path
if (!path.startsWith("/"))
{
String relTo = _request.getDecodedPathInContext();
String relTo = getServletRequestInfo().getDecodedPathInContext();
int slash = relTo.lastIndexOf("/");
if (slash > 1)
relTo = relTo.substring(0, slash + 1);
@ -1145,13 +1135,13 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public int getRemotePort()
{
return Request.getRemotePort(_request);
return Request.getRemotePort(getRequest());
}
@Override
public String getLocalName()
{
ServletChannel servletChannel = _request.getServletChannel();
ServletChannel servletChannel = getServletRequestInfo().getServletChannel();
if (servletChannel != null)
{
String localName = servletChannel.getLocalName();
@ -1164,19 +1154,19 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public String getLocalAddr()
{
return Request.getLocalAddr(_request);
return Request.getLocalAddr(getRequest());
}
@Override
public int getLocalPort()
{
return Request.getLocalPort(_request);
return Request.getLocalPort(getRequest());
}
@Override
public ServletContext getServletContext()
{
return _request.getServletChannel().getServletContext();
return getServletRequestInfo().getServletChannel().getServletContextApi();
}
@Override
@ -1184,10 +1174,11 @@ public class ServletApiRequest implements HttpServletRequest
{
if (!isAsyncSupported())
throw new IllegalStateException("Async Not Supported");
ServletRequestState state = _request.getState();
ServletRequestState state = getServletRequestInfo().getState();
if (_async == null)
_async = new AsyncContextState(state);
AsyncContextEvent event = new AsyncContextEvent(_request.getContext(), _async, state, this, _request.getResponse().getHttpServletResponse());
ServletRequestInfo servletRequestInfo = getServletRequestInfo();
AsyncContextEvent event = new AsyncContextEvent(getServletRequestInfo().getServletContext(), _async, state, this, servletRequestInfo.getServletChannel().getServletContextResponse().getServletApiResponse());
state.startAsync(event);
return _async;
}
@ -1197,10 +1188,10 @@ public class ServletApiRequest implements HttpServletRequest
{
if (!isAsyncSupported())
throw new IllegalStateException("Async Not Supported");
ServletRequestState state = _request.getState();
ServletRequestState state = getServletRequestInfo().getState();
if (_async == null)
_async = new AsyncContextState(state);
AsyncContextEvent event = new AsyncContextEvent(_request.getContext(), _async, state, servletRequest, servletResponse);
AsyncContextEvent event = new AsyncContextEvent(getServletRequestInfo().getServletContext(), _async, state, servletRequest, servletResponse);
state.startAsync(event);
return _async;
}
@ -1208,13 +1199,13 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public HttpServletMapping getHttpServletMapping()
{
return _request._mappedServlet.getServletPathMapping(_request.getDecodedPathInContext());
return getServletRequestInfo().getMatchedResource().getResource().getServletPathMapping(getServletRequestInfo().getDecodedPathInContext());
}
@Override
public boolean isAsyncStarted()
{
return _request.getState().isAsyncStarted();
return getServletRequestInfo().getState().isAsyncStarted();
}
@Override
@ -1231,7 +1222,7 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public AsyncContext getAsyncContext()
{
ServletRequestState state = _request.getServletChannel().getState();
ServletRequestState state = getServletRequestInfo().getServletChannel().getServletRequestState();
if (_async == null || !state.isAsyncStarted())
throw new IllegalStateException(state.getStatusString());
@ -1247,7 +1238,7 @@ public class ServletApiRequest implements HttpServletRequest
@Override
public Map<String, String> getTrailerFields()
{
HttpFields trailers = _request.getTrailers();
HttpFields trailers = getRequest().getTrailers();
if (trailers == null)
return Map.of();
Map<String, String> trailersMap = new HashMap<>();

View File

@ -15,7 +15,6 @@ package org.eclipse.jetty.ee10.servlet;
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;
@ -27,30 +26,29 @@ import java.util.function.Supplier;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler.ServletRequestInfo;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler.ServletResponseInfo;
import org.eclipse.jetty.ee10.servlet.writer.EncodingHttpWriter;
import org.eclipse.jetty.ee10.servlet.writer.Iso88591HttpWriter;
import org.eclipse.jetty.ee10.servlet.writer.ResponseWriter;
import org.eclipse.jetty.ee10.servlet.writer.Utf8HttpWriter;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.SessionManager;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
/**
* The Jetty low level implementation of the ee10 {@link HttpServletResponse} object.
*
* <p>
* This provides the bridges from Servlet {@link HttpServletResponse} to the Jetty Core {@link Response} concepts (provided by the {@link ServletContextResponse})
* </p>
* The Jetty implementation of the ee10 {@link HttpServletResponse} object.
* This provides the bridge from the Servlet {@link HttpServletResponse} to the Jetty Core {@link Response}
* via the {@link ServletContextResponse}.
*/
public class ServletApiResponse implements HttpServletResponse
{
@ -62,16 +60,45 @@ public class ServletApiResponse implements HttpServletResponse
ServletContextResponse.EncodingFrom.SET_LOCALE
);
private final ServletContextResponse _response;
private final ServletChannel _servletChannel;
private final ServletContextHandler.ServletRequestInfo _servletRequestInfo;
private final ServletResponseInfo _servletResponseInfo;
protected ServletApiResponse(ServletContextResponse response)
protected ServletApiResponse(ServletContextResponse servletContextResponse)
{
_response = response;
_servletChannel = servletContextResponse.getServletContextRequest().getServletChannel();
_servletRequestInfo = servletContextResponse.getServletContextRequest();
_servletResponseInfo = servletContextResponse;
}
public ServletContextResponse getResponse()
public ServletChannel getServletChannel()
{
return _response;
return _servletChannel;
}
public ServletRequestInfo getServletRequestInfo()
{
return _servletRequestInfo;
}
/**
* @return The {@link ServletResponseInfo} for the request as provided by
* {@link ServletContextResponse} when wrapped by the {@link ServletContextHandler}.
*/
public ServletResponseInfo getServletResponseInfo()
{
return _servletResponseInfo;
}
/**
* @return The core {@link Response} associated with the API response.
* This may differ from the {@link ServletContextResponse} as wrapped by the
* {@link ServletContextHandler} as it may have subsequently been wrapped before
* being passed to {@link ServletChannel#associate(Request, Response, Callback)}.
*/
public Response getResponse()
{
return getServletChannel().getResponse();
}
@Override
@ -85,22 +112,23 @@ public class ServletApiResponse implements HttpServletResponse
public void addCookie(HttpCookie cookie)
{
Response.addCookie(_response, cookie);
Response.addCookie(getResponse(), cookie);
}
@Override
public boolean containsHeader(String name)
{
return _response.getHeaders().contains(name);
return getResponse().getHeaders().contains(name);
}
@Override
public String encodeURL(String url)
{
SessionManager sessionManager = _response.getServletContextRequest().getServletChannel().getContextHandler().getSessionHandler();
SessionManager sessionManager = getServletChannel().getServletContextHandler().getSessionHandler();
if (sessionManager == null)
return url;
return sessionManager.encodeURI(_response.getServletContextRequest(), url, getResponse().getServletContextRequest().getServletApiRequest().isRequestedSessionIdFromCookie());
return sessionManager.encodeURI(getServletChannel().getRequest(), url,
getServletChannel().getServletContextRequest().getServletApiRequest().isRequestedSessionIdFromCookie());
}
@Override
@ -114,14 +142,14 @@ public class ServletApiResponse implements HttpServletResponse
{
switch (sc)
{
case -1 -> _response.getServletContextRequest().getServletChannel().abort(new IOException(msg));
case -1 -> getServletChannel().abort(new IOException(msg));
case HttpStatus.PROCESSING_102, HttpStatus.EARLY_HINT_103 ->
{
if (!isCommitted())
{
try (Blocker.Callback blocker = Blocker.callback())
{
CompletableFuture<Void> completable = _response.writeInterim(sc, _response.getHeaders().asImmutable());
CompletableFuture<Void> completable = getServletChannel().getServletContextResponse().writeInterim(sc, getResponse().getHeaders().asImmutable());
blocker.completeWith(completable);
blocker.block();
}
@ -131,7 +159,7 @@ public class ServletApiResponse implements HttpServletResponse
{
if (isCommitted())
throw new IllegalStateException("Committed");
_response.getState().sendError(sc, msg);
getServletRequestInfo().getServletRequestState().sendError(sc, msg);
}
}
}
@ -160,7 +188,7 @@ public class ServletApiResponse implements HttpServletResponse
resetBuffer();
try (Blocker.Callback callback = Blocker.callback())
{
Response.sendRedirect(_response.getServletContextRequest(), _response, callback, code, location, false);
Response.sendRedirect(getServletRequestInfo().getRequest(), getResponse(), callback, code, location, false);
callback.block();
}
}
@ -168,25 +196,25 @@ public class ServletApiResponse implements HttpServletResponse
@Override
public void setDateHeader(String name, long date)
{
_response.getHeaders().putDate(name, date);
getResponse().getHeaders().putDate(name, date);
}
@Override
public void addDateHeader(String name, long date)
{
_response.getHeaders().addDateField(name, date);
getResponse().getHeaders().addDateField(name, date);
}
@Override
public void setHeader(String name, String value)
{
_response.getHeaders().put(name, value);
getResponse().getHeaders().put(name, value);
}
@Override
public void addHeader(String name, String value)
{
_response.getHeaders().add(name, value);
getResponse().getHeaders().add(name, value);
}
@Override
@ -194,7 +222,7 @@ public class ServletApiResponse implements HttpServletResponse
{
// TODO do we need int versions?
if (!isCommitted())
_response.getHeaders().put(name, value);
getResponse().getHeaders().put(name, value);
}
@Override
@ -202,136 +230,99 @@ public class ServletApiResponse implements HttpServletResponse
{
// TODO do we need a native version?
if (!isCommitted())
_response.getHeaders().add(name, Integer.toString(value));
getResponse().getHeaders().add(name, Integer.toString(value));
}
@Override
public void setStatus(int sc)
{
_response.setStatus(sc);
getResponse().setStatus(sc);
}
@Override
public int getStatus()
{
return _response.getStatus();
return getResponse().getStatus();
}
@Override
public String getHeader(String name)
{
return _response.getHeaders().get(name);
return getResponse().getHeaders().get(name);
}
@Override
public Collection<String> getHeaders(String name)
{
return _response.getHeaders().getValuesList(name);
return getResponse().getHeaders().getValuesList(name);
}
@Override
public Collection<String> getHeaderNames()
{
return _response.getHeaders().getFieldNamesCollection();
return getResponse().getHeaders().getFieldNamesCollection();
}
@Override
public String getCharacterEncoding()
{
return _response.getCharacterEncoding(false);
return getServletResponseInfo().getCharacterEncoding(false);
}
@Override
public String getContentType()
{
return _response.getContentType();
return getServletResponseInfo().getContentType();
}
@Override
public ServletOutputStream getOutputStream() throws IOException
{
if (_response.getOutputType() == ServletContextResponse.OutputType.WRITER)
if (getServletResponseInfo().getOutputType() == ServletContextResponse.OutputType.WRITER)
throw new IllegalStateException("WRITER");
_response.setOutputType(ServletContextResponse.OutputType.STREAM);
return _response.getHttpOutput();
getServletResponseInfo().setOutputType(ServletContextResponse.OutputType.STREAM);
return getServletChannel().getHttpOutput();
}
@Override
public PrintWriter getWriter() throws IOException
{
if (_response.getOutputType() == ServletContextResponse.OutputType.STREAM)
if (getServletResponseInfo().getOutputType() == ServletContextResponse.OutputType.STREAM)
throw new IllegalStateException("STREAM");
if (_response.getOutputType() == ServletContextResponse.OutputType.NONE)
ResponseWriter writer = getServletResponseInfo().getWriter();
if (getServletResponseInfo().getOutputType() == ServletContextResponse.OutputType.NONE)
{
String encoding = _response.getCharacterEncoding(true);
String encoding = getServletResponseInfo().getCharacterEncoding(true);
Locale locale = getLocale();
if (_response.getWriter() != null && _response.getWriter().isFor(locale, encoding))
_response.getWriter().reopen();
if (writer != null && writer.isFor(locale, encoding))
writer.reopen();
else
{
if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
_response.setWriter(new ResponseWriter(new Iso88591HttpWriter(_response.getHttpOutput()), locale, encoding));
getServletResponseInfo().setWriter(writer = new ResponseWriter(new Iso88591HttpWriter(getServletChannel().getHttpOutput()), locale, encoding));
else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
_response.setWriter(new ResponseWriter(new Utf8HttpWriter(_response.getHttpOutput()), locale, encoding));
getServletResponseInfo().setWriter(writer = new ResponseWriter(new Utf8HttpWriter(getServletChannel().getHttpOutput()), locale, encoding));
else
_response.setWriter(new ResponseWriter(new EncodingHttpWriter(_response.getHttpOutput(), encoding), locale, encoding));
getServletResponseInfo().setWriter(writer = new ResponseWriter(new EncodingHttpWriter(getServletChannel().getHttpOutput(), encoding), locale, encoding));
}
// Set the output type at the end, because setCharacterEncoding() checks for it.
_response.setOutputType(ServletContextResponse.OutputType.WRITER);
getServletResponseInfo().setOutputType(ServletContextResponse.OutputType.WRITER);
}
return _response.getWriter();
return writer;
}
@Override
public void setCharacterEncoding(String encoding)
{
_response.setCharacterEncoding(encoding, ServletContextResponse.EncodingFrom.SET_CHARACTER_ENCODING);
getServletResponseInfo().setCharacterEncoding(encoding, ServletContextResponse.EncodingFrom.SET_CHARACTER_ENCODING);
}
@Override
public void setContentLength(int len)
{
// Protect from setting after committed as default handling
// of a servlet HEAD request ALWAYS sets _content length, even
// if the getHandling committed the response!
if (isCommitted())
return;
if (len > 0)
{
long written = _response.getHttpOutput().getWritten();
if (written > len)
throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
_response.setContentLength(len);
_response.getHeaders().put(HttpHeader.CONTENT_LENGTH, len);
if (_response.isAllContentWritten(written))
{
try
{
_response.closeOutput();
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
}
}
else if (len == 0)
{
long written = _response.getHttpOutput().getWritten();
if (written > 0)
throw new IllegalArgumentException("setContentLength(0) when already written " + written);
_response.setContentLength(len);
_response.getHeaders().put(HttpFields.CONTENT_LENGTH_0);
}
else
{
_response.setContentLength(len);
_response.getHeaders().remove(HttpHeader.CONTENT_LENGTH);
}
setContentLengthLong(len);
}
@Override
@ -342,8 +333,13 @@ public class ServletApiResponse implements HttpServletResponse
// if the getHandling committed the response!
if (isCommitted())
return;
_response.setContentLength(len);
_response.getHeaders().put(HttpHeader.CONTENT_LENGTH, len);
if (len > 0)
getResponse().getHeaders().put(HttpHeader.CONTENT_LENGTH, len);
else if (len == 0)
getResponse().getHeaders().put(HttpFields.CONTENT_LENGTH_0);
else
getResponse().getHeaders().remove(HttpHeader.CONTENT_LENGTH);
}
@Override
@ -354,70 +350,20 @@ public class ServletApiResponse implements HttpServletResponse
if (contentType == null)
{
if (_response.isWriting() && _response.getCharacterEncoding() != null)
throw new IllegalSelectorException();
if (getServletResponseInfo().isWriting() && getServletResponseInfo().getCharacterEncoding() != null)
throw new IllegalStateException();
if (_response.getLocale() == null)
_response.setCharacterEncoding(null);
_response.setMimeType(null);
_response.setContentType(null);
_response.getHeaders().remove(HttpHeader.CONTENT_TYPE);
getResponse().getHeaders().remove(HttpHeader.CONTENT_TYPE);
}
else
{
_response.setContentType(contentType);
_response.setMimeType(MimeTypes.CACHE.get(contentType));
String charset = MimeTypes.getCharsetFromContentType(contentType);
if (charset == null && _response.getMimeType() != null && _response.getMimeType().isCharsetAssumed())
charset = _response.getMimeType().getCharsetString();
if (charset == null)
{
switch (_response.getEncodingFrom())
{
case NOT_SET:
break;
case DEFAULT:
case INFERRED:
case SET_CONTENT_TYPE:
case SET_LOCALE:
case SET_CHARACTER_ENCODING:
{
_response.setContentType(contentType + ";charset=" + _response.getCharacterEncoding());
_response.setMimeType(MimeTypes.CACHE.get(_response.getContentType()));
break;
}
default:
throw new IllegalStateException(_response.getEncodingFrom().toString());
}
}
else if (_response.isWriting() && !charset.equalsIgnoreCase(_response.getCharacterEncoding()))
{
// too late to change the character encoding;
_response.setContentType(MimeTypes.getContentTypeWithoutCharset(_response.getContentType()));
if (_response.getCharacterEncoding() != null && (_response.getMimeType() == null || !_response.getMimeType().isCharsetAssumed()))
_response.setContentType(_response.getContentType() + ";charset=" + _response.getCharacterEncoding());
_response.setMimeType(MimeTypes.CACHE.get(_response.getContentType()));
}
else
{
_response.setRawCharacterEncoding(charset, ServletContextResponse.EncodingFrom.SET_CONTENT_TYPE);
}
if (HttpGenerator.__STRICT || _response.getMimeType() == null)
_response.getHeaders().put(HttpHeader.CONTENT_TYPE, _response.getContentType());
else
{
_response.setContentType(_response.getMimeType().asString());
_response.getHeaders().put(_response.getMimeType().getContentTypeField());
}
getResponse().getHeaders().put(HttpHeader.CONTENT_TYPE, contentType);
}
}
public long getContentCount()
{
return _response.getHttpOutput().getWritten();
return getServletChannel().getHttpOutput().getWritten();
}
@Override
@ -429,20 +375,20 @@ public class ServletApiResponse implements HttpServletResponse
throw new IllegalStateException("cannot set buffer size after response has " + getContentCount() + " bytes already written");
if (size < MIN_BUFFER_SIZE)
size = MIN_BUFFER_SIZE;
_response.getHttpOutput().setBufferSize(size);
getServletChannel().getHttpOutput().setBufferSize(size);
}
@Override
public int getBufferSize()
{
return _response.getHttpOutput().getBufferSize();
return getServletChannel().getHttpOutput().getBufferSize();
}
@Override
public void flushBuffer() throws IOException
{
if (!_response.getHttpOutput().isClosed())
_response.getHttpOutput().flush();
if (!getServletChannel().getHttpOutput().isClosed())
getServletChannel().getHttpOutput().flush();
}
@Override
@ -450,17 +396,17 @@ public class ServletApiResponse implements HttpServletResponse
{
if (isCommitted())
throw new IllegalStateException("Committed");
_response.getHttpOutput().resetBuffer();
_response.getHttpOutput().reopen();
getServletChannel().getHttpOutput().resetBuffer();
getServletChannel().getHttpOutput().reopen();
}
@Override
public boolean isCommitted()
{
// If we are in sendError state, we pretend to be committed
if (_response.getServletContextRequest().getServletChannel().isSendError())
if (getServletChannel().isSendError())
return true;
return _response.getServletContextRequest().getServletChannel().isCommitted();
return getServletChannel().isCommitted();
}
@Override
@ -469,14 +415,13 @@ public class ServletApiResponse implements HttpServletResponse
if (isCommitted())
throw new IllegalStateException("Committed");
_response.reset();
getResponse().reset();
ServletApiRequest servletApiRequest = _response.getServletContextRequest().getServletApiRequest();
ManagedSession session = servletApiRequest.getServletContextRequest().getManagedSession();
ServletApiRequest servletApiRequest = getServletChannel().getServletContextRequest().getServletApiRequest();
ManagedSession session = servletApiRequest.getServletRequestInfo().getManagedSession();
if (session != null && session.isNew())
{
SessionManager sessionManager = servletApiRequest.getServletContextRequest().getSessionManager();
SessionManager sessionManager = servletApiRequest.getServletRequestInfo().getSessionManager();
if (sessionManager != null)
{
HttpCookie cookie = sessionManager.getSessionCookie(session, servletApiRequest.getServletConnection().isSecure());
@ -494,41 +439,41 @@ public class ServletApiResponse implements HttpServletResponse
if (locale == null)
{
_response.setLocale(null);
_response.getHeaders().remove(HttpHeader.CONTENT_LANGUAGE);
if (_response.getEncodingFrom() == ServletContextResponse.EncodingFrom.SET_LOCALE)
_response.setCharacterEncoding(null, ServletContextResponse.EncodingFrom.NOT_SET);
getServletResponseInfo().setLocale(null);
getResponse().getHeaders().remove(HttpHeader.CONTENT_LANGUAGE);
if (getServletResponseInfo().getEncodingFrom() == ServletContextResponse.EncodingFrom.SET_LOCALE)
getServletResponseInfo().setCharacterEncoding(null, ServletContextResponse.EncodingFrom.NOT_SET);
}
else
{
_response.setLocale(locale);
_response.getHeaders().put(HttpHeader.CONTENT_LANGUAGE, StringUtil.replace(locale.toString(), '_', '-'));
getServletResponseInfo().setLocale(locale);
getResponse().getHeaders().put(HttpHeader.CONTENT_LANGUAGE, StringUtil.replace(locale.toString(), '_', '-'));
if (_response.getOutputType() != ServletContextResponse.OutputType.NONE)
if (getServletResponseInfo().getOutputType() != ServletContextResponse.OutputType.NONE)
return;
ServletContextHandler.ServletScopedContext context = _response.getServletContextRequest().getServletChannel().getContext();
ServletContextHandler.ServletScopedContext context = getServletChannel().getContext();
if (context == null)
return;
String charset = context.getServletContextHandler().getLocaleEncoding(locale);
if (!StringUtil.isEmpty(charset) && LOCALE_OVERRIDE.contains(_response.getEncodingFrom()))
_response.setCharacterEncoding(charset, ServletContextResponse.EncodingFrom.SET_LOCALE);
if (!StringUtil.isEmpty(charset) && LOCALE_OVERRIDE.contains(getServletResponseInfo().getEncodingFrom()))
getServletResponseInfo().setCharacterEncoding(charset, ServletContextResponse.EncodingFrom.SET_LOCALE);
}
}
@Override
public Locale getLocale()
{
if (_response.getLocale() == null)
if (getServletResponseInfo().getLocale() == null)
return Locale.getDefault();
return _response.getLocale();
return getServletResponseInfo().getLocale();
}
@Override
public Supplier<Map<String, String>> getTrailerFields()
{
return _response.getTrailers();
return getServletResponseInfo().getTrailers();
}
@Override
@ -536,13 +481,12 @@ public class ServletApiResponse implements HttpServletResponse
{
if (isCommitted())
throw new IllegalStateException("Committed");
HttpVersion version = HttpVersion.fromString(_response.getServletContextRequest().getConnectionMetaData().getProtocol());
HttpVersion version = HttpVersion.fromString(getServletRequestInfo().getRequest().getConnectionMetaData().getProtocol());
if (version == null || version.compareTo(HttpVersion.HTTP_1_1) < 0)
throw new IllegalStateException("Trailers not supported in " + version);
_response.setTrailers(trailers);
_response.setTrailersSupplier(() ->
getServletResponseInfo().setTrailers(trailers);
getResponse().setTrailersSupplier(() ->
{
Map<String, String> map = trailers.get();
if (map == null)
@ -556,6 +500,12 @@ public class ServletApiResponse implements HttpServletResponse
});
}
@Override
public String toString()
{
return "%s@%x{%s,%s}".formatted(this.getClass().getSimpleName(), hashCode(), getResponse(), getServletResponseInfo());
}
static class HttpCookieFacade implements HttpCookie
{
private final Cookie _cookie;
@ -640,7 +590,7 @@ public class ServletApiResponse implements HttpServletResponse
@Override
public boolean equals(Object obj)
{
return HttpCookie.equals(this, obj);
return obj instanceof HttpCookie && HttpCookie.equals(this, obj);
}
@Override

View File

@ -16,7 +16,6 @@ package org.eclipse.jetty.ee10.servlet;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
@ -31,13 +30,15 @@ import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ConnectionMetaData;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.ResponseUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextRequest;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ExceptionUtil;
@ -56,6 +57,13 @@ import static org.eclipse.jetty.util.thread.Invocable.InvocationType.NON_BLOCKIN
* output according to the servlet specification. The combined state so obtained
* is reflected in the behaviour of the contained {@link HttpInput} implementation of
* {@link jakarta.servlet.ServletInputStream}.
* <p>
* This class is reusable over multiple requests for the same {@link ServletContextHandler}
* and is {@link #recycle() recycled} after each use before being
* {@link #associate(ServletContextRequest) associated} with a new {@link ServletContextRequest}
* and then {@link #associate(Request, Response, Callback) associated} with possibly wrapped
* request, response and callback.
* </p>
*
* @see ServletRequestState
* @see HttpInput
@ -67,35 +75,35 @@ public class ServletChannel
private final ServletRequestState _state;
private final ServletContextHandler.ServletScopedContext _context;
private final ServletContextHandler.ServletContextApi _servletContextApi;
private final ConnectionMetaData _connectionMetaData;
private final AtomicLong _requests = new AtomicLong();
private final Connector _connector;
private final Executor _executor;
private final HttpConfiguration _configuration;
private final EndPoint _endPoint;
private final HttpInput _httpInput;
private volatile ServletContextRequest _servletContextRequest;
private volatile boolean _expects100Continue;
private volatile Callback _callback;
// Bytes written after interception (e.g. after compression).
private volatile long _written;
private final HttpOutput _httpOutput;
private ServletContextRequest _servletContextRequest;
private Request _request;
private Response _response;
private Callback _callback;
private boolean _expects100Continue;
private long _written;
public ServletChannel(ServletContextHandler servletContextHandler, Request request)
{
_state = new ServletRequestState(this);
_context = servletContextHandler.getContext();
_servletContextApi = _context.getServletContext();
_connector = request.getConnectionMetaData().getConnector();
_executor = request.getContext();
_configuration = request.getConnectionMetaData().getHttpConfiguration();
_endPoint = request.getConnectionMetaData().getConnection().getEndPoint();
_httpInput = new HttpInput(this);
this(servletContextHandler, request.getConnectionMetaData());
}
public void setCallback(Callback callback)
public ServletChannel(ServletContextHandler servletContextHandler, ConnectionMetaData connectionMetaData)
{
if (_callback != null)
throw new IllegalStateException();
_callback = callback;
_context = servletContextHandler.getContext();
_servletContextApi = _context.getServletContext();
_connectionMetaData = connectionMetaData;
_state = new ServletRequestState(this);
_httpInput = new HttpInput(this);
_httpOutput = new HttpOutput(this);
}
public ConnectionMetaData getConnectionMetaData()
{
return _connectionMetaData;
}
public Callback getCallback()
@ -105,14 +113,18 @@ public class ServletChannel
/**
* Associate this channel with a specific request.
* @param servletContextRequest The request to associate
* This is called by the ServletContextHandler when a core {@link Request} is accepted and associated with
* a servlet mapping.
* @param servletContextRequest The servlet context request to associate
* @see #recycle()
*/
public void associate(ServletContextRequest servletContextRequest)
{
_state.recycle();
_httpInput.reopen();
_servletContextRequest = servletContextRequest;
_httpOutput.recycle();
_request = _servletContextRequest = servletContextRequest;
_response = _servletContextRequest.getServletContextResponse();
_expects100Continue = servletContextRequest.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
if (LOG.isDebugEnabled())
@ -122,24 +134,49 @@ public class ServletChannel
_state);
}
/**
* Associate this channel with possibly wrapped values for
* {@link #getRequest()}, {@link #getResponse()} and {@link #getCallback()}.
* This is called by the {@link ServletHandler} immediately before calling {@link #handle()} on the
* initial dispatch. This allows for handlers between the {@link ServletContextHandler} and the
* {@link ServletHandler} to wrap the instances.
* @param request The request, which may have been wrapped
* after #{@link ServletContextHandler#wrapRequest(Request, Response)}
* @param response The response, which may have been wrapped
* after #{@link ServletContextHandler#wrapResponse(ContextRequest, Response)}
* @param callback The context, which may have been wrapped
* after {@link ServletContextHandler#handle(Request, Response, Callback)}
*/
public void associate(Request request, Response response, Callback callback)
{
if (_callback != null)
throw new IllegalStateException();
if (request != _request && Request.as(request, ServletContextRequest.class) != _servletContextRequest)
throw new IllegalStateException();
_request = request;
_response = response;
_callback = callback;
}
public ServletContextHandler.ServletScopedContext getContext()
{
return _context;
}
public ServletContextHandler getContextHandler()
public ServletContextHandler getServletContextHandler()
{
return _context.getContextHandler();
}
public ServletContextHandler.ServletContextApi getServletContext()
public ServletContextHandler.ServletContextApi getServletContextApi()
{
return _servletContextApi;
}
public HttpOutput getHttpOutput()
{
return _servletContextRequest.getHttpOutput();
return _httpOutput;
}
public HttpInput getHttpInput()
@ -166,7 +203,7 @@ public class ServletChannel
return HostPort.normalizeHost(addr);
}
public ServletRequestState getState()
public ServletRequestState getServletRequestState()
{
return _state;
}
@ -185,7 +222,7 @@ public class ServletChannel
*/
public long getIdleTimeout()
{
return _endPoint.getIdleTimeout();
return _connectionMetaData.getConnection().getEndPoint().getIdleTimeout();
}
/**
@ -197,38 +234,70 @@ public class ServletChannel
*/
public void setIdleTimeout(long timeoutMs)
{
_endPoint.setIdleTimeout(timeoutMs);
_connectionMetaData.getConnection().getEndPoint().setIdleTimeout(timeoutMs);
}
public HttpConfiguration getHttpConfiguration()
{
return _configuration;
return _connectionMetaData.getHttpConfiguration();
}
public Server getServer()
{
return _connector.getServer();
return _context.getContextHandler().getServer();
}
/**
* @return The {@link ServletContextRequest} as wrapped by the {@link ServletContextHandler}.
* @see #getRequest()
*/
public ServletContextRequest getServletContextRequest()
{
return _servletContextRequest;
}
public ServletContextResponse getResponse()
/**
* @return The core {@link Request} associated with the request. This may differ from {@link #getServletContextRequest()}
* if the request was wrapped by another handler after the {@link ServletContextHandler} and passed
* to {@link ServletChannel#associate(Request, Response, Callback)}.
* @see #getServletContextRequest()
* @see #associate(Request, Response, Callback)
*/
public Request getRequest()
{
return _request;
}
/**
* @return The ServetContextResponse as wrapped by the {@link ServletContextHandler}.
* @see #getResponse()
*/
public ServletContextResponse getServletContextResponse()
{
ServletContextRequest request = _servletContextRequest;
return request == null ? null : request.getResponse();
return request == null ? null : request.getServletContextResponse();
}
/**
* @return The core {@link Response} associated with the API response.
* This may differ from {@link #getServletContextResponse()} if the response was wrapped by another handler
* after the {@link ServletContextHandler} and passed to {@link ServletChannel#associate(Request, Response, Callback)}.
* @see #getServletContextResponse()
* @see #associate(Request, Response, Callback)
*/
public Response getResponse()
{
return _response;
}
public Connection getConnection()
{
return _endPoint.getConnection();
return _connectionMetaData.getConnection();
}
public EndPoint getEndPoint()
{
return _endPoint;
return getConnection().getEndPoint();
}
/**
@ -306,7 +375,7 @@ public class ServletChannel
return ((InetSocketAddress)localAddress);
}
SocketAddress local = _endPoint.getLocalSocketAddress();
SocketAddress local = getEndPoint().getLocalSocketAddress();
if (local instanceof InetSocketAddress)
return (InetSocketAddress)local;
return null;
@ -314,7 +383,7 @@ public class ServletChannel
public InetSocketAddress getRemoteAddress()
{
SocketAddress remote = _endPoint.getRemoteSocketAddress();
SocketAddress remote = getEndPoint().getRemoteSocketAddress();
if (remote instanceof InetSocketAddress)
return (InetSocketAddress)remote;
return null;
@ -351,7 +420,7 @@ public class ServletChannel
throw new IOException("Committed before 100 Continue");
try
{
getResponse().writeInterim(HttpStatus.CONTINUE_100, HttpFields.EMPTY).get();
getServletContextResponse().writeInterim(HttpStatus.CONTINUE_100, HttpFields.EMPTY).get();
}
catch (Throwable x)
{
@ -373,6 +442,7 @@ public class ServletChannel
}
/**
* Handle the servlet request. This is called on the initial dispatch and then again on any asynchronous events.
* @return True if the channel is ready to continue handling (ie it is not suspended)
*/
public boolean handle()
@ -413,7 +483,7 @@ public class ServletChannel
_context.getServletContextHandler().requestInitialized(_servletContextRequest, _servletContextRequest.getServletApiRequest());
ServletHandler servletHandler = _context.getServletContextHandler().getServletHandler();
ServletHandler.MappedServlet mappedServlet = _servletContextRequest._mappedServlet;
ServletHandler.MappedServlet mappedServlet = _servletContextRequest.getMatchedResource().getResource();
mappedServlet.handle(servletHandler, Request.getPathInContext(_servletContextRequest), _servletContextRequest.getServletApiRequest(), _servletContextRequest.getHttpServletResponse());
}
@ -465,7 +535,7 @@ public class ServletChannel
// We first worked with the core pathInContext above, but now need to convert to servlet style
String decodedPathInContext = URIUtil.decodePath(pathInContext);
Dispatcher dispatcher = new Dispatcher(getContextHandler(), uri, decodedPathInContext);
Dispatcher dispatcher = new Dispatcher(getServletContextHandler(), uri, decodedPathInContext);
dispatcher.async(asyncContextEvent.getSuppliedRequest(), asyncContextEvent.getSuppliedResponse());
}
finally
@ -487,14 +557,14 @@ public class ServletChannel
try
{
// Get ready to send an error response
getResponse().resetContent();
getServletContextResponse().resetContent();
// the following is needed as you cannot trust the response code and reason
// as those could have been modified after calling sendError
Integer code = (Integer)_servletContextRequest.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (code == null)
code = HttpStatus.INTERNAL_SERVER_ERROR_500;
getResponse().setStatus(code);
getServletContextResponse().setStatus(code);
// The handling of the original dispatch failed, and we are now going to either generate
// and error response ourselves or dispatch for an error page. If there is content left over
@ -502,13 +572,13 @@ public class ServletChannel
// Connection:close. This can't be deferred to COMPLETE as the response will be committed
// by then.
if (!_httpInput.consumeAvailable())
ResponseUtils.ensureNotPersistent(_servletContextRequest, _servletContextRequest.getResponse());
ResponseUtils.ensureNotPersistent(_servletContextRequest, _servletContextRequest.getServletContextResponse());
ContextHandler.ScopedContext context = (ContextHandler.ScopedContext)_servletContextRequest.getAttribute(ErrorHandler.ERROR_CONTEXT);
Request.Handler errorHandler = ErrorHandler.getErrorHandler(getServer(), context == null ? null : context.getContextHandler());
// If we can't have a body or have no ErrorHandler, then create a minimal error response.
if (HttpStatus.hasNoBody(getResponse().getStatus()) || errorHandler == null)
if (HttpStatus.hasNoBody(getServletContextResponse().getStatus()) || errorHandler == null)
{
sendResponseAndComplete();
}
@ -521,7 +591,7 @@ public class ServletChannel
{
// We do not notify ServletRequestListener on this dispatch because it might not
// be dispatched to an error page, so we delegate this responsibility to the ErrorHandler.
dispatch(() -> errorHandler.handle(_servletContextRequest, getResponse(), blocker));
dispatch(() -> errorHandler.handle(_servletContextRequest, getServletContextResponse(), blocker));
blocker.block();
}
}
@ -542,7 +612,7 @@ public class ServletChannel
{
try
{
getResponse().resetContent();
getServletContextResponse().resetContent();
sendResponseAndComplete();
}
catch (Throwable t)
@ -580,7 +650,7 @@ public class ServletChannel
case COMPLETE:
{
if (!getResponse().isCommitted())
if (!getServletContextResponse().isCommitted())
{
/*
TODO: isHandled does not exist and HttpOutput might not be explicitly closed.
@ -593,15 +663,15 @@ public class ServletChannel
*/
// Indicate Connection:close if we can't consume all.
if (getResponse().getStatus() >= 200)
ResponseUtils.ensureConsumeAvailableOrNotPersistent(_servletContextRequest, _servletContextRequest.getResponse());
if (getServletContextResponse().getStatus() >= 200)
ResponseUtils.ensureConsumeAvailableOrNotPersistent(_servletContextRequest, _servletContextRequest.getServletContextResponse());
}
// RFC 7230, section 3.3.
if (!_servletContextRequest.isHead() &&
getResponse().getStatus() != HttpStatus.NOT_MODIFIED_304 &&
!getResponse().isContentComplete(_servletContextRequest.getHttpOutput().getWritten()))
getServletContextResponse().getStatus() != HttpStatus.NOT_MODIFIED_304 &&
getServletContextResponse().isContentIncomplete(_servletContextRequest.getHttpOutput().getWritten()))
{
if (sendErrorOrAbort("Insufficient content written"))
break;
@ -613,7 +683,7 @@ public class ServletChannel
break;
// Set a close callback on the HttpOutput to make it an async callback
getResponse().completeOutput(Callback.from(NON_BLOCKING, () -> _state.completed(null), _state::completed));
getServletContextResponse().completeOutput(Callback.from(NON_BLOCKING, () -> _state.completed(null), _state::completed));
break;
}
@ -654,7 +724,7 @@ public class ServletChannel
return false;
}
getResponse().getServletApiResponse().sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, message);
getServletContextResponse().getServletApiResponse().sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, message);
return true;
}
catch (Throwable x)
@ -667,7 +737,7 @@ public class ServletChannel
private void dispatch(Dispatchable dispatchable) throws Exception
{
_servletContextRequest.getResponse().getHttpOutput().reopen();
_servletContextRequest.getServletContextResponse().getHttpOutput().reopen();
getHttpOutput().reopen();
dispatchable.dispatch();
}
@ -749,7 +819,7 @@ public class ServletChannel
try
{
_state.completing();
getResponse().write(true, getResponse().getHttpOutput().getByteBuffer(), Callback.from(() -> _state.completed(null), _state::completed));
getServletContextResponse().write(true, getServletContextResponse().getHttpOutput().getByteBuffer(), Callback.from(() -> _state.completed(null), _state::completed));
}
catch (Throwable x)
{
@ -854,7 +924,7 @@ public class ServletChannel
protected void execute(Runnable task)
{
_executor.execute(task);
_context.execute(task);
}
/**

View File

@ -19,6 +19,7 @@ import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
@ -35,6 +36,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
@ -42,7 +44,6 @@ import jakarta.servlet.FilterRegistration;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextAttributeEvent;
import jakarta.servlet.ServletContextAttributeListener;
import jakarta.servlet.ServletContextEvent;
@ -66,9 +67,12 @@ import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingListener;
import jakarta.servlet.http.HttpSessionIdListener;
import jakarta.servlet.http.HttpSessionListener;
import org.eclipse.jetty.ee10.servlet.ServletContextResponse.EncodingFrom;
import org.eclipse.jetty.ee10.servlet.ServletContextResponse.OutputType;
import org.eclipse.jetty.ee10.servlet.security.ConstraintAware;
import org.eclipse.jetty.ee10.servlet.security.ConstraintMapping;
import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler;
import org.eclipse.jetty.ee10.servlet.writer.ResponseWriter;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.security.SecurityHandler;
@ -80,6 +84,9 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.ContextRequest;
import org.eclipse.jetty.server.handler.ContextResponse;
import org.eclipse.jetty.session.AbstractSessionManager;
import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.SessionManager;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.DecoratedObjectFactory;
@ -116,7 +123,7 @@ import static jakarta.servlet.ServletContext.TEMPDIR;
* </pre>
* <p>
* This class should have been called ServletContext, but this would have
* cause confusion with {@link ServletContext}.
* cause confusion with {@link jakarta.servlet.ServletContext}.
*/
@ManagedObject("Servlet Context Handler")
public class ServletContextHandler extends ContextHandler
@ -153,7 +160,7 @@ public class ServletContextHandler extends ContextHandler
DESTROYED
}
public static ServletContextHandler getServletContextHandler(ServletContext servletContext, String purpose)
public static ServletContextHandler getServletContextHandler(jakarta.servlet.ServletContext servletContext, String purpose)
{
if (servletContext instanceof ServletContextApi servletContextApi)
return servletContextApi.getContext().getServletContextHandler();
@ -163,7 +170,7 @@ public class ServletContextHandler extends ContextHandler
throw new IllegalStateException("No Jetty ServletContextHandler, " + purpose + " unavailable");
}
public static ServletContextHandler getServletContextHandler(ServletContext servletContext)
public static ServletContextHandler getServletContextHandler(jakarta.servlet.ServletContext servletContext)
{
if (servletContext instanceof ServletContextApi)
return ((ServletContextApi)servletContext).getContext().getServletContextHandler();
@ -171,12 +178,12 @@ public class ServletContextHandler extends ContextHandler
return getCurrentServletContextHandler();
}
public static ServletContext getCurrentServletContext()
public static jakarta.servlet.ServletContext getCurrentServletContext()
{
return getServletContext(ContextHandler.getCurrentContext());
}
public static ServletContext getServletContext(Context context)
public static jakarta.servlet.ServletContext getServletContext(Context context)
{
if (context instanceof ServletScopedContext)
return ((ServletScopedContext)context).getServletContext();
@ -203,7 +210,6 @@ public class ServletContextHandler extends ContextHandler
private Map<String, String> _localeEncodingMap;
private String[] _welcomeFiles;
private Logger _logger;
protected boolean _allowNullPathInfo;
private int _maxFormKeys = Integer.getInteger(MAX_FORM_KEYS_KEY, DEFAULT_MAX_FORM_KEYS);
private int _maxFormContentSize = Integer.getInteger(MAX_FORM_CONTENT_SIZE_KEY, DEFAULT_MAX_FORM_CONTENT_SIZE);
private boolean _usingSecurityManager = getSecurityManager() != null;
@ -339,7 +345,7 @@ public class ServletContextHandler extends ContextHandler
/**
* Get the context path in a form suitable to be returned from {@link HttpServletRequest#getContextPath()}
* or {@link ServletContext#getContextPath()}.
* or {@link jakarta.servlet.ServletContext#getContextPath()}.
*
* @return Returns the encoded contextPath, or empty string for root context
*/
@ -844,7 +850,7 @@ public class ServletContextHandler extends ContextHandler
void exitScope(ServletScopedContext context, ServletContextRequest request);
}
public ServletContext getServletContext()
public jakarta.servlet.ServletContext getServletContext()
{
return getContext().getServletContext();
}
@ -1139,8 +1145,13 @@ public class ServletContextHandler extends ContextHandler
// Get a servlet request, possibly from a cached version in the channel attributes.
Attributes cache = request.getComponents().getCache();
ServletChannel servletChannel = (ServletChannel)cache.getAttribute(ServletChannel.class.getName());
if (servletChannel == null || servletChannel.getContext() != getContext())
Object cachedChannel = cache.getAttribute(ServletChannel.class.getName());
ServletChannel servletChannel;
if (cachedChannel instanceof ServletChannel sc && sc.getContext() == getContext())
{
servletChannel = sc;
}
else
{
servletChannel = new ServletChannel(this, request);
cache.setAttribute(ServletChannel.class.getName(), servletChannel);
@ -1155,16 +1166,15 @@ public class ServletContextHandler extends ContextHandler
protected ContextResponse wrapResponse(ContextRequest request, Response response)
{
if (request instanceof ServletContextRequest servletContextRequest)
return servletContextRequest.getResponse();
return servletContextRequest.getServletContextResponse();
throw new IllegalArgumentException();
}
@Override
protected boolean handleByContextHandler(String pathInContext, ContextRequest request, Response response, Callback callback)
{
ServletContextRequest scopedRequest = Request.as(request, ServletContextRequest.class);
DispatcherType dispatch = scopedRequest.getServletApiRequest().getDispatcherType();
if (dispatch == DispatcherType.REQUEST && isProtectedTarget(scopedRequest.getDecodedPathInContext()))
boolean initialDispatch = request instanceof ServletContextRequest;
if (initialDispatch && isProtectedTarget(pathInContext))
{
Response.writeError(request, response, callback, HttpServletResponse.SC_NOT_FOUND, null);
return true;
@ -2033,7 +2043,7 @@ public class ServletContextHandler extends ContextHandler
}
}
public class ServletContextApi implements ServletContext
public class ServletContextApi implements jakarta.servlet.ServletContext
{
public static final int SERVLET_MAJOR_VERSION = 6;
public static final int SERVLET_MINOR_VERSION = 0;
@ -2695,7 +2705,7 @@ public class ServletContextHandler extends ContextHandler
}
@Override
public ServletContext getContext(String uripath)
public jakarta.servlet.ServletContext getContext(String uripath)
{
//TODO jetty-12 does not currently support cross context dispatch
return null;
@ -3033,4 +3043,81 @@ public class ServletContextHandler extends ContextHandler
super.doStop();
}
}
/**
* The interface used by {@link ServletApiRequest} to access the {@link ServletContextRequest} without
* access to the unwrapped {@link Request} methods.
*/
public interface ServletRequestInfo
{
String getDecodedPathInContext();
ManagedSession getManagedSession();
Charset getQueryEncoding();
default Request getRequest()
{
return getServletChannel().getRequest();
}
List<ServletRequestAttributeListener> getRequestAttributeListeners();
ServletScopedContext getServletContext();
HttpInput getHttpInput();
MatchedResource<ServletHandler.MappedServlet> getMatchedResource();
AbstractSessionManager.RequestedSession getRequestedSession();
ServletChannel getServletChannel();
ServletContextHandler getServletContextHandler();
ServletRequestState getServletRequestState();
SessionManager getSessionManager();
ServletRequestState getState();
void setQueryEncoding(String s);
}
/**
* The interface used by {@link ServletApiResponse} to access the {@link ServletContextResponse} without
* access to the unwrapped {@link Response} methods.
*/
public interface ServletResponseInfo
{
String getCharacterEncoding(boolean setContentType);
String getCharacterEncoding();
String getContentType();
EncodingFrom getEncodingFrom();
Locale getLocale();
OutputType getOutputType();
Response getResponse();
Supplier<Map<String, String>> getTrailers();
ResponseWriter getWriter();
boolean isWriting();
void setCharacterEncoding(String encoding, EncodingFrom encodingFrom);
void setLocale(Locale locale);
void setOutputType(OutputType outputType);
void setTrailers(Supplier<Map<String, String>> trailers);
void setWriter(ResponseWriter responseWriter);
}
}

View File

@ -31,9 +31,7 @@ import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.http.pathmap.MatchedPath;
import org.eclipse.jetty.http.pathmap.MatchedResource;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.SecureRequestCustomizer;
@ -45,7 +43,16 @@ import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.SessionManager;
import org.eclipse.jetty.util.Fields;
public class ServletContextRequest extends ContextRequest
/**
* A core request wrapper that carries the servlet related request state,
* which may be used directly by the associated {@link ServletApiRequest}.
* Non-servlet related state, is used indirectly via {@link ServletChannel#getRequest()}
* which may be a wrapper of this request.
* <p>
* This class is single use only.
* </p>
*/
public class ServletContextRequest extends ContextRequest implements ServletContextHandler.ServletRequestInfo
{
public static final String MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
static final int INPUT_NONE = 0;
@ -57,20 +64,21 @@ public class ServletContextRequest extends ContextRequest
public static ServletContextRequest getServletContextRequest(ServletRequest request)
{
if (request instanceof ServletApiRequest)
return ((ServletApiRequest)request).getServletContextRequest();
if (request instanceof ServletApiRequest servletApiRequest &&
servletApiRequest.getServletRequestInfo() instanceof ServletContextRequest servletContextRequest)
return servletContextRequest;
Object channel = request.getAttribute(ServletChannel.class.getName());
if (channel instanceof ServletChannel)
return ((ServletChannel)channel).getServletContextRequest();
if (request.getAttribute(ServletChannel.class.getName()) instanceof ServletChannel servletChannel)
return servletChannel.getServletContextRequest();
while (request instanceof ServletRequestWrapper)
while (request instanceof ServletRequestWrapper wrapper)
{
request = ((ServletRequestWrapper)request).getRequest();
}
request = wrapper.getRequest();
if (request instanceof ServletApiRequest)
return ((ServletApiRequest)request).getServletContextRequest();
if (request instanceof ServletApiRequest servletApiRequest &&
servletApiRequest.getServletRequestInfo() instanceof ServletContextRequest servletContextRequest)
return servletContextRequest;
}
throw new IllegalStateException("could not find %s for %s".formatted(ServletContextRequest.class.getSimpleName(), request));
}
@ -78,13 +86,11 @@ public class ServletContextRequest extends ContextRequest
private final List<ServletRequestAttributeListener> _requestAttributeListeners = new ArrayList<>();
private final ServletApiRequest _servletApiRequest;
private final ServletContextResponse _response;
final ServletHandler.MappedServlet _mappedServlet;
private final MatchedResource<ServletHandler.MappedServlet> _matchedResource;
private final HttpInput _httpInput;
private final String _decodedPathInContext;
private final ServletChannel _servletChannel;
private final PathSpec _pathSpec;
private final SessionManager _sessionManager;
final MatchedPath _matchedPath;
private Charset _queryEncoding;
private HttpFields _trailers;
private ManagedSession _managedSession;
@ -102,11 +108,9 @@ public class ServletContextRequest extends ContextRequest
super(servletContextApi.getContext(), request);
_servletChannel = servletChannel;
_servletApiRequest = newServletApiRequest();
_mappedServlet = matchedResource.getResource();
_matchedResource = matchedResource;
_httpInput = _servletChannel.getHttpInput();
_decodedPathInContext = decodedPathInContext;
_pathSpec = matchedResource.getPathSpec();
_matchedPath = matchedResource.getMatchedPath();
_response = newServletContextResponse(response);
_sessionManager = sessionManager;
addIdleTimeoutListener(this::onIdleTimeout);
@ -114,7 +118,7 @@ public class ServletContextRequest extends ContextRequest
protected ServletApiRequest newServletApiRequest()
{
if (getHttpURI().hasViolations() && !getServletChannel().getContextHandler().getServletHandler().isDecodeAmbiguousURIs())
if (getHttpURI().hasViolations() && !getServletChannel().getServletContextHandler().getServletHandler().isDecodeAmbiguousURIs())
{
// TODO we should check if current compliance mode allows all the violations?
@ -135,22 +139,25 @@ public class ServletContextRequest extends ContextRequest
private boolean onIdleTimeout(TimeoutException timeout)
{
return _servletChannel.getState().onIdleTimeout(timeout);
return _servletChannel.getServletRequestState().onIdleTimeout(timeout);
}
@Override
public ServletContextHandler getServletContextHandler()
{
return _servletChannel.getServletContextHandler();
}
@Override
public String getDecodedPathInContext()
{
return _decodedPathInContext;
}
public PathSpec getPathSpec()
@Override
public MatchedResource<ServletHandler.MappedServlet> getMatchedResource()
{
return _pathSpec;
}
public MatchedPath getMatchedPath()
{
return _matchedPath;
return _matchedResource;
}
@Override
@ -164,22 +171,24 @@ public class ServletContextRequest extends ContextRequest
_trailers = trailers;
}
@Override
public ServletRequestState getState()
{
return _servletChannel.getState();
return _servletChannel.getServletRequestState();
}
public ServletContextResponse getResponse()
public ServletContextResponse getServletContextResponse()
{
return _response;
}
@Override
public ServletContextHandler.ServletScopedContext getContext()
public ServletContextHandler.ServletScopedContext getServletContext()
{
return (ServletContextHandler.ServletScopedContext)super.getContext();
}
@Override
public HttpInput getHttpInput()
{
return _httpInput;
@ -204,16 +213,18 @@ public class ServletContextRequest extends ContextRequest
/**
* Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any
* getParameter methods.
*
* <p>
* The request attribute "org.eclipse.jetty.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
*
* @param queryEncoding the URI query character encoding
*/
@Override
public void setQueryEncoding(String queryEncoding)
{
_queryEncoding = Charset.forName(queryEncoding);
}
@Override
public Charset getQueryEncoding()
{
return _queryEncoding;
@ -248,8 +259,9 @@ public class ServletContextRequest extends ContextRequest
}
/**
* @return The current {@link ContextHandler.ScopedContext 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
* @return The current {@link ContextHandler.ScopedContext 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 ServletContextHandler.ServletScopedContext getErrorContext()
{
@ -257,12 +269,14 @@ public class ServletContextRequest extends ContextRequest
return _servletChannel.getContext();
}
ServletRequestState getServletRequestState()
@Override
public ServletRequestState getServletRequestState()
{
return _servletChannel.getState();
return _servletChannel.getServletRequestState();
}
ServletChannel getServletChannel()
@Override
public ServletChannel getServletChannel()
{
return _servletChannel;
}
@ -274,19 +288,15 @@ public class ServletContextRequest extends ContextRequest
public HttpServletResponse getHttpServletResponse()
{
return _response.getHttpServletResponse();
}
public ServletHandler.MappedServlet getMappedServlet()
{
return _mappedServlet;
return _response.getServletApiResponse();
}
public String getServletName()
{
return _mappedServlet.getServletHolder().getName();
return getMatchedResource().getResource().getServletHolder().getName();
}
@Override
public List<ServletRequestAttributeListener> getRequestAttributeListeners()
{
return _requestAttributeListeners;
@ -318,6 +328,7 @@ public class ServletContextRequest extends ContextRequest
return isNoParams;
}
@Override
public ManagedSession getManagedSession()
{
return _managedSession;
@ -328,6 +339,7 @@ public class ServletContextRequest extends ContextRequest
_managedSession = managedSession;
}
@Override
public SessionManager getSessionManager()
{
return _sessionManager;
@ -341,6 +353,7 @@ public class ServletContextRequest extends ContextRequest
_managedSession = requestedSession.session();
}
@Override
public AbstractSessionManager.RequestedSession getRequestedSession()
{
return _requestedSession;

View File

@ -25,6 +25,7 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.eclipse.jetty.ee10.servlet.writer.ResponseWriter;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
@ -39,16 +40,22 @@ import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
public class ServletContextResponse extends ContextResponse
/**
* A core response wrapper that carries the servlet related response state,
* which may be used directly by the associated {@link ServletApiResponse}.
* Non servlet related state, is used indirectly via {@link ServletChannel#getResponse()}
* which may be a wrapper of this response.
*/
public class ServletContextResponse extends ContextResponse implements ServletContextHandler.ServletResponseInfo
{
protected enum OutputType
{
NONE, STREAM, WRITER
}
private final HttpOutput _httpOutput;
private final ServletChannel _servletChannel;
private final ServletApiResponse _httpServletResponse;
private final ServletApiResponse _servletApiResponse;
private final HttpFields.Mutable.Wrapper _headers;
private String _characterEncoding;
private String _contentType;
private MimeTypes.Type _mimeType;
@ -61,49 +68,59 @@ public class ServletContextResponse extends ContextResponse
public static ServletContextResponse getServletContextResponse(ServletResponse response)
{
if (response instanceof ServletApiResponse)
return ((ServletApiResponse)response).getResponse();
if (response instanceof ServletApiResponse servletApiResponse)
return servletApiResponse.getServletRequestInfo().getServletChannel().getServletContextResponse();
while (response instanceof ServletResponseWrapper)
{
response = ((ServletResponseWrapper)response).getResponse();
if (response instanceof ServletApiResponse servletApiResponse)
return servletApiResponse.getServletRequestInfo().getServletChannel().getServletContextResponse();
}
if (response instanceof ServletApiResponse)
return ((ServletApiResponse)response).getResponse();
throw new IllegalStateException("could not find %s for %s".formatted(ServletContextResponse.class.getSimpleName(), response));
}
public ServletContextResponse(ServletChannel servletChannel, ServletContextRequest request, Response response)
{
super(servletChannel.getContext(), request, response);
_httpOutput = new HttpOutput(response, servletChannel);
_servletChannel = servletChannel;
_httpServletResponse = newServletApiResponse();
_servletApiResponse = newServletApiResponse();
_headers = new HttpFieldsWrapper(response.getHeaders());
}
protected ResponseWriter getWriter()
@Override
public Response getResponse()
{
return _servletChannel.getResponse();
}
@Override
public ResponseWriter getWriter()
{
return _writer;
}
protected void setWriter(ResponseWriter writer)
@Override
public void setWriter(ResponseWriter writer)
{
_writer = writer;
}
protected Locale getLocale()
@Override
public Locale getLocale()
{
return _locale;
}
protected void setLocale(Locale locale)
@Override
public void setLocale(Locale locale)
{
_locale = locale;
}
protected EncodingFrom getEncodingFrom()
@Override
public EncodingFrom getEncodingFrom()
{
return _encodingFrom;
}
@ -113,47 +130,38 @@ public class ServletContextResponse extends ContextResponse
return _mimeType;
}
protected void setMimeType(MimeTypes.Type mimeType)
{
this._mimeType = mimeType;
}
protected Supplier<Map<String, String>> getTrailers()
@Override
public Supplier<Map<String, String>> getTrailers()
{
return _trailers;
}
@Override
public void setTrailers(Supplier<Map<String, String>> trailers)
{
this._trailers = trailers;
}
protected void setContentType(String contentType)
{
this._contentType = contentType;
}
protected String getCharacterEncoding()
@Override
public String getCharacterEncoding()
{
return _characterEncoding;
}
protected void setCharacterEncoding(String value)
{
_characterEncoding = value;
}
protected void setOutputType(OutputType outputType)
@Override
public void setOutputType(OutputType outputType)
{
_outputType = outputType;
}
protected String getContentType()
@Override
public String getContentType()
{
return _contentType;
}
protected OutputType getOutputType()
@Override
public OutputType getOutputType()
{
return _outputType;
}
@ -170,27 +178,22 @@ public class ServletContextResponse extends ContextResponse
public HttpOutput getHttpOutput()
{
return _httpOutput;
return _servletChannel.getHttpOutput();
}
public ServletRequestState getState()
public ServletRequestState getServletRequestState()
{
return _servletChannel.getState();
return _servletChannel.getServletRequestState();
}
public HttpServletResponse getHttpServletResponse()
{
return _httpServletResponse;
}
public ServletApiResponse getServletApiResponse()
{
return _httpServletResponse;
return _servletApiResponse;
}
public void resetForForward()
{
_httpServletResponse.resetBuffer();
_servletApiResponse.resetBuffer();
_outputType = OutputType.NONE;
}
@ -198,7 +201,7 @@ public class ServletContextResponse extends ContextResponse
{
if (_outputType == OutputType.WRITER)
_writer.reopen();
_httpOutput.reopen();
getHttpOutput().reopen();
}
public void completeOutput(Callback callback)
@ -206,7 +209,7 @@ public class ServletContextResponse extends ContextResponse
if (_outputType == OutputType.WRITER)
_writer.complete(callback);
else
_httpOutput.complete(callback);
getHttpOutput().complete(callback);
}
public boolean isAllContentWritten(long written)
@ -214,9 +217,9 @@ public class ServletContextResponse extends ContextResponse
return (_contentLength >= 0 && written >= _contentLength);
}
public boolean isContentComplete(long written)
public boolean isContentIncomplete(long written)
{
return (_contentLength < 0 || written >= _contentLength);
return (_contentLength >= 0 && written < _contentLength);
}
public void setContentLength(int len)
@ -224,6 +227,12 @@ public class ServletContextResponse extends ContextResponse
setContentLength((long)len);
}
@Override
public HttpFields.Mutable getHeaders()
{
return _headers;
}
public void setContentLength(long len)
{
// Protect from setting after committed as default handling
@ -234,7 +243,7 @@ public class ServletContextResponse extends ContextResponse
if (len > 0)
{
long written = _httpOutput.getWritten();
long written = getHttpOutput().getWritten();
if (written > len)
throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
@ -254,7 +263,7 @@ public class ServletContextResponse extends ContextResponse
}
else if (len == 0)
{
long written = _httpOutput.getWritten();
long written = getHttpOutput().getWritten();
if (written > 0)
throw new IllegalArgumentException("setContentLength(0) when already written " + written);
_contentLength = len;
@ -277,7 +286,7 @@ public class ServletContextResponse extends ContextResponse
if (_outputType == OutputType.WRITER)
_writer.close();
else
_httpOutput.close();
getHttpOutput().close();
}
@Override
@ -285,7 +294,7 @@ public class ServletContextResponse extends ContextResponse
{
super.reset();
_httpServletResponse.resetBuffer();
_servletApiResponse.resetBuffer();
_outputType = OutputType.NONE;
_contentLength = -1;
_contentType = null;
@ -324,7 +333,7 @@ public class ServletContextResponse extends ContextResponse
HttpSession session = getServletContextRequest().getServletApiRequest().getSession(false);
if (session != null && session.isNew())
{
SessionHandler sh = _servletChannel.getContextHandler().getSessionHandler();
SessionHandler sh = _servletChannel.getServletContextHandler().getSessionHandler();
if (sh != null)
{
ManagedSession managedSession = SessionHandler.ServletSessionApi.getSession(session);
@ -346,7 +355,7 @@ public class ServletContextResponse extends ContextResponse
{
if (isCommitted())
throw new IllegalStateException("Committed");
_httpOutput.resetBuffer();
getHttpOutput().resetBuffer();
_outputType = OutputType.NONE;
_contentLength = -1;
_contentType = null;
@ -372,6 +381,7 @@ public class ServletContextResponse extends ContextResponse
return _characterEncoding;
}
@Override
public String getCharacterEncoding(boolean setContentType)
{
// First try explicit char encoding.
@ -400,7 +410,7 @@ public class ServletContextResponse extends ContextResponse
}
// Try any default char encoding for the context.
ServletContext context = _servletChannel.getServletContextRequest().getContext().getServletContext();
ServletContext context = _servletChannel.getServletContextRequest().getServletContext().getServletContext();
if (context != null)
{
encoding = context.getResponseCharacterEncoding();
@ -419,26 +429,14 @@ public class ServletContextResponse extends ContextResponse
return encoding;
}
/**
* Set the Character Encoding and EncodingFrom in the raw, with no manipulation
* of the ContentType value, MimeType value, or headers.
*
* @param encoding the character encoding
* @param from where encoding came from
*/
protected void setRawCharacterEncoding(String encoding, EncodingFrom from)
{
_characterEncoding = encoding;
_encodingFrom = from;
}
/**
* Update the Content-Type, MimeType, and headers from the provided Character Encoding and
* EncodingFrom.
* @param encoding the character encoding
* @param from where encoding came from
*/
protected void setCharacterEncoding(String encoding, EncodingFrom from)
@Override
public void setCharacterEncoding(String encoding, EncodingFrom from)
{
if (isWriting() || isCommitted())
return;
@ -483,6 +481,7 @@ public class ServletContextResponse extends ContextResponse
}
}
@Override
public boolean isWriting()
{
return _outputType == OutputType.WRITER;
@ -530,4 +529,148 @@ public class ServletContextResponse extends ContextResponse
*/
SET_CHARACTER_ENCODING
}
/**
* Wrapper of the response HttpFields to allow specific values to be intercepted.
*/
private class HttpFieldsWrapper extends HttpFields.Mutable.Wrapper
{
public HttpFieldsWrapper(Mutable fields)
{
super(fields);
}
@Override
public HttpField onAddField(HttpField field)
{
if (field.getHeader() != null)
{
switch (field.getHeader())
{
case CONTENT_LENGTH ->
{
if (!isCommitted())
{
return setContentLength(field);
}
}
case CONTENT_TYPE ->
{
if (!isCommitted())
{
return setContentType(field);
}
}
}
}
return super.onAddField(field);
}
@Override
public boolean onRemoveField(HttpField field)
{
if (field.getHeader() != null)
{
switch (field.getHeader())
{
case CONTENT_LENGTH ->
{
if (!isCommitted())
_contentLength = -1;
}
case CONTENT_TYPE ->
{
if (!isCommitted())
{
if (_locale == null)
_characterEncoding = null;
_contentType = null;
_mimeType = null;
}
}
}
}
return true;
}
private HttpField setContentLength(HttpField field)
{
long len = field.getLongValue();
long written = _servletChannel.getHttpOutput().getWritten();
if (len > 0 && written > len)
throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
if (len == 0 && written > 0)
throw new IllegalArgumentException("setContentLength(0) when already written " + written);
_contentLength = len;
if (len > 0 && isAllContentWritten(written))
{
try
{
closeOutput();
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
}
return field;
}
private HttpField setContentType(HttpField field)
{
_contentType = field.getValue();
_mimeType = MimeTypes.CACHE.get(_contentType);
String charset = MimeTypes.getCharsetFromContentType(_contentType);
if (charset == null && _mimeType != null && _mimeType.isCharsetAssumed())
charset = _mimeType.getCharsetString();
if (charset == null)
{
switch (_encodingFrom)
{
case NOT_SET:
break;
case DEFAULT:
case INFERRED:
case SET_CONTENT_TYPE:
case SET_LOCALE:
case SET_CHARACTER_ENCODING:
{
_contentType = _contentType + ";charset=" + _characterEncoding;
_mimeType = MimeTypes.CACHE.get(_contentType);
field = new HttpField(HttpHeader.CONTENT_TYPE, _contentType);
break;
}
default:
throw new IllegalStateException(_encodingFrom.toString());
}
}
else if (isWriting() && !charset.equalsIgnoreCase(_characterEncoding))
{
// too late to change the character encoding;
_contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
if (_characterEncoding != null && (_mimeType == null || !_mimeType.isCharsetAssumed()))
_contentType = _contentType + ";charset=" + _characterEncoding;
_mimeType = MimeTypes.CACHE.get(_contentType);
field = new HttpField(HttpHeader.CONTENT_TYPE, _contentType);
}
else
{
_characterEncoding = charset;
_encodingFrom = ServletContextResponse.EncodingFrom.SET_CONTENT_TYPE;
}
if (HttpGenerator.__STRICT || _mimeType == null)
return field;
_contentType = _mimeType.asString();
return _mimeType.getContentTypeField();
}
}
}

View File

@ -452,10 +452,15 @@ public class ServletHandler extends Handler.Wrapper
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
// We will always have a ServletScopedRequest and MappedServlet otherwise we will not reach ServletHandler.
ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
servletContextRequest.getServletChannel().setCallback(callback);
servletContextRequest.getServletChannel().handle();
// We will always have a ServletContextRequest as we must be within a ServletContextHandler
ServletChannel servletChannel = Request.get(request, ServletContextRequest.class, ServletContextRequest::getServletChannel);
if (LOG.isDebugEnabled())
LOG.debug("handle {} {} {} {}", this, request, response, callback);
// But request, response and/or callback may have been wrapped after the ServletContextHandler, so update the channel.
servletChannel.associate(request, response, callback);
servletChannel.handle();
return true;
}

View File

@ -116,10 +116,10 @@ public class ServletMultiPartFormData
formData.setMaxMemoryFileSize(config.getFileSizeThreshold());
formData.setMaxFileSize(config.getMaxFileSize());
formData.setMaxLength(config.getMaxRequestSize());
ConnectionMetaData connectionMetaData = request.getServletContextRequest().getConnectionMetaData();
ConnectionMetaData connectionMetaData = request.getRequest().getConnectionMetaData();
formData.setPartHeadersMaxLength(connectionMetaData.getHttpConfiguration().getRequestHeaderSize());
ByteBufferPool byteBufferPool = request.getServletContextRequest().getComponents().getByteBufferPool();
ByteBufferPool byteBufferPool = request.getRequest().getComponents().getByteBufferPool();
Connection connection = connectionMetaData.getConnection();
int bufferSize = connection instanceof AbstractConnection c ? c.getInputBufferSize() : 2048;
InputStream input = request.getInputStream();

View File

@ -289,7 +289,7 @@ public class ServletRequestState
public boolean isResponseCommitted()
{
return _servletChannel.getResponse().isCommitted();
return _servletChannel.getServletContextResponse().isCommitted();
}
public boolean isResponseCompleted()
@ -311,7 +311,7 @@ public class ServletRequestState
return false;
case OPEN:
_servletChannel.getResponse().setStatus(500);
_servletChannel.getServletContextResponse().setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
_outputState = OutputState.ABORTED;
return true;
@ -883,7 +883,7 @@ public class ServletRequestState
HttpServletRequest httpServletRequest = servletContextRequest.getServletApiRequest();
final Request request = _servletChannel.getServletContextRequest();
final Response response = _servletChannel.getResponse();
final Response response = _servletChannel.getServletContextResponse();
if (message == null)
message = HttpStatus.getMessage(code);
@ -915,7 +915,7 @@ public class ServletRequestState
request.setAttribute(ERROR_MESSAGE, message);
// Set Jetty Specific Attributes.
request.setAttribute(ErrorHandler.ERROR_CONTEXT, servletContextRequest.getContext());
request.setAttribute(ErrorHandler.ERROR_CONTEXT, servletContextRequest.getServletContext());
request.setAttribute(ErrorHandler.ERROR_MESSAGE, message);
request.setAttribute(ErrorHandler.ERROR_STATUS, code);
@ -1153,7 +1153,7 @@ public class ServletRequestState
public ServletContextHandler getContextHandler()
{
return _servletChannel.getContextHandler();
return _servletChannel.getServletContextHandler();
}
void runInContext(AsyncContextEvent event, Runnable runnable)

View File

@ -656,7 +656,7 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
HttpCookie cookie = access(requestedSession.session(), request.getConnectionMetaData().isSecure());
if (cookie != null)
{
ServletContextResponse servletContextResponse = servletContextRequest.getResponse();
ServletContextResponse servletContextResponse = servletContextRequest.getServletContextResponse();
Response.replaceCookie(servletContextResponse, cookie);
}

View File

@ -714,7 +714,7 @@ public class AsyncServletTest
String response = process("forwarder", "name=orig&one=1", null);
assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
_history.forEach(System.err::println);
// _history.forEach(System.err::println);
assertThat(_history, contains(
"REQUEST /ctx/forwarder/info?name=orig&one=1",

View File

@ -30,7 +30,6 @@ import jakarta.servlet.AsyncContext;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
@ -50,8 +49,9 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
@ -91,10 +91,8 @@ public class GzipHandlerTest
private Server _server;
private LocalConnector _connector;
private GzipHandler gzipHandler;
private ServletContextHandler context;
@BeforeEach
public void init() throws Exception
public void init(boolean gzipInContext) throws Exception
{
_server = new Server();
_connector = new LocalConnector(_server);
@ -104,10 +102,7 @@ public class GzipHandlerTest
gzipHandler.setMinGzipSize(16);
gzipHandler.setInflateBufferSize(4096);
context = new ServletContextHandler(gzipHandler, "/ctx");
_server.setHandler(gzipHandler);
gzipHandler.setHandler(context);
ServletContextHandler context = new ServletContextHandler("/ctx");
context.addServlet(MicroServlet.class, "/micro");
context.addServlet(MicroChunkedServlet.class, "/microchunked");
context.addServlet(TestServlet.class, "/content");
@ -120,6 +115,17 @@ public class GzipHandlerTest
context.addServlet(BufferServlet.class, "/buffer/*");
context.addFilter(CheckFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
if (gzipInContext)
{
_server.setHandler(context);
context.insertHandler(gzipHandler);
}
else
{
_server.setHandler(gzipHandler);
gzipHandler.setHandler(context);
}
_server.start();
}
@ -219,7 +225,7 @@ public class GzipHandlerTest
out.setWriteListener(new WriteListener()
{
int count = writes == null ? 1 : Integer.valueOf(writes);
int count = writes == null ? 1 : Integer.parseInt(writes);
{
response.setContentLength(count * __bytes.length);
@ -233,6 +239,7 @@ public class GzipHandlerTest
if (count-- == 0)
{
out.close();
context.complete();
break;
}
@ -315,13 +322,18 @@ public class GzipHandlerTest
@AfterEach
public void destroy() throws Exception
{
_server.stop();
_server.join();
if (_server != null)
{
_server.stop();
_server.join();
}
}
@Test
public void testNotGzipHandler() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testNotGzipHandler(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -345,9 +357,11 @@ public class GzipHandlerTest
assertEquals(__content, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testBlockingResponse() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testBlockingResponse(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -372,9 +386,11 @@ public class GzipHandlerTest
assertEquals(__content, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testGzipNotModifiedVaryHeader() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipNotModifiedVaryHeader(boolean gzipInContext) throws Exception
{
init(gzipInContext);
HttpTester.Request request;
HttpTester.Response response;
@ -410,9 +426,11 @@ public class GzipHandlerTest
assertThat(response.getCSV("Vary", false), contains("Accept-Encoding"));
}
@Test
public void testAsyncResponse() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testAsyncResponse(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -436,9 +454,11 @@ public class GzipHandlerTest
assertEquals(__content, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testBufferResponse() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testBufferResponse(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -462,9 +482,11 @@ public class GzipHandlerTest
assertEquals(__content, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testAsyncLargeResponse() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testAsyncLargeResponse(boolean gzipInContext) throws Exception
{
init(gzipInContext);
int writes = 100;
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
@ -494,9 +516,11 @@ public class GzipHandlerTest
}
}
@Test
public void testAsyncEmptyResponse() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testAsyncEmptyResponse(boolean gzipInContext) throws Exception
{
init(gzipInContext);
int writes = 0;
_server.getDescendant(GzipHandler.class).setMinGzipSize(0);
@ -517,9 +541,11 @@ public class GzipHandlerTest
assertThat(response.getCSV("Vary", false), contains("Accept-Encoding"));
}
@Test
public void testGzipHandlerWithMultipleAcceptEncodingHeaders() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipHandlerWithMultipleAcceptEncodingHeaders(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -545,9 +571,11 @@ public class GzipHandlerTest
assertEquals(__content, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testGzipNotMicro() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipNotMicro(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -572,9 +600,11 @@ public class GzipHandlerTest
assertEquals(__micro, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testGzipNotMicroChunked() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipNotMicroChunked(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -601,9 +631,11 @@ public class GzipHandlerTest
assertEquals(__micro, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testETagNotGzipHandler() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testETagNotGzipHandler(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -622,9 +654,11 @@ public class GzipHandlerTest
assertThat(response.get("ETag"), is(__contentETag));
}
@Test
public void testETagGzipHandler() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testETagGzipHandler(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -643,9 +677,11 @@ public class GzipHandlerTest
assertThat(response.get("ETag"), is(__contentETagGzip));
}
@Test
public void testDeleteETagGzipHandler() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testDeleteETagGzipHandler(boolean gzipInContext) throws Exception
{
init(gzipInContext);
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -675,9 +711,11 @@ public class GzipHandlerTest
assertThat(response.get("Content-Encoding"), not(equalToIgnoringCase("gzip")));
}
@Test
public void testForwardGzipHandler() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testForwardGzipHandler(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -702,9 +740,11 @@ public class GzipHandlerTest
assertEquals(__content, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testIncludeGzipHandler() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testIncludeGzipHandler(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
@ -729,11 +769,14 @@ public class GzipHandlerTest
assertEquals(__icontent, testOut.toString(StandardCharsets.UTF_8));
}
@Test
public void testIncludeExcludeGzipHandlerInflate() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testIncludeExcludeGzipHandlerInflate(boolean gzipInContext) throws Exception
{
gzipHandler.addExcludedInflationPaths("/ctx/echo/exclude");
gzipHandler.addIncludedInflationPaths("/ctx/echo/include");
init(gzipInContext);
String path = gzipInContext ? "/echo" : "/ctx/echo";
gzipHandler.addExcludedInflationPaths(path + "/exclude");
gzipHandler.addIncludedInflationPaths(path + "/include");
String message = "hello world";
byte[] gzippedMessage = gzipContent(message);
@ -784,9 +827,11 @@ public class GzipHandlerTest
assertThat("Included Paths", Arrays.asList(includedPaths), contains("/foo", "^/bar.*$"));
}
@Test
public void testGzipRequest() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipRequest(boolean gzipInContext) throws Exception
{
init(gzipInContext);
String data = "Hello Nice World! ";
for (int i = 0; i < 10; ++i)
{
@ -816,9 +861,11 @@ public class GzipHandlerTest
assertThat(response.getContent(), is(data));
}
@Test
public void testGzipRequestChunked() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipRequestChunked(boolean gzipInContext) throws Exception
{
init(gzipInContext);
String data = "Hello Nice World! ";
for (int i = 0; i < 10; ++i)
{
@ -848,9 +895,11 @@ public class GzipHandlerTest
assertThat(response.getContent(), is(data));
}
@Test
public void testGzipFormRequest() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipFormRequest(boolean gzipInContext) throws Exception
{
init(gzipInContext);
String data = "name=value";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream output = new GZIPOutputStream(baos);
@ -876,9 +925,11 @@ public class GzipHandlerTest
assertThat(response.getContent(), is("name: value\n"));
}
@Test
public void testGzipBomb() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipBomb(boolean gzipInContext) throws Exception
{
init(gzipInContext);
byte[] data = new byte[512 * 1024];
Arrays.fill(data, (byte)'X');
@ -907,9 +958,11 @@ public class GzipHandlerTest
assertThat(response.getContentBytes().length, is(512 * 1024));
}
@Test
public void testGzipExcludeNewMimeType() throws Exception
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testGzipExcludeNewMimeType(boolean gzipInContext) throws Exception
{
init(gzipInContext);
// setting all excluded mime-types to a mimetype new mime-type
// Note: this mime-type does not exist in MimeTypes object.
gzipHandler.setExcludedMimeTypes("image/webfoo");
@ -955,11 +1008,6 @@ public class GzipHandlerTest
public static class CheckFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
@ -969,10 +1017,5 @@ public class GzipHandlerTest
assertThat(request.getParameter("X-Content-Encoding"), Matchers.nullValue());
chain.doFilter(request, response);
}
@Override
public void destroy()
{
}
}
}

View File

@ -2358,9 +2358,9 @@ public class ServletContextHandlerTest
static class CookieTweakResponseApi extends ServletApiResponse
{
CookieTweakResponseApi(ServletApiResponse response)
CookieTweakResponseApi(ServletContextResponse response)
{
super(response.getResponse());
super(response);
}
@Override
@ -2397,8 +2397,7 @@ public class ServletContextHandlerTest
@Override
protected ServletApiResponse newServletApiResponse()
{
ServletApiResponse servletApiResponse = super.newServletApiResponse();
return new CookieTweakResponseApi(super.newServletApiResponse());
return new CookieTweakResponseApi(this);
}
};
}

View File

@ -767,7 +767,6 @@ public class ServletHandlerTest
server.addConnector(connector);
server.start();
server.dumpStdErr();
assertThat(connector.getResponse("GET /default HTTP/1.0\r\n\r\n"), containsString("mapping='/'"));
assertThat(connector.getResponse("GET /foo HTTP/1.0\r\n\r\n"), containsString("mapping='/foo'"));

View File

@ -40,7 +40,6 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.Response;
import org.eclipse.jetty.ee10.servlet.HttpInput;
import org.eclipse.jetty.ee10.servlet.ListenerHolder;
import org.eclipse.jetty.ee10.servlet.ServletApiRequest;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.eclipse.jetty.http.HttpHeader;
@ -102,7 +101,7 @@ public class GzipWithSendErrorTest
{
if (onComplete != null)
{
ServletContextRequest servletContextRequest = ((ServletApiRequest)sre.getServletRequest()).getServletContextRequest();
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(sre.getServletRequest());
onComplete.accept(servletContextRequest);
}
}

View File

@ -3,16 +3,14 @@
<Configure id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection">
<Call name="addHandler">
<New class="org.eclipse.jetty.ee10.webapp.WebAppContext">
<Set name="contextPath">/test-jetty-webapp</Set>
<Set name="war"><Property name="test.webapps" default="." />/test-jetty-webapp.war</Set>
<Call name="insertHandler">
<Arg>
<New class="org.eclipse.jetty.server.handler.gzip.GzipHandler">
<Set name="minGzipSize">1024</Set>
</New>
</Arg>
</Call>
<New class="org.eclipse.jetty.server.handler.gzip.GzipHandler">
<Set name="minGzipSize">1024</Set>
<Set name="handler">
<New class="org.eclipse.jetty.ee10.webapp.WebAppContext">
<Set name="contextPath">/test-jetty-webapp</Set>
<Set name="war"><Property name="test.webapps" default="." />/test-jetty-webapp.war</Set>
</New>
</Set>
</New>
</Call>
</Configure>

View File

@ -31,7 +31,6 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration.Dynamic;
import jakarta.servlet.ServletSecurityElement;
import jakarta.servlet.http.HttpSessionActivationListener;
@ -1343,11 +1342,10 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
public class ServletApiContext extends ServletContextHandler.ServletContextApi
{
@Override
public ServletContext getContext(String uripath)
public jakarta.servlet.ServletContext getContext(String uripath)
{
ServletContext servletContext = super.getContext(uripath);
jakarta.servlet.ServletContext servletContext = super.getContext(uripath);
if (servletContext != null && _contextWhiteList != null)
{

View File

@ -303,7 +303,7 @@ public class JakartaWebSocketServerContainer extends JakartaWebSocketClientConta
Handshaker handshaker = webSocketMappings.getHandshaker();
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(request);
ServletContextResponse servletContextResponse = servletContextRequest.getResponse();
ServletContextResponse servletContextResponse = servletContextRequest.getServletContextResponse();
FutureCallback callback = new FutureCallback();
try

View File

@ -215,7 +215,7 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
};
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(request);
ServletContextResponse servletContextResponse = servletContextRequest.getResponse();
ServletContextResponse servletContextResponse = servletContextRequest.getServletContextResponse();
WebSocketNegotiator negotiator = WebSocketNegotiator.from(coreCreator, frameHandlerFactory);
Handshaker handshaker = webSocketMappings.getHandshaker();

View File

@ -183,7 +183,7 @@ public abstract class JettyWebSocketServlet extends HttpServlet
throws ServletException, IOException
{
ServletContextRequest request = ServletContextRequest.getServletContextRequest(req);
ServletContextResponse response = request.getResponse();
ServletContextResponse response = request.getServletContextResponse();
// Do preliminary check before proceeding to attempt an upgrade.
if (mapping.getHandshaker().isWebSocketUpgradeRequest(request))

View File

@ -158,7 +158,7 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(request);
ServletContextResponse servletContextResponse = servletContextRequest.getResponse();
ServletContextResponse servletContextResponse = servletContextRequest.getServletContextResponse();
// Do preliminary check before proceeding to attempt an upgrade.
if (mappings.getHandshaker().isWebSocketUpgradeRequest(servletContextRequest))

View File

@ -23,7 +23,6 @@ import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.file.Files;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
@ -1325,7 +1324,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
try
{
ReadableByteChannel rbc = Files.newByteChannel(httpContent.getResource().getPath());
ReadableByteChannel rbc = httpContent.getResource().newReadableByteChannel();
sendContent(rbc, callback);
}
catch (Throwable x)

View File

@ -13,8 +13,6 @@
package org.eclipse.jetty.ee9.servlets;
import java.nio.file.Path;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.junit.jupiter.api.BeforeEach;

View File

@ -18,5 +18,6 @@ webapps/root/
webapps/root/images/
basehome:modules/demo.d/root/index.html|webapps/root/index.html
basehome:modules/demo.d/root/jetty.css|webapps/root/jetty.css
basehome:modules/demo.d/root/favicon.ico|webapps/root/favicon.ico
basehome:modules/demo.d/root/images/jetty-pic.png|webapps/root/images/jetty-pic.png
basehome:modules/demo.d/root/images/webtide_logo.jpg|webapps/root/images/webtide_logo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -35,6 +35,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -579,4 +580,65 @@ public class DemoModulesTests extends AbstractJettyHomeTest
}
}
}
@ParameterizedTest
@MethodSource("provideEnvironmentsToTest")
public void testStaticContent(String env) throws Exception
{
Path jettyBase = newTestJettyBaseDirectory();
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.jettyBase(jettyBase)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
int httpPort = distribution.freePort();
String[] argsConfig =
{
"--add-modules=http," + toEnvironment("demos", env)
};
try (JettyHomeTester.Run runConfig = distribution.start(argsConfig))
{
assertTrue(runConfig.awaitFor(START_TIMEOUT, TimeUnit.SECONDS));
assertEquals(0, runConfig.getExitValue());
String[] argsStart =
{
"jetty.http.port=" + httpPort
};
try (JettyHomeTester.Run runStart = distribution.start(argsStart))
{
assertTrue(runStart.awaitConsoleLogsFor("Started oejs.Server@", START_TIMEOUT, TimeUnit.SECONDS));
startHttpClient();
String rootURI = "http://localhost:%d".formatted(httpPort);
String demoJettyURI = "%s/%s-test".formatted(rootURI, env);
ContentResponse response;
for (String welcome : new String[] {"", "/", "/index.html"})
{
response = client.GET(rootURI + welcome);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(response.getContentAsString(), containsString("Welcome to Jetty 12"));
response = client.GET(demoJettyURI + welcome);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat(response.getContentAsString(), containsString("Eclipse Jetty Demo Webapp"));
}
for (String directory : new String[] {rootURI + "/", demoJettyURI + "/", demoJettyURI + "/rewrite/"})
{
response = client.GET(directory + "jetty-dir.css");
assertThat(response.getStatus(), is(HttpStatus.OK_200));
}
response = client.GET(rootURI + "/favicon.ico");
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
}
}