Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-WebSocketDemand
This commit is contained in:
commit
2030afea62
|
@ -0,0 +1,153 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.docs.programming;
|
||||
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.content.AsyncContent;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ContentDocs
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ContentDocs.class);
|
||||
|
||||
// tag::echo[]
|
||||
public void echo(Content.Source source, Content.Sink sink, Callback callback)
|
||||
{
|
||||
Callback echo = new Callback()
|
||||
{
|
||||
private Content.Chunk chunk;
|
||||
|
||||
public void succeeded()
|
||||
{
|
||||
// release any previous chunk
|
||||
if (chunk != null)
|
||||
{
|
||||
chunk.release();
|
||||
// complete if it was the last
|
||||
if (chunk.isLast())
|
||||
{
|
||||
callback.succeeded();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// read the next chunk
|
||||
chunk = source.read();
|
||||
|
||||
if (chunk == null)
|
||||
{
|
||||
// if no chunk, demand more and call succeeded when demand is met.
|
||||
source.demand(this::succeeded);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Content.Chunk.isFailure(chunk, true))
|
||||
{
|
||||
// if it is a persistent failure, then fail the callback
|
||||
callback.failed(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk.hasRemaining() || chunk.isLast())
|
||||
{
|
||||
// if chunk has content or is last, write it to the sink and resume this loop in callback
|
||||
sink.write(chunk.isLast(), chunk.getByteBuffer(), this);
|
||||
return;
|
||||
}
|
||||
|
||||
chunk.release();
|
||||
}
|
||||
}
|
||||
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
source.fail(x);
|
||||
callback.failed(x);
|
||||
}
|
||||
};
|
||||
source.demand(echo::succeeded);
|
||||
}
|
||||
// tag::echo[]
|
||||
|
||||
public static void testEcho() throws Exception
|
||||
{
|
||||
AsyncContent source = new AsyncContent();
|
||||
AsyncContent sink = new AsyncContent();
|
||||
|
||||
Callback.Completable echoCallback = new Callback.Completable();
|
||||
new ContentDocs().echo(source, sink, echoCallback);
|
||||
|
||||
Content.Chunk chunk = sink.read();
|
||||
if (chunk != null)
|
||||
throw new IllegalStateException("No chunk expected yet");
|
||||
|
||||
FutureCallback onContentAvailable = new FutureCallback();
|
||||
sink.demand(onContentAvailable::succeeded);
|
||||
if (onContentAvailable.isDone())
|
||||
throw new IllegalStateException("No demand expected yet");
|
||||
|
||||
Callback.Completable writeCallback = new Callback.Completable();
|
||||
Content.Sink.write(source, false, "One", writeCallback);
|
||||
if (writeCallback.isDone())
|
||||
throw new IllegalStateException("Should wait until first chunk is consumed");
|
||||
|
||||
onContentAvailable.get();
|
||||
chunk = sink.read();
|
||||
if (!"One".equals(BufferUtil.toString(chunk.getByteBuffer())))
|
||||
throw new IllegalStateException("first chunk is expected");
|
||||
|
||||
if (writeCallback.isDone())
|
||||
throw new IllegalStateException("Should wait until first chunk is consumed");
|
||||
chunk.release();
|
||||
writeCallback.get();
|
||||
|
||||
|
||||
writeCallback = new Callback.Completable();
|
||||
Content.Sink.write(source, true, "Two", writeCallback);
|
||||
if (writeCallback.isDone())
|
||||
throw new IllegalStateException("Should wait until second chunk is consumed");
|
||||
|
||||
onContentAvailable = new FutureCallback();
|
||||
sink.demand(onContentAvailable::succeeded);
|
||||
if (!onContentAvailable.isDone())
|
||||
throw new IllegalStateException("Demand expected for second chunk");
|
||||
|
||||
chunk = sink.read();
|
||||
if (!"Two".equals(BufferUtil.toString(chunk.getByteBuffer())))
|
||||
throw new IllegalStateException("second chunk is expected");
|
||||
chunk.release();
|
||||
writeCallback.get();
|
||||
|
||||
onContentAvailable = new FutureCallback();
|
||||
sink.demand(onContentAvailable::succeeded);
|
||||
if (!onContentAvailable.isDone())
|
||||
throw new IllegalStateException("Demand expected for EOF");
|
||||
|
||||
chunk = sink.read();
|
||||
if (!chunk.isLast())
|
||||
throw new IllegalStateException("EOF expected");
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Exception
|
||||
{
|
||||
testEcho();
|
||||
}
|
||||
}
|
|
@ -78,7 +78,7 @@ public class SessionDocs
|
|||
//tag:schsession[]
|
||||
Server server = new Server();
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(server, "/foo", ServletContextHandler.SESSIONS);
|
||||
ServletContextHandler context = new ServletContextHandler("/foo", ServletContextHandler.SESSIONS);
|
||||
SessionHandler sessions = context.getSessionHandler();
|
||||
//make idle sessions valid for only 5mins
|
||||
sessions.setMaxInactiveInterval(300);
|
||||
|
|
|
@ -63,7 +63,7 @@ public class WebSocketServerDocs
|
|||
Server server = new Server(8080);
|
||||
|
||||
// Create a ServletContextHandler with the given context path.
|
||||
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
|
||||
ServletContextHandler handler = new ServletContextHandler("/ctx");
|
||||
server.setHandler(handler);
|
||||
|
||||
// Ensure that JavaxWebSocketServletContainerInitializer is initialized,
|
||||
|
@ -82,7 +82,7 @@ public class WebSocketServerDocs
|
|||
Server server = new Server(8080);
|
||||
|
||||
// Create a ServletContextHandler with the given context path.
|
||||
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
|
||||
ServletContextHandler handler = new ServletContextHandler("/ctx");
|
||||
server.setHandler(handler);
|
||||
|
||||
// Ensure that JavaxWebSocketServletContainerInitializer is initialized,
|
||||
|
@ -137,7 +137,7 @@ public class WebSocketServerDocs
|
|||
Server server = new Server(8080);
|
||||
|
||||
// Create a ServletContextHandler with the given context path.
|
||||
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
|
||||
ServletContextHandler handler = new ServletContextHandler("/ctx");
|
||||
server.setHandler(handler);
|
||||
|
||||
// Setup the ServerContainer and the WebSocket endpoints for this web application context.
|
||||
|
@ -169,7 +169,7 @@ public class WebSocketServerDocs
|
|||
Server server = new Server(8080);
|
||||
|
||||
// Create a ServletContextHandler with the given context path.
|
||||
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
|
||||
ServletContextHandler handler = new ServletContextHandler("/ctx");
|
||||
server.setHandler(handler);
|
||||
|
||||
// Ensure that JettyWebSocketServletContainerInitializer is initialized,
|
||||
|
@ -188,7 +188,7 @@ public class WebSocketServerDocs
|
|||
Server server = new Server(8080);
|
||||
|
||||
// Create a ServletContextHandler with the given context path.
|
||||
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
|
||||
ServletContextHandler handler = new ServletContextHandler("/ctx");
|
||||
server.setHandler(handler);
|
||||
|
||||
// Ensure that JettyWebSocketServletContainerInitializer is initialized,
|
||||
|
@ -234,7 +234,7 @@ public class WebSocketServerDocs
|
|||
Server server = new Server(8080);
|
||||
|
||||
// Create a ServletContextHandler with the given context path.
|
||||
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
|
||||
ServletContextHandler handler = new ServletContextHandler("/ctx");
|
||||
server.setHandler(handler);
|
||||
|
||||
// Setup the JettyWebSocketServerContainer and the WebSocket endpoints for this web application context.
|
||||
|
@ -301,7 +301,7 @@ public class WebSocketServerDocs
|
|||
Server server = new Server(8080);
|
||||
|
||||
// Create a ServletContextHandler with the given context path.
|
||||
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
|
||||
ServletContextHandler handler = new ServletContextHandler("/ctx");
|
||||
server.setHandler(handler);
|
||||
|
||||
// Setup the JettyWebSocketServerContainer to initialize WebSocket components.
|
||||
|
@ -357,7 +357,8 @@ public class WebSocketServerDocs
|
|||
Server server = new Server(8080);
|
||||
|
||||
// tag::uriTemplatePathSpec[]
|
||||
ServletContextHandler handler = new ServletContextHandler(server, "/ctx");
|
||||
ServletContextHandler handler = new ServletContextHandler("/ctx");
|
||||
server.setHandler(handler);
|
||||
|
||||
// Configure the JettyWebSocketServerContainer.
|
||||
JettyWebSocketServletContainerInitializer.configure(handler, (servletContext, container) ->
|
||||
|
|
|
@ -175,9 +175,9 @@ public interface Response
|
|||
contentSource.demand(demandCallback);
|
||||
return;
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
response.abort(error.getCause());
|
||||
response.abort(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
if (chunk.isLast() && !chunk.hasRemaining())
|
||||
|
|
|
@ -554,7 +554,7 @@ public abstract class HttpReceiver
|
|||
_chunk = inputChunk;
|
||||
if (_chunk == null)
|
||||
return null;
|
||||
if (_chunk instanceof Content.Chunk.Error)
|
||||
if (Content.Chunk.isFailure(_chunk))
|
||||
return _chunk;
|
||||
|
||||
// Retain the input chunk because its ByteBuffer will be referenced by the Inflater.
|
||||
|
@ -748,7 +748,7 @@ public abstract class HttpReceiver
|
|||
LOG.debug("Erroring {}", this);
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
if (currentChunk instanceof Content.Chunk.Error)
|
||||
if (Content.Chunk.isFailure(currentChunk))
|
||||
return false;
|
||||
if (currentChunk != null)
|
||||
currentChunk.release();
|
||||
|
|
|
@ -503,8 +503,8 @@ public abstract class HttpSender
|
|||
}
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
throw error.getCause();
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
throw chunk.getFailure();
|
||||
|
||||
ByteBuffer buffer = chunk.getByteBuffer();
|
||||
contentBuffer = buffer.asReadOnlyBuffer();
|
||||
|
|
|
@ -676,7 +676,7 @@ public class ResponseListeners
|
|||
Content.Chunk currentChunk = chunk;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Content source #{} fail while current chunk is {}", index, currentChunk);
|
||||
if (currentChunk instanceof Content.Chunk.Error)
|
||||
if (Content.Chunk.isFailure(currentChunk))
|
||||
return;
|
||||
if (currentChunk != null)
|
||||
currentChunk.release();
|
||||
|
|
|
@ -176,8 +176,8 @@ public class ConnectionPoolTest
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
throw error.getCause();
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
throw chunk.getFailure();
|
||||
|
||||
if (chunk.hasRemaining())
|
||||
{
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest
|
||||
|
@ -248,11 +247,11 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest
|
|||
.onResponseContentSource((response, contentSource) -> response.abort(new Throwable()).whenComplete((failed, x) ->
|
||||
{
|
||||
Content.Chunk chunk = contentSource.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
contentSource.demand(() ->
|
||||
{
|
||||
Content.Chunk c = contentSource.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, c);
|
||||
assertTrue(Content.Chunk.isFailure(c, true));
|
||||
errorContentLatch.countDown();
|
||||
});
|
||||
}))
|
||||
|
|
|
@ -197,7 +197,7 @@ public class HttpStreamOverFCGI implements HttpStream
|
|||
{
|
||||
if (_chunk == null)
|
||||
_chunk = Content.Chunk.EOF;
|
||||
else if (!_chunk.isLast() && !(_chunk instanceof Content.Chunk.Error))
|
||||
else if (!_chunk.isLast() && !(Content.Chunk.isFailure(_chunk)))
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -563,7 +563,7 @@ public class MultiPart
|
|||
private State state = State.FIRST;
|
||||
private boolean closed;
|
||||
private Runnable demand;
|
||||
private Content.Chunk.Error errorChunk;
|
||||
private Content.Chunk errorChunk;
|
||||
private Part part;
|
||||
|
||||
public AbstractContentSource(String boundary)
|
||||
|
@ -759,7 +759,7 @@ public class MultiPart
|
|||
case CONTENT ->
|
||||
{
|
||||
Content.Chunk chunk = part.getContentSource().read();
|
||||
if (chunk == null || chunk instanceof Content.Chunk.Error)
|
||||
if (chunk == null || Content.Chunk.isFailure(chunk))
|
||||
yield chunk;
|
||||
if (!chunk.isLast())
|
||||
yield chunk;
|
||||
|
|
|
@ -101,9 +101,9 @@ public class MultiPartByteRanges extends CompletableFuture<MultiPartByteRanges.P
|
|||
content.demand(this);
|
||||
return;
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
listener.onFailure(error.getCause());
|
||||
listener.onFailure(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
parse(chunk);
|
||||
|
|
|
@ -117,9 +117,9 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
|
|||
content.demand(this);
|
||||
return;
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
listener.onFailure(error.getCause());
|
||||
listener.onFailure(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
parse(chunk);
|
||||
|
|
|
@ -177,7 +177,7 @@ wml=text/vnd.wap.wml
|
|||
wmlc=application/vnd.wap.wmlc
|
||||
wmls=text/vnd.wap.wmlscript
|
||||
wmlsc=application/vnd.wap.wmlscriptc
|
||||
woff=application/font-woff
|
||||
woff=font/woff
|
||||
woff2=font/woff2
|
||||
wrl=model/vrml
|
||||
wtls-ca-certificate=application/vnd.wap.wtls-ca-certificate
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -607,9 +607,9 @@ public class IdleTimeoutTest extends AbstractTest
|
|||
_request.demand(this::onContentAvailable);
|
||||
return;
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
_callback.failed(error.getCause());
|
||||
_callback.failed(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
chunk.release();
|
||||
|
|
|
@ -53,8 +53,9 @@ public class Content
|
|||
/**
|
||||
* <p>Copies the given content source to the given content sink, notifying
|
||||
* the given callback when the copy is complete (either succeeded or failed).</p>
|
||||
* <p>In case of failures, the content source is {@link Source#fail(Throwable) failed}
|
||||
* too.</p>
|
||||
* <p>In case of {@link Chunk#getFailure() failure chunks},
|
||||
* the content source is {@link Source#fail(Throwable) failed} if the failure
|
||||
* chunk is {@link Chunk#isLast() last}, else the failing is transient and is ignored.</p>
|
||||
*
|
||||
* @param source the source to copy from
|
||||
* @param sink the sink to copy to
|
||||
|
@ -76,6 +77,9 @@ public class Content
|
|||
* <p>If the predicate returns {@code false}, it means that the chunk is not
|
||||
* handled, its callback will not be completed, and the implementation will
|
||||
* handle the chunk and its callback.</p>
|
||||
* <p>In case of {@link Chunk#getFailure() failure chunks} not handled by any {@code chunkHandler},
|
||||
* the content source is {@link Source#fail(Throwable) failed} if the failure
|
||||
* chunk is {@link Chunk#isLast() last}, else the failure is transient and is ignored.</p>
|
||||
*
|
||||
* @param source the source to copy from
|
||||
* @param sink the sink to copy to
|
||||
|
@ -103,10 +107,11 @@ public class Content
|
|||
* return;
|
||||
* }
|
||||
*
|
||||
* // The chunk is an error.
|
||||
* if (chunk instanceof Chunk.Error error) {
|
||||
* // Handle the error.
|
||||
* Throwable cause = error.getCause();
|
||||
* // The chunk is a failure.
|
||||
* if (Content.Chunk.isFailure(chunk)) {
|
||||
* // Handle the failure.
|
||||
* Throwable cause = chunk.getFailure();
|
||||
* boolean transient = !chunk.isLast();
|
||||
* // ...
|
||||
* return;
|
||||
* }
|
||||
|
@ -190,7 +195,7 @@ public class Content
|
|||
* @return the String obtained from the content
|
||||
* @throws IOException if reading the content fails
|
||||
*/
|
||||
public static String asString(Source source, Charset charset) throws IOException
|
||||
static String asString(Source source, Charset charset) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -227,12 +232,12 @@ public class Content
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>Reads, non-blocking, the given content source, until either an error or EOF,
|
||||
* <p>Reads, non-blocking, the given content source, until a {@link Chunk#isFailure(Chunk) failure} or EOF
|
||||
* and discards the content.</p>
|
||||
*
|
||||
* @param source the source to read from
|
||||
* @param callback the callback to notify when the whole content has been read
|
||||
* or an error occurred while reading the content
|
||||
* or a failure occurred while reading the content
|
||||
*/
|
||||
static void consumeAll(Source source, Callback callback)
|
||||
{
|
||||
|
@ -240,7 +245,7 @@ public class Content
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>Reads, blocking if necessary, the given content source, until either an error
|
||||
* <p>Reads, blocking if necessary, the given content source, until a {@link Chunk#isFailure(Chunk) failure}
|
||||
* or EOF, and discards the content.</p>
|
||||
*
|
||||
* @param source the source to read from
|
||||
|
@ -274,13 +279,15 @@ public class Content
|
|||
* <p>The returned chunk could be:</p>
|
||||
* <ul>
|
||||
* <li>{@code null}, to signal that there isn't a chunk of content available</li>
|
||||
* <li>an {@link Chunk.Error error} instance, to signal that there was an error
|
||||
* <li>an {@link Chunk} instance with non null {@link Chunk#getFailure()}, to signal that there was a failure
|
||||
* trying to produce a chunk of content, or that the content production has been
|
||||
* {@link #fail(Throwable) failed} externally</li>
|
||||
* <li>a {@link Chunk} instance, containing the chunk of content.</li>
|
||||
* </ul>
|
||||
* <p>Once a read returns an {@link Chunk.Error error} instance, further reads
|
||||
* will continue to return the same error instance.</p>
|
||||
* <p>Once a read returns an {@link Chunk} instance with non-null {@link Chunk#getFailure()}
|
||||
* then if the failure is {@link Chunk#isLast() last} further reads
|
||||
* will continue to return the same failure chunk instance, otherwise further
|
||||
* {@code read()} operations may return different non-failure chunks.</p>
|
||||
* <p>Once a read returns a {@link Chunk#isLast() last chunk}, further reads will
|
||||
* continue to return a last chunk (although the instance may be different).</p>
|
||||
* <p>The content reader code must ultimately arrange for a call to
|
||||
|
@ -296,7 +303,7 @@ public class Content
|
|||
* race condition (the thread that reads with the thread that invokes the
|
||||
* demand callback).</p>
|
||||
*
|
||||
* @return a chunk of content, possibly an error instance, or {@code null}
|
||||
* @return a chunk of content, possibly a failure instance, or {@code null}
|
||||
* @see #demand(Runnable)
|
||||
* @see Retainable
|
||||
*/
|
||||
|
@ -327,18 +334,38 @@ public class Content
|
|||
void demand(Runnable demandCallback);
|
||||
|
||||
/**
|
||||
* <p>Fails this content source, possibly failing and discarding accumulated
|
||||
* content chunks that were not yet read.</p>
|
||||
* <p>Fails this content source with a {@link Chunk#isLast() last} {@link Chunk#getFailure() failure chunk},
|
||||
* failing and discarding accumulated content chunks that were not yet read.</p>
|
||||
* <p>The failure may be notified to the content reader at a later time, when
|
||||
* the content reader reads a content chunk, via an {@link Chunk.Error} instance.</p>
|
||||
* the content reader reads a content chunk, via a {@link Chunk} instance
|
||||
* with a non null {@link Chunk#getFailure()}.</p>
|
||||
* <p>If {@link #read()} has returned a last chunk, this is a no operation.</p>
|
||||
* <p>Typical failure: the content being aborted by user code, or idle timeouts.</p>
|
||||
* <p>If this method has already been called, then it is a no operation.</p>
|
||||
*
|
||||
* @param failure the cause of the failure
|
||||
* @see Chunk#getFailure()
|
||||
*/
|
||||
void fail(Throwable failure);
|
||||
|
||||
/**
|
||||
* <p>Fails this content source with a {@link Chunk#getFailure() failure chunk}
|
||||
* that may or not may be {@link Chunk#isLast() last}.
|
||||
* If {@code last} is {@code true}, then the failure is persistent and a call to this method acts
|
||||
* as {@link #fail(Throwable)}. Otherwise the failure is transient and a
|
||||
* {@link Chunk#getFailure() failure chunk} will be {@link #read() read} in order with content chunks,
|
||||
* and subsequent calls to {@link #read() read} may produce other content.</p>
|
||||
* <p>A {@code Content.Source} or its {@link #read() reader} may treat a transient failure as persistent.</p>
|
||||
*
|
||||
* @param failure A failure.
|
||||
* @param last true if the failure is persistent, false if the failure is transient.
|
||||
* @see Chunk#getFailure()
|
||||
*/
|
||||
default void fail(Throwable failure, boolean last)
|
||||
{
|
||||
fail(failure);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Rewinds this content, if possible, so that subsequent reads return
|
||||
* chunks starting from the beginning of this content.</p>
|
||||
|
@ -555,14 +582,52 @@ public class Content
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>Creates an {@link Error error chunk} with the given failure.</p>
|
||||
* <p>Creates an {@link Chunk#isFailure(Chunk) failure chunk} with the given failure
|
||||
* and {@link Chunk#isLast()} returning true.</p>
|
||||
*
|
||||
* @param failure the cause of the failure
|
||||
* @return a new Error.Chunk
|
||||
* @return a new {@link Chunk#isFailure(Chunk) failure chunk}
|
||||
*/
|
||||
static Error from(Throwable failure)
|
||||
static Chunk from(Throwable failure)
|
||||
{
|
||||
return new Error(failure);
|
||||
return from(failure, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates an {@link Chunk#isFailure(Chunk) failure chunk} with the given failure
|
||||
* and given {@link Chunk#isLast() last} state.</p>
|
||||
*
|
||||
* @param failure the cause of the failure
|
||||
* @param last true if the failure is terminal, else false for transient failure
|
||||
* @return a new {@link Chunk#isFailure(Chunk) failure chunk}
|
||||
*/
|
||||
static Chunk from(Throwable failure, boolean last)
|
||||
{
|
||||
return new Chunk()
|
||||
{
|
||||
public Throwable getFailure()
|
||||
{
|
||||
return failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast()
|
||||
{
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Chunk@%x{c=%s,l=%b}", hashCode(), failure, last);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -581,8 +646,12 @@ public class Content
|
|||
* <td>{@code null}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link Chunk#isFailure(Chunk) Failure} and {@link Chunk#isLast() last}</td>
|
||||
* <td>{@link Error Error}</td>
|
||||
* <td>{@link Error Error}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link Chunk#isFailure(Chunk) Failure} and {@link Chunk#isLast() not last}</td>
|
||||
* <td>{@code null}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link #isLast()}</td>
|
||||
|
@ -597,18 +666,57 @@ public class Content
|
|||
*/
|
||||
static Chunk next(Chunk chunk)
|
||||
{
|
||||
if (chunk == null || chunk instanceof Error)
|
||||
return chunk;
|
||||
if (chunk == null)
|
||||
return null;
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
return chunk.isLast() ? chunk : null;
|
||||
if (chunk.isLast())
|
||||
return EOF;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chunk The chunk to test for an {@link Chunk#getFailure() failure}.
|
||||
* @return True if the chunk is non-null and {@link Chunk#getFailure() chunk.getError()} returns non-null.
|
||||
*/
|
||||
static boolean isFailure(Chunk chunk)
|
||||
{
|
||||
return chunk != null && chunk.getFailure() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chunk The chunk to test for an {@link Chunk#getFailure() failure}
|
||||
* @param last The {@link Chunk#isLast() last} status to test for.
|
||||
* @return True if the chunk is non-null and {@link Chunk#getFailure()} returns non-null
|
||||
* and {@link Chunk#isLast()} matches the passed status.
|
||||
*/
|
||||
static boolean isFailure(Chunk chunk, boolean last)
|
||||
{
|
||||
return chunk != null && chunk.getFailure() != null && chunk.isLast() == last;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ByteBuffer of this Chunk
|
||||
*/
|
||||
ByteBuffer getByteBuffer();
|
||||
|
||||
/**
|
||||
* Get a failure (which may be from a {@link Source#fail(Throwable) failure} or
|
||||
* a {@link Source#fail(Throwable, boolean) warning}), if any, associated with the chunk.
|
||||
* <ul>
|
||||
* <li>A {@code chunk} must not have a failure and a {@link #getByteBuffer()} with content.</li>
|
||||
* <li>A {@code chunk} with a failure may or may not be {@link #isLast() last}.</li>
|
||||
* <li>A {@code chunk} with a failure must not be {@link #canRetain() retainable}.</li>
|
||||
* </ul>
|
||||
* @return A {@link Throwable} indicating the failure or null if there is no failure or warning.
|
||||
* @see Source#fail(Throwable)
|
||||
* @see Source#fail(Throwable, boolean)
|
||||
*/
|
||||
default Throwable getFailure()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this is the last Chunk
|
||||
*/
|
||||
|
@ -674,46 +782,6 @@ public class Content
|
|||
return asChunk(getByteBuffer().asReadOnlyBuffer(), isLast(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A chunk that wraps a failure.</p>
|
||||
* <p>Error Chunks are always last and have no bytes to read,
|
||||
* as such they are <em>terminal</em> Chunks.</p>
|
||||
*
|
||||
* @see #from(Throwable)
|
||||
*/
|
||||
final class Error implements Chunk
|
||||
{
|
||||
private final Throwable cause;
|
||||
|
||||
private Error(Throwable cause)
|
||||
{
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public Throwable getCause()
|
||||
{
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer getByteBuffer()
|
||||
{
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x{c=%s}", getClass().getSimpleName(), hashCode(), cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Implementations of this interface may process {@link Chunk}s being copied by the
|
||||
* {@link Content#copy(Source, Sink, Processor, Callback)} method, so that
|
||||
|
|
|
@ -148,14 +148,14 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump
|
|||
public int getTotalKeys()
|
||||
{
|
||||
int keys = 0;
|
||||
for (final ManagedSelector selector : _selectors)
|
||||
for (ManagedSelector selector : _selectors)
|
||||
{
|
||||
keys += selector.getTotalKeys();
|
||||
if (selector != null)
|
||||
keys += selector.getTotalKeys();
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the number of selectors in use
|
||||
*/
|
||||
|
|
|
@ -51,8 +51,8 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
|
||||
private final AutoLock.WithCondition lock = new AutoLock.WithCondition();
|
||||
private final SerializedInvoker invoker = new SerializedInvoker();
|
||||
private final Queue<AsyncChunk> chunks = new ArrayDeque<>();
|
||||
private Content.Chunk.Error errorChunk;
|
||||
private final Queue<Content.Chunk> chunks = new ArrayDeque<>();
|
||||
private Content.Chunk persistentFailure;
|
||||
private boolean readClosed;
|
||||
private boolean writeClosed;
|
||||
private Runnable demandCallback;
|
||||
|
@ -62,7 +62,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
* {@inheritDoc}
|
||||
* <p>The write completes:</p>
|
||||
* <ul>
|
||||
* <li>immediately with a failure when this instance is closed or already in error</li>
|
||||
* <li>immediately with a failure when this instance is closed or already has a failure</li>
|
||||
* <li>successfully when a non empty {@link Content.Chunk} returned by {@link #read()} is released</li>
|
||||
* <li>successfully just before the {@link Content.Chunk} is returned by {@link #read()},
|
||||
* for any empty chunk {@link Content.Chunk}.</li>
|
||||
|
@ -79,7 +79,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
* or succeeded if and only if the chunk is terminal, as non-terminal
|
||||
* chunks have to bind the succeeding of the callback to their release.
|
||||
*/
|
||||
private void offer(AsyncChunk chunk)
|
||||
private void offer(Content.Chunk chunk)
|
||||
{
|
||||
Throwable failure = null;
|
||||
boolean wasEmpty = false;
|
||||
|
@ -89,9 +89,9 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
{
|
||||
failure = new IOException("closed");
|
||||
}
|
||||
else if (errorChunk != null)
|
||||
else if (persistentFailure != null)
|
||||
{
|
||||
failure = errorChunk.getCause();
|
||||
failure = persistentFailure.getFailure();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -105,14 +105,14 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
if (length == UNDETERMINED_LENGTH)
|
||||
{
|
||||
length = 0;
|
||||
for (AsyncChunk c : chunks)
|
||||
for (Content.Chunk c : chunks)
|
||||
length += c.remaining();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failure != null)
|
||||
chunk.failed(failure);
|
||||
if (failure != null && chunk instanceof AsyncChunk asyncChunk)
|
||||
asyncChunk.failed(failure);
|
||||
if (wasEmpty)
|
||||
invoker.run(this::invokeDemandCallback);
|
||||
}
|
||||
|
@ -125,14 +125,14 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
{
|
||||
// Always wrap the exception to make sure
|
||||
// the stack trace comes from flush().
|
||||
if (errorChunk != null)
|
||||
throw new IOException(errorChunk.getCause());
|
||||
if (persistentFailure != null)
|
||||
throw new IOException(persistentFailure.getFailure());
|
||||
if (chunks.isEmpty())
|
||||
return;
|
||||
// Special case for a last empty chunk that may not be read.
|
||||
if (writeClosed && chunks.size() == 1)
|
||||
{
|
||||
AsyncChunk chunk = chunks.peek();
|
||||
Content.Chunk chunk = chunks.peek();
|
||||
if (chunk.isLast() && !chunk.hasRemaining())
|
||||
return;
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
@Override
|
||||
public Content.Chunk read()
|
||||
{
|
||||
AsyncChunk current;
|
||||
Content.Chunk current;
|
||||
try (AutoLock.WithCondition condition = lock.lock())
|
||||
{
|
||||
if (length == UNDETERMINED_LENGTH)
|
||||
|
@ -181,8 +181,8 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
{
|
||||
if (readClosed)
|
||||
return Content.Chunk.EOF;
|
||||
if (errorChunk != null)
|
||||
return errorChunk;
|
||||
if (persistentFailure != null)
|
||||
return persistentFailure;
|
||||
return null;
|
||||
}
|
||||
readClosed = current.isLast();
|
||||
|
@ -195,7 +195,12 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
return current;
|
||||
|
||||
// If the chunk is not reference counted, we can succeed it now and return a chunk with a noop release.
|
||||
current.succeeded();
|
||||
if (current instanceof AsyncChunk asyncChunk)
|
||||
asyncChunk.succeeded();
|
||||
|
||||
if (Content.Chunk.isFailure(current))
|
||||
return current;
|
||||
|
||||
return current.isLast() ? Content.Chunk.EOF : Content.Chunk.EMPTY;
|
||||
}
|
||||
|
||||
|
@ -208,7 +213,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
if (this.demandCallback != null)
|
||||
throw new IllegalStateException("demand pending");
|
||||
this.demandCallback = Objects.requireNonNull(demandCallback);
|
||||
invoke = !chunks.isEmpty() || readClosed || errorChunk != null;
|
||||
invoke = !chunks.isEmpty() || readClosed || persistentFailure != null;
|
||||
}
|
||||
if (invoke)
|
||||
invoker.run(this::invokeDemandCallback);
|
||||
|
@ -241,22 +246,35 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
|||
@Override
|
||||
public void fail(Throwable failure)
|
||||
{
|
||||
List<AsyncChunk> drained;
|
||||
List<Content.Chunk> drained;
|
||||
try (AutoLock.WithCondition condition = lock.lock())
|
||||
{
|
||||
if (readClosed)
|
||||
return;
|
||||
if (errorChunk != null)
|
||||
if (persistentFailure != null)
|
||||
return;
|
||||
errorChunk = Content.Chunk.from(failure);
|
||||
persistentFailure = Content.Chunk.from(failure);
|
||||
drained = List.copyOf(chunks);
|
||||
chunks.clear();
|
||||
condition.signal();
|
||||
}
|
||||
drained.forEach(ac -> ac.failed(failure));
|
||||
drained.forEach(c ->
|
||||
{
|
||||
if (c instanceof AsyncChunk ac)
|
||||
ac.failed(failure);
|
||||
});
|
||||
invoker.run(this::invokeDemandCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(Throwable failure, boolean last)
|
||||
{
|
||||
if (last)
|
||||
fail(failure);
|
||||
else
|
||||
offer(Content.Chunk.from(failure, false));
|
||||
}
|
||||
|
||||
public int count()
|
||||
{
|
||||
try (AutoLock ignored = lock.lock())
|
||||
|
|
|
@ -54,8 +54,12 @@ public class ContentSourceInputStream extends InputStream
|
|||
{
|
||||
if (chunk != null)
|
||||
{
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
throw IO.rethrow(error.getCause());
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
Content.Chunk c = chunk;
|
||||
chunk = null;
|
||||
throw IO.rethrow(c.getFailure());
|
||||
}
|
||||
|
||||
ByteBuffer byteBuffer = chunk.getByteBuffer();
|
||||
if (chunk.isLast() && !byteBuffer.hasRemaining())
|
||||
|
@ -96,8 +100,8 @@ public class ContentSourceInputStream extends InputStream
|
|||
@Override
|
||||
public void close()
|
||||
{
|
||||
// If we have already reached a real EOF or an error, close is a noop.
|
||||
if (chunk == Content.Chunk.EOF || chunk instanceof Content.Chunk.Error)
|
||||
// If we have already reached a real EOF or a persistent failure, close is a noop.
|
||||
if (chunk == Content.Chunk.EOF || Content.Chunk.isFailure(chunk, true))
|
||||
return;
|
||||
|
||||
// If we have a chunk here, then it needs to be released
|
||||
|
|
|
@ -125,10 +125,10 @@ public class ContentSourcePublisher implements Flow.Publisher<Content.Chunk>
|
|||
return;
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
terminate();
|
||||
subscriber.onError(error.getCause());
|
||||
subscriber.onError(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,10 +60,10 @@ public abstract class ContentSourceTransformer implements Content.Source
|
|||
return null;
|
||||
}
|
||||
|
||||
if (rawChunk instanceof Content.Chunk.Error)
|
||||
if (Content.Chunk.isFailure(rawChunk))
|
||||
return rawChunk;
|
||||
|
||||
if (transformedChunk instanceof Content.Chunk.Error)
|
||||
if (Content.Chunk.isFailure(transformedChunk))
|
||||
return transformedChunk;
|
||||
|
||||
transformedChunk = process(rawChunk);
|
||||
|
@ -142,13 +142,14 @@ public abstract class ContentSourceTransformer implements Content.Source
|
|||
* <p>The input chunk is released as soon as this method returns, so
|
||||
* implementations that must hold onto the input chunk must arrange to call
|
||||
* {@link Content.Chunk#retain()} and its correspondent {@link Content.Chunk#release()}.</p>
|
||||
* <p>Implementations should return an {@link Content.Chunk.Error error chunk} in case
|
||||
* <p>Implementations should return an {@link Content.Chunk} with non-null
|
||||
* {@link Content.Chunk#getFailure()} in case
|
||||
* of transformation errors.</p>
|
||||
* <p>Exceptions thrown by this method are equivalent to returning an error chunk.</p>
|
||||
* <p>Implementations of this method may return:</p>
|
||||
* <ul>
|
||||
* <li>{@code null}, if more input chunks are necessary to produce an output chunk</li>
|
||||
* <li>the {@code inputChunk} itself, typically in case of {@link Content.Chunk.Error}s,
|
||||
* <li>the {@code inputChunk} itself, typically in case of non-null {@link Content.Chunk#getFailure()},
|
||||
* or when no transformation is required</li>
|
||||
* <li>a new {@link Content.Chunk} derived from {@code inputChunk}.</li>
|
||||
* </ul>
|
||||
|
|
|
@ -40,7 +40,7 @@ public class InputStreamContentSource implements Content.Source
|
|||
private final ByteBufferPool bufferPool;
|
||||
private int bufferSize = 4096;
|
||||
private Runnable demandCallback;
|
||||
private Content.Chunk.Error errorChunk;
|
||||
private Content.Chunk errorChunk;
|
||||
private boolean closed;
|
||||
|
||||
public InputStreamContentSource(InputStream inputStream)
|
||||
|
|
|
@ -46,7 +46,7 @@ public class PathContentSource implements Content.Source
|
|||
private SeekableByteChannel channel;
|
||||
private long totalRead;
|
||||
private Runnable demandCallback;
|
||||
private Content.Chunk.Error errorChunk;
|
||||
private Content.Chunk errorChunk;
|
||||
|
||||
public PathContentSource(Path path)
|
||||
{
|
||||
|
|
|
@ -16,9 +16,13 @@ package org.eclipse.jetty.io.internal;
|
|||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IteratingNestedCallback;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ContentCopier extends IteratingNestedCallback
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ContentCopier.class);
|
||||
|
||||
private final Content.Source source;
|
||||
private final Content.Sink sink;
|
||||
private final Content.Chunk.Processor chunkProcessor;
|
||||
|
@ -56,8 +60,15 @@ public class ContentCopier extends IteratingNestedCallback
|
|||
if (chunkProcessor != null && chunkProcessor.process(current, this))
|
||||
return Action.SCHEDULED;
|
||||
|
||||
if (current instanceof Error error)
|
||||
throw error.getCause();
|
||||
if (Content.Chunk.isFailure(current))
|
||||
{
|
||||
if (current.isLast())
|
||||
throw current.getFailure();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("ignored transient failure", current.getFailure());
|
||||
succeeded();
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
|
||||
sink.write(current.isLast(), current.getByteBuffer(), this);
|
||||
return Action.SCHEDULED;
|
||||
|
|
|
@ -44,9 +44,9 @@ public class ContentSourceByteBuffer implements Runnable
|
|||
return;
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
promise.failed(error.getCause());
|
||||
promise.failed(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,9 +41,9 @@ public class ContentSourceConsumer implements Invocable.Task
|
|||
return;
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
callback.failed(error.getCause());
|
||||
callback.failed(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,9 +42,9 @@ public class ContentSourceString
|
|||
content.demand(this::convert);
|
||||
return;
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
promise.failed(error.getCause());
|
||||
promise.failed(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
text.append(chunk.getByteBuffer());
|
||||
|
|
|
@ -33,7 +33,6 @@ import static org.hamcrest.Matchers.is;
|
|||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -101,7 +100,7 @@ public class AsyncContentTest
|
|||
|
||||
// We must read the error.
|
||||
chunk = async.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
|
||||
// Offering more should fail.
|
||||
CountDownLatch failLatch = new CountDownLatch(1);
|
||||
|
@ -209,14 +208,14 @@ public class AsyncContentTest
|
|||
assertThat(chunk.release(), is(true));
|
||||
callback1.assertNoFailureWithSuccesses(1);
|
||||
|
||||
Exception error1 = new Exception("test1");
|
||||
async.fail(error1);
|
||||
Exception failure1 = new Exception("test1");
|
||||
async.fail(failure1);
|
||||
|
||||
chunk = async.read();
|
||||
assertSame(error1, ((Content.Chunk.Error)chunk).getCause());
|
||||
assertSame(failure1, chunk.getFailure());
|
||||
|
||||
callback2.assertSingleFailureSameInstanceNoSuccess(error1);
|
||||
callback3.assertSingleFailureSameInstanceNoSuccess(error1);
|
||||
callback2.assertSingleFailureSameInstanceNoSuccess(failure1);
|
||||
callback3.assertSingleFailureSameInstanceNoSuccess(failure1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,10 @@ package org.eclipse.jetty.io;
|
|||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
@ -27,6 +29,7 @@ import java.util.concurrent.ConcurrentLinkedDeque;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
@ -49,13 +52,14 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class ContentSourceTest
|
||||
{
|
||||
|
@ -237,7 +241,7 @@ public class ContentSourceTest
|
|||
|
||||
// We must read the error.
|
||||
chunk = source.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -259,7 +263,7 @@ public class ContentSourceTest
|
|||
source.fail(new CancellationException());
|
||||
|
||||
Content.Chunk chunk = source.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
source.demand(latch::countDown);
|
||||
|
@ -285,7 +289,7 @@ public class ContentSourceTest
|
|||
});
|
||||
|
||||
chunk = source.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -560,4 +564,63 @@ public class ContentSourceTest
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncContentWithWarnings()
|
||||
{
|
||||
AsyncContent content = new AsyncContent();
|
||||
|
||||
Content.Sink.write(content, false, "One", Callback.NOOP);
|
||||
content.fail(new TimeoutException("test"), false);
|
||||
Content.Sink.write(content, true, "Two", Callback.NOOP);
|
||||
|
||||
Content.Chunk chunk = content.read();
|
||||
assertFalse(chunk.isLast());
|
||||
assertFalse(Content.Chunk.isFailure(chunk));
|
||||
assertThat(BufferUtil.toString(chunk.getByteBuffer()), is("One"));
|
||||
|
||||
chunk = content.read();
|
||||
assertFalse(chunk.isLast());
|
||||
assertTrue(Content.Chunk.isFailure(chunk));
|
||||
assertThat(chunk.getFailure(), instanceOf(TimeoutException.class));
|
||||
|
||||
chunk = content.read();
|
||||
assertTrue(chunk.isLast());
|
||||
assertFalse(Content.Chunk.isFailure(chunk));
|
||||
assertThat(BufferUtil.toString(chunk.getByteBuffer()), is("Two"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncContentWithWarningsAsInputStream() throws Exception
|
||||
{
|
||||
AsyncContent content = new AsyncContent();
|
||||
|
||||
Content.Sink.write(content, false, "One", Callback.NOOP);
|
||||
content.fail(new TimeoutException("test"), false);
|
||||
Content.Sink.write(content, true, "Two", Callback.NOOP);
|
||||
|
||||
InputStream in = Content.Source.asInputStream(content);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int len = in.read(buffer);
|
||||
assertThat(len, is(3));
|
||||
assertThat(new String(buffer, 0, 3, StandardCharsets.ISO_8859_1), is("One"));
|
||||
|
||||
try
|
||||
{
|
||||
int ignored = in.read();
|
||||
fail();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
assertThat(ioe.getCause(), instanceOf(TimeoutException.class));
|
||||
}
|
||||
|
||||
len = in.read(buffer);
|
||||
assertThat(len, is(3));
|
||||
assertThat(new String(buffer, 0, 3, StandardCharsets.ISO_8859_1), is("Two"));
|
||||
|
||||
len = in.read(buffer);
|
||||
assertThat(len, is(-1));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -259,11 +258,11 @@ public class ContentSourceTransformerTest
|
|||
chunk.release();
|
||||
|
||||
chunk = transformer.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
|
||||
// Trying to read again returns the error again.
|
||||
chunk = transformer.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
|
||||
// Make sure that the source is failed.
|
||||
assertEquals(0, source.count());
|
||||
|
@ -284,11 +283,11 @@ public class ContentSourceTransformerTest
|
|||
chunk.release();
|
||||
|
||||
chunk = transformer.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
|
||||
// Trying to read again returns the error again.
|
||||
chunk = transformer.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -306,11 +305,11 @@ public class ContentSourceTransformerTest
|
|||
source.fail(new IOException());
|
||||
|
||||
chunk = transformer.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
|
||||
// Trying to read again returns the error again.
|
||||
chunk = transformer.read();
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
}
|
||||
|
||||
private static class WordSplitLowCaseTransformer extends ContentSourceTransformer
|
||||
|
|
|
@ -24,24 +24,15 @@
|
|||
<!-- configuration that may be set here. -->
|
||||
<!-- =============================================================== -->
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
<Arg name="threadPool"><Ref refid="threadPool"/></Arg>
|
||||
|
||||
<Call name="addBean">
|
||||
<Arg><Ref refid="byteBufferPool"/></Arg>
|
||||
</Call>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Add shared Scheduler instance -->
|
||||
<!-- =========================================================== -->
|
||||
<Call name="addBean">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.util.thread.ScheduledExecutorScheduler">
|
||||
<Arg name="name"><Property name="jetty.scheduler.name"/></Arg>
|
||||
<Arg name="daemon" type="boolean"><Property name="jetty.scheduler.daemon" default="false" /></Arg>
|
||||
<Arg name="threads" type="int"><Property name="jetty.scheduler.threads" default="-1" /></Arg>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
<Arg name="threadPool"><Ref refid="threadPool"/></Arg>
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.util.thread.ScheduledExecutorScheduler">
|
||||
<Arg name="name"><Property name="jetty.scheduler.name"/></Arg>
|
||||
<Arg name="daemon" type="boolean"><Property name="jetty.scheduler.daemon" default="false" /></Arg>
|
||||
<Arg name="threads" type="int"><Property name="jetty.scheduler.threads" default="-1" /></Arg>
|
||||
</New>
|
||||
</Arg>
|
||||
<Arg><Ref refid="byteBufferPool"/></Arg>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Http Configuration. -->
|
||||
|
|
|
@ -160,9 +160,9 @@ public class FormFields extends CompletableFuture<Fields> implements Runnable
|
|||
return;
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
completeExceptionally(error.getCause());
|
||||
completeExceptionally(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -216,21 +216,6 @@ public interface Handler extends LifeCycle, Destroyable, Request.Handler
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Make a {@link Container} the parent of a {@link Handler}</p>
|
||||
* @param parent The {@link Container} that will be the parent
|
||||
* @param handler The {@link Handler} that will be the child
|
||||
*/
|
||||
static void setAsParent(Container parent, Handler handler)
|
||||
{
|
||||
if (parent instanceof Collection collection)
|
||||
collection.addHandler(handler);
|
||||
else if (parent instanceof Singleton wrapper)
|
||||
wrapper.setHandler(handler);
|
||||
else if (parent != null)
|
||||
throw new IllegalArgumentException("Unknown parent type: " + parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.eclipse.jetty.util.StaticException;
|
|||
/**
|
||||
* A HttpStream is an abstraction that together with {@link MetaData.Request}, represents the
|
||||
* flow of data from and to a single request and response cycle. It is roughly analogous to the
|
||||
* Stream within a HTTP/2 connection, in that a connection can have many streams, each used once
|
||||
* Stream within an HTTP/2 connection, in that a connection can have many streams, each used once
|
||||
* and each representing a single request and response exchange.
|
||||
*/
|
||||
public interface HttpStream extends Callback
|
||||
|
@ -42,7 +42,7 @@ public interface HttpStream extends Callback
|
|||
|
||||
/**
|
||||
* @return an ID unique within the lifetime scope of the associated protocol connection.
|
||||
* This may be a protocol ID (eg HTTP/2 stream ID) or it may be unrelated to the protocol.
|
||||
* This may be a protocol ID (e.g. HTTP/2 stream ID) or it may be unrelated to the protocol.
|
||||
*/
|
||||
String getId();
|
||||
|
||||
|
@ -50,7 +50,7 @@ public interface HttpStream extends Callback
|
|||
* <p>Reads a chunk of content, with the same semantic as {@link Content.Source#read()}.</p>
|
||||
* <p>This method is called from the implementation of {@link Request#read()}.</p>
|
||||
*
|
||||
* @return a chunk of content, possibly an {@link Chunk.Error error} or {@code null}.
|
||||
* @return a chunk of content, possibly with non-null {@link Chunk#getFailure()} or {@code null}.
|
||||
*/
|
||||
Content.Chunk read();
|
||||
|
||||
|
@ -125,8 +125,8 @@ public interface HttpStream extends Callback
|
|||
content.release();
|
||||
|
||||
// if the input failed, then fail the stream for same reason
|
||||
if (content instanceof Chunk.Error error)
|
||||
return error.getCause();
|
||||
if (Content.Chunk.isFailure(content))
|
||||
return content.getFailure();
|
||||
|
||||
if (content.isLast())
|
||||
return null;
|
||||
|
|
|
@ -69,12 +69,14 @@ import org.eclipse.jetty.util.thread.Invocable;
|
|||
* return true;
|
||||
* }
|
||||
*
|
||||
* if (chunk instanceof Content.Chunk.Error error)
|
||||
* if (Content.Chunk.isError(chunk))
|
||||
* {
|
||||
* Throwable failure = error.getCause();
|
||||
*
|
||||
* // Handle errors.
|
||||
* // Mark the handling as complete, either generating a custom
|
||||
* // If the chunk is not last, then the error can be ignored and reading can be tried again.
|
||||
* // Otherwise, if the chunk is last, or we do not wish to ignore a non-last error, then
|
||||
* // mark the handling as complete, either generating a custom
|
||||
* // response and succeeding the callback, or failing the callback.
|
||||
* callback.failed(failure);
|
||||
* return true;
|
||||
|
|
|
@ -440,11 +440,12 @@ public class ResourceService
|
|||
|
||||
protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
if (!Objects.requireNonNull(content).getResource().isDirectory())
|
||||
throw new IllegalArgumentException("content must be a directory");
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("sendWelcome(content={}, pathInContext={}, endsWithSlash={}, req={}, resp={}, callback={})",
|
||||
content, pathInContext, endsWithSlash, request, response, callback);
|
||||
}
|
||||
|
||||
// Redirect to directory
|
||||
if (!endsWithSlash)
|
||||
|
@ -460,7 +461,7 @@ public class ResourceService
|
|||
}
|
||||
|
||||
// process optional Welcome behaviors
|
||||
if (welcome(request, response, callback))
|
||||
if (welcome(content, request, response, callback))
|
||||
return;
|
||||
|
||||
if (!passConditionalHeaders(request, response, content, callback))
|
||||
|
@ -499,9 +500,9 @@ public class ResourceService
|
|||
{
|
||||
}
|
||||
|
||||
private boolean welcome(Request request, Response response, Callback callback) throws Exception
|
||||
private boolean welcome(HttpContent content, Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
WelcomeAction welcomeAction = processWelcome(request);
|
||||
WelcomeAction welcomeAction = processWelcome(content, request);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("welcome(req={}, rsp={}, cbk={}) welcomeAction={}", request, response, callback, welcomeAction);
|
||||
|
||||
|
@ -581,18 +582,15 @@ public class ResourceService
|
|||
Response.writeError(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500);
|
||||
}
|
||||
|
||||
private WelcomeAction processWelcome(Request request) throws IOException
|
||||
private WelcomeAction processWelcome(HttpContent content, Request request) throws IOException
|
||||
{
|
||||
String welcomeTarget = getWelcomeFactory().getWelcomeTarget(request);
|
||||
String welcomeTarget = getWelcomeFactory().getWelcomeTarget(content, request);
|
||||
if (welcomeTarget == null)
|
||||
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())
|
||||
|
@ -601,6 +599,9 @@ public class ResourceService
|
|||
case SERVE -> welcomeTarget;
|
||||
};
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("welcome {} {}", welcomeMode, welcomeTarget);
|
||||
|
||||
return new WelcomeAction(welcomeTarget, welcomeMode);
|
||||
}
|
||||
|
||||
|
@ -892,7 +893,7 @@ public class ResourceService
|
|||
* @return The URI path of the matching welcome target in context or null
|
||||
* if no welcome target was found
|
||||
*/
|
||||
String getWelcomeTarget(Request request) throws IOException;
|
||||
String getWelcomeTarget(HttpContent content, Request request) throws IOException;
|
||||
}
|
||||
|
||||
private static class ContentWriterIteratingCallback extends IteratingCallback
|
||||
|
|
|
@ -180,14 +180,6 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
|
|||
_classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public ContextHandler(Handler.Container parent, String contextPath)
|
||||
{
|
||||
this(contextPath);
|
||||
Container.setAsParent(parent, this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServer(Server server)
|
||||
{
|
||||
|
|
|
@ -342,9 +342,9 @@ public class DelayedHandler extends Handler.Wrapper
|
|||
getRequest().demand(this::readAndParse);
|
||||
return;
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
_formData.completeExceptionally(error.getCause());
|
||||
_formData.completeExceptionally(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
_formData.parse(chunk);
|
||||
|
|
|
@ -160,7 +160,7 @@ public abstract class EventsHandler extends Handler.Wrapper
|
|||
{
|
||||
try
|
||||
{
|
||||
onResponseWrite(request, last, content.asReadOnlyBuffer());
|
||||
onResponseWrite(request, last, content == null ? null : content.asReadOnlyBuffer());
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -229,7 +229,7 @@ public abstract class EventsHandler extends Handler.Wrapper
|
|||
* {@link Request#read()}).
|
||||
*
|
||||
* @param request the request object. The {@code read()}, {@code demand(Runnable)} and {@code fail(Throwable)} methods must not be called by the listener.
|
||||
* @param chunk a potentially null request content chunk, including {@link org.eclipse.jetty.io.Content.Chunk.Error}
|
||||
* @param chunk a potentially null request content chunk, including {@link org.eclipse.jetty.io.Content.Chunk#isFailure(Content.Chunk) error}
|
||||
* and {@link org.eclipse.jetty.http.Trailers} chunks.
|
||||
* If a reference to the chunk (or its {@link ByteBuffer}) is kept,
|
||||
* then {@link Content.Chunk#retain()} must be called.
|
||||
|
|
|
@ -18,7 +18,6 @@ import org.eclipse.jetty.http.HttpHeader;
|
|||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
@ -42,13 +41,6 @@ public class MovedContextHandler extends ContextHandler
|
|||
setAllowNullPathInContext(true);
|
||||
}
|
||||
|
||||
public MovedContextHandler(Handler.Container parent, String contextPath, String redirectURI)
|
||||
{
|
||||
Handler.Container.setAsParent(parent, this);
|
||||
setContextPath(contextPath);
|
||||
setRedirectURI(redirectURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the redirect status code, by default 303
|
||||
*/
|
||||
|
|
|
@ -122,7 +122,7 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
|
||||
protected ResourceService.WelcomeFactory setupWelcomeFactory()
|
||||
{
|
||||
return request ->
|
||||
return (content, request) ->
|
||||
{
|
||||
if (_welcomes == null)
|
||||
return null;
|
||||
|
@ -131,7 +131,7 @@ public class ResourceHandler extends Handler.Wrapper
|
|||
{
|
||||
String pathInContext = Request.getPathInContext(request);
|
||||
String welcomeInContext = URIUtil.addPaths(pathInContext, welcome);
|
||||
Resource welcomePath = _baseResource.resolve(pathInContext).resolve(welcome);
|
||||
Resource welcomePath = content.getResource().resolve(welcome);
|
||||
if (Resources.isReadableFile(welcomePath))
|
||||
return welcomeInContext;
|
||||
}
|
||||
|
|
|
@ -268,7 +268,7 @@ public class StatisticsHandler extends EventsHandler
|
|||
|
||||
protected class MinimumDataRateRequest extends Request.Wrapper
|
||||
{
|
||||
private Content.Chunk.Error _errorContent;
|
||||
private Content.Chunk _errorContent;
|
||||
|
||||
private MinimumDataRateRequest(Request request)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -159,7 +159,7 @@ public class GzipRequest extends Request.Wrapper
|
|||
_chunk = inputChunk;
|
||||
if (_chunk == null)
|
||||
return null;
|
||||
if (_chunk instanceof Content.Chunk.Error)
|
||||
if (Content.Chunk.isFailure(_chunk))
|
||||
return _chunk;
|
||||
if (_chunk.isLast() && !_chunk.hasRemaining())
|
||||
return Content.Chunk.EOF;
|
||||
|
|
|
@ -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;
|
||||
|
@ -120,7 +119,7 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
/**
|
||||
* Failure passed to {@link #onFailure(Throwable)}
|
||||
*/
|
||||
private Content.Chunk.Error _failure;
|
||||
private Content.Chunk _failure;
|
||||
/**
|
||||
* Listener for {@link #onFailure(Throwable)} events
|
||||
*/
|
||||
|
@ -157,7 +156,6 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
_streamSendState = StreamSendState.SENDING;
|
||||
|
||||
// Recycle.
|
||||
_requestAttributes.clearAttributes();
|
||||
_responseHeaders.reset();
|
||||
_handling = null;
|
||||
_handled = false;
|
||||
|
@ -402,9 +400,9 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
{
|
||||
_failure = Content.Chunk.from(x);
|
||||
}
|
||||
else if (ExceptionUtil.areNotAssociated(_failure.getCause(), x) && _failure.getCause().getClass() != x.getClass())
|
||||
else if (ExceptionUtil.areNotAssociated(_failure.getFailure(), x) && _failure.getFailure().getClass() != x.getClass())
|
||||
{
|
||||
_failure.getCause().addSuppressed(x);
|
||||
_failure.getFailure().addSuppressed(x);
|
||||
}
|
||||
|
||||
// If not handled, then we just fail the request callback
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -1178,16 +1176,15 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
{
|
||||
long length = BufferUtil.length(content);
|
||||
|
||||
long totalWritten;
|
||||
HttpChannelState httpChannelState;
|
||||
HttpStream stream = null;
|
||||
Throwable failure = null;
|
||||
Throwable failure;
|
||||
MetaData.Response responseMetaData = null;
|
||||
try (AutoLock ignored = _request._lock.lock())
|
||||
{
|
||||
httpChannelState = _request.lockedGetHttpChannelState();
|
||||
long committedContentLength = httpChannelState._committedContentLength;
|
||||
totalWritten = _contentBytesWritten + length;
|
||||
long totalWritten = _contentBytesWritten + length;
|
||||
long contentLength = committedContentLength >= 0 ? committedContentLength : getHeaders().getLongField(HttpHeader.CONTENT_LENGTH);
|
||||
|
||||
if (_writeCallback != null)
|
||||
|
@ -1195,11 +1192,14 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
else
|
||||
{
|
||||
failure = getFailure(httpChannelState);
|
||||
if (failure == null && contentLength >= 0)
|
||||
if (failure == null && contentLength >= 0 && totalWritten != contentLength)
|
||||
{
|
||||
// If the content length were not compatible with what was written, then we need to abort.
|
||||
String lengthError = (totalWritten > contentLength) ? "written %d > %d content-length"
|
||||
: (last && totalWritten < contentLength) ? "written %d < %d content-length" : null;
|
||||
String lengthError = null;
|
||||
if (totalWritten > contentLength)
|
||||
lengthError = "written %d > %d content-length";
|
||||
else if (last && !(totalWritten == 0 && HttpMethod.HEAD.is(_request.getMethod())))
|
||||
lengthError = "written %d < %d content-length";
|
||||
if (lengthError != null)
|
||||
{
|
||||
String message = lengthError.formatted(totalWritten, contentLength);
|
||||
|
@ -1245,8 +1245,8 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
|
||||
protected Throwable getFailure(HttpChannelState httpChannelState)
|
||||
{
|
||||
Content.Chunk.Error failure = httpChannelState._failure;
|
||||
return failure == null ? null : failure.getCause();
|
||||
Content.Chunk failure = httpChannelState._failure;
|
||||
return failure == null ? null : failure.getFailure();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1441,7 +1441,7 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
long totalWritten = response._contentBytesWritten;
|
||||
long committedContentLength = httpChannelState._committedContentLength;
|
||||
|
||||
if (committedContentLength >= 0 && committedContentLength != totalWritten)
|
||||
if (committedContentLength >= 0 && committedContentLength != totalWritten && !(totalWritten == 0 && HttpMethod.HEAD.is(_request.getMethod())))
|
||||
failure = new IOException("content-length %d != %d written".formatted(committedContentLength, totalWritten));
|
||||
|
||||
// is the request fully consumed?
|
||||
|
@ -1696,7 +1696,7 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
protected void onError(Runnable task, Throwable failure)
|
||||
{
|
||||
ChannelRequest request;
|
||||
Content.Chunk.Error error;
|
||||
Content.Chunk error;
|
||||
boolean callbackCompleted;
|
||||
try (AutoLock ignore = _lock.lock())
|
||||
{
|
||||
|
@ -1728,9 +1728,9 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
{
|
||||
// We are already in error, so we will not handle this one,
|
||||
// but we will add as suppressed if we have not seen it already.
|
||||
Throwable cause = error.getCause();
|
||||
Throwable cause = error.getFailure();
|
||||
if (ExceptionUtil.areNotAssociated(cause, failure))
|
||||
error.getCause().addSuppressed(failure);
|
||||
error.getFailure().addSuppressed(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1098,8 +1098,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
{
|
||||
BadMessageException bad = new BadMessageException("Early EOF");
|
||||
|
||||
if (stream._chunk instanceof Error error)
|
||||
error.getCause().addSuppressed(bad);
|
||||
if (Content.Chunk.isFailure(stream._chunk))
|
||||
stream._chunk.getFailure().addSuppressed(bad);
|
||||
else
|
||||
{
|
||||
if (stream._chunk != null)
|
||||
|
|
|
@ -657,9 +657,9 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
|
|||
request.demand(this);
|
||||
return;
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
callback.failed(error.getCause());
|
||||
callback.failed(chunk.getFailure());
|
||||
return;
|
||||
}
|
||||
// copy buffer
|
||||
|
|
|
@ -710,9 +710,9 @@ public class GracefulHandlerTest
|
|||
}
|
||||
}
|
||||
LOG.debug("chunk = {}", chunk);
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
Response.writeError(request, response, callback, error.getCause());
|
||||
Response.writeError(request, response, callback, chunk.getFailure());
|
||||
return true;
|
||||
}
|
||||
bytesRead += chunk.remaining();
|
||||
|
|
|
@ -66,7 +66,6 @@ import static org.hamcrest.Matchers.sameInstance;
|
|||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -1214,8 +1213,8 @@ public class HttpChannelTest
|
|||
Request rq = handling.get().getRequest();
|
||||
Content.Chunk chunk = rq.read();
|
||||
assertTrue(chunk.isLast());
|
||||
assertInstanceOf(Content.Chunk.Error.class, chunk);
|
||||
assertThat(((Content.Chunk.Error)chunk).getCause(), sameInstance(failure));
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
assertThat(chunk.getFailure(), sameInstance(failure));
|
||||
|
||||
CountDownLatch demand = new CountDownLatch(1);
|
||||
// Callback serialized until after onError task
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
|
@ -542,10 +543,10 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
continue;
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
earlyEOFException.countDown();
|
||||
throw IO.rethrow(error.getCause());
|
||||
throw IO.rethrow(chunk.getFailure());
|
||||
}
|
||||
|
||||
if (chunk.hasRemaining())
|
||||
|
@ -1334,6 +1335,75 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeadHandled() throws Exception
|
||||
{
|
||||
startServer(new Handler.Abstract()
|
||||
{
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.getHeaders().put(HttpHeader.CONTENT_LENGTH, 10);
|
||||
if (HttpMethod.HEAD.is(request.getMethod()))
|
||||
{
|
||||
if (request.getHttpURI().getCanonicalPath().equals("/writeNull"))
|
||||
response.write(true, null, callback);
|
||||
else
|
||||
callback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
Content.Sink.write(response, true, "123456789\n", callback);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
_httpConfiguration.setSendDateHeader(false);
|
||||
_httpConfiguration.setSendServerVersion(false);
|
||||
_httpConfiguration.setSendXPoweredBy(false);
|
||||
|
||||
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
|
||||
{
|
||||
OutputStream os = client.getOutputStream();
|
||||
InputStream is = client.getInputStream();
|
||||
|
||||
os.write("""
|
||||
GET / HTTP/1.1
|
||||
Host: localhost
|
||||
|
||||
HEAD / HTTP/1.1
|
||||
Host: localhost
|
||||
|
||||
HEAD /writeNull HTTP/1.1
|
||||
Host: localhost
|
||||
|
||||
GET / HTTP/1.1
|
||||
Host: localhost
|
||||
Connection: close
|
||||
|
||||
""".getBytes(StandardCharsets.ISO_8859_1));
|
||||
|
||||
String in = IO.toString(is);
|
||||
assertThat(in.replace("\r", ""), is("""
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 10
|
||||
|
||||
123456789
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 10
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 10
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 10
|
||||
Connection: close
|
||||
|
||||
123456789
|
||||
"""));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockedClient() throws Exception
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
|
@ -39,11 +40,11 @@ import org.eclipse.jetty.util.NanoTime;
|
|||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
|
@ -57,7 +58,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@Disabled // TODO
|
||||
public class StopTest
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StopTest.class);
|
||||
|
@ -93,7 +93,7 @@ public class StopTest
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
stopper.start();
|
||||
|
@ -108,10 +108,7 @@ public class StopTest
|
|||
).getBytes());
|
||||
client.getOutputStream().flush();
|
||||
|
||||
while (!connector.isShutdown())
|
||||
{
|
||||
Thread.sleep(10);
|
||||
}
|
||||
await().atMost(10, TimeUnit.SECONDS).until(connector::isShutdown);
|
||||
|
||||
handler.latchB.countDown();
|
||||
|
||||
|
@ -281,89 +278,83 @@ public class StopTest
|
|||
LocalConnector connector = new LocalConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
StatisticsHandler stats = new StatisticsHandler();
|
||||
ContextHandler context = new ContextHandler("/");
|
||||
StatisticsHandler stats = new StatisticsHandler(context);
|
||||
server.setHandler(stats);
|
||||
|
||||
ContextHandler context = new ContextHandler(stats, "/");
|
||||
|
||||
Exchanger<Void> exchanger0 = new Exchanger<>();
|
||||
Exchanger<Void> exchanger1 = new Exchanger<>();
|
||||
/* TODO
|
||||
context.setHandler(new AbstractHandler()
|
||||
context.setHandler(new Handler.Abstract()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException, ServletException
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
exchanger0.exchange(null);
|
||||
exchanger1.exchange(null);
|
||||
|
||||
response.setStatus(200);
|
||||
Content.Sink.write(response, true, "The Response", callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
throw new ServletException(x);
|
||||
callback.failed(x);
|
||||
}
|
||||
|
||||
baseRequest.setHandled(true);
|
||||
response.setStatus(200);
|
||||
response.getWriter().println("The Response");
|
||||
response.getWriter().close();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
server.setStopTimeout(1000);
|
||||
server.start();
|
||||
|
||||
LocalEndPoint endp = connector.executeRequest(
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n"
|
||||
);
|
||||
|
||||
exchanger0.exchange(null);
|
||||
exchanger1.exchange(null);
|
||||
|
||||
String response = endp.getResponse();
|
||||
assertThat(response, containsString("200 OK"));
|
||||
assertThat(response, Matchers.not(containsString("Connection: close")));
|
||||
|
||||
endp.addInputAndExecute(BufferUtil.toBuffer("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"));
|
||||
|
||||
exchanger0.exchange(null);
|
||||
|
||||
FutureCallback stopped = new FutureCallback();
|
||||
new Thread(() ->
|
||||
try (LocalEndPoint endp = connector.executeRequest(
|
||||
"""
|
||||
GET / HTTP/1.1\r
|
||||
Host: localhost\r
|
||||
\r
|
||||
"""
|
||||
))
|
||||
{
|
||||
try
|
||||
{
|
||||
server.stop();
|
||||
stopped.succeeded();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
stopped.failed(e);
|
||||
}
|
||||
}).start();
|
||||
exchanger0.exchange(null);
|
||||
exchanger1.exchange(null);
|
||||
|
||||
long start = NanoTime.now();
|
||||
while (!connector.isShutdown())
|
||||
{
|
||||
assertThat(NanoTime.secondsSince(start), lessThan(10L));
|
||||
Thread.sleep(10);
|
||||
String response = endp.getResponse();
|
||||
assertThat(response, containsString("200 OK"));
|
||||
assertThat(response, Matchers.not(containsString("Connection: close")));
|
||||
|
||||
endp.addInputAndExecute(BufferUtil.toBuffer("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"));
|
||||
|
||||
exchanger0.exchange(null);
|
||||
|
||||
FutureCallback stopped = new FutureCallback();
|
||||
new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
server.stop();
|
||||
stopped.succeeded();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
stopped.failed(e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
await().atMost(10, TimeUnit.SECONDS).until(connector::isShutdown);
|
||||
|
||||
// Check new connections rejected!
|
||||
assertThrows(IllegalStateException.class, () -> connector.getResponse("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"));
|
||||
|
||||
// Check completed 200 has close
|
||||
exchanger1.exchange(null);
|
||||
response = endp.getResponse();
|
||||
assertThat(response, containsString("200 OK"));
|
||||
assertThat(response, Matchers.containsString("Connection: close"));
|
||||
stopped.get(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// Check new connections rejected!
|
||||
assertThrows(IllegalStateException.class, () -> connector.getResponse("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"));
|
||||
|
||||
// Check completed 200 has close
|
||||
exchanger1.exchange(null);
|
||||
response = endp.getResponse();
|
||||
assertThat(response, containsString("200 OK"));
|
||||
assertThat(response, Matchers.containsString("Connection: close"));
|
||||
stopped.get(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -374,85 +365,81 @@ public class StopTest
|
|||
LocalConnector connector = new LocalConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
ContextHandler context = new ContextHandler(server, "/");
|
||||
|
||||
ContextHandler context = new ContextHandler("/");
|
||||
server.setHandler(context);
|
||||
StatisticsHandler stats = new StatisticsHandler();
|
||||
context.setHandler(stats);
|
||||
|
||||
Exchanger<Void> exchanger0 = new Exchanger<>();
|
||||
Exchanger<Void> exchanger1 = new Exchanger<>();
|
||||
/* TODO
|
||||
stats.setHandler(new AbstractHandler()
|
||||
stats.setHandler(new Handler.Abstract()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException, ServletException
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
exchanger0.exchange(null);
|
||||
exchanger1.exchange(null);
|
||||
|
||||
response.setStatus(200);
|
||||
Content.Sink.write(response, true, "The Response", callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
throw new ServletException(x);
|
||||
callback.failed(x);
|
||||
}
|
||||
|
||||
baseRequest.setHandled(true);
|
||||
response.setStatus(200);
|
||||
response.getWriter().println("The Response");
|
||||
response.getWriter().close();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
server.start();
|
||||
|
||||
LocalEndPoint endp = connector.executeRequest(
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n"
|
||||
);
|
||||
|
||||
exchanger0.exchange(null);
|
||||
exchanger1.exchange(null);
|
||||
|
||||
String response = endp.getResponse();
|
||||
assertThat(response, containsString("200 OK"));
|
||||
assertThat(response, Matchers.not(containsString("Connection: close")));
|
||||
|
||||
endp.addInputAndExecute(BufferUtil.toBuffer("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"));
|
||||
exchanger0.exchange(null);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
new Thread(() ->
|
||||
try (LocalEndPoint endp = connector.executeRequest(
|
||||
"""
|
||||
GET / HTTP/1.1\r
|
||||
Host: localhost\r
|
||||
\r
|
||||
"""
|
||||
))
|
||||
{
|
||||
try
|
||||
exchanger0.exchange(null);
|
||||
exchanger1.exchange(null);
|
||||
|
||||
String response = endp.getResponse();
|
||||
assertThat(response, containsString("200 OK"));
|
||||
assertThat(response, Matchers.not(containsString("Connection: close")));
|
||||
|
||||
endp.addInputAndExecute(BufferUtil.toBuffer("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n"));
|
||||
exchanger0.exchange(null);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
new Thread(() ->
|
||||
{
|
||||
context.stop();
|
||||
latch.countDown();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
while (context.isStarted())
|
||||
{
|
||||
Thread.sleep(10);
|
||||
try
|
||||
{
|
||||
context.stop();
|
||||
latch.countDown();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
|
||||
await().atMost(10, TimeUnit.SECONDS).until(context::isStopped);
|
||||
|
||||
// Check new connections accepted, but don't find context!
|
||||
String unavailable = connector.getResponse("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n");
|
||||
assertThat(unavailable, containsString(" 404 Not Found"));
|
||||
|
||||
// Check completed 200 does not have close
|
||||
exchanger1.exchange(null);
|
||||
response = endp.getResponse();
|
||||
assertThat(response, containsString("200 OK"));
|
||||
assertThat(response, Matchers.not(Matchers.containsString("Connection: close")));
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
// Check new connections accepted, but don't find context!
|
||||
String unavailable = connector.getResponse("GET / HTTP/1.1\r\nHost:localhost\r\n\r\n");
|
||||
assertThat(unavailable, containsString(" 404 Not Found"));
|
||||
|
||||
// Check completed 200 does not have close
|
||||
exchanger1.exchange(null);
|
||||
response = endp.getResponse();
|
||||
assertThat(response, containsString("200 OK"));
|
||||
assertThat(response, Matchers.not(Matchers.containsString("Connection: close")));
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -486,12 +473,12 @@ public class StopTest
|
|||
ContextHandler context2 = new ContextHandler("/two")
|
||||
{
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
protected void doStart()
|
||||
{
|
||||
context2Started.set(true);
|
||||
}
|
||||
};
|
||||
contexts.setHandlers(new Handler[]{context0, context1, context2});
|
||||
contexts.setHandlers(context0, context1, context2);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -535,30 +522,19 @@ public class StopTest
|
|||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.getHeaders().put(HttpHeader.CONTENT_LENGTH, 2);
|
||||
response.write(true, ByteBuffer.wrap("a".getBytes()), new Callback()
|
||||
request.getContext().run(() ->
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
latchA.countDown();
|
||||
latchB.await();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
response.write(true, ByteBuffer.wrap("b".getBytes()), callback);
|
||||
latchA.countDown();
|
||||
latchB.await();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
callback.failed(x);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
response.write(true, ByteBuffer.wrap("ab".getBytes()), callback);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,9 +105,9 @@ public class DumpHandler extends Handler.Abstract
|
|||
}
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
callback.failed(error.getCause());
|
||||
callback.failed(chunk.getFailure());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -100,9 +100,9 @@ public class StatisticsHandlerTest
|
|||
return true;
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error errorContent)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
callback.failed(errorContent.getCause());
|
||||
callback.failed(chunk.getFailure());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -284,8 +284,8 @@ public class ThreadLimitHandlerTest
|
|||
request.demand(this);
|
||||
return;
|
||||
}
|
||||
if (chunk instanceof Error error)
|
||||
throw error.getCause();
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
throw chunk.getFailure();
|
||||
|
||||
if (chunk.hasRemaining())
|
||||
read.addAndGet(chunk.remaining());
|
||||
|
|
|
@ -63,7 +63,6 @@ import org.junit.jupiter.params.provider.MethodSource;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -256,8 +255,8 @@ public class HttpClientTest extends AbstractTest
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
throw IO.rethrow(error.getCause());
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
throw IO.rethrow(chunk.getFailure());
|
||||
|
||||
total += chunk.remaining();
|
||||
if (total >= sleep)
|
||||
|
@ -941,7 +940,7 @@ public class HttpClientTest extends AbstractTest
|
|||
assertThat(chunks2.stream().mapToInt(c -> c.getByteBuffer().remaining()).sum(), is(totalBytes));
|
||||
assertThat(chunks3.stream().mapToInt(c -> c.getByteBuffer().remaining()).sum(), is(0));
|
||||
assertThat(chunks3.size(), is(1));
|
||||
assertThat(chunks3.get(0), instanceOf(Content.Chunk.Error.class));
|
||||
assertTrue(Content.Chunk.isFailure(chunks3.get(0), true));
|
||||
|
||||
chunks1.forEach(Content.Chunk::release);
|
||||
chunks2.forEach(Content.Chunk::release);
|
||||
|
@ -983,7 +982,7 @@ public class HttpClientTest extends AbstractTest
|
|||
assertThat(chunks3Latch.await(5, TimeUnit.SECONDS), is(true));
|
||||
assertThat(chunks3.stream().mapToInt(c -> c.getByteBuffer().remaining()).sum(), is(0));
|
||||
assertThat(chunks3.size(), is(1));
|
||||
assertThat(chunks3.get(0), instanceOf(Content.Chunk.Error.class));
|
||||
assertTrue(Content.Chunk.isFailure(chunks3.get(0), true));
|
||||
|
||||
chunks1.forEach(Content.Chunk::release);
|
||||
chunks2.forEach(Content.Chunk::release);
|
||||
|
|
|
@ -130,8 +130,8 @@ public class ServerTimeoutsTest extends AbstractTest
|
|||
|
||||
// Reads should yield the idle timeout.
|
||||
Content.Chunk chunk = requestRef.get().read();
|
||||
assertThat(chunk, instanceOf(Content.Chunk.Error.class));
|
||||
Throwable cause = ((Content.Chunk.Error)chunk).getCause();
|
||||
assertTrue(Content.Chunk.isFailure(chunk, true));
|
||||
Throwable cause = chunk.getFailure();
|
||||
assertThat(cause, instanceOf(TimeoutException.class));
|
||||
|
||||
// Complete the callback as the error listener promised.
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -400,6 +400,7 @@ public abstract class IteratingCallback implements Callback
|
|||
break;
|
||||
case PENDING:
|
||||
{
|
||||
_state = State.FAILED;
|
||||
failure = true;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.util;
|
|||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
|
@ -322,4 +323,44 @@ public class IteratingCallbackTest
|
|||
return isSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleFailures() throws Exception
|
||||
{
|
||||
AtomicInteger process = new AtomicInteger();
|
||||
AtomicInteger failure = new AtomicInteger();
|
||||
IteratingCallback icb = new IteratingCallback()
|
||||
{
|
||||
@Override
|
||||
protected Action process() throws Throwable
|
||||
{
|
||||
process.incrementAndGet();
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable cause)
|
||||
{
|
||||
super.onCompleteFailure(cause);
|
||||
failure.incrementAndGet();
|
||||
}
|
||||
};
|
||||
|
||||
icb.iterate();
|
||||
assertEquals(1, process.get());
|
||||
assertEquals(0, failure.get());
|
||||
|
||||
icb.failed(new Throwable("test1"));
|
||||
|
||||
assertEquals(1, process.get());
|
||||
assertEquals(1, failure.get());
|
||||
|
||||
icb.succeeded();
|
||||
assertEquals(1, process.get());
|
||||
assertEquals(1, failure.get());
|
||||
|
||||
icb.failed(new Throwable("test2"));
|
||||
assertEquals(1, process.get());
|
||||
assertEquals(1, failure.get());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,5 +28,7 @@ public interface ServerUpgradeResponse extends Response
|
|||
|
||||
void addExtensions(List<ExtensionConfig> configs);
|
||||
|
||||
void removeExtensions(List<ExtensionConfig> configs);
|
||||
|
||||
void setExtensions(List<ExtensionConfig> configs);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ public class TestJettyJspServlet
|
|||
_server = new Server();
|
||||
_connector = new LocalConnector(_server);
|
||||
_server.addConnector(_connector);
|
||||
ServletContextHandler context = new ServletContextHandler(_server, "/context", true, false);
|
||||
ServletContextHandler context = new ServletContextHandler("/context", true, false);
|
||||
_server.setHandler(context);
|
||||
context.setClassLoader(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
|
||||
ServletHolder jspHolder = context.addServlet(JettyJspServlet.class, "/*");
|
||||
|
|
|
@ -70,7 +70,7 @@ public class Http2Server
|
|||
|
||||
server.addBean(LoggerFactory.getILoggerFactory());
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
|
||||
ServletContextHandler context = new ServletContextHandler("/", ServletContextHandler.SESSIONS);
|
||||
Path docroot = Paths.get("src/main/resources/docroot");
|
||||
if (!Files.exists(docroot))
|
||||
throw new FileNotFoundException(docroot.toString());
|
||||
|
|
|
@ -38,16 +38,17 @@ public class ManyServletContexts
|
|||
server.setHandler(contexts);
|
||||
|
||||
// Configure context "/" (root) for servlets
|
||||
ServletContextHandler root = new ServletContextHandler(contexts, "/",
|
||||
ServletContextHandler root = new ServletContextHandler("/",
|
||||
ServletContextHandler.SESSIONS);
|
||||
contexts.addHandler(root);
|
||||
// Add servlets to root context
|
||||
root.addServlet(new ServletHolder(new HelloServlet("Hello")), "/");
|
||||
root.addServlet(new ServletHolder(new HelloServlet("Ciao")), "/it/*");
|
||||
root.addServlet(new ServletHolder(new HelloServlet("Bonjour")), "/fr/*");
|
||||
|
||||
// Configure context "/other" for servlets
|
||||
ServletContextHandler other = new ServletContextHandler(contexts,
|
||||
"/other", ServletContextHandler.SESSIONS);
|
||||
ServletContextHandler other = new ServletContextHandler("/other", ServletContextHandler.SESSIONS);
|
||||
contexts.addHandler(other);
|
||||
// Add servlets to /other context
|
||||
other.addServlet(DefaultServlet.class.getCanonicalName(), "/");
|
||||
other.addServlet(new ServletHolder(new HelloServlet("YO!")), "*.yo");
|
||||
|
|
|
@ -36,8 +36,9 @@ public class ProxyServer
|
|||
server.setHandler(proxy);
|
||||
|
||||
// Setup proxy servlet
|
||||
ServletContextHandler context = new ServletContextHandler(proxy, "/",
|
||||
ServletContextHandler context = new ServletContextHandler("/",
|
||||
ServletContextHandler.SESSIONS);
|
||||
proxy.setHandler(context);
|
||||
ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
|
||||
proxyServlet.setInitParameter("blackList", "www.eclipse.org");
|
||||
context.addServlet(proxyServlet, "/*");
|
||||
|
|
|
@ -36,7 +36,8 @@ public class ChatServletTest
|
|||
server = new Server();
|
||||
connector = new LocalConnector(server);
|
||||
server.addConnector(connector);
|
||||
ServletContextHandler context = new ServletContextHandler(server, "/");
|
||||
ServletContextHandler context = new ServletContextHandler("/");
|
||||
server.setHandler(context);
|
||||
ServletHolder dispatch = context.addServlet(ChatServlet.class, "/chat/*");
|
||||
dispatch.setInitParameter("asyncTimeout", "500");
|
||||
server.start();
|
||||
|
|
|
@ -44,7 +44,8 @@ public class DispatchServletTest
|
|||
server = new Server();
|
||||
connector = new LocalConnector(server);
|
||||
server.addConnector(connector);
|
||||
context = new ServletContextHandler(server, "/tests");
|
||||
context = new ServletContextHandler("/tests");
|
||||
server.setHandler(context);
|
||||
server.start();
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,8 @@ public class TryFilesFilterTest
|
|||
sslConnector = new ServerConnector(server, serverSslContextFactory);
|
||||
server.addConnector(sslConnector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(server, "/");
|
||||
ServletContextHandler context = new ServletContextHandler("/");
|
||||
server.setHandler(context);
|
||||
|
||||
FilterHolder filterHolder = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
forwardPath = "/index.php";
|
||||
|
|
|
@ -257,9 +257,9 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
public Content.Chunk read()
|
||||
{
|
||||
Content.Chunk chunk = super.read();
|
||||
if (chunk instanceof Content.Chunk.Error error)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
onClientRequestFailure(request, proxyRequest, response, error.getCause());
|
||||
onClientRequestFailure(request, proxyRequest, response, chunk.getFailure());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -109,7 +109,8 @@ public class AsyncMiddleManServletTest
|
|||
serverConnector = new ServerConnector(server);
|
||||
server.addConnector(serverConnector);
|
||||
|
||||
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
|
||||
ServletContextHandler appCtx = new ServletContextHandler("/", true, false);
|
||||
server.setHandler(appCtx);
|
||||
ServletHolder appServletHolder = new ServletHolder(servlet);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
|
@ -136,7 +137,8 @@ public class AsyncMiddleManServletTest
|
|||
proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
|
||||
proxy.addConnector(proxyConnector);
|
||||
|
||||
ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false);
|
||||
ServletContextHandler proxyContext = new ServletContextHandler("/", true, false);
|
||||
proxy.setHandler(proxyContext);
|
||||
this.proxyServlet = proxyServlet;
|
||||
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
|
||||
proxyServletHolder.setInitParameters(initParams);
|
||||
|
|
|
@ -93,7 +93,8 @@ public class BalancerServletTest
|
|||
ServerConnector connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(server, CONTEXT_PATH, ServletContextHandler.SESSIONS);
|
||||
ServletContextHandler context = new ServletContextHandler(CONTEXT_PATH, ServletContextHandler.SESSIONS);
|
||||
server.setHandler(context);
|
||||
context.addServlet(servletHolder, SERVLET_PATH + "/*");
|
||||
|
||||
if (nodeName != null)
|
||||
|
|
|
@ -177,7 +177,7 @@ public class ClientAuthProxyTest
|
|||
proxyConnector = new ServerConnector(proxy, 1, 1, ssl, http);
|
||||
proxy.addConnector(proxyConnector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(proxy, "/");
|
||||
ServletContextHandler context = new ServletContextHandler("/");
|
||||
context.addServlet(new ServletHolder(servlet), "/*");
|
||||
proxy.setHandler(context);
|
||||
|
||||
|
|
|
@ -109,7 +109,8 @@ public class ForwardProxyServerTest
|
|||
connectHandler.setConnectTimeout(1000);
|
||||
proxy.setHandler(connectHandler);
|
||||
|
||||
ServletContextHandler proxyHandler = new ServletContextHandler(connectHandler, "/");
|
||||
ServletContextHandler proxyHandler = new ServletContextHandler("/");
|
||||
connectHandler.setHandler(proxyHandler);
|
||||
proxyHandler.addServlet(new ServletHolder(proxyServlet), "/*");
|
||||
|
||||
proxy.start();
|
||||
|
|
|
@ -35,7 +35,8 @@ public class ProxyServer
|
|||
server.setHandler(proxy);
|
||||
|
||||
// Setup proxy servlet
|
||||
ServletContextHandler context = new ServletContextHandler(proxy, "/", ServletContextHandler.SESSIONS);
|
||||
ServletContextHandler context = new ServletContextHandler("/", ServletContextHandler.SESSIONS);
|
||||
proxy.setHandler(context);
|
||||
ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
|
||||
// proxyServlet.setInitParameter("whiteList", "google.com, www.eclipse.org, localhost");
|
||||
// proxyServlet.setInitParameter("blackList", "google.com/calendar/*, www.eclipse.org/committers/");
|
||||
|
|
|
@ -99,7 +99,8 @@ public class ProxyServletFailureTest
|
|||
proxy.addConnector(proxyConnector);
|
||||
proxyConnector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setDelayDispatchUntilContent(false);
|
||||
|
||||
ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
|
||||
ServletContextHandler proxyCtx = new ServletContextHandler("/", true, false);
|
||||
proxy.setHandler(proxyCtx);
|
||||
|
||||
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
|
||||
proxyServletHolder.setInitParameters(initParams);
|
||||
|
@ -129,7 +130,8 @@ public class ProxyServletFailureTest
|
|||
serverConnector = new ServerConnector(server);
|
||||
server.addConnector(serverConnector);
|
||||
|
||||
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
|
||||
ServletContextHandler appCtx = new ServletContextHandler("/", true, false);
|
||||
server.setHandler(appCtx);
|
||||
ServletHolder appServletHolder = new ServletHolder(servlet);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
|
|
|
@ -77,7 +77,8 @@ public class ProxyServletLoadTest
|
|||
serverConnector = new ServerConnector(server);
|
||||
server.addConnector(serverConnector);
|
||||
|
||||
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
|
||||
ServletContextHandler appCtx = new ServletContextHandler("/", true, false);
|
||||
server.setHandler(appCtx);
|
||||
ServletHolder appServletHolder = new ServletHolder(servlet);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
|
@ -96,7 +97,8 @@ public class ProxyServletLoadTest
|
|||
proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
|
||||
proxy.addConnector(proxyConnector);
|
||||
|
||||
ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false);
|
||||
ServletContextHandler proxyContext = new ServletContextHandler("/", true, false);
|
||||
proxy.setHandler(proxyContext);
|
||||
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
|
||||
proxyContext.addServlet(proxyServletHolder, "/*");
|
||||
|
||||
|
|
|
@ -152,7 +152,8 @@ public class ProxyServletTest
|
|||
new HttpConnectionFactory());
|
||||
server.addConnector(tlsServerConnector);
|
||||
|
||||
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
|
||||
ServletContextHandler appCtx = new ServletContextHandler("/", true, false);
|
||||
server.setHandler(appCtx);
|
||||
ServletHolder appServletHolder = new ServletHolder(servlet);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
|
@ -184,7 +185,8 @@ public class ProxyServletTest
|
|||
proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
|
||||
proxy.addConnector(proxyConnector);
|
||||
|
||||
proxyContext = new ServletContextHandler(proxy, "/", true, false);
|
||||
proxyContext = new ServletContextHandler("/", true, false);
|
||||
proxy.setHandler(proxyContext);
|
||||
this.proxyServlet = proxyServlet;
|
||||
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
|
||||
proxyServletHolder.setInitParameters(initParams);
|
||||
|
|
|
@ -49,7 +49,8 @@ public class ReverseProxyTest
|
|||
serverConnector = new ServerConnector(server);
|
||||
server.addConnector(serverConnector);
|
||||
|
||||
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
|
||||
ServletContextHandler appCtx = new ServletContextHandler("/", true, false);
|
||||
server.setHandler(appCtx);
|
||||
ServletHolder appServletHolder = new ServletHolder(servlet);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
|
@ -66,7 +67,8 @@ public class ReverseProxyTest
|
|||
proxyConnector = new ServerConnector(proxy, new HttpConnectionFactory(configuration));
|
||||
proxy.addConnector(proxyConnector);
|
||||
|
||||
ServletContextHandler proxyContext = new ServletContextHandler(proxy, "/", true, false);
|
||||
ServletContextHandler proxyContext = new ServletContextHandler("/", true, false);
|
||||
proxy.setHandler(proxyContext);
|
||||
ServletHolder proxyServletHolder = new ServletHolder(new AsyncMiddleManServlet()
|
||||
{
|
||||
@Override
|
||||
|
|
|
@ -358,8 +358,9 @@ public class Runner
|
|||
statsHandler.setHandler(oldHandler);
|
||||
_server.setHandler(statsHandler);
|
||||
|
||||
ServletContextHandler statsContext = new ServletContextHandler(_contexts, "/stats");
|
||||
ServletContextHandler statsContext = new ServletContextHandler("/stats");
|
||||
statsContext.setSessionHandler(new SessionHandler());
|
||||
_contexts.addHandler(statsContext);
|
||||
if (_statsPropFile != null)
|
||||
{
|
||||
ResourceFactory resourceFactory = ResourceFactory.of(statsContext);
|
||||
|
@ -457,10 +458,11 @@ public class Runner
|
|||
else
|
||||
{
|
||||
// assume it is a WAR file
|
||||
WebAppContext webapp = new WebAppContext(_contexts, ctx.toString(), contextPath);
|
||||
WebAppContext webapp = new WebAppContext(ctx.toString(), contextPath);
|
||||
webapp.setConfigurationClasses(PLUS_CONFIGURATION_CLASSES);
|
||||
webapp.setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN,
|
||||
CONTAINER_INCLUDE_JAR_PATTERN);
|
||||
_contexts.addHandler(webapp);
|
||||
}
|
||||
|
||||
//reset
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory;
|
|||
class AsyncContentProducer implements ContentProducer
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AsyncContentProducer.class);
|
||||
private static final Content.Chunk.Error RECYCLED_ERROR_CHUNK = Content.Chunk.from(new StaticException("ContentProducer has been recycled"));
|
||||
private static final Content.Chunk RECYCLED_ERROR_CHUNK = Content.Chunk.from(new StaticException("ContentProducer has been recycled"), true);
|
||||
|
||||
final AutoLock _lock;
|
||||
private final ServletChannel _servletChannel;
|
||||
|
@ -101,10 +101,10 @@ class AsyncContentProducer implements ContentProducer
|
|||
public boolean isError()
|
||||
{
|
||||
assertLocked();
|
||||
boolean error = _chunk instanceof Content.Chunk.Error;
|
||||
boolean failure = Content.Chunk.isFailure(_chunk);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("isError = {} {}", error, this);
|
||||
return error;
|
||||
LOG.debug("isFailure = {} {}", failure, this);
|
||||
return failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
@ -1015,23 +1016,14 @@ public class DefaultServlet extends HttpServlet
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getWelcomeTarget(Request coreRequest)
|
||||
public String getWelcomeTarget(HttpContent content, Request coreRequest)
|
||||
{
|
||||
String[] welcomes = _servletContextHandler.getWelcomeFiles();
|
||||
if (welcomes == null)
|
||||
return null;
|
||||
|
||||
HttpServletRequest request = getServletRequest(coreRequest);
|
||||
String pathInContext = Request.getPathInContext(coreRequest);
|
||||
String includedServletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
|
||||
String requestTarget;
|
||||
if (includedServletPath != null)
|
||||
requestTarget = getIncludedPathInContext(request, includedServletPath, isPathInfoOnly());
|
||||
else
|
||||
requestTarget = isPathInfoOnly() ? request.getPathInfo() : pathInContext;
|
||||
|
||||
String welcomeTarget = null;
|
||||
Resource base = _baseResource.resolve(requestTarget);
|
||||
Resource base = content.getResource();
|
||||
if (Resources.isReadableDirectory(base))
|
||||
{
|
||||
for (String welcome : welcomes)
|
||||
|
@ -1040,13 +1032,16 @@ public class DefaultServlet extends HttpServlet
|
|||
|
||||
// If the welcome resource is a file, it has
|
||||
// precedence over resources served by Servlets.
|
||||
Resource welcomePath = base.resolve(welcome);
|
||||
Resource welcomePath = content.getResource().resolve(welcome);
|
||||
if (Resources.isReadableFile(welcomePath))
|
||||
return welcomeInContext;
|
||||
|
||||
// Check whether a Servlet may serve the welcome resource.
|
||||
if (_welcomeServletMode != WelcomeServletMode.NONE && welcomeTarget == null)
|
||||
{
|
||||
if (isPathInfoOnly() && !isIncluded(getServletRequest(coreRequest)))
|
||||
welcomeTarget = URIUtil.addPaths(getServletRequest(coreRequest).getPathInfo(), welcome);
|
||||
|
||||
ServletHandler.MappedServlet entry = _servletContextHandler.getServletHandler().getMappedServlet(welcomeInContext);
|
||||
// Is there a different Servlet that may serve the welcome resource?
|
||||
if (entry != null && entry.getServletHolder().getServletInstance() != DefaultServlet.this)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -255,14 +255,14 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
return read;
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error errorChunk)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
Throwable error = errorChunk.getCause();
|
||||
Throwable failure = chunk.getFailure();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("read error={} {}", error, this);
|
||||
if (error instanceof IOException)
|
||||
throw (IOException)error;
|
||||
throw new IOException(error);
|
||||
LOG.debug("read failure={} {}", failure, this);
|
||||
if (failure instanceof IOException)
|
||||
throw (IOException)failure;
|
||||
throw new IOException(failure);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -343,14 +343,14 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
return;
|
||||
}
|
||||
|
||||
if (chunk instanceof Content.Chunk.Error errorChunk)
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
Throwable error = errorChunk.getCause();
|
||||
Throwable failure = chunk.getFailure();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("running error={} {}", error, this);
|
||||
LOG.debug("running failure={} {}", failure, this);
|
||||
// TODO is this necessary to add here?
|
||||
_servletChannel.getResponse().getHeaders().add(HttpFields.CONNECTION_CLOSE);
|
||||
_readListener.onError(error);
|
||||
_servletChannel.getServletContextResponse().getHeaders().add(HttpFields.CONNECTION_CLOSE);
|
||||
_readListener.onError(failure);
|
||||
}
|
||||
else if (chunk.isLast() && !chunk.hasRemaining())
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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,100 @@ 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();
|
||||
int status = getResponse().getStatus();
|
||||
return status == 0 ? 200 : status;
|
||||
}
|
||||
|
||||
@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 +334,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 +351,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 +376,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 +397,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 +416,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 +440,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 +482,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 +501,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 +591,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
|
||||
|
|
|
@ -16,16 +16,8 @@ package org.eclipse.jetty.ee10.servlet;
|
|||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletRequestState.Action;
|
||||
|
@ -38,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;
|
||||
|
@ -63,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
|
||||
|
@ -74,37 +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 final Listener _combinedListener;
|
||||
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);
|
||||
_combinedListener = new Listeners(_connector, servletContextHandler);
|
||||
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()
|
||||
|
@ -114,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())
|
||||
|
@ -131,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()
|
||||
|
@ -175,7 +203,7 @@ public class ServletChannel
|
|||
return HostPort.normalizeHost(addr);
|
||||
}
|
||||
|
||||
public ServletRequestState getState()
|
||||
public ServletRequestState getServletRequestState()
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
@ -194,7 +222,7 @@ public class ServletChannel
|
|||
*/
|
||||
public long getIdleTimeout()
|
||||
{
|
||||
return _endPoint.getIdleTimeout();
|
||||
return _connectionMetaData.getConnection().getEndPoint().getIdleTimeout();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -206,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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -315,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;
|
||||
|
@ -323,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;
|
||||
|
@ -360,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)
|
||||
{
|
||||
|
@ -382,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()
|
||||
|
@ -422,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());
|
||||
}
|
||||
|
@ -474,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
|
||||
|
@ -496,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
|
||||
|
@ -511,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();
|
||||
}
|
||||
|
@ -530,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();
|
||||
}
|
||||
}
|
||||
|
@ -551,7 +612,7 @@ public class ServletChannel
|
|||
{
|
||||
try
|
||||
{
|
||||
getResponse().resetContent();
|
||||
getServletContextResponse().resetContent();
|
||||
sendResponseAndComplete();
|
||||
}
|
||||
catch (Throwable t)
|
||||
|
@ -589,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.
|
||||
|
@ -602,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;
|
||||
|
@ -622,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;
|
||||
}
|
||||
|
@ -663,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)
|
||||
|
@ -676,22 +737,9 @@ public class ServletChannel
|
|||
|
||||
private void dispatch(Dispatchable dispatchable) throws Exception
|
||||
{
|
||||
try
|
||||
{
|
||||
_servletContextRequest.getResponse().getHttpOutput().reopen();
|
||||
getHttpOutput().reopen();
|
||||
_combinedListener.onBeforeDispatch(_servletContextRequest);
|
||||
dispatchable.dispatch();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
_combinedListener.onDispatchFailure(_servletContextRequest, x);
|
||||
throw x;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_combinedListener.onAfterDispatch(_servletContextRequest);
|
||||
}
|
||||
_servletContextRequest.getServletContextResponse().getHttpOutput().reopen();
|
||||
getHttpOutput().reopen();
|
||||
dispatchable.dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -771,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)
|
||||
{
|
||||
|
@ -824,7 +872,6 @@ public class ServletChannel
|
|||
void onTrailers(HttpFields trailers)
|
||||
{
|
||||
_servletContextRequest.setTrailers(trailers);
|
||||
_combinedListener.onRequestTrailers(_servletContextRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -851,10 +898,7 @@ public class ServletChannel
|
|||
// Recycle always done here even if an abort is called.
|
||||
recycle();
|
||||
if (_state.completeResponse())
|
||||
{
|
||||
_combinedListener.onComplete(servletContextRequest);
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCommitted()
|
||||
|
@ -880,7 +924,7 @@ public class ServletChannel
|
|||
|
||||
protected void execute(Runnable task)
|
||||
{
|
||||
_executor.execute(task);
|
||||
_context.execute(task);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -897,9 +941,7 @@ public class ServletChannel
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("abort {}", this, failure);
|
||||
Callback callback = _callback;
|
||||
_combinedListener.onResponseFailure(_servletContextRequest, failure);
|
||||
callback.failed(failure);
|
||||
_callback.failed(failure);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -907,320 +949,4 @@ public class ServletChannel
|
|||
{
|
||||
void dispatch() throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Listener for Channel events.</p>
|
||||
* <p>HttpChannel will emit events for the various phases it goes through while
|
||||
* processing an HTTP request and response.</p>
|
||||
* <p>Implementations of this interface may listen to those events to track
|
||||
* timing and/or other values such as request URI, etc.</p>
|
||||
* <p>The events parameters, especially the {@link Request} object, may be
|
||||
* in a transient state depending on the event, and not all properties/features
|
||||
* of the parameters may be available inside a listener method.</p>
|
||||
* <p>It is recommended that the event parameters are <em>not</em> acted upon
|
||||
* in the listener methods, or undefined behavior may result. For example, it
|
||||
* would be a bad idea to try to read some content from the
|
||||
* {@link jakarta.servlet.ServletInputStream} in listener methods. On the other
|
||||
* hand, it is legit to store request attributes in one listener method that
|
||||
* may be possibly retrieved in another listener method in a later event.</p>
|
||||
* <p>Listener methods are invoked synchronously from the thread that is
|
||||
* performing the request processing, and they should not call blocking code
|
||||
* (otherwise the request processing will be blocked as well).</p>
|
||||
* <p>Listener instances that are set as a bean on the {@link Connector} are
|
||||
* also added. If additional listeners are added
|
||||
* using the deprecated {@code HttpChannel#addListener(Listener)}</p> method,
|
||||
* then an instance of {@code TransientListeners} must be added to the connector
|
||||
* in order for them to be invoked.
|
||||
*/
|
||||
// TODO: looks like a lot of these methods are never called.
|
||||
public interface Listener extends EventListener
|
||||
{
|
||||
/**
|
||||
* Invoked just after the HTTP request line and headers have been parsed.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onRequestBegin(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked just before calling the application.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onBeforeDispatch(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the application threw an exception.
|
||||
*
|
||||
* @param request the request object
|
||||
* @param failure the exception thrown by the application
|
||||
*/
|
||||
default void onDispatchFailure(Request request, Throwable failure)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked just after the application returns from the first invocation.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onAfterDispatch(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked every time a request content chunk has been parsed, just before
|
||||
* making it available to the application.
|
||||
*
|
||||
* @param request the request object
|
||||
* @param content a {@link ByteBuffer#slice() slice} of the request content chunk
|
||||
*/
|
||||
default void onRequestContent(Request request, ByteBuffer content)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the end of the request content is detected.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onRequestContentEnd(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the request trailers have been parsed.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onRequestTrailers(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the request has been fully parsed.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onRequestEnd(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the request processing failed.
|
||||
*
|
||||
* @param request the request object
|
||||
* @param failure the request failure
|
||||
*/
|
||||
default void onRequestFailure(Request request, Throwable failure)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked just before the response line is written to the network.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onResponseBegin(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked just after the response is committed (that is, the response
|
||||
* line, headers and possibly some content have been written to the
|
||||
* network).
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onResponseCommit(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked after a response content chunk has been written to the network.
|
||||
*
|
||||
* @param request the request object
|
||||
* @param content a {@link ByteBuffer#slice() slice} of the response content chunk
|
||||
*/
|
||||
default void onResponseContent(Request request, ByteBuffer content)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the response has been fully written.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onResponseEnd(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the response processing failed.
|
||||
*
|
||||
* @param request the request object
|
||||
* @param failure the response failure
|
||||
*/
|
||||
default void onResponseFailure(Request request, Throwable failure)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the request <em>and</em> response processing are complete.
|
||||
*
|
||||
* @param request the request object
|
||||
*/
|
||||
default void onComplete(Request request)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static class Listeners implements Listener
|
||||
{
|
||||
private final List<Listener> _listeners;
|
||||
|
||||
private Listeners(Connector connector, ServletContextHandler servletContextHandler)
|
||||
{
|
||||
Collection<Listener> connectorListeners = connector.getBeans(Listener.class);
|
||||
List<Listener> handlerListeners = servletContextHandler.getEventListeners().stream()
|
||||
.filter(l -> l instanceof Listener)
|
||||
.map(Listener.class::cast)
|
||||
.toList();
|
||||
_listeners = new ArrayList<>(connectorListeners);
|
||||
_listeners.addAll(handlerListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestBegin(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onRequestBegin, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeforeDispatch(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onBeforeDispatch, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDispatchFailure(Request request, Throwable failure)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onDispatchFailure, request, failure));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterDispatch(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onAfterDispatch, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestContent(Request request, ByteBuffer content)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onRequestContent, request, content));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestContentEnd(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onRequestContentEnd, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestTrailers(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onRequestTrailers, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestEnd(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onRequestEnd, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestFailure(Request request, Throwable failure)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onRequestFailure, request, failure));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseBegin(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onResponseBegin, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseCommit(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onResponseCommit, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseContent(Request request, ByteBuffer content)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onResponseContent, request, content));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseEnd(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onResponseEnd, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponseFailure(Request request, Throwable failure)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onResponseFailure, request, failure));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Request request)
|
||||
{
|
||||
_listeners.forEach(l -> notify(l::onComplete, request));
|
||||
}
|
||||
|
||||
private void notify(Consumer<Request> consumer, Request request)
|
||||
{
|
||||
try
|
||||
{
|
||||
consumer.accept(request);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("failure while notifying %s event for %s".formatted(ServletChannel.Listener.class.getSimpleName(), request));
|
||||
}
|
||||
}
|
||||
|
||||
private void notify(BiConsumer<Request, Throwable> consumer, Request request, Throwable failure)
|
||||
{
|
||||
try
|
||||
{
|
||||
consumer.accept(request, failure);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("failure while notifying %s event for %s".formatted(ServletChannel.Listener.class.getSimpleName(), request));
|
||||
}
|
||||
}
|
||||
|
||||
private void notify(BiConsumer<Request, ByteBuffer> consumer, Request request, ByteBuffer byteBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
consumer.accept(request, byteBuffer.slice());
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("failure while notifying %s event for %s".formatted(ServletChannel.Listener.class.getSimpleName(), request));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,26 +160,30 @@ 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();
|
||||
ServletContextHandler sch = getCurrentServletContextHandler();
|
||||
if (sch != null)
|
||||
return sch;
|
||||
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();
|
||||
return null;
|
||||
|
||||
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();
|
||||
|
@ -199,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;
|
||||
|
@ -230,46 +240,40 @@ public class ServletContextHandler extends ContextHandler
|
|||
|
||||
public ServletContextHandler(String contextPath)
|
||||
{
|
||||
this(null, contextPath);
|
||||
this(contextPath, null, null, null, null);
|
||||
}
|
||||
|
||||
public ServletContextHandler(int options)
|
||||
{
|
||||
this(null, null, options);
|
||||
this(null, options);
|
||||
}
|
||||
|
||||
public ServletContextHandler(Container parent, String contextPath)
|
||||
public ServletContextHandler(String contextPath, int options)
|
||||
{
|
||||
this(parent, contextPath, null, null, null, null);
|
||||
this(contextPath, null, null, null, null, options);
|
||||
}
|
||||
|
||||
public ServletContextHandler(Container parent, String contextPath, int options)
|
||||
public ServletContextHandler(String contextPath, boolean sessions, boolean security)
|
||||
{
|
||||
this(parent, contextPath, null, null, null, null, options);
|
||||
this(contextPath, (sessions ? SESSIONS : 0) | (security ? SECURITY : 0));
|
||||
}
|
||||
|
||||
public ServletContextHandler(Container parent, String contextPath, boolean sessions, boolean security)
|
||||
public ServletContextHandler(SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
|
||||
{
|
||||
this(parent, contextPath, (sessions ? SESSIONS : 0) | (security ? SECURITY : 0));
|
||||
this(null, sessionHandler, securityHandler, servletHandler, errorHandler);
|
||||
}
|
||||
|
||||
public ServletContextHandler(Container parent, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
|
||||
public ServletContextHandler(String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
|
||||
{
|
||||
this(parent, null, sessionHandler, securityHandler, servletHandler, errorHandler);
|
||||
this(contextPath, sessionHandler, securityHandler, servletHandler, errorHandler, 0);
|
||||
}
|
||||
|
||||
public ServletContextHandler(Container parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
|
||||
{
|
||||
this(parent, contextPath, sessionHandler, securityHandler, servletHandler, errorHandler, 0);
|
||||
}
|
||||
|
||||
public ServletContextHandler(Container parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler, int options)
|
||||
public ServletContextHandler(String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler, int options)
|
||||
{
|
||||
_servletContext = newServletContextApi();
|
||||
|
||||
if (contextPath != null)
|
||||
setContextPath(contextPath);
|
||||
Container.setAsParent(parent, this);
|
||||
|
||||
_options = options;
|
||||
_sessionHandler = sessionHandler;
|
||||
|
@ -335,7 +339,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
|
||||
*/
|
||||
|
@ -840,7 +844,7 @@ public class ServletContextHandler extends ContextHandler
|
|||
void exitScope(ServletScopedContext context, ServletContextRequest request);
|
||||
}
|
||||
|
||||
public ServletContext getServletContext()
|
||||
public jakarta.servlet.ServletContext getServletContext()
|
||||
{
|
||||
return getContext().getServletContext();
|
||||
}
|
||||
|
@ -1135,8 +1139,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);
|
||||
|
@ -1151,16 +1160,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;
|
||||
|
@ -2029,7 +2037,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;
|
||||
|
@ -2691,7 +2699,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;
|
||||
|
@ -3029,4 +3037,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue