409842 - Suspended request completed by a request thread does not set read interest.
HttpConnection.completed() now performs the right check about the current connection.
This commit is contained in:
parent
bacff75b31
commit
e65e4e168d
|
@ -21,10 +21,8 @@ package org.eclipse.jetty.server;
|
|||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
|
||||
|
@ -342,7 +340,7 @@ public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
|
|||
_transport.completed();
|
||||
}
|
||||
|
||||
LOG.debug("{} handle exit", this);
|
||||
LOG.debug("{} handle exit, result {}", this, next);
|
||||
|
||||
return next!=Next.WAIT;
|
||||
}
|
||||
|
|
|
@ -19,11 +19,9 @@
|
|||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.http.HttpGenerator;
|
||||
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
|
||||
|
@ -379,7 +377,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
reset();
|
||||
|
||||
// if we are not called from the onfillable thread, schedule completion
|
||||
if (getCurrentConnection()==null)
|
||||
if (getCurrentConnection()!=this)
|
||||
{
|
||||
if (_parser.isStart())
|
||||
{
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 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.servlet;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||
import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
|
||||
import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AsyncServletLongPollTest
|
||||
{
|
||||
@Rule
|
||||
public TestTracker tracker = new TestTracker();
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
private ServletContextHandler context;
|
||||
private String uri;
|
||||
|
||||
protected void prepare(HttpServlet servlet) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new ServerConnector(server);
|
||||
server.addConnector(connector);
|
||||
String contextPath = "/context";
|
||||
context = new ServletContextHandler(server, contextPath, ServletContextHandler.NO_SESSIONS);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
String servletPath = "/path";
|
||||
context.addServlet(servletHolder, servletPath);
|
||||
uri = contextPath + servletPath;
|
||||
server.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void destroy() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuspendedRequestCompletedByAnotherRequest() throws Exception
|
||||
{
|
||||
final CountDownLatch asyncLatch = new CountDownLatch(1);
|
||||
prepare(new HttpServlet()
|
||||
{
|
||||
private volatile AsyncContext asyncContext;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
int suspend = 0;
|
||||
String param = request.getParameter("suspend");
|
||||
if (param != null)
|
||||
suspend = Integer.parseInt(param);
|
||||
|
||||
if (suspend > 0)
|
||||
{
|
||||
asyncContext = request.startAsync();
|
||||
asyncLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
int error = 0;
|
||||
String param = request.getParameter("error");
|
||||
if (param != null)
|
||||
error = Integer.parseInt(param);
|
||||
|
||||
final AsyncContext asyncContext = this.asyncContext;
|
||||
if (asyncContext != null)
|
||||
{
|
||||
HttpServletResponse asyncResponse = (HttpServletResponse)asyncContext.getResponse();
|
||||
asyncResponse.sendError(error);
|
||||
asyncContext.complete();
|
||||
}
|
||||
else
|
||||
{
|
||||
response.sendError(404);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try (Socket socket1 = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
int wait = 1000;
|
||||
String request1 = "GET " + uri + "?suspend=" + wait + " HTTP/1.1\r\n" +
|
||||
"Host: localhost:" + connector.getLocalPort() + "\r\n" +
|
||||
"\r\n";
|
||||
OutputStream output1 = socket1.getOutputStream();
|
||||
output1.write(request1.getBytes("UTF-8"));
|
||||
output1.flush();
|
||||
|
||||
Assert.assertTrue(asyncLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
String error = "408";
|
||||
try (Socket socket2 = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
String request2 = "DELETE " + uri + "?error=" + error + " HTTP/1.1\r\n" +
|
||||
"Host: localhost:" + connector.getLocalPort() + "\r\n" +
|
||||
"\r\n";
|
||||
OutputStream output2 = socket2.getOutputStream();
|
||||
output2.write(request2.getBytes("UTF-8"));
|
||||
output2.flush();
|
||||
|
||||
SimpleHttpParser parser2 = new SimpleHttpParser();
|
||||
BufferedReader input2 = new BufferedReader(new InputStreamReader(socket2.getInputStream(), "UTF-8"));
|
||||
SimpleHttpResponse response2 = parser2.readResponse(input2);
|
||||
Assert.assertEquals("200", response2.getCode());
|
||||
}
|
||||
|
||||
socket1.setSoTimeout(2 * wait);
|
||||
SimpleHttpParser parser1 = new SimpleHttpParser();
|
||||
BufferedReader input1 = new BufferedReader(new InputStreamReader(socket1.getInputStream(), "UTF-8"));
|
||||
SimpleHttpResponse response1 = parser1.readResponse(input1);
|
||||
Assert.assertEquals(error, response1.getCode());
|
||||
|
||||
// Now try to make another request on the first connection
|
||||
// to verify that we set correctly the read interest (#409842)
|
||||
String request3 = "GET " + uri + " HTTP/1.1\r\n" +
|
||||
"Host: localhost:" + connector.getLocalPort() + "\r\n" +
|
||||
"\r\n";
|
||||
output1.write(request3.getBytes("UTF-8"));
|
||||
output1.flush();
|
||||
|
||||
SimpleHttpResponse response3 = parser1.readResponse(input1);
|
||||
Assert.assertEquals("200", response3.getCode());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,14 +18,11 @@
|
|||
|
||||
package org.eclipse.jetty.servlet;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.AsyncEvent;
|
||||
import javax.servlet.AsyncListener;
|
||||
|
@ -45,6 +42,8 @@ import org.junit.Assert;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
|
||||
public class AsyncServletTest
|
||||
{
|
||||
|
@ -60,7 +59,7 @@ public class AsyncServletTest
|
|||
{
|
||||
_connector = new ServerConnector(_server);
|
||||
_server.setConnectors(new Connector[]{ _connector });
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SECURITY|ServletContextHandler.NO_SESSIONS);
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
|
||||
context.setContextPath("/ctx");
|
||||
_server.setHandler(context);
|
||||
_servletHandler=context.getServletHandler();
|
||||
|
@ -333,18 +332,6 @@ public class AsyncServletTest
|
|||
assertContains("ERROR: /ctx/path/info",response);
|
||||
}
|
||||
|
||||
|
||||
protected void assertContains(String content,String response)
|
||||
{
|
||||
Assert.assertThat(response,Matchers.containsString(content));
|
||||
}
|
||||
|
||||
protected void assertNotContains(String content,String response)
|
||||
{
|
||||
Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAsyncRead() throws Exception
|
||||
{
|
||||
|
@ -358,7 +345,7 @@ public class AsyncServletTest
|
|||
"Connection: close\r\n"+
|
||||
"\r\n";
|
||||
|
||||
try (Socket socket = new Socket("localhost",_port);)
|
||||
try (Socket socket = new Socket("localhost",_port))
|
||||
{
|
||||
socket.setSoTimeout(10000);
|
||||
socket.getOutputStream().write(header.getBytes("ISO-8859-1"));
|
||||
|
@ -382,7 +369,6 @@ public class AsyncServletTest
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public synchronized String process(String query,String content) throws Exception
|
||||
{
|
||||
String request = "GET /ctx/path/info";
|
||||
|
@ -401,13 +387,11 @@ public class AsyncServletTest
|
|||
}
|
||||
|
||||
int port=_port;
|
||||
String response=null;
|
||||
try (Socket socket = new Socket("localhost",port);)
|
||||
try (Socket socket = new Socket("localhost",port))
|
||||
{
|
||||
socket.setSoTimeout(1000000);
|
||||
socket.getOutputStream().write(request.getBytes("UTF-8"));
|
||||
|
||||
response = IO.toString(socket.getInputStream());
|
||||
return IO.toString(socket.getInputStream());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -415,22 +399,23 @@ public class AsyncServletTest
|
|||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected void assertContains(String content,String response)
|
||||
{
|
||||
Assert.assertThat(response,Matchers.containsString(content));
|
||||
}
|
||||
|
||||
|
||||
protected void assertNotContains(String content,String response)
|
||||
{
|
||||
Assert.assertThat(response,Matchers.not(Matchers.containsString(content)));
|
||||
}
|
||||
|
||||
private static class AsyncServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = -8161977157098646562L;
|
||||
private Timer _timer=new Timer();
|
||||
private final Timer _timer=new Timer();
|
||||
|
||||
public AsyncServlet()
|
||||
{}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
|
@ -676,7 +661,7 @@ public class AsyncServletTest
|
|||
public void onTimeout(AsyncEvent event) throws IOException
|
||||
{
|
||||
((HttpServletResponse)event.getSuppliedResponse()).addHeader("history","onTimeout");
|
||||
String action=((HttpServletRequest)event.getSuppliedRequest()).getParameter("timeout");
|
||||
String action=event.getSuppliedRequest().getParameter("timeout");
|
||||
if (action!=null)
|
||||
{
|
||||
((HttpServletResponse)event.getSuppliedResponse()).addHeader("history",action);
|
||||
|
@ -684,7 +669,7 @@ public class AsyncServletTest
|
|||
event.getAsyncContext().dispatch();
|
||||
if ("complete".equals(action))
|
||||
{
|
||||
((HttpServletResponse)event.getSuppliedResponse()).getOutputStream().println("COMPLETED\n");
|
||||
event.getSuppliedResponse().getOutputStream().println("COMPLETED\n");
|
||||
event.getAsyncContext().complete();
|
||||
}
|
||||
}
|
||||
|
@ -706,5 +691,4 @@ public class AsyncServletTest
|
|||
((HttpServletResponse)event.getSuppliedResponse()).addHeader("history","onComplete");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.servlet.LEVEL=DEBUG
|
Loading…
Reference in New Issue