Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-httpcontentFactoryCleanup
This commit is contained in:
commit
06f9e5ec18
|
@ -69,11 +69,11 @@ jobs:
|
|||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
- name: Set up Maven
|
||||
uses: stCarolas/setup-maven@v4.5
|
||||
with:
|
||||
maven-version: 3.8.6
|
||||
maven-version: 3.8.6
|
||||
|
||||
- name: Set up Maven
|
||||
run:
|
||||
|
|
86
VERSION.txt
86
VERSION.txt
|
@ -72,6 +72,92 @@ jetty-12.0.0.alpha3 - 07 December 2022
|
|||
without .jar extension
|
||||
+ 9006 WebSocket Message InputStream read() returns signed byte
|
||||
|
||||
jetty-11.0.13 - 07 December 2022
|
||||
+ 7117 Timeout with Expect 100 continue when using ProxyServlet
|
||||
+ 7286 WebSocket write can time out even if the frame / callback has not been
|
||||
failed.
|
||||
+ 7993 HttpClient idleTimeout configuration being ignored/overridden
|
||||
+ 8330 Persistent OpenId sessions can throw IllegalStateException
|
||||
+ 8460 Log or throw exception if DefaultSessionIdManager is used but has not
|
||||
been started.
|
||||
+ 8536 HotSwapHandler race condition
|
||||
+ 8558 Idle timeout occured sometimes on HTTP/2 client with
|
||||
`InputStreamResponseListener`
|
||||
+ 8584 org.eclipse.jetty.client.HttpRequest.send() never returns
|
||||
+ 8591 Indicate units of HttpClient properties
|
||||
+ 8623 Use AutoLock in InputStreamResponseListener
|
||||
+ 8628 Pseudo restore `PathMappings.getMatch(String)` for backwards compat
|
||||
reasons
|
||||
+ 8678 Jetty client is not responding to GO_AWAY packet received from (Jetty)
|
||||
Server and continue to send traffic on same connection
|
||||
+ 8695 Update quiche to 0.16.0
|
||||
+ 8712 ELContextCleaner no longer needed.
|
||||
+ 8716 Multiple Host header values handled poorly
|
||||
+ 8721 jetty:effective-web-xml doesn't generate quickstart information for web
|
||||
fragment jars that contain META-INF/resources
|
||||
+ 8723 Provide a thread-safe way to modify HttpClient proxies at runtime
|
||||
+ 8750 AbstractProxyServlet.onServerResponseHeaders does not support headers
|
||||
with empty values
|
||||
+ 8753 Starting HttpClient with destinationIdleTimeout set throws NPE.
|
||||
+ 8770 Review whether to send request body in redirects
|
||||
+ 8779 CompactPathRule drops query section on use
|
||||
+ 8786 KeyStoreScanner is not able to monitor a symlink file and always
|
||||
resolves to the target.
|
||||
+ 8810 `ArrayRetainableByteBufferPool` inefficiently calculates bucket indices
|
||||
+ 8811 HTTP/2 session shutdown race may cause `Server.stop()` to block until
|
||||
stop timeout
|
||||
+ 8863 Provide a possibility to name virtual threads
|
||||
+ 8895 Generate downloadable version of javadocs documentation in website
|
||||
deploy script
|
||||
+ 8897 Update Conditional request handling for RFC7232
|
||||
+ 8905 GzipHandler fails to set Vary header on 304 responses
|
||||
+ 8913 Review Jetty XML syntax to allow calling JDK methods
|
||||
+ 8942 Use Logback 1.3.x for Jetty 10.0.x
|
||||
+ 9006 WebSocket Message InputStream read() returns signed byte
|
||||
|
||||
jetty-10.0.13 - 07 December 2022
|
||||
+ 7117 Timeout with Expect 100 continue when using ProxyServlet
|
||||
+ 7286 WebSocket write can time out even if the frame / callback has not been
|
||||
failed.
|
||||
+ 7993 HttpClient idleTimeout configuration being ignored/overridden
|
||||
+ 8330 Persistent OpenId sessions can throw IllegalStateException
|
||||
+ 8460 Log or throw exception if DefaultSessionIdManager is used but has not
|
||||
been started.
|
||||
+ 8536 HotSwapHandler race condition
|
||||
+ 8558 Idle timeout occured sometimes on HTTP/2 client with
|
||||
`InputStreamResponseListener`
|
||||
+ 8584 org.eclipse.jetty.client.HttpRequest.send() never returns
|
||||
+ 8591 Indicate units of HttpClient properties
|
||||
+ 8623 Use AutoLock in InputStreamResponseListener
|
||||
+ 8628 Pseudo restore `PathMappings.getMatch(String)` for backwards compat
|
||||
reasons
|
||||
+ 8678 Jetty client is not responding to GO_AWAY packet received from (Jetty)
|
||||
Server and continue to send traffic on same connection
|
||||
+ 8695 Update quiche to 0.16.0
|
||||
+ 8712 ELContextCleaner no longer needed.
|
||||
+ 8716 Multiple Host header values handled poorly
|
||||
+ 8721 jetty:effective-web-xml doesn't generate quickstart information for web
|
||||
fragment jars that contain META-INF/resources
|
||||
+ 8723 Provide a thread-safe way to modify HttpClient proxies at runtime
|
||||
+ 8750 AbstractProxyServlet.onServerResponseHeaders does not support headers
|
||||
with empty values
|
||||
+ 8753 Starting HttpClient with destinationIdleTimeout set throws NPE.
|
||||
+ 8770 Review whether to send request body in redirects
|
||||
+ 8779 CompactPathRule drops query section on use
|
||||
+ 8786 KeyStoreScanner is not able to monitor a symlink file and always
|
||||
resolves to the target.
|
||||
+ 8810 `ArrayRetainableByteBufferPool` inefficiently calculates bucket indices
|
||||
+ 8811 HTTP/2 session shutdown race may cause `Server.stop()` to block until
|
||||
stop timeout
|
||||
+ 8863 Provide a possibility to name virtual threads
|
||||
+ 8895 Generate downloadable version of javadocs documentation in website
|
||||
deploy script
|
||||
+ 8897 Update Conditional request handling for RFC7232
|
||||
+ 8905 GzipHandler fails to set Vary header on 304 responses
|
||||
+ 8913 Review Jetty XML syntax to allow calling JDK methods
|
||||
+ 8942 Use Logback 1.3.x for Jetty 10.0.x
|
||||
+ 9006 WebSocket Message InputStream read() returns signed byte
|
||||
|
||||
jetty-12.0.0.alpha1 - 15 September 2022
|
||||
+ 8474 Jetty 12 : Resource API Review
|
||||
+ 8493 Review HTTP client feature `setRemoveIdleDestinations`
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.eclipse.jetty.util.Jetty;
|
|||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
|
@ -47,7 +46,6 @@ public class ConscryptHTTP2ClientTest
|
|||
{
|
||||
@Tag("external")
|
||||
@Test
|
||||
@Disabled("issue google/conscrypt#667")
|
||||
public void testConscryptHTTP2Client() throws Exception
|
||||
{
|
||||
String host = "webtide.com";
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.eclipse.jetty.client.api.Result;
|
|||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -31,7 +30,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
@Disabled
|
||||
public class ExternalSiteTest
|
||||
{
|
||||
private HttpClient client;
|
||||
|
|
|
@ -165,7 +165,6 @@ public class BufferedResponseHandler extends Handler.Wrapper
|
|||
}
|
||||
}
|
||||
|
||||
// Install buffered interceptor and handle.
|
||||
return (rq, rs, callback) ->
|
||||
{
|
||||
BufferedResponse bufferedResponse = new BufferedResponse(rq, rs, callback);
|
||||
|
|
|
@ -19,109 +19,118 @@ import org.eclipse.jetty.util.thread.AutoLock;
|
|||
|
||||
/**
|
||||
* This specialized callback implements a pattern that allows
|
||||
* a large job to be broken into smaller tasks using iteration
|
||||
* rather than recursion.
|
||||
* a large asynchronous task to be broken into smaller
|
||||
* asynchronous sub-tasks using iteration rather than recursion.
|
||||
* <p>
|
||||
* A typical example is the write of a large content to a socket,
|
||||
* divided in chunks. Chunk C1 is written by thread T1, which
|
||||
* also invokes the callback, which writes chunk C2, which invokes
|
||||
* the callback again, which writes chunk C3, and so forth.
|
||||
* </p>
|
||||
* <p>
|
||||
* The problem with the example is that if the callback thread
|
||||
* The problem with the example above is that if the callback thread
|
||||
* is the same that performs the I/O operation, then the process
|
||||
* is recursive and may result in a stack overflow.
|
||||
* To avoid the stack overflow, a thread dispatch must be performed,
|
||||
* causing context switching and cache misses, affecting performance.
|
||||
* </p>
|
||||
* <p>
|
||||
* To avoid this issue, this callback uses an AtomicReference to
|
||||
* record whether success callback has been called during the processing
|
||||
* of a sub task, and if so then the processing iterates rather than
|
||||
* recurring.
|
||||
* </p>
|
||||
* To avoid this issue, this callback atomically records whether
|
||||
* the callback for an asynchronous sub-task has been called
|
||||
* during the processing of the asynchronous sub-task, and if so
|
||||
* then the processing of the large asynchronous task iterates
|
||||
* rather than recursing.
|
||||
* <p>
|
||||
* Subclasses must implement method {@link #process()} where the sub
|
||||
* task is executed and a suitable {@link IteratingCallback.Action} is
|
||||
* returned to this callback to indicate the overall progress of the job.
|
||||
* This callback is passed to the asynchronous execution of each sub
|
||||
* task and a call the {@link #succeeded()} on this callback represents
|
||||
* the completion of the sub task.
|
||||
* </p>
|
||||
* Subclasses must implement method {@link #process()} where the
|
||||
* asynchronous sub-task is initiated and a suitable {@link Action}
|
||||
* is returned to this callback to indicate the overall progress of
|
||||
* the large asynchronous task.
|
||||
* This callback is passed to the asynchronous sub-task, and a call
|
||||
* to {@link #succeeded()} on this callback represents the successful
|
||||
* completion of the asynchronous sub-task, while a call to
|
||||
* {@link #failed(Throwable)} on this callback represents the
|
||||
* completion with a failure of the large asynchronous task.
|
||||
*/
|
||||
public abstract class IteratingCallback implements Callback
|
||||
{
|
||||
/**
|
||||
* The internal states of this callback
|
||||
* The internal states of this callback.
|
||||
*/
|
||||
private enum State
|
||||
{
|
||||
/**
|
||||
* This callback is IDLE, ready to iterate.
|
||||
* This callback is idle, ready to iterate.
|
||||
*/
|
||||
IDLE,
|
||||
|
||||
/**
|
||||
* This callback is iterating calls to {@link #process()} and is dealing with
|
||||
* the returns. To get into processing state, it much of held the lock state
|
||||
* and set iterating to true.
|
||||
* This callback is just about to call {@link #process()},
|
||||
* or within it, or just exited from it, either normally
|
||||
* or by throwing.
|
||||
*/
|
||||
PROCESSING,
|
||||
|
||||
/**
|
||||
* Waiting for a schedule callback
|
||||
* Method {@link #process()} returned {@link Action#SCHEDULED}
|
||||
* and this callback is waiting for the asynchronous sub-task
|
||||
* to complete.
|
||||
*/
|
||||
PENDING,
|
||||
|
||||
/**
|
||||
* Called by a schedule callback
|
||||
* The asynchronous sub-task was completed successfully
|
||||
* via a call to {@link #succeeded()} while in
|
||||
* {@link #PROCESSING} state.
|
||||
*/
|
||||
CALLED,
|
||||
|
||||
/**
|
||||
* The overall job has succeeded as indicated by a {@link Action#SUCCEEDED} return
|
||||
* from {@link IteratingCallback#process()}
|
||||
* The iteration terminated successfully as indicated by
|
||||
* {@link Action#SUCCEEDED} returned from
|
||||
* {@link IteratingCallback#process()}.
|
||||
*/
|
||||
SUCCEEDED,
|
||||
|
||||
/**
|
||||
* The overall job has failed as indicated by a call to {@link IteratingCallback#failed(Throwable)}
|
||||
* The iteration terminated with a failure via a call
|
||||
* to {@link IteratingCallback#failed(Throwable)}.
|
||||
*/
|
||||
FAILED,
|
||||
|
||||
/**
|
||||
* This callback has been closed and cannot be reset.
|
||||
* This callback has been {@link #close() closed} and
|
||||
* cannot be {@link #reset() reset}.
|
||||
*/
|
||||
CLOSED
|
||||
}
|
||||
|
||||
/**
|
||||
* The indication of the overall progress of the overall job that
|
||||
* implementations of {@link #process()} must return.
|
||||
* The indication of the overall progress of the iteration
|
||||
* that implementations of {@link #process()} must return.
|
||||
*/
|
||||
protected enum Action
|
||||
{
|
||||
/**
|
||||
* Indicates that {@link #process()} has no more work to do,
|
||||
* but the overall job is not completed yet, probably waiting
|
||||
* but the iteration is not completed yet, probably waiting
|
||||
* for additional events to trigger more work.
|
||||
*/
|
||||
IDLE,
|
||||
/**
|
||||
* Indicates that {@link #process()} is executing asynchronously
|
||||
* a sub task, where the execution has started but the callback
|
||||
* Indicates that {@link #process()} has initiated an asynchronous
|
||||
* sub-task, where the execution has started but the callback
|
||||
* that signals the completion of the asynchronous sub-task
|
||||
* may have not yet been invoked.
|
||||
*/
|
||||
SCHEDULED,
|
||||
|
||||
/**
|
||||
* Indicates that {@link #process()} has completed the overall job.
|
||||
* Indicates that {@link #process()} has completed the whole
|
||||
* iteration successfully.
|
||||
*/
|
||||
SUCCEEDED
|
||||
}
|
||||
|
||||
private final AutoLock _lock = new AutoLock();
|
||||
private State _state;
|
||||
private Throwable _failure;
|
||||
private boolean _iterate;
|
||||
|
||||
protected IteratingCallback()
|
||||
|
@ -135,11 +144,10 @@ public abstract class IteratingCallback implements Callback
|
|||
}
|
||||
|
||||
/**
|
||||
* Method called by {@link #iterate()} to process the sub task.
|
||||
* Method called by {@link #iterate()} to process the asynchronous sub-task.
|
||||
* <p>
|
||||
* Implementations must start the asynchronous execution of the sub task
|
||||
* Implementations must initiate the asynchronous execution of the sub-task
|
||||
* (if any) and return an appropriate action:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>{@link Action#IDLE} when no sub tasks are available for execution
|
||||
* but the overall job is not completed yet</li>
|
||||
|
@ -149,7 +157,7 @@ public abstract class IteratingCallback implements Callback
|
|||
* </ul>
|
||||
*
|
||||
* @return the appropriate Action
|
||||
* @throws Throwable if the sub task processing throws
|
||||
* @throws Throwable if the sub-task processing throws
|
||||
*/
|
||||
protected abstract Action process() throws Throwable;
|
||||
|
||||
|
@ -174,16 +182,18 @@ public abstract class IteratingCallback implements Callback
|
|||
|
||||
/**
|
||||
* This method must be invoked by applications to start the processing
|
||||
* of sub tasks. It can be called at any time by any thread, and it's
|
||||
* contract is that when called, then the {@link #process()} method will
|
||||
* be called during or soon after, either by the calling thread or by
|
||||
* another thread.
|
||||
* of asynchronous sub-tasks.
|
||||
* <p>
|
||||
* It can be called at any time by any thread, and its contract is that
|
||||
* when called, then the {@link #process()} method will be called during
|
||||
* or soon after, either by the calling thread or by another thread, but
|
||||
* in either case by one thread only.
|
||||
*/
|
||||
public void iterate()
|
||||
{
|
||||
boolean process = false;
|
||||
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
|
@ -219,14 +229,15 @@ public abstract class IteratingCallback implements Callback
|
|||
// This should only ever be called when in processing state, however a failed or close call
|
||||
// may happen concurrently, so state is not assumed.
|
||||
|
||||
boolean onCompleteSuccess = false;
|
||||
boolean notifyCompleteSuccess = false;
|
||||
Throwable notifyCompleteFailure = null;
|
||||
|
||||
// While we are processing
|
||||
processing:
|
||||
while (true)
|
||||
{
|
||||
// Call process to get the action that we have to take.
|
||||
Action action;
|
||||
Action action = null;
|
||||
try
|
||||
{
|
||||
action = process();
|
||||
|
@ -234,52 +245,53 @@ public abstract class IteratingCallback implements Callback
|
|||
catch (Throwable x)
|
||||
{
|
||||
failed(x);
|
||||
break;
|
||||
// Fall through to possibly invoke onCompleteFailure().
|
||||
}
|
||||
|
||||
// acted on the action we have just received
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
case PROCESSING:
|
||||
{
|
||||
switch (action)
|
||||
if (action != null)
|
||||
{
|
||||
case IDLE:
|
||||
switch (action)
|
||||
{
|
||||
// Has iterate been called while we were processing?
|
||||
if (_iterate)
|
||||
case IDLE:
|
||||
{
|
||||
// yes, so skip idle and keep processing
|
||||
_iterate = false;
|
||||
_state = State.PROCESSING;
|
||||
continue processing;
|
||||
// Has iterate been called while we were processing?
|
||||
if (_iterate)
|
||||
{
|
||||
// yes, so skip idle and keep processing
|
||||
_iterate = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// No, so we can go idle
|
||||
_state = State.IDLE;
|
||||
break processing;
|
||||
}
|
||||
case SCHEDULED:
|
||||
{
|
||||
// we won the race against the callback, so the callback has to process and we can break processing
|
||||
_state = State.PENDING;
|
||||
break processing;
|
||||
}
|
||||
case SUCCEEDED:
|
||||
{
|
||||
// we lost the race against the callback,
|
||||
_iterate = false;
|
||||
_state = State.SUCCEEDED;
|
||||
notifyCompleteSuccess = true;
|
||||
break processing;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// No, so we can go idle
|
||||
_state = State.IDLE;
|
||||
break processing;
|
||||
}
|
||||
|
||||
case SCHEDULED:
|
||||
{
|
||||
// we won the race against the callback, so the callback has to process and we can break processing
|
||||
_state = State.PENDING;
|
||||
break processing;
|
||||
}
|
||||
|
||||
case SUCCEEDED:
|
||||
{
|
||||
// we lost the race against the callback,
|
||||
_iterate = false;
|
||||
_state = State.SUCCEEDED;
|
||||
onCompleteSuccess = true;
|
||||
break processing;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
throw new IllegalStateException(String.format("%s[action=%s]", this, action));
|
||||
}
|
||||
|
@ -290,12 +302,16 @@ public abstract class IteratingCallback implements Callback
|
|||
throw new IllegalStateException(String.format("%s[action=%s]", this, action));
|
||||
// we lost the race, so we have to keep processing
|
||||
_state = State.PROCESSING;
|
||||
continue processing;
|
||||
continue;
|
||||
}
|
||||
|
||||
case SUCCEEDED:
|
||||
case FAILED:
|
||||
case CLOSED:
|
||||
notifyCompleteFailure = _failure;
|
||||
_failure = null;
|
||||
break processing;
|
||||
|
||||
case SUCCEEDED:
|
||||
break processing;
|
||||
|
||||
case IDLE:
|
||||
|
@ -306,20 +322,23 @@ public abstract class IteratingCallback implements Callback
|
|||
}
|
||||
}
|
||||
|
||||
if (onCompleteSuccess)
|
||||
if (notifyCompleteSuccess)
|
||||
onCompleteSuccess();
|
||||
else if (notifyCompleteFailure != null)
|
||||
onCompleteFailure(notifyCompleteFailure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the sub task succeeds.
|
||||
* Subclasses that override this method must always remember to call
|
||||
* {@code super.succeeded()}.
|
||||
* Method to invoke when the asynchronous sub-task succeeds.
|
||||
* <p>
|
||||
* Subclasses that override this method must always remember
|
||||
* to call {@code super.succeeded()}.
|
||||
*/
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
boolean process = false;
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
|
@ -351,15 +370,24 @@ public abstract class IteratingCallback implements Callback
|
|||
}
|
||||
|
||||
/**
|
||||
* Invoked when the sub task fails.
|
||||
* Subclasses that override this method must always remember to call
|
||||
* {@code super.failed(Throwable)}.
|
||||
* Method to invoke when the asynchronous sub-task fails,
|
||||
* or to fail the overall asynchronous task and therefore
|
||||
* terminate the iteration.
|
||||
* <p>
|
||||
* Subclasses that override this method must always remember
|
||||
* to call {@code super.failed(Throwable)}.
|
||||
* <p>
|
||||
* Eventually, {@link #onCompleteFailure(Throwable)} is
|
||||
* called, either by the caller thread or by the processing
|
||||
* thread.
|
||||
*
|
||||
* @see #isFailed()
|
||||
*/
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
boolean failure = false;
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
|
@ -370,12 +398,15 @@ public abstract class IteratingCallback implements Callback
|
|||
case CALLED:
|
||||
// too late!.
|
||||
break;
|
||||
|
||||
case PENDING:
|
||||
{
|
||||
failure = true;
|
||||
break;
|
||||
}
|
||||
case PROCESSING:
|
||||
{
|
||||
_state = State.FAILED;
|
||||
failure = true;
|
||||
_failure = x;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -386,10 +417,19 @@ public abstract class IteratingCallback implements Callback
|
|||
onCompleteFailure(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to invoke to forbid further invocations to {@link #iterate()}
|
||||
* and {@link #reset()}.
|
||||
* <p>
|
||||
* When this method is invoked during processing, it behaves like invoking
|
||||
* {@link #failed(Throwable)}.
|
||||
*
|
||||
* @see #isClosed()
|
||||
*/
|
||||
public void close()
|
||||
{
|
||||
String failure = null;
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
|
@ -399,12 +439,18 @@ public abstract class IteratingCallback implements Callback
|
|||
_state = State.CLOSED;
|
||||
break;
|
||||
|
||||
case PROCESSING:
|
||||
_failure = new IOException(String.format("Close %s in state %s", this, _state));
|
||||
_state = State.CLOSED;
|
||||
break;
|
||||
|
||||
case CLOSED:
|
||||
break;
|
||||
|
||||
default:
|
||||
failure = String.format("Close %s in state %s", this, _state);
|
||||
_state = State.CLOSED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,43 +458,47 @@ public abstract class IteratingCallback implements Callback
|
|||
onCompleteFailure(new IOException(failure));
|
||||
}
|
||||
|
||||
/*
|
||||
* only for testing
|
||||
* @return whether this callback is idle and {@link #iterate()} needs to be called
|
||||
/**
|
||||
* @return whether this callback is idle, and {@link #iterate()} needs to be called
|
||||
*/
|
||||
boolean isIdle()
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
return _state == State.IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this callback has been {@link #close() closed}
|
||||
*/
|
||||
public boolean isClosed()
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
return _state == State.CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this callback has failed
|
||||
* @return whether this callback has been {@link #failed(Throwable) failed}
|
||||
*/
|
||||
public boolean isFailed()
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
return _state == State.FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this callback has succeeded
|
||||
* @return whether this callback and the overall asynchronous task has been succeeded
|
||||
*
|
||||
* @see #onCompleteSuccess()
|
||||
*/
|
||||
public boolean isSucceeded()
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
return _state == State.SUCCEEDED;
|
||||
}
|
||||
|
@ -457,15 +507,15 @@ public abstract class IteratingCallback implements Callback
|
|||
/**
|
||||
* Resets this callback.
|
||||
* <p>
|
||||
* A callback can only be reset to IDLE from the
|
||||
* SUCCEEDED or FAILED states or if it is already IDLE.
|
||||
* </p>
|
||||
* A callback can only be reset to the idle state from the
|
||||
* {@link #isSucceeded() succeeded} or {@link #isFailed() failed} states
|
||||
* or if it is already idle.
|
||||
*
|
||||
* @return true if the reset was successful
|
||||
*/
|
||||
public boolean reset()
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
try (AutoLock ignored = _lock.lock())
|
||||
{
|
||||
switch (_state)
|
||||
{
|
||||
|
@ -474,8 +524,9 @@ public abstract class IteratingCallback implements Callback
|
|||
|
||||
case SUCCEEDED:
|
||||
case FAILED:
|
||||
_iterate = false;
|
||||
_state = State.IDLE;
|
||||
_failure = null;
|
||||
_iterate = false;
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
invoker.goals = verify
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.ee10.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-project</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>Jetty :: Simple :: Base Webapp</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>Simple Base Webapp Index</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.ee10.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-project</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jetty-simple-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>Jetty :: Simple :: Webapp</name>
|
||||
|
||||
<properties>
|
||||
<jetty.port.file>${project.build.directory}/jetty-start-port.txt</jetty.port.file>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<type>war</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-maven-plugin</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||
<overlays>
|
||||
<overlay>
|
||||
<groupId>org.eclipse.jetty.ee10.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<includes>
|
||||
<include>index.html</include>
|
||||
</includes>
|
||||
</overlay>
|
||||
<overlay/>
|
||||
</overlays>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>run-tests</id>
|
||||
<phase>integration-test</phase>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>IntegrationTest*.java</include>
|
||||
</includes>
|
||||
<systemPropertyVariables>
|
||||
<jetty.port.file>${jetty.port.file}</jetty.port.file>
|
||||
<context.path>/setbycontextxml</context.path>
|
||||
<contentCheck>Simple Base Webapp Index</contentCheck>
|
||||
<pathToCheck>/index.html</pathToCheck>
|
||||
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
|
||||
</systemPropertyVariables>
|
||||
<dependenciesToScan>
|
||||
<dependency>org.eclipse.jetty.ee10:jetty-ee10-maven-plugin</dependency>
|
||||
</dependenciesToScan>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.jetty.ee10</groupId>
|
||||
<artifactId>jetty-ee10-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>start-jetty</id>
|
||||
<phase>pre-integration-test</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<contextXml>${basedir}/src/config/context.xml</contextXml>
|
||||
<systemProperties>
|
||||
<jetty.port.file>${jetty.port.file}</jetty.port.file>
|
||||
<jetty.deployMode>EMBED</jetty.deployMode>
|
||||
</systemProperties>
|
||||
<jettyXmls>
|
||||
<jettyXml>${basedir}/src/config/jetty.xml</jettyXml>
|
||||
</jettyXmls>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||
|
||||
<Configure class="org.eclipse.jetty.ee10.webapp.WebAppContext">
|
||||
|
||||
<Set name="contextPath">/setbycontextxml</Set>
|
||||
|
||||
</Configure>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
||||
<Set name="secureScheme">https</Set>
|
||||
<Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
|
||||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">1024</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Arg name="server"><Ref refid="Server" /></Arg>
|
||||
<Arg name="factories">
|
||||
<Array type="org.eclipse.jetty.server.ConnectionFactory">
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
|
||||
<Arg name="config"><Ref refid="httpConfig" /></Arg>
|
||||
</New>
|
||||
</Item>
|
||||
</Array>
|
||||
</Arg>
|
||||
<Call name="addEventListener">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.ee10.maven.plugin.ServerConnectorListener">
|
||||
<Set name="fileName"><Property name="jetty.port.file" default="port.txt"/></Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
<Set name="host" property="jetty.host"/>
|
||||
<Set name="port" property="jetty.port"/>
|
||||
<Set name="idleTimeout">30000</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</Configure>
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>Simple WebApp Index</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.ee10.its</groupId>
|
||||
<artifactId>it-parent-pom</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.eclipse.jetty.ee10.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-project</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Jetty :: Simple Overlay</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<jetty.version>@project.version@</jetty.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>jetty-simple-base-webapp</module>
|
||||
<module>jetty-simple-webapp</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee10.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>war</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
File buildLog = new File( basedir, 'build.log' )
|
||||
assert buildLog.text.contains( 'Started Server' )
|
||||
assert buildLog.text.contains( 'Running org.eclipse.jetty.ee10.maven.plugin.it.IntegrationTestGetContent')
|
|
@ -105,6 +105,8 @@ public class MavenProjectHelper
|
|||
*/
|
||||
public MavenProject getMavenProjectFor(Artifact artifact)
|
||||
{
|
||||
if (artifact == null)
|
||||
return null;
|
||||
return artifactToReactorProjectMap.get(artifact.getId());
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
|
||||
|
@ -21,10 +20,8 @@ import org.eclipse.jetty.http.BadMessageException;
|
|||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.Trailers;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.util.NanoTime;
|
||||
import org.eclipse.jetty.util.StaticException;
|
||||
import org.eclipse.jetty.util.component.Destroyable;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -40,12 +37,9 @@ class AsyncContentProducer implements ContentProducer
|
|||
|
||||
final AutoLock _lock;
|
||||
private final ServletChannel _servletChannel;
|
||||
private HttpInput.Interceptor _interceptor;
|
||||
private Content.Chunk _rawChunk;
|
||||
private Content.Chunk _transformedChunk;
|
||||
private boolean _error;
|
||||
private Content.Chunk _chunk;
|
||||
private long _firstByteNanoTime = Long.MIN_VALUE;
|
||||
private long _rawBytesArrived;
|
||||
private long _bytesArrived;
|
||||
|
||||
/**
|
||||
* @param servletChannel The ServletChannel to produce input from.
|
||||
|
@ -66,19 +60,10 @@ class AsyncContentProducer implements ContentProducer
|
|||
|
||||
// Make sure that the chunk has been fully consumed before destroying the interceptor and also make sure
|
||||
// that asking this instance for chunks between recycle and reopen will only produce error'ed chunks.
|
||||
if (_rawChunk == null)
|
||||
_rawChunk = RECYCLED_ERROR_CHUNK;
|
||||
else if (!_rawChunk.isTerminal())
|
||||
throw new IllegalStateException("ContentProducer with unconsumed raw chunk cannot be recycled");
|
||||
|
||||
if (_transformedChunk == null)
|
||||
_transformedChunk = RECYCLED_ERROR_CHUNK;
|
||||
else if (!_transformedChunk.isTerminal())
|
||||
throw new IllegalStateException("ContentProducer with unconsumed transformed chunk cannot be recycled");
|
||||
|
||||
if (_interceptor instanceof Destroyable)
|
||||
((Destroyable)_interceptor).destroy();
|
||||
_interceptor = null;
|
||||
if (_chunk == null)
|
||||
_chunk = RECYCLED_ERROR_CHUNK;
|
||||
else if (!_chunk.isTerminal())
|
||||
throw new IllegalStateException("ContentProducer with unconsumed chunk cannot be recycled");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,32 +72,16 @@ class AsyncContentProducer implements ContentProducer
|
|||
assertLocked();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("reopening {}", this);
|
||||
_rawChunk = null;
|
||||
_transformedChunk = null;
|
||||
_error = false;
|
||||
_chunk = null;
|
||||
_firstByteNanoTime = Long.MIN_VALUE;
|
||||
_rawBytesArrived = 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpInput.Interceptor getInterceptor()
|
||||
{
|
||||
assertLocked();
|
||||
return _interceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterceptor(HttpInput.Interceptor interceptor)
|
||||
{
|
||||
assertLocked();
|
||||
this._interceptor = interceptor;
|
||||
_bytesArrived = 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available()
|
||||
{
|
||||
assertLocked();
|
||||
Content.Chunk chunk = nextTransformedChunk();
|
||||
Content.Chunk chunk = produceChunk();
|
||||
int available = chunk == null ? 0 : chunk.remaining();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("available = {} {}", available, this);
|
||||
|
@ -123,7 +92,7 @@ class AsyncContentProducer implements ContentProducer
|
|||
public boolean hasChunk()
|
||||
{
|
||||
assertLocked();
|
||||
boolean hasChunk = _rawChunk != null;
|
||||
boolean hasChunk = _chunk != null;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("hasChunk = {} {}", hasChunk, this);
|
||||
return hasChunk;
|
||||
|
@ -133,9 +102,10 @@ class AsyncContentProducer implements ContentProducer
|
|||
public boolean isError()
|
||||
{
|
||||
assertLocked();
|
||||
boolean error = _chunk instanceof Content.Chunk.Error;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("isError = {} {}", _error, this);
|
||||
return _error;
|
||||
LOG.debug("isError = {} {}", error, this);
|
||||
return error;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -151,7 +121,7 @@ class AsyncContentProducer implements ContentProducer
|
|||
if (period > 0)
|
||||
{
|
||||
long minimumData = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1);
|
||||
if (getRawBytesArrived() < minimumData)
|
||||
if (getBytesArrived() < minimumData)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("checkMinDataRate check failed {}", this);
|
||||
|
@ -171,12 +141,12 @@ class AsyncContentProducer implements ContentProducer
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getRawBytesArrived()
|
||||
public long getBytesArrived()
|
||||
{
|
||||
assertLocked();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("getRawBytesArrived = {} {}", _rawBytesArrived, this);
|
||||
return _rawBytesArrived;
|
||||
LOG.debug("getBytesArrived = {} {}", _bytesArrived, this);
|
||||
return _bytesArrived;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -198,28 +168,16 @@ class AsyncContentProducer implements ContentProducer
|
|||
|
||||
private boolean consumeCurrentChunk()
|
||||
{
|
||||
if (_transformedChunk != null && !_transformedChunk.isTerminal())
|
||||
{
|
||||
if (_transformedChunk != _rawChunk)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("releasing current transformed chunk {}", this);
|
||||
_transformedChunk.skip(_transformedChunk.remaining());
|
||||
_transformedChunk.release();
|
||||
}
|
||||
_transformedChunk = null;
|
||||
}
|
||||
|
||||
if (_rawChunk != null && !_rawChunk.isTerminal())
|
||||
if (_chunk != null && !_chunk.isTerminal())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("releasing current raw chunk {}", this);
|
||||
_rawChunk.skip(_rawChunk.remaining());
|
||||
_rawChunk.release();
|
||||
_rawChunk = _rawChunk.isLast() ? Content.Chunk.EOF : null;
|
||||
LOG.debug("releasing current chunk {}", this);
|
||||
_chunk.skip(_chunk.remaining());
|
||||
_chunk.release();
|
||||
_chunk = _chunk.isLast() ? Content.Chunk.EOF : null;
|
||||
}
|
||||
|
||||
return _rawChunk != null && _rawChunk.isLast();
|
||||
return _chunk != null && _chunk.isLast();
|
||||
}
|
||||
|
||||
private boolean consumeAvailableChunks()
|
||||
|
@ -251,7 +209,7 @@ class AsyncContentProducer implements ContentProducer
|
|||
public Content.Chunk nextChunk()
|
||||
{
|
||||
assertLocked();
|
||||
Content.Chunk chunk = nextTransformedChunk();
|
||||
Content.Chunk chunk = produceChunk();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("nextChunk = {} {}", chunk, this);
|
||||
if (chunk != null)
|
||||
|
@ -265,24 +223,20 @@ class AsyncContentProducer implements ContentProducer
|
|||
assertLocked();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("reclaim {} {}", chunk, this);
|
||||
if (_transformedChunk == chunk)
|
||||
{
|
||||
chunk.release();
|
||||
if (_transformedChunk == _rawChunk)
|
||||
_rawChunk = null;
|
||||
_transformedChunk = null;
|
||||
}
|
||||
assert chunk == _chunk;
|
||||
chunk.release();
|
||||
_chunk = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady()
|
||||
{
|
||||
assertLocked();
|
||||
Content.Chunk chunk = nextTransformedChunk();
|
||||
Content.Chunk chunk = produceChunk();
|
||||
if (chunk != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("isReady(), got transformed chunk {} {}", chunk, this);
|
||||
LOG.debug("isReady(), got chunk {} {}", chunk, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -303,197 +257,71 @@ class AsyncContentProducer implements ContentProducer
|
|||
return _servletChannel.getState().isInputUnready();
|
||||
}
|
||||
|
||||
private Content.Chunk nextTransformedChunk()
|
||||
private Content.Chunk produceChunk()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("nextTransformedChunk {}", this);
|
||||
LOG.debug("produceChunk() {}", this);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (_transformedChunk != null)
|
||||
if (_chunk != null)
|
||||
{
|
||||
if (_transformedChunk.isTerminal() || _transformedChunk.hasRemaining())
|
||||
if (_chunk.isTerminal() || _chunk.hasRemaining())
|
||||
{
|
||||
if (_transformedChunk instanceof Content.Chunk.Error && !_error)
|
||||
{
|
||||
// In case the _rawChunk was set by consumeAvailable(), check the ServletChannel
|
||||
// to see if it has a more precise error. Otherwise, the exact same
|
||||
// terminal chunk will be returned by the ServletChannel; do not do that
|
||||
// if the _error flag was set, meaning the current error is definitive.
|
||||
Content.Chunk refreshedRawChunk = produceRawChunk();
|
||||
if (refreshedRawChunk != null)
|
||||
_rawChunk = _transformedChunk = refreshedRawChunk;
|
||||
_error = _rawChunk instanceof Content.Chunk.Error;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("refreshed raw chunk: {} {}", _rawChunk, this);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("transformed chunk not yet depleted, returning it {}", this);
|
||||
return _transformedChunk;
|
||||
LOG.debug("chunk not yet depleted, returning it {}", this);
|
||||
return _chunk;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("current transformed chunk depleted {}", this);
|
||||
LOG.debug("current chunk depleted {}", this);
|
||||
|
||||
_transformedChunk.release();
|
||||
_transformedChunk = null;
|
||||
_chunk.release();
|
||||
_chunk = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_rawChunk == null)
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("producing new raw chunk {}", this);
|
||||
_rawChunk = produceRawChunk();
|
||||
if (_rawChunk == null)
|
||||
LOG.debug("reading new chunk {}", this);
|
||||
_chunk = readChunk();
|
||||
if (_chunk == null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("channel has no new raw chunk {}", this);
|
||||
LOG.debug("channel has no new chunk {}", this);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("transforming raw chunk {}", this);
|
||||
transformRawChunk();
|
||||
assert _chunk != null;
|
||||
// Release the chunk immediately, if it is empty.
|
||||
if (!_chunk.hasRemaining() && !_chunk.isTerminal())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("releasing empty chunk {}", this);
|
||||
_chunk.release();
|
||||
_chunk = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void transformRawChunk()
|
||||
{
|
||||
assert _rawChunk != null;
|
||||
if (_interceptor != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("intercepting raw chunk {}", this);
|
||||
_transformedChunk = intercept();
|
||||
|
||||
// If the interceptor generated a terminal chunk, _rawChunk must become that terminal chunk.
|
||||
if (_transformedChunk != null && _transformedChunk.isTerminal() && _transformedChunk != _rawChunk)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("interceptor generated a terminal chunk, _rawChunk must become that terminal chunk {}", this);
|
||||
_rawChunk.release();
|
||||
_rawChunk = _transformedChunk;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the interceptor generated a null chunk, release the raw chunk now if it is empty.
|
||||
if (_transformedChunk == null && !_rawChunk.hasRemaining() && !_rawChunk.isTerminal())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("interceptor generated a null chunk, releasing the empty raw chunk now {}", this);
|
||||
_rawChunk.release();
|
||||
_rawChunk = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the interceptor returned the raw chunk, release the raw chunk now if it is empty.
|
||||
if (_transformedChunk == _rawChunk && !_rawChunk.hasRemaining() && !_rawChunk.isTerminal())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("interceptor returned the raw chunk, releasing the empty raw chunk now {}", this);
|
||||
_rawChunk.release();
|
||||
_rawChunk = _transformedChunk = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Release the raw chunk now if it is empty.
|
||||
if (!_rawChunk.hasRemaining() && !_rawChunk.isTerminal())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("releasing the empty raw chunk now {}", this);
|
||||
_rawChunk.release();
|
||||
_rawChunk = null;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("no interceptor, transformed chunk is raw chunk {}", this);
|
||||
_transformedChunk = _rawChunk;
|
||||
}
|
||||
}
|
||||
|
||||
private Content.Chunk intercept()
|
||||
{
|
||||
try
|
||||
{
|
||||
int remainingBeforeInterception = _rawChunk.remaining();
|
||||
Content.Chunk chunk = _interceptor.readFrom(_rawChunk);
|
||||
if (chunk != null && chunk.isTerminal() && !_rawChunk.isTerminal())
|
||||
{
|
||||
if (chunk instanceof Content.Chunk.Error errorChunk)
|
||||
{
|
||||
// Set the _error flag to mark the chunk as definitive, i.e.:
|
||||
// do not try to produce new raw chunk to get a fresher error
|
||||
// when the terminal chunk was generated by the interceptor.
|
||||
_error = true;
|
||||
if (_servletChannel.getResponse().isCommitted())
|
||||
_servletChannel.abort(errorChunk.getCause());
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("interceptor generated terminal chunk {}", this);
|
||||
}
|
||||
else if (chunk != _rawChunk && !_rawChunk.isTerminal() && _rawChunk.hasRemaining() && _rawChunk.remaining() == remainingBeforeInterception)
|
||||
{
|
||||
IOException failure = new IOException("Interceptor " + _interceptor + " did not consume any of the " + _rawChunk.remaining() + " remaining byte(s) of chunk");
|
||||
if (chunk != null)
|
||||
chunk.release();
|
||||
consumeCurrentChunk();
|
||||
// Set the _error flag to mark the chunk as definitive, i.e.:
|
||||
// do not try to produce new raw chunk to get a fresher error
|
||||
// when the terminal chunk was caused by the interceptor not
|
||||
// consuming the raw chunk.
|
||||
_error = true;
|
||||
Response response = _servletChannel.getResponse();
|
||||
if (response.isCommitted())
|
||||
_servletChannel.abort(failure);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("interceptor did not consume chunk {}", this);
|
||||
chunk = _transformedChunk;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("intercepted raw chunk {}", this);
|
||||
return chunk;
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
IOException failure = new IOException("bad chunk", x);
|
||||
consumeCurrentChunk();
|
||||
// Set the _error flag to mark the chunk as definitive, i.e.:
|
||||
// do not try to produce new raw chunk to get a fresher error
|
||||
// when the terminal chunk was caused by the interceptor throwing.
|
||||
_error = true;
|
||||
Response response = _servletChannel.getResponse();
|
||||
if (response.isCommitted())
|
||||
_servletChannel.abort(failure);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("interceptor threw exception {}", this, x);
|
||||
return _transformedChunk;
|
||||
}
|
||||
}
|
||||
|
||||
private Content.Chunk produceRawChunk()
|
||||
private Content.Chunk readChunk()
|
||||
{
|
||||
Content.Chunk chunk = _servletChannel.getServletContextRequest().read();
|
||||
if (chunk != null)
|
||||
{
|
||||
_rawBytesArrived += chunk.remaining();
|
||||
_bytesArrived += chunk.remaining();
|
||||
if (_firstByteNanoTime == Long.MIN_VALUE)
|
||||
_firstByteNanoTime = NanoTime.now();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("produceRawChunk updated _rawBytesArrived to {} and _firstByteTimeStamp to {} {}", _rawBytesArrived, _firstByteNanoTime, this);
|
||||
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());
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("produceRawChunk produced {} {}", chunk, this);
|
||||
LOG.debug("readChunk() produced {} {}", chunk, this);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
|
@ -506,13 +334,10 @@ class AsyncContentProducer implements ContentProducer
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[r=%s,t=%s,i=%s,error=%b]",
|
||||
return String.format("%s@%x[c=%s]",
|
||||
getClass().getSimpleName(),
|
||||
hashCode(),
|
||||
_rawChunk,
|
||||
_transformedChunk,
|
||||
_interceptor,
|
||||
_error
|
||||
_chunk
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,9 +79,9 @@ class BlockingContentProducer implements ContentProducer
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getRawBytesArrived()
|
||||
public long getBytesArrived()
|
||||
{
|
||||
return _asyncContentProducer.getRawBytesArrived();
|
||||
return _asyncContentProducer.getBytesArrived();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -140,18 +140,6 @@ class BlockingContentProducer implements ContentProducer
|
|||
return ready;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpInput.Interceptor getInterceptor()
|
||||
{
|
||||
return _asyncContentProducer.getInterceptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterceptor(HttpInput.Interceptor interceptor)
|
||||
{
|
||||
_asyncContentProducer.setInterceptor(interceptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContentProducible()
|
||||
{
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.util.component.Destroyable;
|
||||
|
||||
/**
|
||||
* ContentProducer is the bridge between {@link HttpInput} and {@link Content.Source}.
|
||||
|
@ -22,7 +21,6 @@ import org.eclipse.jetty.util.component.Destroyable;
|
|||
public interface ContentProducer
|
||||
{
|
||||
/**
|
||||
* Clear the interceptor and call {@link Destroyable#destroy()} on it if it implements {@link Destroyable}.
|
||||
* A recycled {@link ContentProducer} will only produce special content with a non-null error until
|
||||
* {@link #reopen()} is called.
|
||||
*/
|
||||
|
@ -57,7 +55,7 @@ public interface ContentProducer
|
|||
* Doesn't change state.
|
||||
* @return the byte count produced by the underlying {@link Content.Source}.
|
||||
*/
|
||||
long getRawBytesArrived();
|
||||
long getBytesArrived();
|
||||
|
||||
/**
|
||||
* Get the byte count that can immediately be read from this
|
||||
|
@ -93,8 +91,6 @@ public interface ContentProducer
|
|||
* Get the next content chunk that can be read from or that describes the terminal condition
|
||||
* that was reached (error, eof).
|
||||
* This call may or may not block until some content is available, depending on the implementation.
|
||||
* The returned content is decoded by the interceptor set with {@link #setInterceptor(HttpInput.Interceptor)}
|
||||
* or left as-is if no intercept is set.
|
||||
* After this call, state can be either of UNREADY or IDLE.
|
||||
*
|
||||
* @return the next content chunk that can be read from or null if the implementation does not block
|
||||
|
@ -118,18 +114,6 @@ public interface ContentProducer
|
|||
*/
|
||||
boolean isReady();
|
||||
|
||||
/**
|
||||
* Get the {@link HttpInput.Interceptor}.
|
||||
* @return The {@link HttpInput.Interceptor}, or null if none set.
|
||||
*/
|
||||
HttpInput.Interceptor getInterceptor();
|
||||
|
||||
/**
|
||||
* Set the interceptor.
|
||||
* @param interceptor The interceptor to use.
|
||||
*/
|
||||
void setInterceptor(HttpInput.Interceptor interceptor);
|
||||
|
||||
/**
|
||||
* Wake up the thread that is waiting for the next content.
|
||||
* After this call, state can be READY.
|
||||
|
|
|
@ -23,8 +23,6 @@ import jakarta.servlet.ServletInputStream;
|
|||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Context;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.component.Destroyable;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -85,59 +83,6 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current Interceptor, or null if none set
|
||||
*/
|
||||
public Interceptor getInterceptor()
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
{
|
||||
return _contentProducer.getInterceptor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the interceptor.
|
||||
*
|
||||
* @param interceptor The interceptor to use.
|
||||
*/
|
||||
public void setInterceptor(Interceptor interceptor)
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("setting interceptor to {} on {}", interceptor, this);
|
||||
_contentProducer.setInterceptor(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link Interceptor}, chaining it to the existing one if
|
||||
* an {@link Interceptor} is already set.
|
||||
*
|
||||
* @param interceptor the next {@link Interceptor} in a chain
|
||||
*/
|
||||
public void addInterceptor(Interceptor interceptor)
|
||||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
{
|
||||
Interceptor currentInterceptor = _contentProducer.getInterceptor();
|
||||
if (currentInterceptor == null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("adding single interceptor: {} on {}", interceptor, this);
|
||||
_contentProducer.setInterceptor(interceptor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChainedInterceptor chainedInterceptor = new ChainedInterceptor(currentInterceptor, interceptor);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("adding chained interceptor: {} on {}", chainedInterceptor, this);
|
||||
_contentProducer.setInterceptor(chainedInterceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int get(Content.Chunk chunk, byte[] bytes, int offset, int length)
|
||||
{
|
||||
length = Math.min(chunk.remaining(), length);
|
||||
|
@ -175,7 +120,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
{
|
||||
try (AutoLock lock = _lock.lock())
|
||||
{
|
||||
return _contentProducer.getRawBytesArrived();
|
||||
return _contentProducer.getBytesArrived();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -450,111 +395,4 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
" cp=" + _contentProducer +
|
||||
" eof=" + _consumedEof;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>{@link Content.Chunk} interceptor that can be registered using {@link #setInterceptor(Interceptor)} or
|
||||
* {@link #addInterceptor(Interceptor)}.
|
||||
* When {@link Content.Chunk} instances are generated, they are passed to the registered interceptor (if any)
|
||||
* that is then responsible for providing the actual content that is consumed by {@link #read(byte[], int, int)} and its
|
||||
* sibling methods.</p>
|
||||
* A minimal implementation could be as simple as:
|
||||
* <pre>
|
||||
* public HttpInput.Content readFrom(HttpInput.Content content)
|
||||
* {
|
||||
* LOGGER.debug("read content: {}", asString(content));
|
||||
* return content;
|
||||
* }
|
||||
* </pre>
|
||||
* which would not do anything with the content besides logging it. A more involved implementation could look like the
|
||||
* following:
|
||||
* <pre>
|
||||
* public HttpInput.Content readFrom(HttpInput.Content content)
|
||||
* {
|
||||
* if (content.hasContent())
|
||||
* this.processedContent = processContent(content.getByteBuffer());
|
||||
* if (content.isEof())
|
||||
* disposeResources();
|
||||
* return content.isSpecial() ? content : this.processedContent;
|
||||
* }
|
||||
* </pre>
|
||||
* Implementors of this interface must keep the following in mind:
|
||||
* <ul>
|
||||
* <li>Calling {@link Content.Chunk#getByteBuffer()} when {@link Content.Chunk#isTerminal()} returns <code>true</code> throws
|
||||
* {@link IllegalStateException}.</li>
|
||||
* <li>A {@link Content.Chunk} can both be non-special and have {@code content == Content.EOF} return <code>true</code>.</li>
|
||||
* <li>{@link Content.Chunk} extends {@link Callback} to manage the lifecycle of the contained byte buffer. The code calling
|
||||
* {@link #readFrom(Content.Chunk)} is responsible for managing the lifecycle of both the passed and the returned content
|
||||
* instances, once {@link ByteBuffer#hasRemaining()} returns <code>false</code> {@code HttpInput} will make sure
|
||||
* {@link Callback#succeeded()} is called, or {@link Callback#failed(Throwable)} if an error occurs.</li>
|
||||
* <li>After {@link #readFrom(Content.Chunk)} is called for the first time, subsequent {@link #readFrom(Content.Chunk)} calls will
|
||||
* occur only after the contained byte buffer is empty (see above) or at any time if the returned content was special.</li>
|
||||
* <li>Once {@link #readFrom(Content.Chunk)} returned a special content, subsequent calls to {@link #readFrom(Content.Chunk)} must
|
||||
* always return the same special content.</li>
|
||||
* <li>Implementations implementing both this interface and {@link Destroyable} will have their
|
||||
* {@link Destroyable#destroy()} method called when {@link #recycle()} is called.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public interface Interceptor
|
||||
{
|
||||
/**
|
||||
* @param content The content to be intercepted.
|
||||
* The content will be modified with any data the interceptor consumes. There is no requirement
|
||||
* that all the data is consumed by the interceptor but at least one byte must be consumed
|
||||
* unless the returned content is the passed content instance.
|
||||
* @return The intercepted content or null if interception is completed for that content.
|
||||
*/
|
||||
Content.Chunk readFrom(Content.Chunk content);
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link Interceptor} that chains two other {@link Interceptor}s together.
|
||||
* The {@link Interceptor#readFrom(Content.Chunk)} calls the previous {@link Interceptor}'s
|
||||
* {@link Interceptor#readFrom(Content.Chunk)} and then passes any {@link Content.Chunk} returned
|
||||
* to the next {@link Interceptor}.
|
||||
*/
|
||||
private static class ChainedInterceptor implements Interceptor, Destroyable
|
||||
{
|
||||
private final Interceptor _prev;
|
||||
private final Interceptor _next;
|
||||
|
||||
ChainedInterceptor(Interceptor prev, Interceptor next)
|
||||
{
|
||||
_prev = prev;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
Interceptor getPrev()
|
||||
{
|
||||
return _prev;
|
||||
}
|
||||
|
||||
Interceptor getNext()
|
||||
{
|
||||
return _next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content.Chunk readFrom(Content.Chunk content)
|
||||
{
|
||||
Content.Chunk c = getPrev().readFrom(content);
|
||||
if (c == null)
|
||||
return null;
|
||||
return getNext().readFrom(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
if (_prev instanceof Destroyable)
|
||||
((Destroyable)_prev).destroy();
|
||||
if (_next instanceof Destroyable)
|
||||
((Destroyable)_next).destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getClass().getSimpleName() + "@" + hashCode() + " [p=" + _prev + ",n=" + _next + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,75 +122,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
UNREADY, // write operating in progress, isReady has returned false
|
||||
}
|
||||
|
||||
/**
|
||||
* The HttpOutput.Interceptor is a single intercept point for all
|
||||
* output written to the HttpOutput: via writer; via output stream;
|
||||
* asynchronously; or blocking.
|
||||
* <p>
|
||||
* The Interceptor can be used to implement translations (eg Gzip) or
|
||||
* additional buffering that acts on all output. Interceptors are
|
||||
* created in a chain, so that multiple concerns may intercept.
|
||||
* <p>
|
||||
* The {@link DefaultInterceptor} is always the
|
||||
* last link in any Interceptor chain.
|
||||
* <p>
|
||||
* Responses are committed by the first call to
|
||||
* {@link #write(ByteBuffer, boolean, Callback)}
|
||||
* and closed by a call to {@link #write(ByteBuffer, boolean, Callback)}
|
||||
* with the last boolean set true. If no content is available to commit
|
||||
* or close, then a null buffer is passed.
|
||||
*/
|
||||
public interface Interceptor
|
||||
{
|
||||
/**
|
||||
* Write content.
|
||||
* The response is committed by the first call to write and is closed by
|
||||
* a call with last == true. Empty content buffers may be passed to
|
||||
* force a commit or close.
|
||||
*
|
||||
* @param content The content to be written or an empty buffer.
|
||||
* @param last True if this is the last call to write
|
||||
* @param callback The callback to use to indicate {@link Callback#succeeded()}
|
||||
* or {@link Callback#failed(Throwable)}.
|
||||
*/
|
||||
void write(ByteBuffer content, boolean last, Callback callback);
|
||||
|
||||
/**
|
||||
* @return The next Interceptor in the chain or null if this is the
|
||||
* last Interceptor in the chain.
|
||||
*/
|
||||
Interceptor getNextInterceptor();
|
||||
|
||||
/**
|
||||
* Reset the buffers.
|
||||
* <p>If the Interceptor contains buffers then reset them.
|
||||
*
|
||||
* @throws IllegalStateException Thrown if the response has been
|
||||
* committed and buffers and/or headers cannot be reset.
|
||||
*/
|
||||
default void resetBuffer() throws IllegalStateException
|
||||
{
|
||||
Interceptor next = getNextInterceptor();
|
||||
if (next != null)
|
||||
next.resetBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultInterceptor implements Interceptor
|
||||
{
|
||||
@Override
|
||||
public void write(ByteBuffer content, boolean last, Callback callback)
|
||||
{
|
||||
_response.write(last, content, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interceptor getNextInterceptor()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HttpOutput.class);
|
||||
private static final ThreadLocal<CharsetEncoder> _encoder = new ThreadLocal<>();
|
||||
|
||||
|
@ -198,13 +129,11 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
private final ServletChannel _servletChannel;
|
||||
private final Response _response;
|
||||
private final ByteBufferPool _byteBufferPool;
|
||||
|
||||
private ServletRequestState _channelState;
|
||||
private SharedBlockingCallback _writeBlocker;
|
||||
private final ServletRequestState _channelState;
|
||||
private final SharedBlockingCallback _writeBlocker;
|
||||
private ApiState _apiState = ApiState.BLOCKING;
|
||||
private State _state = State.OPEN;
|
||||
private boolean _softClose = false;
|
||||
private Interceptor _interceptor;
|
||||
private long _written;
|
||||
private long _flushed;
|
||||
private long _firstByteNanoTime = -1;
|
||||
|
@ -223,7 +152,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
_byteBufferPool = _response.getRequest().getComponents().getByteBufferPool();
|
||||
|
||||
_channelState = _servletChannel.getState();
|
||||
_interceptor = new DefaultInterceptor();
|
||||
_writeBlocker = new WriteBlocker(_servletChannel);
|
||||
HttpConfiguration config = _servletChannel.getHttpConfiguration();
|
||||
_bufferSize = config.getOutputBufferSize();
|
||||
|
@ -240,16 +168,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
return _response;
|
||||
}
|
||||
|
||||
public Interceptor getInterceptor()
|
||||
{
|
||||
return _interceptor;
|
||||
}
|
||||
|
||||
public void setInterceptor(Interceptor interceptor)
|
||||
{
|
||||
_interceptor = interceptor;
|
||||
}
|
||||
|
||||
public boolean isWritten()
|
||||
{
|
||||
return _written > 0;
|
||||
|
@ -292,8 +210,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
else
|
||||
_firstByteNanoTime = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
_interceptor.write(content, last, callback);
|
||||
_response.write(last, content, callback);
|
||||
}
|
||||
|
||||
private void onWriteComplete(boolean last, Throwable failure)
|
||||
|
@ -1316,9 +1233,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
|
||||
/**
|
||||
* <p>Invoked when bytes have been flushed to the network.</p>
|
||||
* <p>The number of flushed bytes may be different from the bytes written
|
||||
* by the application if an {@link Interceptor} changed them, for example
|
||||
* by compressing them.</p>
|
||||
*
|
||||
* @param bytes the number of bytes flushed
|
||||
* @throws IOException if the minimum data rate, when set, is not respected
|
||||
|
@ -1348,7 +1262,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
_state = State.OPEN;
|
||||
_apiState = ApiState.BLOCKING;
|
||||
_softClose = true; // Stay closed until next request
|
||||
_interceptor = new DefaultInterceptor();
|
||||
HttpConfiguration config = _connectionMetaData.getHttpConfiguration();
|
||||
_bufferSize = config.getOutputBufferSize();
|
||||
_commitSize = config.getOutputAggregationSize();
|
||||
|
@ -1368,7 +1281,6 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
{
|
||||
try (AutoLock l = _channelState.lock())
|
||||
{
|
||||
_interceptor.resetBuffer();
|
||||
if (BufferUtil.hasContent(_aggregate))
|
||||
BufferUtil.clear(_aggregate);
|
||||
_written = 0;
|
||||
|
|
|
@ -345,6 +345,7 @@ public class ServletContextRequest extends ContextRequest
|
|||
private String _method;
|
||||
private ServletMultiPartFormData.Parts _parts;
|
||||
private ServletPathMapping _servletPathMapping;
|
||||
private boolean _asyncSupported = true;
|
||||
|
||||
public static Session getSession(HttpSession httpSession)
|
||||
{
|
||||
|
@ -696,7 +697,7 @@ public class ServletContextRequest extends ContextRequest
|
|||
if (getRequestedSessionId() == null || _coreSession == null)
|
||||
return false;
|
||||
//check requestedId (which may have worker suffix) against the actual session id
|
||||
return getSessionManager().getSessionIdManager().getId(getRequestedSessionId()).equals(_coreSession.getId());
|
||||
return _coreSession.isValid() && getSessionManager().getSessionIdManager().getId(getRequestedSessionId()).equals(_coreSession.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1362,6 +1363,8 @@ public class ServletContextRequest extends ContextRequest
|
|||
@Override
|
||||
public AsyncContext startAsync() throws IllegalStateException
|
||||
{
|
||||
if (!isAsyncSupported())
|
||||
throw new IllegalStateException("Async Not Supported");
|
||||
ServletRequestState state = getState();
|
||||
if (_async == null)
|
||||
_async = new AsyncContextState(state);
|
||||
|
@ -1374,6 +1377,8 @@ public class ServletContextRequest extends ContextRequest
|
|||
@Override
|
||||
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
|
||||
{
|
||||
if (!isAsyncSupported())
|
||||
throw new IllegalStateException("Async Not Supported");
|
||||
ServletRequestState state = getState();
|
||||
if (_async == null)
|
||||
_async = new AsyncContextState(state);
|
||||
|
@ -1398,7 +1403,12 @@ public class ServletContextRequest extends ContextRequest
|
|||
@Override
|
||||
public boolean isAsyncSupported()
|
||||
{
|
||||
return true;
|
||||
return _asyncSupported;
|
||||
}
|
||||
|
||||
public void setAsyncSupported(boolean asyncSupported)
|
||||
{
|
||||
_asyncSupported = asyncSupported;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.util.Set;
|
|||
import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
import jakarta.servlet.GenericServlet;
|
||||
import jakarta.servlet.MultipartConfigElement;
|
||||
import jakarta.servlet.Servlet;
|
||||
|
@ -39,12 +38,9 @@ import jakarta.servlet.ServletContext;
|
|||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletRequestWrapper;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.ServletSecurityElement;
|
||||
import jakarta.servlet.UnavailableException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.ee10.servlet.security.IdentityService;
|
||||
import org.eclipse.jetty.ee10.servlet.security.RunAsToken;
|
||||
|
@ -1376,49 +1372,24 @@ public class ServletHolder extends Holder<Servlet> implements Comparable<Servlet
|
|||
}
|
||||
|
||||
@Override
|
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
|
||||
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
if (req.isAsyncSupported())
|
||||
if (request.isAsyncSupported())
|
||||
{
|
||||
if (req instanceof HttpServletRequest httpServletRequest)
|
||||
ServletContextRequest servletContextRequest = ServletContextRequest.getServletContextRequest(request);
|
||||
servletContextRequest.getServletApiRequest().setAsyncSupported(false);
|
||||
try
|
||||
{
|
||||
getWrapped().service(new HttpServletRequestWrapper(httpServletRequest)
|
||||
{
|
||||
@Override
|
||||
public boolean isAsyncSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext startAsync() throws IllegalStateException
|
||||
{
|
||||
throw new IllegalStateException("Async Not Supported");
|
||||
}
|
||||
}, res);
|
||||
getWrapped().service(request, response);
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
//TODO is this necessary to support?
|
||||
getWrapped().service(new ServletRequestWrapper(req)
|
||||
{
|
||||
@Override
|
||||
public boolean isAsyncSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext startAsync() throws IllegalStateException
|
||||
{
|
||||
throw new IllegalStateException("Async Not Supported");
|
||||
}
|
||||
}, res);
|
||||
servletContextRequest.getServletApiRequest().setAsyncSupported(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getWrapped().service(req, res);
|
||||
getWrapped().service(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,17 +27,10 @@ public class EncodingHttpWriter extends HttpWriter
|
|||
{
|
||||
final Writer _converter;
|
||||
|
||||
public EncodingHttpWriter(HttpOutput out, String encoding)
|
||||
public EncodingHttpWriter(HttpOutput out, String encoding) throws IOException
|
||||
{
|
||||
super(out);
|
||||
try
|
||||
{
|
||||
_converter = new OutputStreamWriter(_bytes, encoding);
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
_converter = new OutputStreamWriter(_bytes, encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -182,7 +182,6 @@ public class AsyncServletTest
|
|||
assertThat(response, Matchers.startsWith("HTTP/1.1 200 OK"));
|
||||
assertThat(_history, contains(
|
||||
"REQUEST /ctx/noasync/info",
|
||||
"wrapped REQ",
|
||||
"initial"
|
||||
));
|
||||
|
||||
|
@ -199,7 +198,6 @@ public class AsyncServletTest
|
|||
assertThat(response, Matchers.startsWith("HTTP/1.1 500 "));
|
||||
assertThat(_history, contains(
|
||||
"REQUEST /ctx/noasync/info?start=200",
|
||||
"wrapped REQ",
|
||||
"initial",
|
||||
"ERROR /ctx/error/custom?start=200",
|
||||
"wrapped REQ",
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.ee10.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class CharacterEncodingTest
|
||||
{
|
||||
public static class CharsetChangeToJsonMimeTypeSetCharsetToNullServlet extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
// set an unknown character encoding
|
||||
response.setCharacterEncoding("allez-les-bleus");
|
||||
|
||||
// here we should have UnsupportedEncodingException
|
||||
try
|
||||
{
|
||||
response.getWriter();
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
// nothing we only test we throw this exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Server server;
|
||||
private static LocalConnector connector;
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new LocalConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
context.addServlet(CharsetChangeToJsonMimeTypeSetCharsetToNullServlet.class, "/character-encoding/not-exists/*");
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopServer()
|
||||
{
|
||||
try
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownCharacterEncoding() throws Exception
|
||||
{
|
||||
HttpTester.Request request = new HttpTester.Request();
|
||||
request.setMethod("GET");
|
||||
request.setURI("/character-encoding/not-exists/");
|
||||
request.setVersion(HttpVersion.HTTP_1_1);
|
||||
request.setHeader("Host", "test");
|
||||
|
||||
ByteBuffer responseBuffer = connector.getResponse(request.generate());
|
||||
HttpTester.Response response = HttpTester.parseResponse(responseBuffer);
|
||||
|
||||
// Now test for properly formatted HTTP Response Headers.
|
||||
assertThat("Response Code", response.getStatus(), is(200));
|
||||
|
||||
}
|
||||
}
|
|
@ -14,14 +14,12 @@
|
|||
package org.eclipse.jetty.ee10.test.client.transport;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Deque;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
@ -53,9 +51,7 @@ import org.eclipse.jetty.client.util.BufferingResponseListener;
|
|||
import org.eclipse.jetty.client.util.InputStreamRequestContent;
|
||||
import org.eclipse.jetty.client.util.OutputStreamRequestContent;
|
||||
import org.eclipse.jetty.client.util.StringRequestContent;
|
||||
import org.eclipse.jetty.ee10.servlet.HttpInput;
|
||||
import org.eclipse.jetty.ee10.servlet.HttpOutput;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
|
@ -64,7 +60,6 @@ import org.eclipse.jetty.http2.api.Session;
|
|||
import org.eclipse.jetty.http2.client.transport.internal.HttpConnectionOverHTTP2;
|
||||
import org.eclipse.jetty.http2.internal.HTTP2Session;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.logging.StacklessLogging;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
@ -80,9 +75,7 @@ import org.junit.jupiter.api.Disabled;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static java.nio.ByteBuffer.wrap;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.eclipse.jetty.util.BufferUtil.toArray;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
|
@ -1199,174 +1192,6 @@ public class AsyncIOServletTest extends AbstractTest
|
|||
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testAsyncIntercepted(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
System.err.println("Service " + request);
|
||||
|
||||
HttpInput httpInput = Objects.requireNonNull(ServletContextRequest.getServletContextRequest(request)).getHttpInput();
|
||||
httpInput.addInterceptor(new HttpInput.Interceptor()
|
||||
{
|
||||
int state = 0;
|
||||
Content.Chunk saved;
|
||||
|
||||
@Override
|
||||
public Content.Chunk readFrom(Content.Chunk chunk)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
// null transform
|
||||
chunk.skip(chunk.remaining());
|
||||
chunk.release();
|
||||
state++;
|
||||
return null;
|
||||
|
||||
case 1:
|
||||
{
|
||||
// copy transform
|
||||
if (!chunk.hasRemaining())
|
||||
{
|
||||
state++;
|
||||
return chunk;
|
||||
}
|
||||
ByteBuffer copy = wrap(toArray(chunk.getByteBuffer()));
|
||||
chunk.skip(copy.remaining());
|
||||
chunk.release();
|
||||
return Content.Chunk.from(copy, false);
|
||||
}
|
||||
|
||||
case 2:
|
||||
// byte by byte
|
||||
if (!chunk.hasRemaining())
|
||||
{
|
||||
state++;
|
||||
return chunk;
|
||||
}
|
||||
byte[] b = new byte[1];
|
||||
int l = chunk.get(b, 0, 1);
|
||||
if (!chunk.hasRemaining())
|
||||
chunk.release();
|
||||
return Content.Chunk.from(wrap(b, 0, l), false);
|
||||
|
||||
case 3:
|
||||
{
|
||||
// double vision
|
||||
if (!chunk.hasRemaining())
|
||||
{
|
||||
if (saved == null)
|
||||
{
|
||||
state++;
|
||||
return chunk;
|
||||
}
|
||||
Content.Chunk ref = saved;
|
||||
saved = null;
|
||||
return ref;
|
||||
}
|
||||
|
||||
byte[] data = toArray(chunk.getByteBuffer());
|
||||
chunk.skip(data.length);
|
||||
chunk.release();
|
||||
saved = Content.Chunk.from(wrap(data), false);
|
||||
return Content.Chunk.from(wrap(data), false);
|
||||
}
|
||||
|
||||
default:
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
ServletInputStream input = request.getInputStream();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
input.setReadListener(new ReadListener()
|
||||
{
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
while (input.isReady())
|
||||
{
|
||||
int b = input.read();
|
||||
if (b > 0)
|
||||
{
|
||||
// System.err.printf("0x%2x %s %n", b, Character.isISOControl(b)?"?":(""+(char)b));
|
||||
out.write(b);
|
||||
}
|
||||
else if (b < 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
response.getOutputStream().write(out.toByteArray());
|
||||
asyncContext.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable x)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AsyncRequestContent content = new AsyncRequestContent();
|
||||
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
|
||||
String expected =
|
||||
"S1" +
|
||||
"S2" +
|
||||
"S3S3" +
|
||||
"S4" +
|
||||
"S5" +
|
||||
"S6";
|
||||
|
||||
client.newRequest(newURI(transport))
|
||||
.method(HttpMethod.POST)
|
||||
.body(content)
|
||||
.send(new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
{
|
||||
Response response = result.getResponse();
|
||||
assertThat(response.getStatus(), Matchers.equalTo(HttpStatus.OK_200));
|
||||
assertThat(getContentAsString(), Matchers.equalTo(expected));
|
||||
clientLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
content.write(BufferUtil.toBuffer("S0"), Callback.NOOP);
|
||||
content.flush();
|
||||
content.write(BufferUtil.toBuffer("S1"), Callback.NOOP);
|
||||
content.flush();
|
||||
content.write(BufferUtil.toBuffer("S2"), Callback.NOOP);
|
||||
content.flush();
|
||||
content.write(BufferUtil.toBuffer("S3"), Callback.NOOP);
|
||||
content.flush();
|
||||
content.write(BufferUtil.toBuffer("S4"), Callback.NOOP);
|
||||
content.flush();
|
||||
content.write(BufferUtil.toBuffer("S5"), Callback.NOOP);
|
||||
content.flush();
|
||||
content.write(BufferUtil.toBuffer("S6"), Callback.NOOP);
|
||||
content.close();
|
||||
|
||||
assertTrue(clientLatch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testAsyncEcho(Transport transport) throws Exception
|
||||
|
@ -1443,236 +1268,6 @@ public class AsyncIOServletTest extends AbstractTest
|
|||
assertThat(resultRef.get().getResponse().getStatus(), Matchers.equalTo(HttpStatus.OK_200));
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: there is no GzipHttpInputInterceptor anymore, use something else.
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testAsyncInterceptedTwice(Transport transport) throws Exception
|
||||
{
|
||||
init(transport);
|
||||
start(transport, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
System.err.println("Service " + request);
|
||||
|
||||
HttpInput httpInput = ((Request)request).getHttpInput();
|
||||
httpInput.addInterceptor(new GzipHttpInputInterceptor(new InflaterPool(-1, true), ((Request)request).getHttpChannel().getByteBufferPool(), 1024));
|
||||
httpInput.addInterceptor(chunk ->
|
||||
{
|
||||
if (chunk.isTerminal())
|
||||
return chunk;
|
||||
ByteBuffer byteBuffer = chunk.getByteBuffer();
|
||||
byte[] bytes = new byte[2];
|
||||
bytes[1] = byteBuffer.get();
|
||||
bytes[0] = byteBuffer.get();
|
||||
return Content.Chunk.from(wrap(bytes), false);
|
||||
});
|
||||
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
ServletInputStream input = request.getInputStream();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
input.setReadListener(new ReadListener()
|
||||
{
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
while (input.isReady())
|
||||
{
|
||||
int b = input.read();
|
||||
if (b > 0)
|
||||
{
|
||||
// System.err.printf("0x%2x %s %n", b, Character.isISOControl(b)?"?":(""+(char)b));
|
||||
out.write(b);
|
||||
}
|
||||
else if (b < 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
response.getOutputStream().write(out.toByteArray());
|
||||
asyncContext.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable x)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AsyncRequestContent contentProvider = new AsyncRequestContent();
|
||||
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
|
||||
String expected =
|
||||
"0S" +
|
||||
"1S" +
|
||||
"2S" +
|
||||
"3S" +
|
||||
"4S" +
|
||||
"5S" +
|
||||
"6S";
|
||||
|
||||
client.newRequest(newURI(transport))
|
||||
.method(HttpMethod.POST)
|
||||
.path(scenario.servletPath)
|
||||
.body(contentProvider)
|
||||
.send(new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
{
|
||||
Response response = result.getResponse();
|
||||
assertThat(response.getStatus(), Matchers.equalTo(HttpStatus.OK_200));
|
||||
assertThat(getContentAsString(), Matchers.equalTo(expected));
|
||||
clientLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
contentProvider.write(gzipToBuffer("S" + i), Callback.NOOP);
|
||||
contentProvider.flush();
|
||||
}
|
||||
contentProvider.close();
|
||||
|
||||
assertTrue(clientLatch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
*/
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testAsyncInterceptedTwiceWithNulls(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
System.err.println("Service " + request);
|
||||
|
||||
HttpInput httpInput = ((ServletContextRequest)request).getHttpInput();
|
||||
httpInput.addInterceptor(chunk ->
|
||||
{
|
||||
if (!chunk.hasRemaining())
|
||||
return chunk;
|
||||
|
||||
// skip contents with odd numbers
|
||||
ByteBuffer duplicate = chunk.getByteBuffer().duplicate();
|
||||
duplicate.get();
|
||||
byte integer = duplicate.get();
|
||||
int idx = Character.getNumericValue(integer);
|
||||
Content.Chunk chunkCopy = Content.Chunk.from(chunk.getByteBuffer().duplicate(), false);
|
||||
chunk.skip(chunk.remaining());
|
||||
chunk.release();
|
||||
if (idx % 2 == 0)
|
||||
return chunkCopy;
|
||||
return null;
|
||||
});
|
||||
httpInput.addInterceptor(chunk ->
|
||||
{
|
||||
if (!chunk.hasRemaining())
|
||||
return chunk;
|
||||
|
||||
// reverse the bytes
|
||||
ByteBuffer byteBuffer = chunk.getByteBuffer();
|
||||
byte[] bytes = new byte[2];
|
||||
bytes[1] = byteBuffer.get();
|
||||
bytes[0] = byteBuffer.get();
|
||||
return Content.Chunk.from(wrap(bytes), false);
|
||||
});
|
||||
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
ServletInputStream input = request.getInputStream();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
input.setReadListener(new ReadListener()
|
||||
{
|
||||
@Override
|
||||
public void onDataAvailable() throws IOException
|
||||
{
|
||||
while (input.isReady())
|
||||
{
|
||||
int b = input.read();
|
||||
if (b > 0)
|
||||
{
|
||||
// System.err.printf("0x%2x %s %n", b, Character.isISOControl(b)?"?":(""+(char)b));
|
||||
out.write(b);
|
||||
}
|
||||
else if (b < 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllDataRead() throws IOException
|
||||
{
|
||||
response.getOutputStream().write(out.toByteArray());
|
||||
asyncContext.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable x)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AsyncRequestContent contentProvider = new AsyncRequestContent();
|
||||
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
|
||||
String expected =
|
||||
"0S" +
|
||||
"2S" +
|
||||
"4S" +
|
||||
"6S";
|
||||
|
||||
client.newRequest(newURI(transport))
|
||||
.method(HttpMethod.POST)
|
||||
.body(contentProvider)
|
||||
.send(new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
{
|
||||
Response response = result.getResponse();
|
||||
assertThat(response.getStatus(), Matchers.equalTo(HttpStatus.OK_200));
|
||||
assertThat(getContentAsString(), Matchers.equalTo(expected));
|
||||
clientLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
contentProvider.write(BufferUtil.toBuffer("S0"), Callback.NOOP);
|
||||
contentProvider.flush();
|
||||
contentProvider.write(BufferUtil.toBuffer("S1"), Callback.NOOP);
|
||||
contentProvider.flush();
|
||||
contentProvider.write(BufferUtil.toBuffer("S2"), Callback.NOOP);
|
||||
contentProvider.flush();
|
||||
contentProvider.write(BufferUtil.toBuffer("S3"), Callback.NOOP);
|
||||
contentProvider.flush();
|
||||
contentProvider.write(BufferUtil.toBuffer("S4"), Callback.NOOP);
|
||||
contentProvider.flush();
|
||||
contentProvider.write(BufferUtil.toBuffer("S5"), Callback.NOOP);
|
||||
contentProvider.flush();
|
||||
contentProvider.write(BufferUtil.toBuffer("S6"), Callback.NOOP);
|
||||
contentProvider.close();
|
||||
|
||||
assertTrue(clientLatch.await(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testWriteListenerFromOtherThread(Transport transport) throws Exception
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<jakarta.servlet.api.version>6.0.0</jakarta.servlet.api.version>
|
||||
<jakarta.servlet.jsp.api.version>3.1.0</jakarta.servlet.jsp.api.version>
|
||||
<jakarta.servlet.jsp.jstl.api.version>3.0.0</jakarta.servlet.jsp.jstl.api.version>
|
||||
<jakarta.servlet.jsp.jstl.impl.version>3.0.0</jakarta.servlet.jsp.jstl.impl.version> <!-- TODO: remove? -->
|
||||
<jakarta.servlet.jsp.jstl.impl.version>3.0.1</jakarta.servlet.jsp.jstl.impl.version>
|
||||
<jakarta.ws.rs.api.version>3.1.0</jakarta.ws.rs.api.version>
|
||||
<jakarta.xml.bind.api.version>4.0.0</jakarta.xml.bind.api.version>
|
||||
<jakarta.xml.bind.impl.version>4.0.0</jakarta.xml.bind.impl.version>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
invoker.goals = verify
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.ee9.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-project</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>Jetty :: Simple :: Base Webapp</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>Simple Base Webapp Index</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,140 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.ee9.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-project</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>jetty-simple-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>Jetty :: Simple :: Webapp</name>
|
||||
|
||||
<properties>
|
||||
<jetty.port.file>${project.build.directory}/jetty-start-port.txt</jetty.port.file>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<type>war</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-maven-plugin</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||
<overlays>
|
||||
<overlay>
|
||||
<groupId>org.eclipse.jetty.ee9.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<includes>
|
||||
<include>index.html</include>
|
||||
</includes>
|
||||
</overlay>
|
||||
<overlay/>
|
||||
</overlays>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>run-tests</id>
|
||||
<phase>integration-test</phase>
|
||||
<goals>
|
||||
<goal>test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>IntegrationTest*.java</include>
|
||||
</includes>
|
||||
<systemPropertyVariables>
|
||||
<jetty.port.file>${jetty.port.file}</jetty.port.file>
|
||||
<context.path>/setbycontextxml</context.path>
|
||||
<contentCheck>Simple Base Webapp Index</contentCheck>
|
||||
<pathToCheck>/index.html</pathToCheck>
|
||||
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
|
||||
</systemPropertyVariables>
|
||||
<dependenciesToScan>
|
||||
<dependency>org.eclipse.jetty.ee9:jetty-ee9-maven-plugin</dependency>
|
||||
</dependenciesToScan>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.eclipse.jetty.ee9</groupId>
|
||||
<artifactId>jetty-ee9-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>start-jetty</id>
|
||||
<phase>pre-integration-test</phase>
|
||||
<goals>
|
||||
<goal>start</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<contextXml>${basedir}/src/config/context.xml</contextXml>
|
||||
<systemProperties>
|
||||
<jetty.port.file>${jetty.port.file}</jetty.port.file>
|
||||
<jetty.deployMode>EMBED</jetty.deployMode>
|
||||
</systemProperties>
|
||||
<jettyXmls>
|
||||
<jettyXml>${basedir}/src/config/jetty.xml</jettyXml>
|
||||
</jettyXmls>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||
|
||||
<Configure class="org.eclipse.jetty.ee9.webapp.WebAppContext">
|
||||
|
||||
<Set name="contextPath">/setbycontextxml</Set>
|
||||
|
||||
</Configure>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
||||
<Set name="secureScheme">https</Set>
|
||||
<Set name="securePort"><Property name="jetty.secure.port" default="8443" /></Set>
|
||||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">1024</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Arg name="server"><Ref refid="Server" /></Arg>
|
||||
<Arg name="factories">
|
||||
<Array type="org.eclipse.jetty.server.ConnectionFactory">
|
||||
<Item>
|
||||
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
|
||||
<Arg name="config"><Ref refid="httpConfig" /></Arg>
|
||||
</New>
|
||||
</Item>
|
||||
</Array>
|
||||
</Arg>
|
||||
<Call name="addEventListener">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.ee9.maven.plugin.ServerConnectorListener">
|
||||
<Set name="fileName"><Property name="jetty.port.file" default="port.txt"/></Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
<Set name="host" property="jetty.host"/>
|
||||
<Set name="port" property="jetty.port"/>
|
||||
<Set name="idleTimeout">30000</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</Configure>
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<h1>Simple WebApp Index</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.ee9.its</groupId>
|
||||
<artifactId>it-parent-pom</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.eclipse.jetty.ee9.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-project</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Jetty :: Simple Overlay</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<jetty.version>@project.version@</jetty.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>jetty-simple-base-webapp</module>
|
||||
<module>jetty-simple-webapp</module>
|
||||
</modules>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.ee9.its.jetty-start-overlay-it</groupId>
|
||||
<artifactId>jetty-simple-base-webapp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>war</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
File buildLog = new File( basedir, 'build.log' )
|
||||
assert buildLog.text.contains( 'Started Server' )
|
||||
assert buildLog.text.contains( 'Running org.eclipse.jetty.ee9.maven.plugin.it.IntegrationTestGetContent')
|
|
@ -194,7 +194,7 @@ public abstract class AbstractUnassembledWebAppMojo extends AbstractWebAppMojo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//process any overlays and the war type artifacts, and
|
||||
//sets up the base resource collection for the webapp
|
||||
mavenProjectHelper.getOverlayManager().applyOverlays(webApp);
|
||||
|
|
|
@ -94,7 +94,7 @@ public class OverlayManager
|
|||
//if a war matches an overlay config
|
||||
Artifact a = warPlugin.getWarArtifact(config.getGroupId(), config.getArtifactId(), config.getClassifier());
|
||||
if (a != null)
|
||||
{
|
||||
{
|
||||
matchedWarArtifacts.add(a);
|
||||
Resource resource = ResourceFactory.root().newJarFileResource(a.getFile().toPath().toUri()); // TODO leak
|
||||
SelectiveJarResource r = new SelectiveJarResource(resource);
|
||||
|
@ -127,7 +127,7 @@ public class OverlayManager
|
|||
*/
|
||||
protected Resource unpackOverlay(Overlay overlay)
|
||||
throws IOException
|
||||
{
|
||||
{
|
||||
if (overlay.getResource() == null)
|
||||
return null; //nothing to unpack
|
||||
|
||||
|
|
|
@ -109,6 +109,8 @@ public class MavenProjectHelper
|
|||
*/
|
||||
public MavenProject getMavenProjectFor(Artifact artifact)
|
||||
{
|
||||
if (artifact == null)
|
||||
return null;
|
||||
return artifactToReactorProjectMap.get(artifact.getId());
|
||||
}
|
||||
|
||||
|
|
|
@ -25,17 +25,10 @@ public class EncodingHttpWriter extends HttpWriter
|
|||
{
|
||||
final Writer _converter;
|
||||
|
||||
public EncodingHttpWriter(HttpOutput out, String encoding)
|
||||
public EncodingHttpWriter(HttpOutput out, String encoding) throws IOException
|
||||
{
|
||||
super(out);
|
||||
try
|
||||
{
|
||||
_converter = new OutputStreamWriter(_bytes, encoding);
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
_converter = new OutputStreamWriter(_bytes, encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1511,7 +1511,7 @@ public class Request implements HttpServletRequest
|
|||
if (getRequestedSessionId() == null || _coreSession == null)
|
||||
return false;
|
||||
|
||||
return (_sessionManager.getSessionIdManager().getId(getRequestedSessionId()).equals(_coreSession.getId()));
|
||||
return (_coreSession.isValid() && _sessionManager.getSessionIdManager().getId(getRequestedSessionId()).equals(_coreSession.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 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.ee9.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class CharacterEncodingTest
|
||||
{
|
||||
public static class CharsetChangeToJsonMimeTypeSetCharsetToNullServlet extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
// set an unknown character encoding
|
||||
response.setCharacterEncoding("allez-les-bleus");
|
||||
|
||||
// here we should have UnsupportedEncodingException
|
||||
try
|
||||
{
|
||||
response.getWriter();
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
// nothing we only test we throw this exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Server server;
|
||||
private static LocalConnector connector;
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new LocalConnector(server);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
context.addServlet(CharsetChangeToJsonMimeTypeSetCharsetToNullServlet.class, "/character-encoding/not-exists/*");
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void stopServer()
|
||||
{
|
||||
try
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownCharacterEncoding() throws Exception
|
||||
{
|
||||
HttpTester.Request request = new HttpTester.Request();
|
||||
request.setMethod("GET");
|
||||
request.setURI("/character-encoding/not-exists/");
|
||||
request.setVersion(HttpVersion.HTTP_1_1);
|
||||
request.setHeader("Host", "test");
|
||||
|
||||
ByteBuffer responseBuffer = connector.getResponse(request.generate());
|
||||
HttpTester.Response response = HttpTester.parseResponse(responseBuffer);
|
||||
|
||||
// Now test for properly formatted HTTP Response Headers.
|
||||
assertThat("Response Code", response.getStatus(), is(200));
|
||||
|
||||
}
|
||||
}
|
23
pom.xml
23
pom.xml
|
@ -33,7 +33,7 @@
|
|||
<awaitility.version>4.2.0</awaitility.version>
|
||||
<bndlib.version>6.3.1</bndlib.version>
|
||||
<build-support.version>1.5</build-support.version>
|
||||
<checkstyle.version>10.3.3</checkstyle.version>
|
||||
<checkstyle.version>10.3.4</checkstyle.version>
|
||||
<commons-codec.version>1.15</commons-codec.version>
|
||||
<commons.compress.version>1.22</commons.compress.version>
|
||||
<commons.io.version>2.11.0</commons.io.version>
|
||||
|
@ -43,15 +43,15 @@
|
|||
<felix.version>7.0.5</felix.version>
|
||||
<findbugs.jsr305.version>3.0.2</findbugs.jsr305.version>
|
||||
<google.errorprone.version>2.15.0</google.errorprone.version>
|
||||
<grpc.version>1.49.0</grpc.version>
|
||||
<grpc.version>1.49.2</grpc.version>
|
||||
<gson.version>2.9.1</gson.version>
|
||||
<guava.version>31.1-jre</guava.version>
|
||||
<guice.version>5.1.0</guice.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
<hawtio.version>2.15.1</hawtio.version>
|
||||
<hawtio.version>2.15.2</hawtio.version>
|
||||
<hazelcast.version>4.2.5</hazelcast.version>
|
||||
<infinispan.protostream.version>4.4.4.Final</infinispan.protostream.version>
|
||||
<infinispan.version>11.0.15.Final</infinispan.version>
|
||||
<infinispan.protostream.version>4.5.0.Final</infinispan.protostream.version>
|
||||
<infinispan.version>11.0.16.Final</infinispan.version>
|
||||
<jackson.version>2.13.4</jackson.version>
|
||||
<jboss.logging.annotations.version>2.2.1.Final</jboss.logging.annotations.version>
|
||||
<jboss.logging.processor.version>2.2.1.Final</jboss.logging.processor.version>
|
||||
|
@ -68,12 +68,12 @@
|
|||
<jna.version>5.12.1</jna.version>
|
||||
<jnr-constants.version>0.10.4</jnr-constants.version>
|
||||
<jnr-enxio.version>0.32.13</jnr-enxio.version>
|
||||
<jnr-ffi.version>2.2.11</jnr-ffi.version>
|
||||
<jnr-ffi.version>2.2.12</jnr-ffi.version>
|
||||
<jnr-posix.version>3.1.15</jnr-posix.version>
|
||||
<jnr-unixsocket.version>0.38.17</jnr-unixsocket.version>
|
||||
<json-simple.version>1.1.1</json-simple.version>
|
||||
<json-smart.version>2.4.8</json-smart.version>
|
||||
<junit.version>5.9.0</junit.version>
|
||||
<junit.version>5.9.1</junit.version>
|
||||
<kerb-simplekdc.version>2.0.2</kerb-simplekdc.version>
|
||||
<log4j2.version>2.19.0</log4j2.version>
|
||||
<logback.version>1.4.5</logback.version>
|
||||
|
@ -81,11 +81,11 @@
|
|||
<mariadb.docker.version>10.3.6</mariadb.docker.version>
|
||||
<maven.deps.version>3.8.4</maven.deps.version>
|
||||
<maven-artifact-transfer.version>0.13.1</maven-artifact-transfer.version>
|
||||
<maven.resolver.version>1.8.2</maven.resolver.version>
|
||||
<maven.resolver.version>1.9.2</maven.resolver.version>
|
||||
<maven.version>3.8.6</maven.version>
|
||||
<mongodb.version>3.12.11</mongodb.version>
|
||||
<openpojo.version>0.9.1</openpojo.version>
|
||||
<org.osgi.annotation.version>8.0.1</org.osgi.annotation.version>
|
||||
<org.osgi.annotation.version>8.1.0</org.osgi.annotation.version>
|
||||
<org.osgi.core.version>6.0.0</org.osgi.core.version>
|
||||
<org.osgi.util.function.version>1.2.0</org.osgi.util.function.version>
|
||||
<org.osgi.util.promise.version>1.2.0</org.osgi.util.promise.version>
|
||||
|
@ -96,9 +96,8 @@
|
|||
<testcontainers.version>1.17.5</testcontainers.version>
|
||||
<weld.version>4.0.3.Final</weld.version>
|
||||
<wildfly.common.version>1.6.0.Final</wildfly.common.version>
|
||||
<wildfly.elytron.version>1.20.1.Final</wildfly.elytron.version>
|
||||
<wildfly.elytron.version>2.0.0.Final</wildfly.elytron.version>
|
||||
<xmemcached.version>2.4.7</xmemcached.version>
|
||||
<weld.version>4.0.2.Final</weld.version>
|
||||
|
||||
<!-- some maven plugins versions -->
|
||||
<asciidoctor.maven.plugin.version>2.2.2</asciidoctor.maven.plugin.version>
|
||||
|
@ -123,7 +122,7 @@
|
|||
<maven.gpg.plugin.version>3.0.1</maven.gpg.plugin.version>
|
||||
<maven.install.plugin.version>3.1.0</maven.install.plugin.version>
|
||||
<maven.invoker.plugin.version>3.3.0</maven.invoker.plugin.version>
|
||||
<maven.jar.plugin.version>3.2.2</maven.jar.plugin.version>
|
||||
<maven.jar.plugin.version>3.3.0</maven.jar.plugin.version>
|
||||
<maven.javadoc.plugin.version>3.4.1</maven.javadoc.plugin.version>
|
||||
<maven.plugin-tools.version>3.7.0</maven.plugin-tools.version>
|
||||
<maven-plugin.plugin.version>3.7.0</maven-plugin.plugin.version>
|
||||
|
|
Loading…
Reference in New Issue