diff --git a/VERSION.txt b/VERSION.txt index 023522ec1ee..77804bed2c5 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1,5 +1,16 @@ jetty-10.0.0-SNAPSHOT +jetty-9.4.0.v20161208 - 08 December 2016 + + 1112 How config async support in jsp tag? + + 1124 Allow configuration of WebSocket mappings from Spring + + 1139 Support configuration of properties during --add-to-start + + 1146 jetty.server.HttpInput deadlock + + 1148 Support HTTP/2 HEADERS trailer + + 1151 NPE in ClasspathPattern.match() + + 1153 Make SessionData easier to subclass + + 123 AbstractSessionIdManager can't atomically check for uniqueness of new + session ID + jetty-9.4.0.RC3 - 05 December 2016 + 1051 NCSARequestLog/RolloverFileOutputStream does not roll day after DST ends @@ -405,7 +416,7 @@ jetty-9.3.12.v20160915 - 15 September 2016 + 832 ServerWithJNDI example uses wrong webapp + 841 support reset in buffering interceptors + 844 Implement a Thread Limit Handler - + 845 Improve blocking IO for data rate limiting. + + 845 Improve blocking IO for data rate limiting + 851 MBeanContainer no longer unregisters MBeans when "stopped" + 854 If container.destroy() is called, calling container.start() again should throw an IllegalStateException diff --git a/examples/pom.xml b/examples/pom.xml index f8dc30458b1..915259f49b9 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -25,7 +25,7 @@ async-rest diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 0ce07dc44aa..f8c14c82570 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -240,7 +240,6 @@ public class HttpClient extends ContainerLifeCycle @Override protected void doStop() throws Exception { - cookieStore.removeAll(); decoderFactories.clear(); handlers.clear(); diff --git a/jetty-http-spi/pom.xml b/jetty-http-spi/pom.xml index 74541269b0d..ca982958446 100644 --- a/jetty-http-spi/pom.xml +++ b/jetty-http-spi/pom.xml @@ -29,23 +29,23 @@ ${project.version} provided - - org.powermock - powermock-module-junit4 - 1.6.2 - test - - - org.powermock - powermock-api-mockito - 1.6.2 - test - - - com.openpojo - openpojo - 0.8.1 - test + + org.powermock + powermock-module-junit4 + 1.6.2 + test + + + org.powermock + powermock-api-mockito + 1.6.2 + test + + + com.openpojo + openpojo + 0.8.1 + test diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java index a5ac4a159a1..deab7f4bb69 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java @@ -23,12 +23,17 @@ import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.AsyncContext; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -451,4 +456,139 @@ public class StreamResetTest extends AbstractTest Assert.assertThat(((ISession)client).updateSendWindow(0), Matchers.greaterThan(0)); } } + + @Test + public void testResetAfterAsyncRequestBlockingWriteStalledByFlowControl() throws Exception + { + int windowSize = FlowControlStrategy.DEFAULT_WINDOW_SIZE; + CountDownLatch writeLatch = new CountDownLatch(1); + start(new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + AsyncContext asyncContext = request.startAsync(); + asyncContext.start(() -> + { + try + { + // Make sure we are in async wait before writing. + Thread.sleep(1000); + response.getOutputStream().write(new byte[10 * windowSize]); + asyncContext.complete(); + } + catch (IOException x) + { + writeLatch.countDown(); + } + catch (Throwable x) + { + x.printStackTrace(); + } + }); + } + }); + + Deque dataQueue = new ArrayDeque<>(); + AtomicLong received = new AtomicLong(); + CountDownLatch latch = new CountDownLatch(1); + Session client = newClient(new Session.Listener.Adapter()); + MetaData.Request request = newRequest("GET", new HttpFields()); + HeadersFrame frame = new HeadersFrame(request, null, true); + FuturePromise promise = new FuturePromise<>(); + client.newStream(frame, promise, new Stream.Listener.Adapter() + { + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + dataQueue.offer(frame); + dataQueue.offer(callback); + // Do not consume the data yet. + if (received.addAndGet(frame.getData().remaining()) == windowSize) + latch.countDown(); + } + }); + Stream stream = promise.get(5, TimeUnit.SECONDS); + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + + // Reset and consume. + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + dataQueue.stream() + .filter(item -> item instanceof Callback) + .map(item -> (Callback)item) + .forEach(Callback::succeeded); + + Assert.assertTrue(writeLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testResetAfterAsyncRequestAsyncWriteStalledByFlowControl() throws Exception + { + int windowSize = FlowControlStrategy.DEFAULT_WINDOW_SIZE; + CountDownLatch writeLatch = new CountDownLatch(1); + start(new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + AsyncContext asyncContext = request.startAsync(); + ServletOutputStream output = response.getOutputStream(); + output.setWriteListener(new WriteListener() + { + private boolean written; + + @Override + public void onWritePossible() throws IOException + { + while (output.isReady()) + { + if (written) + { + asyncContext.complete(); + break; + } + else + { + output.write(new byte[10 * windowSize]); + written = true; + } + } + } + + @Override + public void onError(Throwable t) + { + writeLatch.countDown(); + } + }); + } + }); + + Deque dataQueue = new ArrayDeque<>(); + AtomicLong received = new AtomicLong(); + CountDownLatch latch = new CountDownLatch(1); + Session client = newClient(new Session.Listener.Adapter()); + MetaData.Request request = newRequest("GET", new HttpFields()); + HeadersFrame frame = new HeadersFrame(request, null, true); + FuturePromise promise = new FuturePromise<>(); + client.newStream(frame, promise, new Stream.Listener.Adapter() + { + @Override + public void onData(Stream stream, DataFrame frame, Callback callback) + { + dataQueue.offer(callback); + // Do not consume the data yet. + if (received.addAndGet(frame.getData().remaining()) == windowSize) + latch.countDown(); + } + }); + Stream stream = promise.get(5, TimeUnit.SECONDS); + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + + // Reset and consume. + stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP); + dataQueue.forEach(Callback::succeeded); + + Assert.assertTrue(writeLatch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java index 3000d6e1809..1a3b0abd6ed 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java @@ -86,7 +86,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback @Override public void headers(HeadersFrame frame, Callback callback) { - if (!checkWrite(callback)) + if (!startWrite(callback)) return; session.frames(this, this, frame, Frame.EMPTY_ARRAY); } @@ -100,7 +100,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback @Override public void data(DataFrame frame, Callback callback) { - if (!checkWrite(callback)) + if (!startWrite(callback)) return; session.data(this, this, frame); } @@ -114,7 +114,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback session.frames(this, callback, frame, Frame.EMPTY_ARRAY); } - private boolean checkWrite(Callback callback) + private boolean startWrite(Callback callback) { if (writing.compareAndSet(null, callback)) return true; @@ -381,15 +381,22 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback @Override public void succeeded() { - Callback callback = writing.getAndSet(null); - callback.succeeded(); + Callback callback = endWrite(); + if (callback != null) + callback.succeeded(); } @Override public void failed(Throwable x) { - Callback callback = writing.getAndSet(null); - callback.failed(x); + Callback callback = endWrite(); + if (callback != null) + callback.failed(x); + } + + private Callback endWrite() + { + return writing.getAndSet(null); } private void notifyData(Stream stream, DataFrame frame, Callback callback) diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java index 180fcd9ce3e..42568cf10a7 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackContext.java @@ -378,7 +378,7 @@ public class HpackContext _offset = (_offset+1)%_entries.length; _size--; if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] evict %s",hashCode(),entry)); + LOG.debug(String.format("HdrTbl[%x] evict %s",HpackContext.this.hashCode(),entry)); _dynamicTableSizeInBytes-=entry.getSize(); entry._slot=-1; _fieldMap.remove(entry.getHttpField()); @@ -388,7 +388,7 @@ public class HpackContext } if (LOG.isDebugEnabled()) - LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d",hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes)); + LOG.debug(String.format("HdrTbl[%x] entries=%d, size=%d, max=%d",HpackContext.this.hashCode(),_dynamicTable.size(),_dynamicTableSizeInBytes,_maxDynamicTableSizeInBytes)); } } diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index 1ceaad98095..c2597fe7d5e 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -296,6 +296,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel public void onFailure(Throwable failure) { + getHttpTransport().onStreamFailure(failure); if (onEarlyEOF()) handle(); else diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java index b5c7d5afc65..8b8256804e7 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java @@ -197,6 +197,11 @@ public class HttpTransportOverHTTP2 implements HttpTransport stream.data(frame, callback); } + public void onStreamFailure(Throwable failure) + { + transportCallback.failed(failure); + } + public boolean onStreamTimeout(Throwable failure) { return transportCallback.onIdleTimeout(failure); @@ -264,9 +269,10 @@ public class HttpTransportOverHTTP2 implements HttpTransport synchronized (this) { commit = this.commit; - if (state != State.TIMEOUT) + if (state == State.WRITING) { callback = this.callback; + this.callback = null; this.state = State.IDLE; } } @@ -284,9 +290,10 @@ public class HttpTransportOverHTTP2 implements HttpTransport synchronized (this) { commit = this.commit; - if (state != State.TIMEOUT) + if (state == State.WRITING) { callback = this.callback; + this.callback = null; this.state = State.FAILED; } } @@ -317,6 +324,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport if (result) { callback = this.callback; + this.callback = null; this.state = State.TIMEOUT; } } diff --git a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml index 2e9a436c3c4..27d17622858 100644 --- a/jetty-osgi/jetty-osgi-boot-warurl/pom.xml +++ b/jetty-osgi/jetty-osgi-boot-warurl/pom.xml @@ -19,8 +19,8 @@ jetty-util - org.eclipse.osgi - org.eclipse.osgi + org.eclipse.osgi + org.eclipse.osgi diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index c445539317a..74ccdcd4db5 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -95,12 +95,12 @@ org.eclipse.jetty.annotations;resolution:=optional, * - - osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)" - - - osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.webapp.Configuration - + + osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)" + + + osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.webapp.Configuration + <_nouses>true diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml index a5166513163..153d815ee8c 100644 --- a/jetty-osgi/test-jetty-osgi-context/pom.xml +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -19,37 +19,37 @@ ${project.version} - org.eclipse.osgi - org.eclipse.osgi - provided + org.eclipse.osgi + org.eclipse.osgi + provided - org.eclipse.osgi - org.eclipse.osgi.services - provided + org.eclipse.osgi + org.eclipse.osgi.services + provided - org.eclipse.jetty.toolchain - jetty-schemas + org.eclipse.jetty.toolchain + jetty-schemas - + src/main/resources - - src/main/context - + + src/main/context + - + org.apache.maven.plugins maven-deploy-plugin - + true @@ -65,7 +65,7 @@ J2SE-1.5 + compilation time. --> <_nouses>true javax.servlet;version="[3.1,4.1)", @@ -76,7 +76,7 @@ org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.slf4j;resolution:=optional, - org.slf4j.spi;resolution:=optional, + org.slf4j.spi;resolution:=optional, org.slf4j.helpers;resolution:=optional, org.xml.sax, org.xml.sax.helpers, @@ -88,5 +88,5 @@ - + diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml index 67b4ff96d30..8bfbd31ee07 100644 --- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml @@ -19,46 +19,47 @@ jetty-webapp - org.eclipse.osgi - org.eclipse.osgi - provided + org.eclipse.osgi + org.eclipse.osgi + provided - org.eclipse.osgi - org.eclipse.osgi.services - provided + org.eclipse.osgi + org.eclipse.osgi.services + provided - - - - src/main/resources - - - - - org.apache.maven.plugins - maven-deploy-plugin - - - true - - - - org.apache.felix - maven-bundle-plugin - true - - - org.eclipse.jetty.osgi.testapp;singleton:=true - Jetty OSGi Test WebApp - com.acme.osgi.Activator - J2SE-1.5 - - + + + + src/main/resources + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + + true + + + + org.apache.felix + maven-bundle-plugin + true + + + org.eclipse.jetty.osgi.testapp;singleton:=true + Jetty OSGi Test WebApp + com.acme.osgi.Activator + J2SE-1.5 + + org.osgi.framework, org.osgi.service.cm;version="1.2.0", org.osgi.service.packageadmin, @@ -66,17 +67,17 @@ org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.slf4j;resolution:=optional, - org.slf4j.spi;resolution:=optional, + org.slf4j.spi;resolution:=optional, org.slf4j.helpers;resolution:=optional, org.xml.sax, org.xml.sax.helpers, * - - com.acme.osgi - org.eclipse.jetty.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))" - - - - - + + com.acme.osgi + org.eclipse.jetty.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))" + + + + + diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 0f60a54eb1a..9d705d1c137 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -40,7 +40,7 @@ ${exam.version} test - + org.ops4j.pax.exam pax-exam-junit4 @@ -65,13 +65,11 @@ ${url.version} test - - - org.ops4j.pax.tinybundles - tinybundles - 2.1.1 - - + + org.ops4j.pax.tinybundles + tinybundles + 2.1.1 + - - javax.servlet - javax.servlet-api - - + + javax.servlet + javax.servlet-api + org.apache.geronimo.specs @@ -266,7 +263,7 @@ org.eclipse.jetty jetty-util - ${project.version} + ${project.version} runtime diff --git a/jetty-plus/pom.xml b/jetty-plus/pom.xml index 083a7036aa1..05dd0d404f4 100644 --- a/jetty-plus/pom.xml +++ b/jetty-plus/pom.xml @@ -13,29 +13,28 @@ ${project.groupId}.plus - - - org.apache.felix - maven-bundle-plugin - true - - - - osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)" - - - osgi.serviceloader; - osgi.serviceloader=org.eclipse.jetty.webapp.Configuration - - - - - + + + org.apache.felix + maven-bundle-plugin + true + + + + osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)" + + + osgi.serviceloader; + osgi.serviceloader=org.eclipse.jetty.webapp.Configuration + + + + - org.apache.maven.plugins - maven-source-plugin + org.apache.maven.plugins + maven-source-plugin org.codehaus.mojo 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 daf1d28888c..782f15b2ac2 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 @@ -345,7 +345,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor { if (_response.isCommitted()) { - LOG.warn("Error Dispatch already committed"); + if (LOG.isDebugEnabled()) + LOG.debug("Could not perform Error Dispatch because the response is already committed, aborting"); _transport.abort((Throwable)_request.getAttribute(ERROR_EXCEPTION)); } else @@ -359,7 +360,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor _request.setAttribute(ERROR_STATUS_CODE,code); _request.setHandled(false); _response.getHttpOutput().reopen(); - + try { _request.setDispatcherType(DispatcherType.ERROR); @@ -421,7 +422,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor } } catch (Throwable failure) - { + { if ("org.eclipse.jetty.continuation.ContinuationThrowable".equals(failure.getClass().getName())) LOG.ignore(failure); else @@ -554,13 +555,13 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor _oldIdleTimeout=getIdleTimeout(); if (idleTO>=0 && _oldIdleTimeout!=idleTO) setIdleTimeout(idleTO); - + _request.setMetaData(request); if (LOG.isDebugEnabled()) LOG.debug("REQUEST for {} on {}{}{} {} {}{}{}",request.getURIString(),this,System.lineSeparator(), - request.getMethod(),request.getURIString(),request.getHttpVersion(),System.lineSeparator(), - request.getFields()); + request.getMethod(),request.getURIString(),request.getHttpVersion(),System.lineSeparator(), + request.getFields()); } public boolean onContent(HttpInput.Content content) @@ -582,14 +583,14 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor { if (LOG.isDebugEnabled()) LOG.debug("COMPLETE for {} written={}",getRequest().getRequestURI(),getBytesWritten()); - + if (_requestLog!=null ) _requestLog.log(_request, _response); long idleTO=_configuration.getIdleTimeout(); if (idleTO>=0 && getIdleTimeout()!=_oldIdleTimeout) setIdleTimeout(_oldIdleTimeout); - + _transport.onCompleted(); } @@ -606,7 +607,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor Action action; try { - action=_state.handling(); + action=_state.handling(); } catch(IllegalStateException e) { @@ -651,12 +652,12 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor if (LOG.isDebugEnabled()) LOG.debug("sendResponse info={} content={} complete={} committing={} callback={}", - info, - BufferUtil.toDetailString(content), - complete, - committing, - callback); - + info, + BufferUtil.toDetailString(content), + complete, + committing, + callback); + if (committing) { // We need an info to commit @@ -705,8 +706,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor _committedMetaData=info; if (LOG.isDebugEnabled()) LOG.debug("COMMIT for {} on {}{}{} {} {}{}{}",getRequest().getRequestURI(),this,System.lineSeparator(), - info.getStatus(),info.getReason(),info.getHttpVersion(),System.lineSeparator(), - info.getFields()); + info.getStatus(),info.getReason(),info.getHttpVersion(),System.lineSeparator(), + info.getFields()); } public boolean isCommitted() @@ -727,8 +728,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor _written+=BufferUtil.length(content); sendResponse(null,content,complete,callback); } - - @Override + + @Override public void resetBuffer() { if(isCommitted()) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java index 43a43e3cfa5..a1016211e53 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SessionIdManager.java @@ -106,8 +106,9 @@ public interface SessionIdManager extends LifeCycle * @param oldId the old plain session id * @param oldExtendedId the old fully qualified id * @param request the request containing the session + * @return the new session id */ - public void renewSessionId(String oldId, String oldExtendedId, HttpServletRequest request); + public String renewSessionId(String oldId, String oldExtendedId, HttpServletRequest request); /* ------------------------------------------------------------ */ /** diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java index ef63c232451..96fb1fe6f52 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java @@ -483,7 +483,7 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi * @see org.eclipse.jetty.server.SessionIdManager#renewSessionId(java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest) */ @Override - public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request) + public String renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request) { //generate a new id String newClusterId = newSessionId(request.hashCode()); @@ -495,6 +495,8 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi { manager.renewSessionId(oldClusterId, oldNodeId, newClusterId, getExtendedId(newClusterId, request)); } + + return newClusterId; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java index 83e12a2d51b..ea6e021c408 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java @@ -810,7 +810,13 @@ public class Session implements SessionHandler.SessionIf extendedId = getExtendedId(); } - _handler._sessionIdManager.renewSessionId(id, extendedId, request); + String newId = _handler._sessionIdManager.renewSessionId(id, extendedId, request); + try (Lock lock = _lock.lockIfNotHeld()) + { + checkValidForWrite(); + _sessionData.setId(newId); + setExtendedId(_handler._sessionIdManager.getExtendedId(newId, request)); + } setIdChanged(true); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java index 8bb74fbad98..faa6e471f39 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java @@ -148,9 +148,9 @@ public class SessionCookieTest } @Override - public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request) + public String renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request) { - // TODO Auto-generated method stub + return ""; } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index 11f65687b45..a098b55a39a 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -109,7 +109,7 @@ public class Main } private BaseHome baseHome; - private StartArgs startupArgs; + private StartArgs jsvcStartArgs; public Main() throws IOException { @@ -486,7 +486,7 @@ public class Main catch (Throwable e) { e.printStackTrace(); - usageExit(e,ERR_INVOKE_MAIN,startupArgs.isTestingModeEnabled()); + usageExit(e,ERR_INVOKE_MAIN,args.isTestingModeEnabled()); } } @@ -573,11 +573,11 @@ public class Main } catch (ConnectException e) { - usageExit(e,ERR_NOT_STOPPED,startupArgs.isTestingModeEnabled()); + usageExit(e,ERR_NOT_STOPPED,jsvcStartArgs.isTestingModeEnabled()); } catch (Exception e) { - usageExit(e,ERR_UNKNOWN,startupArgs.isTestingModeEnabled()); + usageExit(e,ERR_UNKNOWN,jsvcStartArgs.isTestingModeEnabled()); } } @@ -630,29 +630,35 @@ public class Main { try { - startupArgs = processCommandLine(args); + jsvcStartArgs = processCommandLine(args); } catch (UsageException e) { StartLog.error(e.getMessage()); - usageExit(e.getCause(),e.getExitCode(),startupArgs.isTestingModeEnabled()); + usageExit(e.getCause(),e.getExitCode(),false); } catch (Throwable e) { - usageExit(e,UsageException.ERR_UNKNOWN,startupArgs.isTestingModeEnabled()); + usageExit(e,UsageException.ERR_UNKNOWN,false); } } + // ------------------------------------------------------------ + // implement Apache commons daemon (jsvc) lifecycle methods (init, start, stop, destroy) public void start() throws Exception { - start(startupArgs); + start(jsvcStartArgs); } + // ------------------------------------------------------------ + // implement Apache commons daemon (jsvc) lifecycle methods (init, start, stop, destroy) public void stop() throws Exception { - doStop(startupArgs); + doStop(jsvcStartArgs); } + // ------------------------------------------------------------ + // implement Apache commons daemon (jsvc) lifecycle methods (init, start, stop, destroy) public void destroy() { } diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index 0c4de30e863..45329feeda7 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -73,7 +73,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont private final SessionFactory sessionFactory; private final DecoratedObjectFactory objectFactory; private Masker masker; - + private final int id = ThreadLocalRandom.current().nextInt(); /** @@ -229,14 +229,14 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont this.httpClient.setExecutor(executor); this.httpClient.setByteBufferPool(bufferPool); addBean(this.httpClient); - + if (objectFactory == null) this.objectFactory = new DecoratedObjectFactory(); else this.objectFactory = objectFactory; - + this.extensionRegistry = new WebSocketExtensionFactory(this); - + this.masker = new RandomMasker(); this.eventDriverFactory = new EventDriverFactory(policy); this.sessionFactory = new WebSocketSessionFactory(this); @@ -263,7 +263,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont this.httpClient = new HttpClient(sslContextFactory); this.httpClient.setExecutor(scope.getExecutor()); addBean(this.httpClient); - + this.objectFactory = new DecoratedObjectFactory(); this.extensionRegistry = new WebSocketExtensionFactory(this); @@ -298,7 +298,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont { return connect(websocket,toUri,request,(UpgradeListener)null); } - + /** * Connect to remote websocket endpoint * @@ -357,9 +357,9 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont LOG.debug("connect websocket {} to {}",websocket,toUri); init(); - + WebSocketUpgradeRequest wsReq = new WebSocketUpgradeRequest(this,httpClient,request); - + wsReq.setUpgradeListener(upgradeListener); return wsReq.sendAsync(); } @@ -370,14 +370,14 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont if (LOG.isDebugEnabled()) LOG.debug("Stopping {}",this); - + if (ShutdownThread.isRegistered(this)) { ShutdownThread.deregister(this); } super.doStop(); - + if (LOG.isDebugEnabled()) LOG.debug("Stopped {}",this); } @@ -390,7 +390,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont /** * Return the number of milliseconds for a timeout of an attempted write operation. - * + * * @return number of milliseconds for timeout of an attempted write operation */ public long getAsyncWriteTimeout() @@ -446,7 +446,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont /** * Get the maximum size for buffering of a binary message. - * + * * @return the maximum size of a binary message buffer. */ public int getMaxBinaryMessageBufferSize() @@ -456,7 +456,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont /** * Get the maximum size for a binary message. - * + * * @return the maximum size of a binary message. */ public long getMaxBinaryMessageSize() @@ -466,7 +466,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont /** * Get the max idle timeout for new connections. - * + * * @return the max idle timeout in milliseconds for new connections. */ public long getMaxIdleTimeout() @@ -476,7 +476,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont /** * Get the maximum size for buffering of a text message. - * + * * @return the maximum size of a text message buffer. */ public int getMaxTextMessageBufferSize() @@ -486,7 +486,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont /** * Get the maximum size for a text message. - * + * * @return the maximum size of a text message. */ public long getMaxTextMessageSize() @@ -539,7 +539,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont /** * Factory method for new ConnectionManager - * + * * @return the ConnectionManager instance to use * @deprecated use HttpClient instead */ @@ -594,7 +594,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont /** * Set the timeout for connecting to the remote server. - * + * * @param ms * the timeout in milliseconds */ @@ -618,7 +618,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont { this.httpClient.setDispatchIO(dispatchIO); } - + public void setExecutor(Executor executor) { this.httpClient.setExecutor(executor); @@ -638,7 +638,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont * Set the max idle timeout for new connections. *

