diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java
index 86b02c31b9d2..047125085f68 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java
@@ -15,25 +15,15 @@
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStreamReader;
import java.net.URI;
-import java.nio.ByteBuffer;
import java.nio.file.InvalidPathException;
import java.time.Duration;
import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.Enumeration;
-import java.util.HashSet;
import java.util.List;
-import java.util.ListIterator;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.StringTokenizer;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.DispatcherType;
@@ -48,11 +38,8 @@
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpException;
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.http.HttpStatus;
-import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.content.FileMappingHttpContentFactory;
import org.eclipse.jetty.http.content.HttpContent;
@@ -60,7 +47,6 @@
import org.eclipse.jetty.http.content.ResourceHttpContentFactory;
import org.eclipse.jetty.http.content.ValidatingCachingHttpContentFactory;
import org.eclipse.jetty.http.content.VirtualHttpContentFactory;
-import org.eclipse.jetty.io.ByteBufferInputStream;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Request;
@@ -69,10 +55,8 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
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.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
@@ -80,6 +64,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.eclipse.jetty.util.URIUtil.encodePath;
+
/**
*
The default Servlet, normally mapped to {@code /}, that handles static resources.
* The following init parameters are supported:
@@ -567,9 +553,9 @@ protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse
protected String getEncodedPathInContext(HttpServletRequest req, String includedServletPath)
{
if (includedServletPath != null)
- return URIUtil.encodePath(getIncludedPathInContext(req, includedServletPath, !isDefaultMapping(req)));
+ return encodePath(getIncludedPathInContext(req, includedServletPath, !isDefaultMapping(req)));
else if (!isDefaultMapping(req))
- return URIUtil.encodePath(req.getPathInfo());
+ return encodePath(req.getPathInfo());
else if (req instanceof ServletApiRequest apiRequest)
return Context.getPathInContext(req.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath());
else
@@ -615,442 +601,6 @@ private Resource getResource(URI uri)
return result;
}
- private static class ServletCoreRequest extends Request.Wrapper
- {
- // TODO fully implement this class and move it to the top level
- // TODO Some methods are directed to core that probably should be intercepted
-
- private final HttpServletRequest _servletRequest;
- private final HttpFields _httpFields;
- private final HttpURI _uri;
-
- ServletCoreRequest(HttpServletRequest request)
- {
- super(ServletContextRequest.getServletContextRequest(request));
- _servletRequest = request;
-
- HttpFields.Mutable fields = HttpFields.build();
-
- Enumeration headerNames = request.getHeaderNames();
- while (headerNames.hasMoreElements())
- {
- String headerName = headerNames.nextElement();
- Enumeration headerValues = request.getHeaders(headerName);
- while (headerValues.hasMoreElements())
- {
- String headerValue = headerValues.nextElement();
- fields.add(new HttpField(headerName, headerValue));
- }
- }
-
- _httpFields = fields.asImmutable();
- String includedServletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
- boolean included = includedServletPath != null;
- if (request.getDispatcherType() == DispatcherType.REQUEST)
- _uri = getWrapped().getHttpURI();
- else if (included)
- _uri = Request.newHttpURIFrom(getWrapped(), URIUtil.encodePath(getIncludedPathInContext(request, includedServletPath, false)));
- else
- _uri = Request.newHttpURIFrom(getWrapped(), URIUtil.encodePath(URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo())));
- }
-
- @Override
- public HttpFields getHeaders()
- {
- return _httpFields;
- }
-
- @Override
- public HttpURI getHttpURI()
- {
- return _uri;
- }
-
- @Override
- public String getId()
- {
- return _servletRequest.getRequestId();
- }
-
- @Override
- public String getMethod()
- {
- return _servletRequest.getMethod();
- }
-
- @Override
- public boolean isSecure()
- {
- return _servletRequest.isSecure();
- }
-
- @Override
- public Object removeAttribute(String name)
- {
- Object value = _servletRequest.getAttribute(name);
- _servletRequest.removeAttribute(name);
- return value;
- }
-
- @Override
- public Object setAttribute(String name, Object attribute)
- {
- Object value = _servletRequest.getAttribute(name);
- _servletRequest.setAttribute(name, attribute);
- return value;
- }
-
- @Override
- public Object getAttribute(String name)
- {
- return _servletRequest.getAttribute(name);
- }
-
- @Override
- public Set getAttributeNameSet()
- {
- Set set = new HashSet<>();
- Enumeration e = _servletRequest.getAttributeNames();
- while (e.hasMoreElements())
- set.add(e.nextElement());
- return set;
- }
-
- @Override
- public void clearAttributes()
- {
- Enumeration e = _servletRequest.getAttributeNames();
- while (e.hasMoreElements())
- _servletRequest.removeAttribute(e.nextElement());
- }
- }
-
- private static class HttpServletResponseHttpFields implements HttpFields.Mutable
- {
- private final HttpServletResponse _response;
-
- private HttpServletResponseHttpFields(HttpServletResponse response)
- {
- _response = response;
- }
-
- @Override
- public ListIterator listIterator()
- {
- // The minimum requirement is to implement the listIterator, but it is inefficient.
- // Other methods are implemented for efficiency.
- final ListIterator list = _response.getHeaderNames().stream()
- .map(n -> new HttpField(n, _response.getHeader(n)))
- .collect(Collectors.toList())
- .listIterator();
-
- return new ListIterator<>()
- {
- HttpField _last;
-
- @Override
- public boolean hasNext()
- {
- return list.hasNext();
- }
-
- @Override
- public HttpField next()
- {
- return _last = list.next();
- }
-
- @Override
- public boolean hasPrevious()
- {
- return list.hasPrevious();
- }
-
- @Override
- public HttpField previous()
- {
- return _last = list.previous();
- }
-
- @Override
- public int nextIndex()
- {
- return list.nextIndex();
- }
-
- @Override
- public int previousIndex()
- {
- return list.previousIndex();
- }
-
- @Override
- public void remove()
- {
- if (_last != null)
- {
- // This is not exactly the right semantic for repeated field names
- list.remove();
- _response.setHeader(_last.getName(), null);
- }
- }
-
- @Override
- public void set(HttpField httpField)
- {
- list.set(httpField);
- _response.setHeader(httpField.getName(), httpField.getValue());
- }
-
- @Override
- public void add(HttpField httpField)
- {
- list.add(httpField);
- _response.addHeader(httpField.getName(), httpField.getValue());
- }
- };
- }
-
- @Override
- public Mutable add(String name, String value)
- {
- _response.addHeader(name, value);
- return this;
- }
-
- @Override
- public Mutable add(HttpHeader header, HttpHeaderValue value)
- {
- _response.addHeader(header.asString(), value.asString());
- return this;
- }
-
- @Override
- public Mutable add(HttpHeader header, String value)
- {
- _response.addHeader(header.asString(), value);
- return this;
- }
-
- @Override
- public Mutable add(HttpField field)
- {
- _response.addHeader(field.getName(), field.getValue());
- return this;
- }
-
- @Override
- public Mutable put(HttpField field)
- {
- _response.setHeader(field.getName(), field.getValue());
- return this;
- }
-
- @Override
- public Mutable put(String name, String value)
- {
- _response.setHeader(name, value);
- return this;
- }
-
- @Override
- public Mutable put(HttpHeader header, HttpHeaderValue value)
- {
- _response.setHeader(header.asString(), value.asString());
- return this;
- }
-
- @Override
- public Mutable put(HttpHeader header, String value)
- {
- _response.setHeader(header.asString(), value);
- return this;
- }
-
- @Override
- public Mutable put(String name, List list)
- {
- Objects.requireNonNull(name);
- Objects.requireNonNull(list);
- boolean first = true;
- for (String s : list)
- {
- if (first)
- _response.setHeader(name, s);
- else
- _response.addHeader(name, s);
- first = false;
- }
- return this;
- }
-
- @Override
- public Mutable remove(HttpHeader header)
- {
- _response.setHeader(header.asString(), null);
- return this;
- }
-
- @Override
- public Mutable remove(EnumSet fields)
- {
- for (HttpHeader header : fields)
- remove(header);
- return this;
- }
-
- @Override
- public Mutable remove(String name)
- {
- _response.setHeader(name, null);
- return this;
- }
- }
-
- private static class ServletCoreResponse extends Response.Wrapper
- {
- // TODO fully implement this class and move it to the top level
-
- private final HttpServletResponse _response;
- private final Request _coreRequest;
- private final HttpFields.Mutable _httpFields;
- private final boolean _included;
-
- public ServletCoreResponse(Request coreRequest, HttpServletResponse response, boolean included)
- {
- super(coreRequest, ServletContextResponse.getServletContextResponse(response));
- _coreRequest = coreRequest;
- _response = response;
- HttpFields.Mutable fields = new HttpServletResponseHttpFields(response);
- if (included)
- {
- // If included, accept but ignore mutations.
- fields = new HttpFields.Mutable.Wrapper(fields)
- {
- @Override
- public HttpField onAddField(HttpField field)
- {
- return null;
- }
-
- @Override
- public boolean onRemoveField(HttpField field)
- {
- return false;
- }
- };
- }
- _httpFields = fields;
- _included = included;
- }
-
- @Override
- public HttpFields.Mutable getHeaders()
- {
- return _httpFields;
- }
-
- @Override
- public boolean isCommitted()
- {
- return _response.isCommitted();
- }
-
- public boolean isWriting()
- {
- ServletContextResponse servletContextResponse = Response.as(getWrapped(), ServletContextResponse.class);
- return servletContextResponse.isWriting();
- }
-
- @Override
- public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
- {
- if (_included)
- last = false;
- try
- {
- if (BufferUtil.hasContent(byteBuffer))
- {
- if (isWriting())
- {
- String characterEncoding = _response.getCharacterEncoding();
- try (ByteBufferInputStream bbis = new ByteBufferInputStream(byteBuffer);
- InputStreamReader reader = new InputStreamReader(bbis, characterEncoding))
- {
- IO.copy(reader, _response.getWriter());
- }
-
- if (last)
- _response.getWriter().close();
- }
- else
- {
- BufferUtil.writeTo(byteBuffer, _response.getOutputStream());
- if (last)
- _response.getOutputStream().close();
- }
- }
-
- callback.succeeded();
- }
- catch (Throwable t)
- {
- callback.failed(t);
- }
- }
-
- @Override
- public Request getRequest()
- {
- return _coreRequest;
- }
-
- @Override
- public int getStatus()
- {
- return _response.getStatus();
- }
-
- @Override
- public void setStatus(int code)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{}.setStatus({})", this.getClass().getSimpleName(), code);
- if (_included)
- return;
- _response.setStatus(code);
- }
-
- @Override
- public Supplier getTrailersSupplier()
- {
- return null;
- }
-
- @Override
- public void setTrailersSupplier(Supplier trailers)
- {
- }
-
- @Override
- public void reset()
- {
- _response.reset();
- }
-
- @Override
- public CompletableFuture writeInterim(int status, HttpFields headers)
- {
- 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
{
private final ServletContextHandler _servletContextHandler;
@@ -1208,7 +758,7 @@ private HttpServletRequest getServletRequest(Request request)
{
ServletCoreRequest servletCoreRequest = Request.as(request, ServletCoreRequest.class);
if (servletCoreRequest != null)
- return servletCoreRequest._servletRequest;
+ return servletCoreRequest.getServletRequest();
ServletContextRequest servletContextRequest = Request.as(request, ServletContextRequest.class);
if (servletContextRequest != null)
@@ -1221,7 +771,7 @@ private HttpServletResponse getServletResponse(Response response)
{
ServletCoreResponse servletCoreResponse = Response.as(response, ServletCoreResponse.class);
if (servletCoreResponse != null)
- return servletCoreResponse._response;
+ return servletCoreResponse.getServletResponse();
ServletContextResponse servletContextResponse = Response.as(response, ServletContextResponse.class);
if (servletContextResponse != null)
@@ -1231,7 +781,7 @@ private HttpServletResponse getServletResponse(Response response)
}
}
- private static String getIncludedPathInContext(HttpServletRequest request, String includedServletPath, boolean isPathInfoOnly)
+ static String getIncludedPathInContext(HttpServletRequest request, String includedServletPath, boolean isPathInfoOnly)
{
String servletPath = isPathInfoOnly ? "/" : includedServletPath;
String pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreRequest.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreRequest.java
new file mode 100644
index 000000000000..02514b3b6949
--- /dev/null
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreRequest.java
@@ -0,0 +1,258 @@
+//
+// ========================================================================
+// 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.ee10.servlet;
+
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import jakarta.servlet.DispatcherType;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.http.HttpServletRequest;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.server.Components;
+import org.eclipse.jetty.server.ConnectionMetaData;
+import org.eclipse.jetty.server.Context;
+import org.eclipse.jetty.server.HttpStream;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Session;
+import org.eclipse.jetty.server.TunnelSupport;
+import org.eclipse.jetty.util.URIUtil;
+
+import static org.eclipse.jetty.util.URIUtil.addEncodedPaths;
+import static org.eclipse.jetty.util.URIUtil.encodePath;
+
+/**
+ * Wrap a {@link jakarta.servlet.ServletRequest} as a core {@link Request}.
+ *
+ * Whilst similar to a {@link Request.Wrapper}, this class is not a {@code Wrapper}
+ * as callers should not be able to access {@link Wrapper#getWrapped()} and bypass
+ * the {@link jakarta.servlet.ServletRequest}.
+ *
+ */
+class ServletCoreRequest implements Request
+{
+ private final HttpServletRequest _servletRequest;
+ private final ServletContextRequest _servletContextRequest;
+ private final HttpFields _httpFields;
+ private final HttpURI _uri;
+
+ ServletCoreRequest(HttpServletRequest request)
+ {
+ _servletRequest = request;
+ _servletContextRequest = ServletContextRequest.getServletContextRequest(_servletRequest);
+
+ HttpFields.Mutable fields = HttpFields.build();
+
+ Enumeration headerNames = request.getHeaderNames();
+ while (headerNames.hasMoreElements())
+ {
+ String headerName = headerNames.nextElement();
+ Enumeration headerValues = request.getHeaders(headerName);
+ while (headerValues.hasMoreElements())
+ {
+ String headerValue = headerValues.nextElement();
+ fields.add(new HttpField(headerName, headerValue));
+ }
+ }
+
+ _httpFields = fields.asImmutable();
+ String includedServletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+ boolean included = includedServletPath != null;
+
+ HttpURI.Mutable builder = HttpURI.build(request.getRequestURI());
+ if (included)
+ builder.path(addEncodedPaths(request.getContextPath(), encodePath(DefaultServlet.getIncludedPathInContext(request, includedServletPath, false))));
+ else if (request.getDispatcherType() != DispatcherType.REQUEST)
+ builder.path(addEncodedPaths(request.getContextPath(), encodePath(URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo()))));
+ builder.query(request.getQueryString());
+ _uri = builder.asImmutable();
+ }
+
+ @Override
+ public HttpFields getHeaders()
+ {
+ return _httpFields;
+ }
+
+ @Override
+ public HttpURI getHttpURI()
+ {
+ return _uri;
+ }
+
+ @Override
+ public String getId()
+ {
+ return _servletRequest.getRequestId();
+ }
+
+ @Override
+ public String getMethod()
+ {
+ return _servletRequest.getMethod();
+ }
+
+ public HttpServletRequest getServletRequest()
+ {
+ return _servletRequest;
+ }
+
+ @Override
+ public boolean isSecure()
+ {
+ return _servletRequest.isSecure();
+ }
+
+ @Override
+ public Object removeAttribute(String name)
+ {
+ Object value = _servletRequest.getAttribute(name);
+ _servletRequest.removeAttribute(name);
+ return value;
+ }
+
+ @Override
+ public Object setAttribute(String name, Object attribute)
+ {
+ Object value = _servletRequest.getAttribute(name);
+ _servletRequest.setAttribute(name, attribute);
+ return value;
+ }
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ return _servletRequest.getAttribute(name);
+ }
+
+ @Override
+ public Set getAttributeNameSet()
+ {
+ Set set = new HashSet<>();
+ Enumeration e = _servletRequest.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ set.add(e.nextElement());
+ }
+ return set;
+ }
+
+ @Override
+ public void clearAttributes()
+ {
+ Enumeration e = _servletRequest.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ _servletRequest.removeAttribute(e.nextElement());
+ }
+ }
+
+ @Override
+ public void fail(Throwable failure)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Components getComponents()
+ {
+ return _servletContextRequest.getComponents();
+ }
+
+ @Override
+ public ConnectionMetaData getConnectionMetaData()
+ {
+ return _servletContextRequest.getConnectionMetaData();
+ }
+
+ @Override
+ public Context getContext()
+ {
+ return _servletContextRequest.getContext();
+ }
+
+ @Override
+ public void demand(Runnable demandCallback)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpFields getTrailers()
+ {
+ return _servletContextRequest.getTrailers();
+ }
+
+ @Override
+ public long getBeginNanoTime()
+ {
+ return _servletContextRequest.getBeginNanoTime();
+ }
+
+ @Override
+ public long getHeadersNanoTime()
+ {
+ return _servletContextRequest.getHeadersNanoTime();
+ }
+
+ @Override
+ public Content.Chunk read()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean consumeAvailable()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addIdleTimeoutListener(Predicate onIdleTimeout)
+ {
+ _servletContextRequest.addIdleTimeoutListener(onIdleTimeout);
+ }
+
+ @Override
+ public void addFailureListener(Consumer onFailure)
+ {
+ _servletContextRequest.addFailureListener(onFailure);
+ }
+
+ @Override
+ public TunnelSupport getTunnelSupport()
+ {
+ return null;
+ }
+
+ @Override
+ public void addHttpStreamWrapper(Function wrapper)
+ {
+ _servletContextRequest.addHttpStreamWrapper(wrapper);
+ }
+
+ @Override
+ public Session getSession(boolean create)
+ {
+ return Session.getSession(_servletRequest.getSession(create));
+ }
+}
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreResponse.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreResponse.java
new file mode 100644
index 000000000000..46c65df2624b
--- /dev/null
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreResponse.java
@@ -0,0 +1,378 @@
+//
+// ========================================================================
+// 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.ee10.servlet;
+
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import jakarta.servlet.http.HttpServletResponse;
+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.io.ByteBufferInputStream;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+
+/**
+ * A {@link HttpServletResponse} wrapped as a core {@link Response}.
+ */
+class ServletCoreResponse implements Response
+{
+ private final HttpServletResponse _response;
+ private final Request _coreRequest;
+ private final HttpFields.Mutable _httpFields;
+ private final boolean _included;
+ private final ServletContextResponse _servletContextResponse;
+
+ public ServletCoreResponse(Request coreRequest, HttpServletResponse response, boolean included)
+ {
+ _coreRequest = coreRequest;
+ _response = response;
+ _servletContextResponse = ServletContextResponse.getServletContextResponse(response);
+ HttpFields.Mutable fields = new HttpServletResponseHttpFields(response);
+ if (included)
+ {
+ // If included, accept but ignore mutations.
+ fields = new HttpFields.Mutable.Wrapper(fields)
+ {
+ @Override
+ public HttpField onAddField(HttpField field)
+ {
+ return null;
+ }
+
+ @Override
+ public boolean onRemoveField(HttpField field)
+ {
+ return false;
+ }
+ };
+ }
+ _httpFields = fields;
+ _included = included;
+ }
+
+ @Override
+ public HttpFields.Mutable getHeaders()
+ {
+ return _httpFields;
+ }
+
+ public HttpServletResponse getServletResponse()
+ {
+ return _response;
+ }
+
+ @Override
+ public boolean isLast()
+ {
+ return _servletContextResponse.isLast();
+ }
+
+ @Override
+ public boolean isCompletedSuccessfully()
+ {
+ return _servletContextResponse.isCompletedSuccessfully();
+ }
+
+ @Override
+ public boolean isCommitted()
+ {
+ return _response.isCommitted();
+ }
+
+ public boolean isWriting()
+ {
+ return _servletContextResponse.isWriting();
+ }
+
+ @Override
+ public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
+ {
+ if (_included)
+ last = false;
+ try
+ {
+ if (BufferUtil.hasContent(byteBuffer))
+ {
+ if (isWriting())
+ {
+ String characterEncoding = _response.getCharacterEncoding();
+ try (ByteBufferInputStream bbis = new ByteBufferInputStream(byteBuffer);
+ InputStreamReader reader = new InputStreamReader(bbis, characterEncoding))
+ {
+ IO.copy(reader, _response.getWriter());
+ }
+
+ if (last)
+ _response.getWriter().close();
+ }
+ else
+ {
+ BufferUtil.writeTo(byteBuffer, _response.getOutputStream());
+ if (last)
+ _response.getOutputStream().close();
+ }
+ }
+
+ callback.succeeded();
+ }
+ catch (Throwable t)
+ {
+ callback.failed(t);
+ }
+ }
+
+ @Override
+ public Request getRequest()
+ {
+ return _coreRequest;
+ }
+
+ @Override
+ public int getStatus()
+ {
+ return _response.getStatus();
+ }
+
+ @Override
+ public void setStatus(int code)
+ {
+ if (_included)
+ return;
+ _response.setStatus(code);
+ }
+
+ @Override
+ public Supplier getTrailersSupplier()
+ {
+ return null;
+ }
+
+ @Override
+ public void setTrailersSupplier(Supplier trailers)
+ {
+ }
+
+ @Override
+ public void reset()
+ {
+ _response.reset();
+ }
+
+ @Override
+ public CompletableFuture writeInterim(int status, HttpFields headers)
+ {
+ return null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "%s@%x{%s,%s}".formatted(this.getClass().getSimpleName(), hashCode(), this._coreRequest, _response);
+ }
+
+ private static class HttpServletResponseHttpFields implements HttpFields.Mutable
+ {
+ private final HttpServletResponse _response;
+
+ private HttpServletResponseHttpFields(HttpServletResponse response)
+ {
+ _response = response;
+ }
+
+ @Override
+ public ListIterator listIterator()
+ {
+ // The minimum requirement is to implement the listIterator, but it is inefficient.
+ // Other methods are implemented for efficiency.
+ final ListIterator list = _response.getHeaderNames().stream()
+ .map(n -> new HttpField(n, _response.getHeader(n)))
+ .collect(Collectors.toList())
+ .listIterator();
+
+ return new ListIterator<>()
+ {
+ HttpField _last;
+
+ @Override
+ public boolean hasNext()
+ {
+ return list.hasNext();
+ }
+
+ @Override
+ public HttpField next()
+ {
+ return _last = list.next();
+ }
+
+ @Override
+ public boolean hasPrevious()
+ {
+ return list.hasPrevious();
+ }
+
+ @Override
+ public HttpField previous()
+ {
+ return _last = list.previous();
+ }
+
+ @Override
+ public int nextIndex()
+ {
+ return list.nextIndex();
+ }
+
+ @Override
+ public int previousIndex()
+ {
+ return list.previousIndex();
+ }
+
+ @Override
+ public void remove()
+ {
+ if (_last != null)
+ {
+ // This is not exactly the right semantic for repeated field names
+ list.remove();
+ _response.setHeader(_last.getName(), null);
+ }
+ }
+
+ @Override
+ public void set(HttpField httpField)
+ {
+ list.set(httpField);
+ _response.setHeader(httpField.getName(), httpField.getValue());
+ }
+
+ @Override
+ public void add(HttpField httpField)
+ {
+ list.add(httpField);
+ _response.addHeader(httpField.getName(), httpField.getValue());
+ }
+ };
+ }
+
+ @Override
+ public Mutable add(String name, String value)
+ {
+ _response.addHeader(name, value);
+ return this;
+ }
+
+ @Override
+ public Mutable add(HttpHeader header, HttpHeaderValue value)
+ {
+ _response.addHeader(header.asString(), value.asString());
+ return this;
+ }
+
+ @Override
+ public Mutable add(HttpHeader header, String value)
+ {
+ _response.addHeader(header.asString(), value);
+ return this;
+ }
+
+ @Override
+ public Mutable add(HttpField field)
+ {
+ _response.addHeader(field.getName(), field.getValue());
+ return this;
+ }
+
+ @Override
+ public Mutable put(HttpField field)
+ {
+ _response.setHeader(field.getName(), field.getValue());
+ return this;
+ }
+
+ @Override
+ public Mutable put(String name, String value)
+ {
+ _response.setHeader(name, value);
+ return this;
+ }
+
+ @Override
+ public Mutable put(HttpHeader header, HttpHeaderValue value)
+ {
+ _response.setHeader(header.asString(), value.asString());
+ return this;
+ }
+
+ @Override
+ public Mutable put(HttpHeader header, String value)
+ {
+ _response.setHeader(header.asString(), value);
+ return this;
+ }
+
+ @Override
+ public Mutable put(String name, List list)
+ {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(list);
+ boolean first = true;
+ for (String s : list)
+ {
+ if (first)
+ _response.setHeader(name, s);
+ else
+ _response.addHeader(name, s);
+ first = false;
+ }
+ return this;
+ }
+
+ @Override
+ public Mutable remove(HttpHeader header)
+ {
+ _response.setHeader(header.asString(), null);
+ return this;
+ }
+
+ @Override
+ public Mutable remove(EnumSet fields)
+ {
+ for (HttpHeader header : fields)
+ remove(header);
+ return this;
+ }
+
+ @Override
+ public Mutable remove(String name)
+ {
+ _response.setHeader(name, null);
+ return this;
+ }
+ }
+}