432270 - Slow requests with response content delimited by EOF fail.
Fixed also in the FastCGI module.
This commit is contained in:
parent
eb45d45dbf
commit
5eeda38f0a
|
@ -215,6 +215,10 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
||||||
LOG.debug("Closed {}", this);
|
LOG.debug("Closed {}", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void release(Connection connection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void close(Connection connection)
|
public void close(Connection connection)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,8 +140,11 @@ public abstract class PoolingHttpDestination<C extends Connection> extends HttpD
|
||||||
|
|
||||||
protected abstract void send(C connection, HttpExchange exchange);
|
protected abstract void send(C connection, HttpExchange exchange);
|
||||||
|
|
||||||
public void release(C connection)
|
@Override
|
||||||
|
public void release(Connection c)
|
||||||
{
|
{
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
C connection = (C)c;
|
||||||
LOG.debug("{} released", connection);
|
LOG.debug("{} released", connection);
|
||||||
HttpClient client = getHttpClient();
|
HttpClient client = getHttpClient();
|
||||||
if (client.isRunning())
|
if (client.isRunning())
|
||||||
|
|
|
@ -28,8 +28,6 @@ import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
import org.eclipse.jetty.fcgi.generator.Generator;
|
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
|
||||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.io.IdleTimeout;
|
import org.eclipse.jetty.io.IdleTimeout;
|
||||||
|
|
||||||
|
@ -83,42 +81,43 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
return receiver.abort(cause);
|
return receiver.abort(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void responseBegin(int code, String reason)
|
protected boolean responseBegin(int code, String reason)
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
if (exchange == null)
|
||||||
{
|
return false;
|
||||||
exchange.getResponse().version(version).status(code).reason(reason);
|
exchange.getResponse().version(version).status(code).reason(reason);
|
||||||
receiver.responseBegin(exchange);
|
return receiver.responseBegin(exchange);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void responseHeader(HttpField field)
|
protected boolean responseHeader(HttpField field)
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
return exchange != null && receiver.responseHeader(exchange, field);
|
||||||
receiver.responseHeader(exchange, field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void responseHeaders()
|
protected boolean responseHeaders()
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
return exchange != null && receiver.responseHeaders(exchange);
|
||||||
receiver.responseHeaders(exchange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void content(ByteBuffer buffer)
|
protected boolean content(ByteBuffer buffer)
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
return exchange != null && receiver.responseContent(exchange, buffer);
|
||||||
receiver.responseContent(exchange, buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void responseSuccess()
|
protected boolean responseSuccess()
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange != null)
|
return exchange != null && receiver.responseSuccess(exchange);
|
||||||
receiver.responseSuccess(exchange);
|
}
|
||||||
|
|
||||||
|
protected boolean responseFailure(Throwable failure)
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
return exchange != null && receiver.responseFailure(failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -126,12 +125,10 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
{
|
{
|
||||||
super.exchangeTerminated(result);
|
super.exchangeTerminated(result);
|
||||||
idle.onClose();
|
idle.onClose();
|
||||||
boolean close = result.isFailed();
|
|
||||||
HttpFields responseHeaders = result.getResponse().getHeaders();
|
HttpFields responseHeaders = result.getResponse().getHeaders();
|
||||||
close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
if (result.isFailed())
|
||||||
if (close)
|
connection.close(result.getFailure());
|
||||||
connection.close();
|
else if (!connection.closeByHTTP(responseHeaders))
|
||||||
else
|
|
||||||
connection.release(this);
|
connection.release(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +151,8 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
@Override
|
@Override
|
||||||
protected void onIdleExpired(TimeoutException timeout)
|
protected void onIdleExpired(TimeoutException timeout)
|
||||||
{
|
{
|
||||||
LOG.debug("Idle timeout for request {}", request);
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Idle timeout for request {}", request);
|
||||||
connection.abort(timeout);
|
connection.abort(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport
|
||||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
{
|
{
|
||||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination);
|
HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination, isMultiplexed());
|
||||||
LOG.debug("Created {}", connection);
|
LOG.debug("Created {}", connection);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
|
|
|
@ -31,7 +31,6 @@ import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpConnection;
|
import org.eclipse.jetty.client.HttpConnection;
|
||||||
import org.eclipse.jetty.client.HttpDestination;
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.PoolingHttpDestination;
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
@ -39,6 +38,9 @@ import org.eclipse.jetty.fcgi.FCGI;
|
||||||
import org.eclipse.jetty.fcgi.generator.Flusher;
|
import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
import org.eclipse.jetty.fcgi.parser.ClientParser;
|
import org.eclipse.jetty.fcgi.parser.ClientParser;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.io.AbstractConnection;
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
@ -55,14 +57,16 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
private final AtomicBoolean closed = new AtomicBoolean();
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
private final Flusher flusher;
|
private final Flusher flusher;
|
||||||
private final HttpDestination destination;
|
private final HttpDestination destination;
|
||||||
|
private final boolean multiplexed;
|
||||||
private final Delegate delegate;
|
private final Delegate delegate;
|
||||||
private final ClientParser parser;
|
private final ClientParser parser;
|
||||||
|
|
||||||
public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination)
|
public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination, boolean multiplexed)
|
||||||
{
|
{
|
||||||
super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
|
super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
|
||||||
this.flusher = new Flusher(endPoint);
|
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
|
this.multiplexed = multiplexed;
|
||||||
|
this.flusher = new Flusher(endPoint);
|
||||||
this.delegate = new Delegate(destination);
|
this.delegate = new Delegate(destination);
|
||||||
this.parser = new ClientParser(new ResponseListener());
|
this.parser = new ClientParser(new ResponseListener());
|
||||||
requests.addLast(0);
|
requests.addLast(0);
|
||||||
|
@ -103,7 +107,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int read = endPoint.fill(buffer);
|
int read = endPoint.fill(buffer);
|
||||||
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
|
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'.
|
||||||
LOG.debug("Read {} bytes from {}", read, endPoint);
|
LOG.debug("Read {} bytes from {}", read, endPoint);
|
||||||
if (read > 0)
|
if (read > 0)
|
||||||
{
|
{
|
||||||
|
@ -124,7 +128,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
catch (Exception x)
|
catch (Exception x)
|
||||||
{
|
{
|
||||||
LOG.debug(x);
|
LOG.debug(x);
|
||||||
// TODO: fail and close ?
|
close(x);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -140,7 +144,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
|
|
||||||
private void shutdown()
|
private void shutdown()
|
||||||
{
|
{
|
||||||
close(new EOFException());
|
// Close explicitly only if we are idle, since the request may still
|
||||||
|
// be in progress, otherwise close only if we can fail the responses.
|
||||||
|
if (channels.isEmpty())
|
||||||
|
close();
|
||||||
|
else
|
||||||
|
failAndClose(new EOFException());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -153,13 +162,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
protected void release(HttpChannelOverFCGI channel)
|
protected void release(HttpChannelOverFCGI channel)
|
||||||
{
|
{
|
||||||
channels.remove(channel.getRequest());
|
channels.remove(channel.getRequest());
|
||||||
if (destination instanceof PoolingHttpDestination)
|
destination.release(this);
|
||||||
{
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
PoolingHttpDestination<HttpConnectionOverFCGI> fcgiDestination =
|
|
||||||
(PoolingHttpDestination<HttpConnectionOverFCGI>)destination;
|
|
||||||
fcgiDestination.release(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -168,7 +171,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
close(new AsynchronousCloseException());
|
close(new AsynchronousCloseException());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void close(Throwable failure)
|
protected void close(Throwable failure)
|
||||||
{
|
{
|
||||||
if (closed.compareAndSet(false, true))
|
if (closed.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
|
@ -184,6 +187,16 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean closeByHTTP(HttpFields fields)
|
||||||
|
{
|
||||||
|
if (multiplexed)
|
||||||
|
return false;
|
||||||
|
if (!fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()))
|
||||||
|
return false;
|
||||||
|
close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected void abort(Throwable failure)
|
protected void abort(Throwable failure)
|
||||||
{
|
{
|
||||||
for (HttpChannelOverFCGI channel : channels.values())
|
for (HttpChannelOverFCGI channel : channels.values())
|
||||||
|
@ -195,6 +208,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
channels.clear();
|
channels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void failAndClose(Throwable failure)
|
||||||
|
{
|
||||||
|
boolean result = false;
|
||||||
|
for (HttpChannelOverFCGI channel : channels.values())
|
||||||
|
result |= channel.responseFailure(failure);
|
||||||
|
if (result)
|
||||||
|
close(failure);
|
||||||
|
}
|
||||||
|
|
||||||
private int acquireRequest()
|
private int acquireRequest()
|
||||||
{
|
{
|
||||||
synchronized (requests)
|
synchronized (requests)
|
||||||
|
@ -322,8 +344,23 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
{
|
{
|
||||||
channel.responseSuccess();
|
if (channel.responseSuccess())
|
||||||
releaseRequest(request);
|
releaseRequest(request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
noChannel(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int request, Throwable failure)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
if (channel.responseFailure(failure))
|
||||||
|
releaseRequest(request);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -88,21 +88,36 @@ public class ServerGenerator extends Generator
|
||||||
return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT);
|
return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, Callback callback)
|
public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, boolean aborted, Callback callback)
|
||||||
{
|
{
|
||||||
Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT);
|
if (aborted)
|
||||||
if (lastContent)
|
|
||||||
{
|
{
|
||||||
// Generate the FCGI_END_REQUEST
|
Result result = new Result(byteBufferPool, callback);
|
||||||
request &= 0xFF_FF;
|
if (lastContent)
|
||||||
ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false);
|
result.append(generateEndRequest(request, true), true);
|
||||||
BufferUtil.clearToFill(endRequestBuffer);
|
else
|
||||||
endRequestBuffer.putInt(0x01_03_00_00 + request);
|
result.append(BufferUtil.EMPTY_BUFFER, false);
|
||||||
endRequestBuffer.putInt(0x00_08_00_00);
|
return result;
|
||||||
endRequestBuffer.putLong(0x00L);
|
|
||||||
endRequestBuffer.flip();
|
|
||||||
result = result.append(endRequestBuffer, true);
|
|
||||||
}
|
}
|
||||||
return result;
|
else
|
||||||
|
{
|
||||||
|
Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT);
|
||||||
|
if (lastContent)
|
||||||
|
result.append(generateEndRequest(request, false), true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByteBuffer generateEndRequest(int request, boolean aborted)
|
||||||
|
{
|
||||||
|
request &= 0xFF_FF;
|
||||||
|
ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false);
|
||||||
|
BufferUtil.clearToFill(endRequestBuffer);
|
||||||
|
endRequestBuffer.putInt(0x01_03_00_00 + request);
|
||||||
|
endRequestBuffer.putInt(0x00_08_00_00);
|
||||||
|
endRequestBuffer.putInt(aborted ? 1 : 0);
|
||||||
|
endRequestBuffer.putInt(0);
|
||||||
|
endRequestBuffer.flip();
|
||||||
|
return endRequestBuffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,5 +98,13 @@ public class ClientParser extends Parser
|
||||||
for (StreamContentParser streamParser : streamParsers)
|
for (StreamContentParser streamParser : streamParsers)
|
||||||
streamParser.end(request);
|
streamParser.end(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int request, Throwable failure)
|
||||||
|
{
|
||||||
|
listener.onFailure(request, failure);
|
||||||
|
for (StreamContentParser streamParser : streamParsers)
|
||||||
|
streamParser.end(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,8 +107,12 @@ public class EndRequestContentParser extends ContentParser
|
||||||
|
|
||||||
private void onEnd()
|
private void onEnd()
|
||||||
{
|
{
|
||||||
// TODO: if protocol != 0, invoke an error callback
|
if (application != 0)
|
||||||
listener.onEnd(getRequest());
|
listener.onFailure(getRequest(), new Exception("FastCGI application returned code " + application));
|
||||||
|
else if (protocol != 0)
|
||||||
|
listener.onFailure(getRequest(), new Exception("FastCGI server returned code " + protocol));
|
||||||
|
else
|
||||||
|
listener.onEnd(getRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset()
|
private void reset()
|
||||||
|
|
|
@ -100,6 +100,8 @@ public abstract class Parser
|
||||||
|
|
||||||
public void onEnd(int request);
|
public void onEnd(int request);
|
||||||
|
|
||||||
|
public void onFailure(int request, Throwable failure);
|
||||||
|
|
||||||
public static class Adapter implements Listener
|
public static class Adapter implements Listener
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,6 +123,12 @@ public abstract class Parser
|
||||||
public void onEnd(int request)
|
public void onEnd(int request)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int request, Throwable failure)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ public class ClientParserTest
|
||||||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null);
|
Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null);
|
||||||
Generator.Result result2 = generator.generateResponseContent(id, null, true, null);
|
Generator.Result result2 = generator.generateResponseContent(id, null, true, false, null);
|
||||||
|
|
||||||
final AtomicInteger verifier = new AtomicInteger();
|
final AtomicInteger verifier = new AtomicInteger();
|
||||||
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
|
@ -162,7 +162,7 @@ public class ClientParserTest
|
||||||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
||||||
Generator.Result result2 = generator.generateResponseContent(id, content, true, null);
|
Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null);
|
||||||
|
|
||||||
final AtomicInteger verifier = new AtomicInteger();
|
final AtomicInteger verifier = new AtomicInteger();
|
||||||
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
|
@ -214,7 +214,7 @@ public class ClientParserTest
|
||||||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
||||||
Generator.Result result2 = generator.generateResponseContent(id, content, true, null);
|
Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null);
|
||||||
|
|
||||||
final AtomicInteger totalLength = new AtomicInteger();
|
final AtomicInteger totalLength = new AtomicInteger();
|
||||||
final AtomicBoolean verifier = new AtomicBoolean();
|
final AtomicBoolean verifier = new AtomicBoolean();
|
||||||
|
|
|
@ -24,6 +24,8 @@ import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
import org.eclipse.jetty.fcgi.generator.Generator;
|
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||||
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
|
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
|
||||||
import org.eclipse.jetty.http.HttpGenerator;
|
import org.eclipse.jetty.http.HttpGenerator;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.server.HttpTransport;
|
import org.eclipse.jetty.server.HttpTransport;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
@ -35,6 +37,8 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
private final Flusher flusher;
|
private final Flusher flusher;
|
||||||
private final int request;
|
private final int request;
|
||||||
private volatile boolean head;
|
private volatile boolean head;
|
||||||
|
private volatile boolean shutdown;
|
||||||
|
private volatile boolean aborted;
|
||||||
|
|
||||||
public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request)
|
public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request)
|
||||||
{
|
{
|
||||||
|
@ -47,13 +51,15 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
||||||
{
|
{
|
||||||
boolean head = this.head = info.isHead();
|
boolean head = this.head = info.isHead();
|
||||||
|
boolean shutdown = this.shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||||
|
|
||||||
if (head)
|
if (head)
|
||||||
{
|
{
|
||||||
if (lastContent)
|
if (lastContent)
|
||||||
{
|
{
|
||||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||||
info.getHttpFields(), new Callback.Adapter());
|
info.getHttpFields(), new Callback.Adapter());
|
||||||
Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback);
|
Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback);
|
||||||
flusher.flush(headersResult, contentResult);
|
flusher.flush(headersResult, contentResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -67,9 +73,12 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
{
|
{
|
||||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||||
info.getHttpFields(), new Callback.Adapter());
|
info.getHttpFields(), new Callback.Adapter());
|
||||||
Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, callback);
|
Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, aborted, callback);
|
||||||
flusher.flush(headersResult, contentResult);
|
flusher.flush(headersResult, contentResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastContent && shutdown)
|
||||||
|
flusher.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,7 +88,7 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
{
|
{
|
||||||
if (lastContent)
|
if (lastContent)
|
||||||
{
|
{
|
||||||
Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback);
|
Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback);
|
||||||
flusher.flush(result);
|
flusher.flush(result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -90,18 +99,22 @@ public class HttpTransportOverFCGI implements HttpTransport
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback);
|
Generator.Result result = generator.generateResponseContent(request, content, lastContent, aborted, callback);
|
||||||
flusher.flush(result);
|
flusher.flush(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastContent && shutdown)
|
||||||
|
flusher.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort()
|
||||||
|
{
|
||||||
|
aborted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void completed()
|
public void completed()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void abort()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,5 +175,17 @@ public class ServerFCGIConnection extends AbstractConnection
|
||||||
channel.dispatch();
|
channel.dispatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(int request, Throwable failure)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.remove(request);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request {} failure on {}: {}", request, channel, failure);
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
channel.badMessage(400, failure.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.net.URI;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -40,6 +41,8 @@ import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||||
|
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
||||||
|
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.IO;
|
import org.eclipse.jetty.toolchain.test.IO;
|
||||||
|
@ -551,4 +554,77 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEarlyEOF() throws Exception
|
||||||
|
{
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
// Promise some content, then flush the headers, then fail to send the content.
|
||||||
|
response.setContentLength(16);
|
||||||
|
response.flushBuffer();
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch (ExecutionException x)
|
||||||
|
{
|
||||||
|
// Expected.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSmallContentDelimitedByEOFWithSlowRequest() throws Exception
|
||||||
|
{
|
||||||
|
testContentDelimitedByEOFWithSlowRequest(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBigContentDelimitedByEOFWithSlowRequest() throws Exception
|
||||||
|
{
|
||||||
|
testContentDelimitedByEOFWithSlowRequest(128 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testContentDelimitedByEOFWithSlowRequest(int length) throws Exception
|
||||||
|
{
|
||||||
|
final byte[] data = new byte[length];
|
||||||
|
new Random().nextBytes(data);
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setHeader("Connection", "close");
|
||||||
|
response.getOutputStream().write(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0}));
|
||||||
|
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.content(content);
|
||||||
|
FutureResponseListener listener = new FutureResponseListener(request);
|
||||||
|
request.send(listener);
|
||||||
|
// Wait some time to simulate a slow request.
|
||||||
|
Thread.sleep(1000);
|
||||||
|
content.close();
|
||||||
|
|
||||||
|
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertArrayEquals(data, response.getContent());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue