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