* Existing connections will not have their max idle timeout adjusted. - * + * * @param ms * the timeout in milliseconds */ @@ -664,7 +664,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont { return this.httpClient; } - + @Override public String toString() { diff --git a/tests/test-continuation/pom.xml b/tests/test-continuation/pom.xml index c0c0ef46698..d87e27c75c4 100644 --- a/tests/test-continuation/pom.xml +++ b/tests/test-continuation/pom.xml @@ -28,11 +28,11 @@ - org.eclipse.jetty + org.eclipse.jetty jetty-servlet ${project.version} provided - + org.eclipse.jetty jetty-continuation @@ -43,6 +43,5 @@ jetty-test-helper compile - diff --git a/tests/test-quickstart/pom.xml b/tests/test-quickstart/pom.xml index 596a65f79da..8a0fe6907ec 100644 --- a/tests/test-quickstart/pom.xml +++ b/tests/test-quickstart/pom.xml @@ -103,7 +103,7 @@ org.eclipse.jetty.toolchain jetty-test-helper - + diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/NullCacheRenewSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/NullCacheRenewSessionTest.java new file mode 100644 index 00000000000..d10ff742b5e --- /dev/null +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/NullCacheRenewSessionTest.java @@ -0,0 +1,284 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.webapp.WebAppContext; +import org.junit.Test; + + +/** + * NullCacheRenewSessionTest + * + * Test that changes the session id during a request + * on a SessionHandler that does not use session + * caching. + */ +public class NullCacheRenewSessionTest +{ + /** + * MemorySessionDataStore + * + * Make a fake session data store that creates a new SessionData object + * every time load(id) is called. + */ + public static class MemorySessionDataStore extends AbstractSessionDataStore + { + public Map _map = new HashMap<>(); + + + /** + * @see org.eclipse.jetty.server.session.SessionDataStore#isPassivating() + */ + @Override + public boolean isPassivating() + { + return false; + } + + /** + * @see org.eclipse.jetty.server.session.SessionDataStore#exists(java.lang.String) + */ + @Override + public boolean exists(String id) throws Exception + { + return _map.containsKey(id); + } + + /** + * @see org.eclipse.jetty.server.session.SessionDataMap#load(java.lang.String) + */ + @Override + public SessionData load(String id) throws Exception + { + SessionData sd = _map.get(id); + if (sd == null) + return null; + SessionData nsd = new SessionData(id,"","",System.currentTimeMillis(),System.currentTimeMillis(), System.currentTimeMillis(),0 ); + nsd.copy(sd); + return nsd; + } + + /** + * @see org.eclipse.jetty.server.session.SessionDataMap#delete(java.lang.String) + */ + @Override + public boolean delete(String id) throws Exception + { + return (_map.remove(id) != null); + } + + /** + * @see org.eclipse.jetty.server.session.AbstractSessionDataStore#doStore(java.lang.String, org.eclipse.jetty.server.session.SessionData, long) + */ + @Override + public void doStore(String id, SessionData data, long lastSaveTime) throws Exception + { + _map.put(id, data); + } + + /** + * @see org.eclipse.jetty.server.session.AbstractSessionDataStore#doGetExpired(java.util.Set) + */ + @Override + public Set doGetExpired(Set candidates) + { + return Collections.emptySet(); + } + + } + + public static class NullCacheServer extends AbstractTestServer + { + + /** + * @param port + * @param maxInactivePeriod + * @param scavengePeriod + * @param evictionPolicy + * @throws Exception + */ + public NullCacheServer(int port, int maxInactivePeriod, int scavengePeriod, int evictionPolicy) throws Exception + { + super(port, maxInactivePeriod, scavengePeriod, evictionPolicy); + } + + /** + * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionHandler() + */ + @Override + public SessionHandler newSessionHandler() + { + SessionHandler handler = new TestSessionHandler(); + SessionCache ss = new NullSessionCache(handler); + handler.setSessionCache(ss); + ss.setSessionDataStore(new MemorySessionDataStore()); + return handler; + } + + } + + + + @Test + /** + * @throws Exception + */ + public void testSessionRenewal() throws Exception + { + String contextPath = ""; + String servletMapping = "/server"; + int maxInactive = 1; + int scavengePeriod = 3; + AbstractTestServer server = new NullCacheServer (0, maxInactive, scavengePeriod, SessionCache.NEVER_EVICT); + + WebAppContext context = server.addWebAppContext(".", contextPath); + context.setParentLoaderPriority(true); + context.addServlet(TestServlet.class, servletMapping); + TestHttpSessionIdListener testListener = new TestHttpSessionIdListener(); + context.addEventListener(testListener); + + + + HttpClient client = new HttpClient(); + try + { + server.start(); + int port=server.getPort(); + + client.start(); + + //make a request to create a session + ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create"); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + + String sessionCookie = response.getHeaders().get("Set-Cookie"); + assertTrue(sessionCookie != null); + assertFalse(testListener.isCalled()); + + //make a request to change the sessionid + Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=renew"); + request.header("Cookie", sessionCookie); + ContentResponse renewResponse = request.send(); + + assertEquals(HttpServletResponse.SC_OK,renewResponse.getStatus()); + String renewSessionCookie = renewResponse.getHeaders().get("Set-Cookie"); + assertNotNull(renewSessionCookie); + assertNotSame(sessionCookie, renewSessionCookie); + assertTrue(testListener.isCalled()); + } + finally + { + client.stop(); + server.stop(); + } + } + + + + public static class TestHttpSessionIdListener implements HttpSessionIdListener + { + boolean called = false; + + @Override + public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) + { + assertNotNull(event.getSession()); + assertNotSame(oldSessionId, event.getSession().getId()); + called = true; + } + + public boolean isCalled() + { + return called; + } + } + + + public static class TestServlet extends HttpServlet + { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + String action = request.getParameter("action"); + if ("create".equals(action)) + { + HttpSession session = request.getSession(true); + assertTrue(session.isNew()); + } + else if ("renew".equals(action)) + { + HttpSession beforeSession = request.getSession(false); + assertTrue(beforeSession != null); + String beforeSessionId = beforeSession.getId(); + + ((Session)beforeSession).renewId(request); + + HttpSession afterSession = request.getSession(false); + + assertTrue(afterSession != null); + String afterSessionId = afterSession.getId(); + + assertTrue(beforeSession==afterSession); //same object + assertFalse(beforeSessionId.equals(afterSessionId)); //different id + + SessionHandler sessionManager = ((Session)afterSession).getSessionHandler(); + DefaultSessionIdManager sessionIdManager = (DefaultSessionIdManager)sessionManager.getSessionIdManager(); + + assertTrue(sessionIdManager.isIdInUse(afterSessionId)); //new session id should be in use + assertFalse(sessionIdManager.isIdInUse(beforeSessionId)); + + HttpSession session = sessionManager.getSession(afterSessionId); + assertNotNull(session); + session = sessionManager.getSession(beforeSessionId); + assertNull(session); + + if (((Session)afterSession).isIdChanged()) + { + ((org.eclipse.jetty.server.Response)response).addCookie(sessionManager.getSessionCookie(afterSession, request.getContextPath(), request.isSecure())); + } + } + } + } + +}