From 79086f3fe3c52eb85eb94dc3fa796f69011967fd Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 13 Aug 2015 01:33:28 +0200 Subject: [PATCH] 474634 - AsyncListener.onError() handling. Interim work on getting the right behavior for onError(). --- .../jetty/server/AsyncContextEvent.java | 32 +- .../org/eclipse/jetty/server/HttpChannel.java | 347 ++++++++------- .../jetty/server/HttpChannelState.java | 200 ++++++--- .../jetty/server/AsyncRequestReadTest.java | 48 +- .../jetty/servlet/AsyncServletTest.java | 419 +++++++++++------- 5 files changed, 638 insertions(+), 408 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java index d2ec0e002e6..ee30187ef68 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java @@ -77,7 +77,7 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable { return _context; } - + public Context getContext() { return _context; @@ -100,12 +100,12 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable { return _dispatchPath; } - + public void setTimeoutTask(Scheduler.Task task) { _timeoutTask = task; } - + public void cancelTimeoutTask() { Scheduler.Task task=_timeoutTask; @@ -119,28 +119,28 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable { return _asyncContext; } - + @Override public Throwable getThrowable() { return _throwable; } - - public void setThrowable(Throwable throwable) - { - _throwable=throwable; - } + +// public void setThrowable(Throwable throwable) +// { +// _throwable=throwable; +// } public void setDispatchContext(ServletContext context) { _dispatchContext=context; } - + public void setDispatchPath(String path) { _dispatchPath=path; } - + public void completed() { _timeoutTask=null; @@ -158,7 +158,15 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable Scheduler.Task task=_timeoutTask; _timeoutTask=null; if (task!=null) - _state.expired(); + _state.onTimeout(); + } + + public void addThrowable(Throwable e) + { + if (_throwable==null) + _throwable=e; + else + _throwable.addSuppressed(e); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 973b2b92be2..e419b32300d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -116,7 +116,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor { return _written; } - + /** * @return the number of requests handled by this connection */ @@ -232,8 +232,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor * If the associated response has the Expect header set to 100 Continue, * then accessing the input stream indicates that the handler/servlet * is ready for the request body and thus a 100 Continue response is sent. - * - * @param available estimate of the number of bytes that are available + * + * @param available estimate of the number of bytes that are available * @throws IOException if the InputStream cannot be created */ public void continue100(int available) throws IOException @@ -270,179 +270,201 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor LOG.debug("{} handle {} ", this,_request.getHttpURI()); HttpChannelState.Action action = _state.handling(); - try + + // Loop here to handle async request redispatches. + // The loop is controlled by the call to async.unhandle in the + // finally block below. Unhandle will return false only if an async dispatch has + // already happened when unhandle is called. + while (!getServer().isStopped()) { - // Loop here to handle async request redispatches. - // The loop is controlled by the call to async.unhandle in the - // finally block below. Unhandle will return false only if an async dispatch has - // already happened when unhandle is called. - loop: while (action.ordinal() customizers = _configuration.getCustomizers(); + if (!customizers.isEmpty()) { - ContextHandler handler=_state.getContextHandler(); - if (handler!=null) - handler.handle(_request.getHttpInput()); - else - _request.getHttpInput().run(); - break; + for (HttpConfiguration.Customizer customizer : customizers) + customizer.customize(getConnector(), _configuration, _request); + } + getServer().handle(this); + break; + } + + case ASYNC_DISPATCH: + { + _request.setHandled(false); + _response.getHttpOutput().reopen(); + _request.setDispatcherType(DispatcherType.ASYNC); + getServer().handleAsync(this); + break; + } + + case ERROR_DISPATCH: + { + _request.setHandled(false); + _response.getHttpOutput().reopen(); + _request.setDispatcherType(DispatcherType.ERROR); + + Throwable ex = _state.getAsyncContextEvent().getThrowable(); + String reason; + if (ex == null || ex instanceof TimeoutException) + { + reason = "Async Timeout"; + } + else + { + reason = "Async Exception"; + _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ex); } - case WRITE_CALLBACK: + _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 500); + _request.setAttribute(RequestDispatcher.ERROR_MESSAGE, reason); + _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, _request.getRequestURI()); + + _response.setStatusWithReason(500, reason); + + ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(), _state.getContextHandler()); + if (eh instanceof ErrorHandler.ErrorPageMapper) { - ContextHandler handler=_state.getContextHandler(); + String error_page = ((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest()); + if (error_page != null) + _state.getAsyncContextEvent().setDispatchPath(error_page); + } - if (handler!=null) - handler.handle(_response.getHttpOutput()); + getServer().handleAsync(this); + break; + } + + case READ_CALLBACK: + { + ContextHandler handler=_state.getContextHandler(); + if (handler!=null) + handler.handle(_request.getHttpInput()); + else + _request.getHttpInput().run(); + break; + } + + case WRITE_CALLBACK: + { + ContextHandler handler=_state.getContextHandler(); + if (handler!=null) + handler.handle(_response.getHttpOutput()); + else + _response.getHttpOutput().run(); + break; + } + + case ASYNC_ERROR: + { + _state.onError(); + break; + } + + case COMPLETING: + { + try + { + if (!_response.isCommitted() && !_request.isHandled()) + _response.sendError(404); else - _response.getHttpOutput().run(); - break; - } - - default: - break loop; + _response.closeOutput(); +// _state=COMPLETE; + } + catch (Throwable x) + { +// state = ERROR; + } + break; + // async.complete(); + // state -> COMPLETING + // flush output + // try { flush(); state -> COMPLETED; } + // catch (x) { state -> ERROR } + // unhandle(); + // COMPLETED -> call asyncListeners.onComplete() -> TERMINATED + // ERROR -> call asyncListeners.onError(); -> TERMINATED + // unhandle(); + // TERMINATED -> break out of loop. + } + case COMPLETED: + { + _state.onComplete(); + // TODO: verify this code is needed and whether + // TODO: it's needed for onError() case too. + _request.setHandled(true); + onCompleted(); + // TODO: set action to TERMINATED. + break; + } + default: + { + throw new IllegalStateException("state="+_state); } } - catch (EofException|QuietServletException|BadMessageException e) - { - error=true; - LOG.debug(e); - _state.error(e); - _request.setHandled(true); - handleException(e); - } - catch (Exception e) + } + catch (EofException|QuietServletException|BadMessageException e) + { + error=true; + LOG.debug(e); + _state.error(e); + _request.setHandled(true); + handleException(e); + } + catch (Exception e) + { + error=true; + if (_connector.isStarted()) + LOG.warn(String.valueOf(_request.getHttpURI()), e); + else + LOG.debug(String.valueOf(_request.getHttpURI()), e); + _state.error(e); + _request.setHandled(true); + handleException(e); + } + catch (Throwable e) + { + if ("ContinuationThrowable".equals(e.getClass().getSimpleName())) + LOG.ignore(e); + else { error=true; if (_connector.isStarted()) LOG.warn(String.valueOf(_request.getHttpURI()), e); else LOG.debug(String.valueOf(_request.getHttpURI()), e); + LOG.warn(String.valueOf(_request.getHttpURI()), e); _state.error(e); _request.setHandled(true); handleException(e); } - catch (Throwable e) - { - if ("ContinuationThrowable".equals(e.getClass().getSimpleName())) - LOG.ignore(e); - else - { - error=true; - if (_connector.isStarted()) - LOG.warn(String.valueOf(_request.getHttpURI()), e); - else - LOG.debug(String.valueOf(_request.getHttpURI()), e); - LOG.warn(String.valueOf(_request.getHttpURI()), e); - _state.error(e); - _request.setHandled(true); - handleException(e); - } - } - finally - { - if (error && _state.isAsyncStarted()) - _state.errorComplete(); - action = _state.unhandle(); - } } - - if (action==Action.COMPLETE) + finally { - try - { - _state.completed(); - - if (!_response.isCommitted() && !_request.isHandled()) - { - _response.sendError(404); - } - else - { - // Complete generating the response - _response.closeOutput(); - } - } - catch(EofException|ClosedChannelException e) - { - LOG.debug(e); - } - catch(Exception e) - { - LOG.warn("complete failed",e); - } - finally - { - _request.setHandled(true); - onCompleted(); - } + if (error && _state.isAsyncStarted()) + _state.errorComplete(); + action = _state.unhandle(); } } - finally - { - } if (LOG.isDebugEnabled()) LOG.debug("{} handle exit, result {}", this, action); @@ -531,7 +553,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor HttpFields fields = _response.getHttpFields(); if (_configuration.getSendDateHeader() && !fields.contains(HttpHeader.DATE)) fields.put(_connector.getServer().getDateField()); - + _request.setMetaData(request); } @@ -539,7 +561,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor { if (LOG.isDebugEnabled()) LOG.debug("{} content {}", this, content); - + return _request.getHttpInput().addContent(content); } @@ -554,10 +576,10 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor { if (_requestLog!=null ) _requestLog.log(_request, _response); - + _transport.onCompleted(); } - + public boolean onEarlyEOF() { return _request.getHttpInput().earlyEOF(); @@ -570,7 +592,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor try { - if (_state.handling()==Action.REQUEST_DISPATCH) + if (_state.handling()==Action.DISPATCH) { ByteBuffer content=null; HttpFields fields=new HttpFields(); @@ -588,10 +610,11 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor } finally { - if (_state.unhandle()==Action.COMPLETE) - _state.completed(); - else - throw new IllegalStateException(); + // TODO: review whether it's the right state to check. + if (_state.unhandle()==Action.COMPLETING) + _state.onComplete(); + else + throw new IllegalStateException(); // TODO: don't throw from finally blocks ! onCompleted(); } } @@ -605,7 +628,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor if (info==null) info = _response.newResponseMetaData(); commit(info); - + // wrap callback to process 100 responses final int status=info.getStatus(); final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback); @@ -641,7 +664,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor throw failure; } } - + protected void commit (MetaData.Response info) { _committedMetaData=info; @@ -667,7 +690,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor _written+=BufferUtil.length(content); sendResponse(null,content,complete,callback); } - + public HttpOutput.Interceptor getNextInterceptor() { return null; @@ -712,13 +735,13 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor { _callback = callback; } - + @Override public boolean isNonBlocking() { return _callback.isNonBlocking(); } - + @Override public void succeeded() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 9ebbf8b977d..6540700246f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -44,17 +44,18 @@ public class HttpChannelState private final static long DEFAULT_TIMEOUT=Long.getLong("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT",30000L); - /** The dispatched state of the HttpChannel, used to control the overall livecycle + /** + * The dispatched state of the HttpChannel, used to control the overall lifecycle */ public enum State { IDLE, // Idle request DISPATCHED, // Request dispatched to filter/servlet - ASYNC_WAIT, // Suspended and parked - ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT - ASYNC_IO, // Has been dispatched for async IO - COMPLETING, // Request is completable - COMPLETED, // Request is complete + ASYNC_WAIT, // Suspended and waiting + ASYNC_WOKEN, // Dispatch to handle from ASYNC_WAIT + ASYNC_IO, // Dispatched for async IO + COMPLETING, // Response is completable + COMPLETED, // Response is completed UPGRADED // Request upgraded the connection } @@ -63,13 +64,16 @@ public class HttpChannelState */ public enum Action { - REQUEST_DISPATCH, // handle a normal request dispatch + DISPATCH, // handle a normal request dispatch ASYNC_DISPATCH, // handle an async request dispatch - ASYNC_EXPIRED, // handle an async timeout + ERROR_DISPATCH, // handle a normal error + ASYNC_ERROR, // handle an async error WRITE_CALLBACK, // handle an IO write callback READ_CALLBACK, // handle an IO read callback + COMPLETING, // Completing the response + COMPLETED, // Response completed + TERMINATED, // No further actions WAIT, // Wait for further events - COMPLETE // Complete the channel } /** @@ -79,11 +83,13 @@ public class HttpChannelState */ public enum Async { - STARTED, - DISPATCH, - COMPLETE, - EXPIRING, - EXPIRED + STARTED, // AsyncContext.startAsync() has been called + DISPATCH, // + COMPLETE, // AsyncContext.complete() has been called + EXPIRING, // AsyncContext timeout just happened + EXPIRED, // AsyncContext timeout has been processed + ERRORING, // An error just happened + ERRORED // The error has been processed } private final boolean DEBUG=LOG.isDebugEnabled(); @@ -161,11 +167,16 @@ public class HttpChannelState } } + private String getStatusStringLocked() + { + return String.format("s=%s i=%b a=%s",_state,_initial,_async); + } + public String getStatusString() { try(Locker.Lock lock= _locker.lock()) { - return String.format("s=%s i=%b a=%s",_state,_initial,_async); + return getStatusStringLocked(); } } @@ -183,10 +194,10 @@ public class HttpChannelState case IDLE: _initial=true; _state=State.DISPATCHED; - return Action.REQUEST_DISPATCH; + return Action.DISPATCH; case COMPLETING: - return Action.COMPLETE; + return Action.COMPLETING; case COMPLETED: return Action.WAIT; @@ -214,7 +225,7 @@ public class HttpChannelState { case COMPLETE: _state=State.COMPLETING; - return Action.COMPLETE; + return Action.COMPLETING; case DISPATCH: _state=State.DISPATCHED; _async=null; @@ -224,9 +235,14 @@ public class HttpChannelState case EXPIRED: _state=State.DISPATCHED; _async=null; - return Action.ASYNC_EXPIRED; + return Action.ERROR_DISPATCH; case STARTED: return Action.WAIT; + case ERRORING: + _state=State.DISPATCHED; + return Action.ASYNC_ERROR; + default: + throw new IllegalStateException(String.valueOf(async)); } } @@ -245,7 +261,7 @@ public class HttpChannelState try(Locker.Lock lock= _locker.lock()) { if (_state!=State.DISPATCHED || _async!=null) - throw new IllegalStateException(this.getStatusString()); + throw new IllegalStateException(this.getStatusStringLocked()); _async=Async.STARTED; _event=event; @@ -263,6 +279,7 @@ public class HttpChannelState } catch(Exception e) { + // TODO Async Dispatch Error LOG.warn(e); } } @@ -274,7 +291,7 @@ public class HttpChannelState try(Locker.Lock lock= _locker.lock()) { if (_event!=null) - _event.setThrowable(th); + _event.addThrowable(th); } } @@ -298,11 +315,15 @@ public class HttpChannelState { switch(_state) { + case COMPLETED: + return Action.COMPLETED; + case DISPATCHED: case ASYNC_IO: break; + default: - throw new IllegalStateException(this.getStatusString()); + throw new IllegalStateException(this.getStatusStringLocked()); } if (_async!=null) @@ -313,19 +334,19 @@ public class HttpChannelState case COMPLETE: _state=State.COMPLETING; _async=null; - action = Action.COMPLETE; + action=Action.COMPLETING; break; case DISPATCH: _state=State.DISPATCHED; _async=null; - action = Action.ASYNC_DISPATCH; + action=Action.ASYNC_DISPATCH; break; case EXPIRED: _state=State.DISPATCHED; _async=null; - action = Action.ASYNC_EXPIRED; + action = Action.ERROR_DISPATCH; break; case STARTED: @@ -339,33 +360,44 @@ public class HttpChannelState { _asyncWrite=false; _state=State.ASYNC_IO; - action = Action.WRITE_CALLBACK; + action=Action.WRITE_CALLBACK; } else { schedule_event=_event; read_interested=_asyncReadUnready; _state=State.ASYNC_WAIT; - action = Action.WAIT; + action=Action.WAIT; } break; case EXPIRING: schedule_event=_event; _state=State.ASYNC_WAIT; - action = Action.WAIT; + action=Action.WAIT; + break; + + case ERRORING: + _state=State.DISPATCHED; + action=Action.ERROR_DISPATCH; + break; + + case ERRORED: + _state=State.DISPATCHED; + action=Action.ASYNC_ERROR; + _async=null; break; default: _state=State.COMPLETING; - action = Action.COMPLETE; + action=Action.COMPLETING; break; } } else { _state=State.COMPLETING; - action = Action.COMPLETE; + action=Action.COMPLETING; } } @@ -381,8 +413,15 @@ public class HttpChannelState boolean dispatch; try(Locker.Lock lock= _locker.lock()) { - if (_async!=Async.STARTED && _async!=Async.EXPIRING) - throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString()); + switch(_async) + { + case STARTED: + case EXPIRING: + case ERRORED: + break; + default: + throw new IllegalStateException(this.getStatusStringLocked()); + } _async=Async.DISPATCH; if (context!=null) @@ -415,9 +454,9 @@ public class HttpChannelState scheduleDispatch(); } - protected void expired() + protected void onTimeout() { - final List aListeners; + final List listeners; AsyncContextEvent event; try(Locker.Lock lock= _locker.lock()) { @@ -425,15 +464,15 @@ public class HttpChannelState return; _async=Async.EXPIRING; event=_event; - aListeners=_asyncListeners; + listeners=_asyncListeners; } if (LOG.isDebugEnabled()) LOG.debug("Async timeout {}",this); - - if (aListeners!=null) + + if (listeners!=null) { - for (AsyncListener listener : aListeners) + for (AsyncListener listener : listeners) { try { @@ -442,8 +481,8 @@ public class HttpChannelState catch(Exception e) { LOG.debug(e); - event.setThrowable(e); - _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e); + event.addThrowable(e); + _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable()); break; } } @@ -454,7 +493,17 @@ public class HttpChannelState { if (_async==Async.EXPIRING) { - _async=Async.EXPIRED; + // If the listeners did not call dispatch() or complete(), + // then the container must generate an error. + if (event.getThrowable()==null) + { + _async=Async.EXPIRED; + _event.addThrowable(new TimeoutException("Async API violation")); + } + else + { + _async=Async.ERRORING; + } if (_state==State.ASYNC_WAIT) { _state=State.ASYNC_WOKEN; @@ -477,8 +526,15 @@ public class HttpChannelState boolean handle=false; try(Locker.Lock lock= _locker.lock()) { - if (_async!=Async.STARTED && _async!=Async.EXPIRING) - throw new IllegalStateException(this.getStatusString()); + switch(_async) + { + case STARTED: + case EXPIRING: + case ERRORED: + break; + default: + throw new IllegalStateException(this.getStatusStringLocked()); + } _async=Async.COMPLETE; if (_state==State.ASYNC_WAIT) { @@ -510,22 +566,58 @@ public class HttpChannelState cancelTimeout(); } - protected void completed() + protected void onError() { final List aListeners; final AsyncContextEvent event; + + try(Locker.Lock lock= _locker.lock()) + { + if (_state!=State.DISPATCHED/* || _async!=Async.ERRORING*/) + throw new IllegalStateException(this.getStatusStringLocked()); + + aListeners=_asyncListeners; + event=_event; + _async=Async.ERRORED; + } + + if (event!=null && aListeners!=null) + { + event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable()); + event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage()); + for (AsyncListener listener : aListeners) + { + try + { + listener.onError(event); + } + catch(Exception x) + { + LOG.info("Exception while invoking listener " + listener, x); + } + } + } + } + + + protected void onComplete() + { + final List aListeners; + final AsyncContextEvent event; + try(Locker.Lock lock= _locker.lock()) { switch(_state) { case COMPLETING: - _state=State.COMPLETED; aListeners=_asyncListeners; event=_event; + _state=State.COMPLETED; + _async=null; break; default: - throw new IllegalStateException(this.getStatusString()); + throw new IllegalStateException(this.getStatusStringLocked()); } } @@ -533,20 +625,11 @@ public class HttpChannelState { if (aListeners!=null) { - if (event.getThrowable()!=null) - { - event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable()); - event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage()); - } - for (AsyncListener listener : aListeners) { try { - if (event.getThrowable()!=null) - listener.onError(event); - else - listener.onComplete(event); + listener.onComplete(event); } catch(Exception e) { @@ -554,7 +637,6 @@ public class HttpChannelState } } } - event.completed(); } } @@ -568,7 +650,7 @@ public class HttpChannelState { case DISPATCHED: case ASYNC_IO: - throw new IllegalStateException(getStatusString()); + throw new IllegalStateException(getStatusStringLocked()); case UPGRADED: return; default: @@ -596,7 +678,7 @@ public class HttpChannelState case COMPLETED: break; default: - throw new IllegalStateException(getStatusString()); + throw new IllegalStateException(getStatusStringLocked()); } _asyncListeners=null; _state=State.UPGRADED; diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java index 9078f39fdb3..d188105fad5 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java @@ -18,11 +18,6 @@ package org.eclipse.jetty.server; -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -47,6 +42,11 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + public class AsyncRequestReadTest { private static Server server; @@ -75,11 +75,11 @@ public class AsyncRequestReadTest { server.setHandler(new AsyncStreamHandler()); server.start(); - + try (final Socket socket = new Socket("localhost",connector.getLocalPort())) { socket.setSoTimeout(1000); - + byte[] content = new byte[32*4096]; Arrays.fill(content, (byte)120); @@ -93,8 +93,8 @@ public class AsyncRequestReadTest byte[] h=header.getBytes(StandardCharsets.ISO_8859_1); out.write(h); out.write(content); - - + + header= "POST / HTTP/1.1\r\n"+ "Host: localhost\r\n"+ @@ -123,7 +123,7 @@ public class AsyncRequestReadTest { server.setHandler(new AsyncStreamHandler()); server.start(); - + asyncReadTest(64,4,4,20); asyncReadTest(256,16,16,50); asyncReadTest(256,1,128,10); @@ -184,7 +184,7 @@ public class AsyncRequestReadTest final AsyncContext async = request.startAsync(); // System.err.println("handle "+request.getContentLength()); - + new Thread() { @Override @@ -194,7 +194,7 @@ public class AsyncRequestReadTest try(InputStream in = request.getInputStream();) { // System.err.println("reading..."); - + byte[] b = new byte[4*4096]; int read; while((read =in.read(b))>=0) @@ -216,18 +216,18 @@ public class AsyncRequestReadTest }.start(); } } - + @Test public void testPartialRead() throws Exception { server.setHandler(new PartialReaderHandler()); server.start(); - + try (final Socket socket = new Socket("localhost",connector.getLocalPort())) { - socket.setSoTimeout(1000); - + socket.setSoTimeout(10000); + byte[] content = new byte[32*4096]; Arrays.fill(content, (byte)88); @@ -241,7 +241,7 @@ public class AsyncRequestReadTest byte[] h=header.getBytes(StandardCharsets.ISO_8859_1); out.write(h); out.write(content); - + header= "POST /?read=10 HTTP/1.1\r\n"+ "Host: localhost\r\n"+ "Content-Length: "+content.length+"\r\n"+ @@ -252,7 +252,7 @@ public class AsyncRequestReadTest out.write(h); out.write(content); out.flush(); - + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); assertThat(in.readLine(),containsString("HTTP/1.1 200 OK")); assertThat(in.readLine(),containsString("Content-Length:")); @@ -273,11 +273,11 @@ public class AsyncRequestReadTest { server.setHandler(new PartialReaderHandler()); server.start(); - + try (final Socket socket = new Socket("localhost",connector.getLocalPort())) { socket.setSoTimeout(10000); - + byte[] content = new byte[32*4096]; Arrays.fill(content, (byte)88); @@ -308,11 +308,11 @@ public class AsyncRequestReadTest { server.setHandler(new PartialReaderHandler()); server.start(); - + try (final Socket socket = new Socket("localhost",connector.getLocalPort())) { socket.setSoTimeout(1000); - + byte[] content = new byte[32*4096]; Arrays.fill(content, (byte)88); @@ -334,7 +334,7 @@ public class AsyncRequestReadTest assertThat(in.readLine(),containsString("Server:")); in.readLine(); assertThat(in.readLine(),containsString("XXXXXXX")); - + socket.close(); } } @@ -346,7 +346,7 @@ public class AsyncRequestReadTest { httpResponse.setStatus(200); request.setHandled(true); - + BufferedReader in = request.getReader(); PrintWriter out =httpResponse.getWriter(); int read=Integer.valueOf(request.getParameter("read")); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java index 37130be6a4e..26f694360c8 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.servlet; -import static org.junit.Assert.assertEquals; - import java.io.IOException; import java.io.InputStream; import java.net.Socket; @@ -43,6 +41,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.QuietServletException; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Response; @@ -60,6 +59,10 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + @RunWith(AdvancedRunner.class) public class AsyncServletTest @@ -79,7 +82,7 @@ public class AsyncServletTest { _connector = new ServerConnector(_server); _server.setConnectors(new Connector[]{ _connector }); - + _log=new ArrayList<>(); RequestLog log=new Log(); RequestLogHandler logHandler = new RequestLogHandler(); @@ -91,7 +94,7 @@ public class AsyncServletTest ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); context.setContextPath("/ctx"); logHandler.setHandler(context); - + _servletHandler=context.getServletHandler(); ServletHolder holder=new ServletHolder(_servlet); holder.setAsyncSupported(true); @@ -116,7 +119,7 @@ public class AsyncServletTest public void testNormal() throws Exception { String response=process(null,null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n",response); @@ -129,7 +132,7 @@ public class AsyncServletTest public void testSleep() throws Exception { String response=process("sleep=200",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n",response); @@ -139,32 +142,32 @@ public class AsyncServletTest } @Test - public void testSuspend() throws Exception + public void testStart() throws Exception { _expectedCode="500 "; - String response=process("suspend=200",null); + String response=process("start=200",null); Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 500 Async Timeout")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ "history: onTimeout\r\n"+ "history: ERROR /ctx/path/info\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); - assertContains("ERROR: /ctx/path/info",response); + assertContains("ERROR DISPATCH: /ctx/path/info",response); } @Test - public void testSuspendOnTimeoutDispatch() throws Exception + public void testStartOnTimeoutDispatch() throws Exception { - String response=process("suspend=200&timeout=dispatch",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=200&timeout=dispatch",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ "history: onTimeout\r\n"+ "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ @@ -175,14 +178,71 @@ public class AsyncServletTest } @Test - public void testSuspendOnTimeoutComplete() throws Exception + public void testStartOnTimeoutError() throws Exception { - String response=process("suspend=200&timeout=complete",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + _expectedCode="500 "; + String response=process("start=200&timeout=error",null); + assertThat(response,startsWith("HTTP/1.1 500 Async Exception\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ + "history: onTimeout\r\n"+ + "history: error\r\n"+ + "history: onError\r\n"+ + "history: ERROR /ctx/path/info\r\n"+ + "history: !initial\r\n"+ + "history: onComplete\r\n",response); + + assertContains("ERROR DISPATCH",response); + } + + @Test + public void testStartOnTimeoutErrorComplete() throws Exception + { + String response=process("start=200&timeout=error&error=complete",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); + assertContains( + "history: REQUEST /ctx/path/info\r\n"+ + "history: initial\r\n"+ + "history: start\r\n"+ + "history: onTimeout\r\n"+ + "history: error\r\n"+ + "history: onError\r\n"+ + "history: complete\r\n",response); + + assertContains("COMPLETED",response); + } + + @Test + public void testStartOnTimeoutErrorDispatch() throws Exception + { + String response=process("start=200&timeout=error&error=dispatch",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); + assertContains( + "history: REQUEST /ctx/path/info\r\n"+ + "history: initial\r\n"+ + "history: start\r\n"+ + "history: onTimeout\r\n"+ + "history: error\r\n"+ + "history: onError\r\n"+ + "history: dispatch\r\n"+ + "history: ASYNC /ctx/path/info\r\n"+ + "history: !initial\r\n"+ + "history: onComplete\r\n",response); + + assertContains("DISPATCHED",response); + } + + @Test + public void testStartOnTimeoutComplete() throws Exception + { + String response=process("start=200&timeout=complete",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); + assertContains( + "history: REQUEST /ctx/path/info\r\n"+ + "history: initial\r\n"+ + "history: start\r\n"+ "history: onTimeout\r\n"+ "history: complete\r\n"+ "history: onComplete\r\n",response); @@ -191,15 +251,15 @@ public class AsyncServletTest } @Test - public void testSuspendWaitResume() throws Exception + public void testStartWaitDispatch() throws Exception { - String response=process("suspend=200&resume=10",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=200&dispatch=10",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); @@ -207,15 +267,15 @@ public class AsyncServletTest } @Test - public void testSuspendResume() throws Exception + public void testStartDispatch() throws Exception { - String response=process("suspend=200&resume=0",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=200&dispatch=0",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); @@ -223,14 +283,32 @@ public class AsyncServletTest } @Test - public void testSuspendWaitComplete() throws Exception + public void testStartError() throws Exception { - String response=process("suspend=200&complete=50",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + _expectedCode="500 "; + String response=process("start=200&throw=1",null); + assertThat(response,startsWith("HTTP/1.1 500 Server Error\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ + /* TODO should there be an onError call? + "history: onError\r\n"+ + "history: onComplete\r\n" + */"", + response); + assertContains("HTTP ERROR: 500",response); + } + + @Test + public void testStartWaitComplete() throws Exception + { + String response=process("start=200&complete=50",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); + assertContains( + "history: REQUEST /ctx/path/info\r\n"+ + "history: initial\r\n"+ + "history: start\r\n"+ "history: complete\r\n"+ "history: onComplete\r\n",response); assertContains("COMPLETED",response); @@ -239,14 +317,14 @@ public class AsyncServletTest } @Test - public void testSuspendComplete() throws Exception + public void testStartComplete() throws Exception { - String response=process("suspend=200&complete=0",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=200&complete=0",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ "history: complete\r\n"+ "history: onComplete\r\n",response); assertContains("COMPLETED",response); @@ -255,19 +333,20 @@ public class AsyncServletTest } @Test - public void testSuspendWaitResumeSuspendWaitResume() throws Exception + public void testStartWaitDispatchStartWaitDispatch() throws Exception { - String response=process("suspend=1000&resume=10&suspend2=1000&resume2=10",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=1000&dispatch=10&start2=1000&dispatch2=10",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: !initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: onStartAsync\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); @@ -275,58 +354,61 @@ public class AsyncServletTest } @Test - public void testSuspendWaitResumeSuspendComplete() throws Exception + public void testStartWaitDispatchStartComplete() throws Exception { - String response=process("suspend=1000&resume=10&suspend2=1000&complete2=10",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=1000&dispatch=10&start2=1000&complete2=10",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: !initial\r\n"+ - "history: suspend\r\n"+ + "history: onStartAsync\r\n"+ + "history: start\r\n"+ "history: complete\r\n"+ "history: onComplete\r\n",response); assertContains("COMPLETED",response); } @Test - public void testSuspendWaitResumeSuspend() throws Exception + public void testStartWaitDispatchStart() throws Exception { _expectedCode="500 "; - String response=process("suspend=1000&resume=10&suspend2=10",null); + String response=process("start=1000&dispatch=10&start2=10",null); assertEquals("HTTP/1.1 500 Async Timeout",response.substring(0,26)); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: !initial\r\n"+ - "history: suspend\r\n"+ + "history: onStartAsync\r\n"+ + "history: start\r\n"+ "history: onTimeout\r\n"+ "history: ERROR /ctx/path/info\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); - assertContains("ERROR: /ctx/path/info",response); + assertContains("ERROR DISPATCH: /ctx/path/info",response); } @Test - public void testSuspendTimeoutSuspendResume() throws Exception + public void testStartTimeoutStartDispatch() throws Exception { - String response=process("suspend=10&suspend2=1000&resume2=10",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=10&start2=1000&dispatch2=10",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ "history: onTimeout\r\n"+ "history: ERROR /ctx/path/info\r\n"+ "history: !initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: onStartAsync\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); @@ -334,53 +416,55 @@ public class AsyncServletTest } @Test - public void testSuspendTimeoutSuspendComplete() throws Exception + public void testStartTimeoutStartComplete() throws Exception { - String response=process("suspend=10&suspend2=1000&complete2=10",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=10&start2=1000&complete2=10",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ "history: onTimeout\r\n"+ "history: ERROR /ctx/path/info\r\n"+ "history: !initial\r\n"+ - "history: suspend\r\n"+ + "history: onStartAsync\r\n"+ + "history: start\r\n"+ "history: complete\r\n"+ "history: onComplete\r\n",response); assertContains("COMPLETED",response); } @Test - public void testSuspendTimeoutSuspend() throws Exception + public void testStartTimeoutStart() throws Exception { _expectedCode="500 "; - String response=process("suspend=10&suspend2=10",null); + String response=process("start=10&start2=10",null); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ "history: onTimeout\r\n"+ "history: ERROR /ctx/path/info\r\n"+ "history: !initial\r\n"+ - "history: suspend\r\n"+ + "history: onStartAsync\r\n"+ + "history: start\r\n"+ "history: onTimeout\r\n"+ "history: ERROR /ctx/path/info\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); - assertContains("ERROR: /ctx/path/info",response); + assertContains("ERROR DISPATCH: /ctx/path/info",response); } @Test public void testWrapStartDispatch() throws Exception { - String response=process("wrap=true&suspend=200&resume=20",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("wrap=true&start=200&dispatch=20",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: wrapped REQ RSP\r\n"+ "history: !initial\r\n"+ @@ -392,49 +476,49 @@ public class AsyncServletTest @Test public void testStartDispatchEncodedPath() throws Exception { - String response=process("suspend=200&resume=20&path=/p%20th3",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("start=200&dispatch=20&path=/p%20th3",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/p%20th3\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); assertContains("DISPATCHED",response); } - - + + @Test public void testFwdStartDispatch() throws Exception { - String response=process("fwd","suspend=200&resume=20",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("fwd","start=200&dispatch=20",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: FWD REQUEST /ctx/fwd/info\r\n"+ "history: FORWARD /ctx/path1\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: FWD ASYNC /ctx/fwd/info\r\n"+ "history: FORWARD /ctx/path1\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); assertContains("DISPATCHED",response); } - + @Test public void testFwdStartDispatchPath() throws Exception { - String response=process("fwd","suspend=200&resume=20&path=/path2",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("fwd","start=200&dispatch=20&path=/path2",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: FWD REQUEST /ctx/fwd/info\r\n"+ "history: FORWARD /ctx/path1\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path2\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); @@ -444,45 +528,45 @@ public class AsyncServletTest @Test public void testFwdWrapStartDispatch() throws Exception { - String response=process("fwd","wrap=true&suspend=200&resume=20",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("fwd","wrap=true&start=200&dispatch=20",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: FWD REQUEST /ctx/fwd/info\r\n"+ "history: FORWARD /ctx/path1\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path1\r\n"+ "history: wrapped REQ RSP\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); assertContains("DISPATCHED",response); } - + @Test public void testFwdWrapStartDispatchPath() throws Exception { - String response=process("fwd","wrap=true&suspend=200&resume=20&path=/path2",null); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + String response=process("fwd","wrap=true&start=200&dispatch=20&path=/path2",null); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: FWD REQUEST /ctx/fwd/info\r\n"+ "history: FORWARD /ctx/path1\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ - "history: resume\r\n"+ + "history: start\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path2\r\n"+ "history: wrapped REQ RSP\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); assertContains("DISPATCHED",response); } - - + + @Test public void testAsyncRead() throws Exception { _expectedLogs=2; - String header="GET /ctx/path/info?suspend=2000&resume=1500 HTTP/1.1\r\n"+ + String header="GET /ctx/path/info?start=2000&dispatch=1500 HTTP/1.1\r\n"+ "Host: localhost\r\n"+ "Content-Length: 10\r\n"+ "\r\n"; @@ -503,13 +587,13 @@ public class AsyncServletTest socket.getOutputStream().write(close.getBytes(StandardCharsets.ISO_8859_1)); String response = IO.toString(socket.getInputStream()); - assertEquals("HTTP/1.1 200 OK",response.substring(0,15)); + assertThat(response,startsWith("HTTP/1.1 200 OK\r\n")); assertContains( "history: REQUEST /ctx/path/info\r\n"+ "history: initial\r\n"+ - "history: suspend\r\n"+ + "history: start\r\n"+ "history: async-read=10\r\n"+ - "history: resume\r\n"+ + "history: dispatch\r\n"+ "history: ASYNC /ctx/path/info\r\n"+ "history: !initial\r\n"+ "history: onComplete\r\n",response); @@ -520,7 +604,7 @@ public class AsyncServletTest { return process("path",query,content); } - + public synchronized String process(String path,String query,String content) throws Exception { String request = "GET /ctx/"+path+"/info"; @@ -543,6 +627,7 @@ public class AsyncServletTest { socket.setSoTimeout(1000000); socket.getOutputStream().write(request.getBytes(StandardCharsets.UTF_8)); + socket.getOutputStream().flush(); return IO.toString(socket.getInputStream()); } catch(Exception e) @@ -574,7 +659,7 @@ public class AsyncServletTest request.getServletContext().getRequestDispatcher("/path1").forward(request,response); } } - + private static class AsyncServlet extends HttpServlet { private static final long serialVersionUID = -8161977157098646562L; @@ -593,36 +678,36 @@ public class AsyncServletTest { // ignored } - + // System.err.println(request.getDispatcherType()+" "+request.getRequestURI()); response.addHeader("history",request.getDispatcherType()+" "+request.getRequestURI()); if (request instanceof ServletRequestWrapper || response instanceof ServletResponseWrapper) response.addHeader("history","wrapped"+((request instanceof ServletRequestWrapper)?" REQ":"")+((response instanceof ServletResponseWrapper)?" RSP":"")); - + boolean wrap="true".equals(request.getParameter("wrap")); int read_before=0; long sleep_for=-1; - long suspend_for=-1; - long suspend2_for=-1; - long resume_after=-1; - long resume2_after=-1; + long start_for=-1; + long start2_for=-1; + long dispatch_after=-1; + long dispatch2_after=-1; long complete_after=-1; long complete2_after=-1; - + if (request.getParameter("read")!=null) read_before=Integer.parseInt(request.getParameter("read")); if (request.getParameter("sleep")!=null) sleep_for=Integer.parseInt(request.getParameter("sleep")); - if (request.getParameter("suspend")!=null) - suspend_for=Integer.parseInt(request.getParameter("suspend")); - if (request.getParameter("suspend2")!=null) - suspend2_for=Integer.parseInt(request.getParameter("suspend2")); - if (request.getParameter("resume")!=null) - resume_after=Integer.parseInt(request.getParameter("resume")); + if (request.getParameter("start")!=null) + start_for=Integer.parseInt(request.getParameter("start")); + if (request.getParameter("start2")!=null) + start2_for=Integer.parseInt(request.getParameter("start2")); + if (request.getParameter("dispatch")!=null) + dispatch_after=Integer.parseInt(request.getParameter("dispatch")); final String path=request.getParameter("path"); - if (request.getParameter("resume2")!=null) - resume2_after=Integer.parseInt(request.getParameter("resume2")); + if (request.getParameter("dispatch2")!=null) + dispatch2_after=Integer.parseInt(request.getParameter("dispatch2")); if (request.getParameter("complete")!=null) complete_after=Integer.parseInt(request.getParameter("complete")); if (request.getParameter("complete2")!=null) @@ -669,13 +754,16 @@ public class AsyncServletTest }.start(); } - if (suspend_for>=0) + if (start_for>=0) { final AsyncContext async=wrap?request.startAsync(new HttpServletRequestWrapper(request),new HttpServletResponseWrapper(response)):request.startAsync(); - if (suspend_for>0) - async.setTimeout(suspend_for); + if (start_for>0) + async.setTimeout(start_for); async.addListener(__listener); - response.addHeader("history","suspend"); + response.addHeader("history","start"); + + if ("1".equals(request.getParameter("throw"))) + throw new QuietServletException(new Exception("test throw in async 1")); if (complete_after>0) { @@ -709,15 +797,15 @@ public class AsyncServletTest response.addHeader("history","complete"); async.complete(); } - else if (resume_after>0) + else if (dispatch_after>0) { - TimerTask resume = new TimerTask() + TimerTask dispatch = new TimerTask() { @Override public void run() { - ((HttpServletResponse)async.getResponse()).addHeader("history","resume"); - if (path!=null) + ((HttpServletResponse)async.getResponse()).addHeader("history","dispatch"); + if (path!=null) { int q=path.indexOf('?'); String uriInContext=(q>=0) @@ -731,13 +819,13 @@ public class AsyncServletTest }; synchronized (_timer) { - _timer.schedule(resume,resume_after); + _timer.schedule(dispatch,dispatch_after); } } - else if (resume_after==0) + else if (dispatch_after==0) { - ((HttpServletResponse)async.getResponse()).addHeader("history","resume"); - if (path!=null) + ((HttpServletResponse)async.getResponse()).addHeader("history","dispatch"); + if (path!=null) async.dispatch(path); else async.dispatch(); @@ -767,18 +855,20 @@ public class AsyncServletTest { response.addHeader("history","!initial"); - if (suspend2_for>=0 && request.getAttribute("2nd")==null) + if (start2_for>=0 && request.getAttribute("2nd")==null) { final AsyncContext async=wrap?request.startAsync(new HttpServletRequestWrapper(request),new HttpServletResponseWrapper(response)):request.startAsync(); async.addListener(__listener); request.setAttribute("2nd","cycle"); - if (suspend2_for>0) + if (start2_for>0) { - async.setTimeout(suspend2_for); + async.setTimeout(start2_for); } - // continuation.addContinuationListener(__listener); - response.addHeader("history","suspend"); + response.addHeader("history","start"); + + if ("2".equals(request.getParameter("throw"))) + throw new QuietServletException(new Exception("test throw in async 2")); if (complete2_after>0) { @@ -812,23 +902,23 @@ public class AsyncServletTest response.addHeader("history","complete"); async.complete(); } - else if (resume2_after>0) + else if (dispatch2_after>0) { - TimerTask resume = new TimerTask() + TimerTask dispatch = new TimerTask() { @Override public void run() { - response.addHeader("history","resume"); + response.addHeader("history","dispatch"); async.dispatch(); } }; synchronized (_timer) { - _timer.schedule(resume,resume2_after); + _timer.schedule(dispatch,dispatch2_after); } } - else if (resume2_after==0) + else if (dispatch2_after==0) { response.addHeader("history","dispatch"); async.dispatch(); @@ -836,7 +926,7 @@ public class AsyncServletTest } else if(request.getDispatcherType()==DispatcherType.ERROR) { - response.getOutputStream().println("ERROR: "+request.getContextPath()+request.getServletPath()+request.getPathInfo()); + response.getOutputStream().println("ERROR DISPATCH: "+request.getContextPath()+request.getServletPath()+request.getPathInfo()); } else { @@ -858,12 +948,20 @@ public class AsyncServletTest if (action!=null) { ((HttpServletResponse)event.getSuppliedResponse()).addHeader("history",action); - if ("dispatch".equals(action)) - event.getAsyncContext().dispatch(); - if ("complete".equals(action)) + + switch(action) { - event.getSuppliedResponse().getOutputStream().println("COMPLETED\n"); - event.getAsyncContext().complete(); + case "dispatch": + event.getAsyncContext().dispatch(); + break; + + case "complete": + event.getSuppliedResponse().getOutputStream().println("COMPLETED\n"); + event.getAsyncContext().complete(); + break; + + case "error": + throw new RuntimeException("error in onTimeout"); } } } @@ -871,11 +969,30 @@ public class AsyncServletTest @Override public void onStartAsync(AsyncEvent event) throws IOException { + ((HttpServletResponse)event.getSuppliedResponse()).addHeader("history","onStartAsync"); } @Override public void onError(AsyncEvent event) throws IOException { + ((HttpServletResponse)event.getSuppliedResponse()).addHeader("history","onError"); + String action=event.getSuppliedRequest().getParameter("error"); + if (action!=null) + { + ((HttpServletResponse)event.getSuppliedResponse()).addHeader("history",action); + + switch(action) + { + case "dispatch": + event.getAsyncContext().dispatch(); + break; + + case "complete": + event.getSuppliedResponse().getOutputStream().println("COMPLETED\n"); + event.getAsyncContext().complete(); + break; + } + } } @Override @@ -889,7 +1006,7 @@ public class AsyncServletTest { @Override public void log(Request request, Response response) - { + { int status = response.getCommittedMetaData().getStatus(); long written = response.getHttpChannel().getBytesWritten(); _log.add(status+" "+written+" "+request.getRequestURI());