Merge branch 'master' into extra-start-dirs
This commit is contained in:
commit
de8485d53e
|
@ -215,6 +215,10 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl
|
|||
LOG.debug("Closed {}", this);
|
||||
}
|
||||
|
||||
public void release(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);
|
||||
|
||||
public void release(C connection)
|
||||
@Override
|
||||
public void release(Connection c)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
C connection = (C)c;
|
||||
LOG.debug("{} released", connection);
|
||||
HttpClient client = getHttpClient();
|
||||
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.http.HttpField;
|
||||
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.io.IdleTimeout;
|
||||
|
||||
|
@ -83,42 +81,43 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
return receiver.abort(cause);
|
||||
}
|
||||
|
||||
protected void responseBegin(int code, String reason)
|
||||
protected boolean responseBegin(int code, String reason)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
{
|
||||
exchange.getResponse().version(version).status(code).reason(reason);
|
||||
receiver.responseBegin(exchange);
|
||||
}
|
||||
if (exchange == null)
|
||||
return false;
|
||||
exchange.getResponse().version(version).status(code).reason(reason);
|
||||
return receiver.responseBegin(exchange);
|
||||
}
|
||||
|
||||
protected void responseHeader(HttpField field)
|
||||
protected boolean responseHeader(HttpField field)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.responseHeader(exchange, field);
|
||||
return exchange != null && receiver.responseHeader(exchange, field);
|
||||
}
|
||||
|
||||
protected void responseHeaders()
|
||||
protected boolean responseHeaders()
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.responseHeaders(exchange);
|
||||
return exchange != null && receiver.responseHeaders(exchange);
|
||||
}
|
||||
|
||||
protected void content(ByteBuffer buffer)
|
||||
protected boolean content(ByteBuffer buffer)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.responseContent(exchange, buffer);
|
||||
return exchange != null && receiver.responseContent(exchange, buffer);
|
||||
}
|
||||
|
||||
protected void responseSuccess()
|
||||
protected boolean responseSuccess()
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange != null)
|
||||
receiver.responseSuccess(exchange);
|
||||
return exchange != null && receiver.responseSuccess(exchange);
|
||||
}
|
||||
|
||||
protected boolean responseFailure(Throwable failure)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
return exchange != null && receiver.responseFailure(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -126,12 +125,10 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
{
|
||||
super.exchangeTerminated(result);
|
||||
idle.onClose();
|
||||
boolean close = result.isFailed();
|
||||
HttpFields responseHeaders = result.getResponse().getHeaders();
|
||||
close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||
if (close)
|
||||
connection.close();
|
||||
else
|
||||
if (result.isFailed())
|
||||
connection.close(result.getFailure());
|
||||
else if (!connection.closeByHTTP(responseHeaders))
|
||||
connection.release(this);
|
||||
}
|
||||
|
||||
|
@ -154,7 +151,8 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport
|
|||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
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);
|
||||
@SuppressWarnings("unchecked")
|
||||
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.HttpDestination;
|
||||
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.Request;
|
||||
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.parser.ClientParser;
|
||||
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.ByteBufferPool;
|
||||
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 Flusher flusher;
|
||||
private final HttpDestination destination;
|
||||
private final boolean multiplexed;
|
||||
private final Delegate delegate;
|
||||
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());
|
||||
this.flusher = new Flusher(endPoint);
|
||||
this.destination = destination;
|
||||
this.multiplexed = multiplexed;
|
||||
this.flusher = new Flusher(endPoint);
|
||||
this.delegate = new Delegate(destination);
|
||||
this.parser = new ClientParser(new ResponseListener());
|
||||
requests.addLast(0);
|
||||
|
@ -103,7 +107,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
while (true)
|
||||
{
|
||||
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);
|
||||
if (read > 0)
|
||||
{
|
||||
|
@ -124,7 +128,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
catch (Exception x)
|
||||
{
|
||||
LOG.debug(x);
|
||||
// TODO: fail and close ?
|
||||
close(x);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -140,7 +144,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
|
||||
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
|
||||
|
@ -153,13 +162,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
protected void release(HttpChannelOverFCGI channel)
|
||||
{
|
||||
channels.remove(channel.getRequest());
|
||||
if (destination instanceof PoolingHttpDestination)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
PoolingHttpDestination<HttpConnectionOverFCGI> fcgiDestination =
|
||||
(PoolingHttpDestination<HttpConnectionOverFCGI>)destination;
|
||||
fcgiDestination.release(this);
|
||||
}
|
||||
destination.release(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -168,7 +171,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
close(new AsynchronousCloseException());
|
||||
}
|
||||
|
||||
private void close(Throwable failure)
|
||||
protected void close(Throwable failure)
|
||||
{
|
||||
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)
|
||||
{
|
||||
for (HttpChannelOverFCGI channel : channels.values())
|
||||
|
@ -195,6 +208,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
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()
|
||||
{
|
||||
synchronized (requests)
|
||||
|
@ -322,8 +344,23 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
HttpChannelOverFCGI channel = channels.get(request);
|
||||
if (channel != null)
|
||||
{
|
||||
channel.responseSuccess();
|
||||
releaseRequest(request);
|
||||
if (channel.responseSuccess())
|
||||
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
|
||||
{
|
||||
|
|
|
@ -88,21 +88,36 @@ public class ServerGenerator extends Generator
|
|||
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 (lastContent)
|
||||
if (aborted)
|
||||
{
|
||||
// Generate the FCGI_END_REQUEST
|
||||
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.putLong(0x00L);
|
||||
endRequestBuffer.flip();
|
||||
result = result.append(endRequestBuffer, true);
|
||||
Result result = new Result(byteBufferPool, callback);
|
||||
if (lastContent)
|
||||
result.append(generateEndRequest(request, true), true);
|
||||
else
|
||||
result.append(BufferUtil.EMPTY_BUFFER, false);
|
||||
return result;
|
||||
}
|
||||
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)
|
||||
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()
|
||||
{
|
||||
// TODO: if protocol != 0, invoke an error callback
|
||||
listener.onEnd(getRequest());
|
||||
if (application != 0)
|
||||
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()
|
||||
|
|
|
@ -100,6 +100,8 @@ public abstract class Parser
|
|||
|
||||
public void onEnd(int request);
|
||||
|
||||
public void onFailure(int request, Throwable failure);
|
||||
|
||||
public static class Adapter implements Listener
|
||||
{
|
||||
@Override
|
||||
|
@ -121,6 +123,12 @@ public abstract class Parser
|
|||
public void onEnd(int request)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(int request, Throwable failure)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ public class ClientParserTest
|
|||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||
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();
|
||||
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||
|
@ -162,7 +162,7 @@ public class ClientParserTest
|
|||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||
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();
|
||||
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||
|
@ -214,7 +214,7 @@ public class ClientParserTest
|
|||
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||
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 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.ServerGenerator;
|
||||
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.server.HttpTransport;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
@ -35,6 +37,8 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
private final Flusher flusher;
|
||||
private final int request;
|
||||
private volatile boolean head;
|
||||
private volatile boolean shutdown;
|
||||
private volatile boolean aborted;
|
||||
|
||||
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)
|
||||
{
|
||||
boolean head = this.head = info.isHead();
|
||||
boolean shutdown = this.shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||
|
||||
if (head)
|
||||
{
|
||||
if (lastContent)
|
||||
{
|
||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
@ -67,9 +73,12 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
{
|
||||
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||
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);
|
||||
}
|
||||
|
||||
if (lastContent && shutdown)
|
||||
flusher.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,7 +88,7 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
@ -90,18 +99,22 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
}
|
||||
else
|
||||
{
|
||||
Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback);
|
||||
Generator.Result result = generator.generateResponseContent(request, content, lastContent, aborted, callback);
|
||||
flusher.flush(result);
|
||||
}
|
||||
|
||||
if (lastContent && shutdown)
|
||||
flusher.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort()
|
||||
{
|
||||
aborted = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,5 +175,17 @@ public class ServerFCGIConnection extends AbstractConnection
|
|||
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.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
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.Result;
|
||||
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.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.toolchain.test.IO;
|
||||
|
@ -551,4 +554,77 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
<instructions>
|
||||
<Bundle-SymbolicName>org.eclipse.jetty.osgi.boot;singleton:=true</Bundle-SymbolicName>
|
||||
<Bundle-Activator>org.eclipse.jetty.osgi.boot.JettyBootstrapActivator</Bundle-Activator>
|
||||
<Provide-Capability>osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer</Provide-Capability>
|
||||
<DynamicImport-Package>org.eclipse.jetty.*;version="[9.1,10.0)"</DynamicImport-Package>
|
||||
<Import-Package>javax.mail;version="1.4.0";resolution:=optional,
|
||||
javax.mail.event;version="1.4.0";resolution:=optional,
|
||||
|
|
|
@ -47,32 +47,33 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* CGI Servlet.
|
||||
* <p/>
|
||||
* The cgi bin directory can be set with the "cgibinResourceBase" init parameter or it will default to the resource base of the context. If the
|
||||
* "cgibinResourceBaseIsRelative" init parameter is set the resource base is relative to the webapp. For example "WEB-INF/cgi" would work.
|
||||
* <br/>
|
||||
* Not that this only works for extracted war files as "jar cf" will not reserve the execute permissions on the cgi files.
|
||||
* <p/>
|
||||
* The "commandPrefix" init parameter may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a
|
||||
* particular file type. For example on windows this can be set to "perl" so that perl scripts are executed.
|
||||
* <p/>
|
||||
* The "Path" init param is passed to the exec environment as PATH. Note: Must be run unpacked somewhere in the filesystem.
|
||||
* <p/>
|
||||
* Any initParameter that starts with ENV_ is used to set an environment variable with the name stripped of the leading ENV_ and using the init parameter value.
|
||||
* <p>
|
||||
*
|
||||
* The following init parameters are used to configure this servlet:
|
||||
* <dl>
|
||||
* <dt>cgibinResourceBase</dt><dd>Path to the cgi bin directory if set or it will default to the resource base of the context.</dd>
|
||||
* <dt>resourceBase</dt><dd>An alias for cgibinResourceBase.</dd>
|
||||
* <dt>cgibinResourceBaseIsRelative</dt><dd>If true then cgibinResourceBase is relative to the webapp (eg "WEB-INF/cgi")</dd>
|
||||
* <dt>commandPrefix</dt><dd>may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a
|
||||
* particular file type. For example on windows this can be set to "perl" so that perl scripts are executed.</dd>
|
||||
* <dt>Path</dt><dd>passed to the exec environment as PATH.</dd>
|
||||
* <dt>ENV_*</dt><dd>used to set an arbitrary environment variable with the name stripped of the leading ENV_ and using the init parameter value</dd>
|
||||
* <dt>useFullPath</dt><dd>If true, the full URI path within the context is used for the exec command, otherwise a search is done for a partial URL that matches an exec Command</dd>
|
||||
* </dl>
|
||||
*
|
||||
*/
|
||||
public class CGI extends HttpServlet
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -6182088932884791073L;
|
||||
private static final long serialVersionUID = -6182088932884791074L;
|
||||
|
||||
private static final Logger LOG = Log.getLogger(CGI.class);
|
||||
|
||||
private boolean _ok;
|
||||
private File _docRoot;
|
||||
private boolean _cgiBinProvided;
|
||||
private String _path;
|
||||
private String _cmdPrefix;
|
||||
private boolean _useFullPath;
|
||||
private EnvList _env;
|
||||
private boolean _ignoreExitState;
|
||||
private boolean _relative;
|
||||
|
@ -83,16 +84,22 @@ public class CGI extends HttpServlet
|
|||
{
|
||||
_env = new EnvList();
|
||||
_cmdPrefix = getInitParameter("commandPrefix");
|
||||
_useFullPath = Boolean.parseBoolean(getInitParameter("useFullPath"));
|
||||
_relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative"));
|
||||
|
||||
String tmp = getInitParameter("cgibinResourceBase");
|
||||
if (tmp == null)
|
||||
if (tmp != null)
|
||||
_cgiBinProvided = true;
|
||||
else
|
||||
{
|
||||
tmp = getInitParameter("resourceBase");
|
||||
if (tmp == null)
|
||||
if (tmp != null)
|
||||
_cgiBinProvided = true;
|
||||
else
|
||||
tmp = getServletContext().getRealPath("/");
|
||||
}
|
||||
else if (_relative)
|
||||
|
||||
if (_relative && _cgiBinProvided)
|
||||
{
|
||||
tmp = getServletContext().getRealPath(tmp);
|
||||
}
|
||||
|
@ -137,10 +144,10 @@ public class CGI extends HttpServlet
|
|||
_env.set("PATH",_path);
|
||||
|
||||
_ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState"));
|
||||
Enumeration e = getInitParameterNames();
|
||||
Enumeration<String> e = getInitParameterNames();
|
||||
while (e.hasMoreElements())
|
||||
{
|
||||
String n = (String)e.nextElement();
|
||||
String n = e.nextElement();
|
||||
if (n != null && n.startsWith("ENV_"))
|
||||
_env.set(n.substring(4),getInitParameter(n));
|
||||
}
|
||||
|
@ -166,7 +173,6 @@ public class CGI extends HttpServlet
|
|||
return;
|
||||
}
|
||||
|
||||
String pathInContext = (_relative?"":StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo());
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("CGI: ContextPath : " + req.getContextPath());
|
||||
|
@ -180,63 +186,69 @@ public class CGI extends HttpServlet
|
|||
// pathInContext may actually comprises scriptName/pathInfo...We will
|
||||
// walk backwards up it until we find the script - the rest must
|
||||
// be the pathInfo;
|
||||
String pathInContext = (_relative ? "" : StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo());
|
||||
File execCmd = new File(_docRoot, pathInContext);
|
||||
String pathInfo = pathInContext;
|
||||
|
||||
String both = pathInContext;
|
||||
String first = both;
|
||||
String last = "";
|
||||
|
||||
File exe = new File(_docRoot,first);
|
||||
|
||||
while ((first.endsWith("/") || !exe.exists()) && first.length() >= 0)
|
||||
if(!_useFullPath)
|
||||
{
|
||||
int index = first.lastIndexOf('/');
|
||||
String path = pathInContext;
|
||||
String info = "";
|
||||
|
||||
first = first.substring(0,index);
|
||||
last = both.substring(index,both.length());
|
||||
exe = new File(_docRoot,first);
|
||||
}
|
||||
|
||||
if (first.length() == 0 || !exe.exists() || exe.isDirectory() || !exe.getCanonicalPath().equals(exe.getAbsolutePath()))
|
||||
{
|
||||
res.sendError(404);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
// Search docroot for a matching execCmd
|
||||
while (path.endsWith("/") && path.length() >= 0)
|
||||
{
|
||||
LOG.debug("CGI: script is " + exe);
|
||||
LOG.debug("CGI: pathInfo is " + last);
|
||||
if(!execCmd.exists())
|
||||
break;
|
||||
|
||||
int index = path.lastIndexOf('/');
|
||||
|
||||
path = path.substring(0,index);
|
||||
info = pathInContext.substring(index,pathInContext.length());
|
||||
execCmd = new File(_docRoot,path);
|
||||
}
|
||||
exec(exe,last,req,res);
|
||||
|
||||
if (path.length() == 0 || !execCmd.exists() || execCmd.isDirectory() || !execCmd.getCanonicalPath().equals(execCmd.getAbsolutePath()))
|
||||
{
|
||||
res.sendError(404);
|
||||
}
|
||||
|
||||
pathInfo = info;
|
||||
}
|
||||
exec(execCmd,pathInfo,req,res);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** executes the CGI process
|
||||
/*
|
||||
* @param root @param path @param req @param res @exception IOException
|
||||
* @param command the command to execute, this command is prefixed by
|
||||
* the context parameter "commandPrefix".
|
||||
* @param pathInfo The PATH_INFO to process,
|
||||
* see http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getPathInfo%28%29. Cannot be null
|
||||
* @param req
|
||||
* @param res
|
||||
* @exception IOException
|
||||
*/
|
||||
private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException
|
||||
{
|
||||
String path = command.getAbsolutePath();
|
||||
File dir = command.getParentFile();
|
||||
String scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length());
|
||||
String scriptPath = getServletContext().getRealPath(scriptName);
|
||||
String pathTranslated = req.getPathTranslated();
|
||||
assert req != null;
|
||||
assert res != null;
|
||||
assert pathInfo != null;
|
||||
assert command != null;
|
||||
|
||||
int len = req.getContentLength();
|
||||
if (len < 0)
|
||||
len = 0;
|
||||
if ((pathTranslated == null) || (pathTranslated.length() == 0))
|
||||
pathTranslated = path;
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("CGI: script is " + command);
|
||||
LOG.debug("CGI: pathInfo is " + pathInfo);
|
||||
}
|
||||
|
||||
String bodyFormEncoded = null;
|
||||
if ((HttpMethod.POST.equals(req.getMethod()) || HttpMethod.PUT.equals(req.getMethod())) && "application/x-www-form-urlencoded".equals(req.getContentType()))
|
||||
{
|
||||
MultiMap<String> parameterMap = new MultiMap<String>();
|
||||
Enumeration names = req.getParameterNames();
|
||||
Enumeration<String> names = req.getParameterNames();
|
||||
while (names.hasMoreElements())
|
||||
{
|
||||
String parameterName = (String)names.nextElement();
|
||||
String parameterName = names.nextElement();
|
||||
parameterMap.addValues(parameterName, req.getParameterValues(parameterName));
|
||||
}
|
||||
bodyFormEncoded = UrlEncoded.encode(parameterMap, Charset.forName(req.getCharacterEncoding()), true);
|
||||
|
@ -247,24 +259,33 @@ public class CGI extends HttpServlet
|
|||
// look at :
|
||||
// http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1
|
||||
env.set("AUTH_TYPE", req.getAuthType());
|
||||
|
||||
int contentLen = req.getContentLength();
|
||||
if (contentLen < 0)
|
||||
contentLen = 0;
|
||||
if (bodyFormEncoded != null)
|
||||
{
|
||||
env.set("CONTENT_LENGTH", Integer.toString(bodyFormEncoded.length()));
|
||||
}
|
||||
else
|
||||
{
|
||||
env.set("CONTENT_LENGTH", Integer.toString(len));
|
||||
env.set("CONTENT_LENGTH", Integer.toString(contentLen));
|
||||
}
|
||||
env.set("CONTENT_TYPE", req.getContentType());
|
||||
env.set("GATEWAY_INTERFACE", "CGI/1.1");
|
||||
if ((pathInfo != null) && (pathInfo.length() > 0))
|
||||
if (pathInfo.length() > 0)
|
||||
{
|
||||
env.set("PATH_INFO", pathInfo);
|
||||
}
|
||||
|
||||
String pathTranslated = req.getPathTranslated();
|
||||
if ((pathTranslated == null) || (pathTranslated.length() == 0))
|
||||
pathTranslated = pathInfo;
|
||||
env.set("PATH_TRANSLATED", pathTranslated);
|
||||
env.set("QUERY_STRING", req.getQueryString());
|
||||
env.set("REMOTE_ADDR", req.getRemoteAddr());
|
||||
env.set("REMOTE_HOST", req.getRemoteHost());
|
||||
|
||||
// The identity information reported about the connection by a
|
||||
// RFC 1413 [11] request to the remote agent, if
|
||||
// available. Servers MAY choose not to support this feature, or
|
||||
|
@ -272,17 +293,33 @@ public class CGI extends HttpServlet
|
|||
// "REMOTE_IDENT" => "NYI"
|
||||
env.set("REMOTE_USER", req.getRemoteUser());
|
||||
env.set("REQUEST_METHOD", req.getMethod());
|
||||
env.set("SCRIPT_NAME", scriptName);
|
||||
|
||||
String scriptPath;
|
||||
String scriptName;
|
||||
// use docRoot for scriptPath, too
|
||||
if(_cgiBinProvided)
|
||||
{
|
||||
scriptPath = command.getAbsolutePath();
|
||||
scriptName = scriptPath.substring(_docRoot.getAbsolutePath().length());
|
||||
}
|
||||
else
|
||||
{
|
||||
String requestURI = req.getRequestURI();
|
||||
scriptName = requestURI.substring(0,requestURI.length() - pathInfo.length());
|
||||
scriptPath = getServletContext().getRealPath(scriptName);
|
||||
}
|
||||
env.set("SCRIPT_FILENAME", scriptPath);
|
||||
env.set("SCRIPT_NAME", scriptName);
|
||||
|
||||
env.set("SERVER_NAME", req.getServerName());
|
||||
env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
|
||||
env.set("SERVER_PROTOCOL", req.getProtocol());
|
||||
env.set("SERVER_SOFTWARE", getServletContext().getServerInfo());
|
||||
|
||||
Enumeration enm = req.getHeaderNames();
|
||||
Enumeration<String> enm = req.getHeaderNames();
|
||||
while (enm.hasMoreElements())
|
||||
{
|
||||
String name = (String)enm.nextElement();
|
||||
String name = enm.nextElement();
|
||||
String value = req.getHeader(name);
|
||||
env.set("HTTP_" + name.toUpperCase(Locale.ENGLISH).replace('-','_'),value);
|
||||
}
|
||||
|
@ -293,29 +330,30 @@ public class CGI extends HttpServlet
|
|||
// "SERVER_URL" => "NYI - http://us0245",
|
||||
// "TZ" => System.getProperty("user.timezone"),
|
||||
|
||||
// are we meant to decode args here ? or does the script get them
|
||||
// via PATH_INFO ? if we are, they should be decoded and passed
|
||||
// are we meant to decode args here? or does the script get them
|
||||
// via PATH_INFO? if we are, they should be decoded and passed
|
||||
// into exec here...
|
||||
String execCmd = path;
|
||||
if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0))
|
||||
String absolutePath = command.getAbsolutePath();
|
||||
String execCmd = absolutePath;
|
||||
|
||||
// escape the execCommand
|
||||
if (execCmd.length() > 0 && execCmd.charAt(0) != '"' && execCmd.indexOf(" ") >= 0)
|
||||
execCmd = "\"" + execCmd + "\"";
|
||||
|
||||
if (_cmdPrefix != null)
|
||||
execCmd = _cmdPrefix + " " + execCmd;
|
||||
|
||||
assert execCmd != null;
|
||||
LOG.debug("Environment: " + env.getExportString());
|
||||
LOG.debug("Command: " + execCmd);
|
||||
|
||||
final Process p;
|
||||
if (dir == null)
|
||||
p = Runtime.getRuntime().exec(execCmd, env.getEnvArray());
|
||||
else
|
||||
p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), dir);
|
||||
final Process p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), _docRoot);
|
||||
|
||||
// hook processes input to browser's output (async)
|
||||
if (bodyFormEncoded != null)
|
||||
writeProcessInput(p, bodyFormEncoded);
|
||||
else if (len > 0)
|
||||
writeProcessInput(p, req.getInputStream(), len);
|
||||
else if (contentLen > 0)
|
||||
writeProcessInput(p, req.getInputStream(), contentLen);
|
||||
|
||||
// hook processes output to browser's input (sync)
|
||||
// if browser closes stream, we should detect it and kill process...
|
||||
|
@ -336,9 +374,9 @@ public class CGI extends HttpServlet
|
|||
{
|
||||
LOG.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// read any headers off the top of our input stream
|
||||
// NOTE: Multiline header items not supported!
|
||||
String line = null;
|
||||
|
@ -383,7 +421,7 @@ public class CGI extends HttpServlet
|
|||
int exitValue = p.exitValue();
|
||||
if (0 != exitValue)
|
||||
{
|
||||
LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + path);
|
||||
LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + absolutePath);
|
||||
if (!res.isCommitted())
|
||||
res.sendError(500,"Failed to exec CGI");
|
||||
}
|
||||
|
@ -393,7 +431,7 @@ public class CGI extends HttpServlet
|
|||
{
|
||||
// browser has probably closed its input stream - we
|
||||
// terminate and clean up...
|
||||
LOG.debug("CGI: Client closed connection!");
|
||||
LOG.debug("CGI: Client closed connection!", e);
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
|
@ -422,6 +460,7 @@ public class CGI extends HttpServlet
|
|||
{
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
|
@ -445,6 +484,7 @@ public class CGI extends HttpServlet
|
|||
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 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.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Map implementation Optimized for Strings keys..
|
||||
* This String Map has been optimized for mapping small sets of
|
||||
* Strings where the most frequently accessed Strings have been put to
|
||||
* the map first.
|
||||
*
|
||||
* It also has the benefit that it can look up entries by substring or
|
||||
* sections of char and byte arrays. This can prevent many String
|
||||
* objects from being created just to look up in the map.
|
||||
*
|
||||
* This map is NOT synchronized.
|
||||
* @deprecated Use {@link Trie}
|
||||
*/
|
||||
public class StringMap<O> extends AbstractMap<String,O>
|
||||
{
|
||||
private final TreeMap<Object, O> _map;
|
||||
|
||||
|
||||
public static final boolean CASE_INSENSTIVE=true;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
private final boolean _caseInsensitive;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Constructor.
|
||||
*/
|
||||
public StringMap()
|
||||
{
|
||||
this(false);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Constructor.
|
||||
* @param ignoreCase
|
||||
*/
|
||||
public StringMap(final boolean ignoreCase)
|
||||
{
|
||||
_caseInsensitive=ignoreCase;
|
||||
_map = new TreeMap<Object,O>(new Comparator<Object>()
|
||||
{
|
||||
@Override
|
||||
public int compare(Object o1, Object o2)
|
||||
{
|
||||
String s1=(o1 instanceof String)?(String)o1:null;
|
||||
ByteBuffer b1=(o1 instanceof ByteBuffer)?(ByteBuffer)o1:null;
|
||||
if (s1==null && b1==null)
|
||||
s1=o1.toString();
|
||||
String s2=(String)o2;
|
||||
|
||||
int n1 = s1==null?b1.remaining():s1.length();
|
||||
int n2 = s2.length();
|
||||
int min = Math.min(n1, n2);
|
||||
for (int i = 0; i < min; i++) {
|
||||
char c1 = s1==null?(char)b1.get(b1.position()+i):s1.charAt(i);
|
||||
char c2 = s2.charAt(i);
|
||||
if (c1 != c2) {
|
||||
if (ignoreCase)
|
||||
{
|
||||
c1 = Character.toUpperCase(c1);
|
||||
c2 = Character.toUpperCase(c2);
|
||||
if (c1 != c2) {
|
||||
c1 = Character.toLowerCase(c1);
|
||||
c2 = Character.toLowerCase(c2);
|
||||
if (c1 != c2) {
|
||||
// No overflow because of numeric promotion
|
||||
return c1 - c2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
return c1 - c2;
|
||||
}
|
||||
}
|
||||
return n1 - n2;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public boolean isIgnoreCase()
|
||||
{
|
||||
return _caseInsensitive;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public O put(String key, O value)
|
||||
{
|
||||
return _map.put(key,value);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public O get(Object key)
|
||||
{
|
||||
return _map.get(key);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public O get(String key)
|
||||
{
|
||||
return _map.get(key);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public O get(String key,int offset,int length)
|
||||
{
|
||||
return _map.get(key.substring(offset,offset+length));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public O get(ByteBuffer buffer)
|
||||
{
|
||||
return _map.get(buffer);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public O remove(Object key)
|
||||
{
|
||||
return _map.remove(key);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public O remove(String key)
|
||||
{
|
||||
return _map.remove(key);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public Set<Map.Entry<String,O>> entrySet()
|
||||
{
|
||||
Object o=_map.entrySet();
|
||||
return Collections.unmodifiableSet((Set<Map.Entry<String,O>>)o);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public int size()
|
||||
{
|
||||
return _map.size();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return _map.isEmpty();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public boolean containsKey(Object key)
|
||||
{
|
||||
return _map.containsKey(key);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public void clear()
|
||||
{
|
||||
_map.clear();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 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.util;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class StringMapTest
|
||||
{
|
||||
StringMap<String> m0;
|
||||
StringMap<String> m1;
|
||||
StringMap<String> m5;
|
||||
StringMap<String> m5i;
|
||||
|
||||
/*
|
||||
* @see TestCase#setUp()
|
||||
*/
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
m0=new StringMap<>();
|
||||
m1=new StringMap<>(false);
|
||||
m1.put("abc", "0");
|
||||
|
||||
m5=new StringMap<>(false);
|
||||
m5.put("a", "0");
|
||||
m5.put("ab", "1");
|
||||
m5.put("abc", "2");
|
||||
m5.put("abb", "3");
|
||||
m5.put("bbb", "4");
|
||||
|
||||
m5i=new StringMap<>(true);
|
||||
m5i.put("ab", "1");
|
||||
m5i.put("abc", "2");
|
||||
m5i.put("abb", "3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize()
|
||||
{
|
||||
Assert.assertEquals(0, m0.size());
|
||||
Assert.assertEquals(1, m1.size());
|
||||
Assert.assertEquals(5, m5.size());
|
||||
Assert.assertEquals(3, m5i.size());
|
||||
|
||||
m1.remove("abc");
|
||||
m5.remove("abc");
|
||||
m5.put("bbb","x");
|
||||
m5i.put("ABC", "x");
|
||||
Assert.assertEquals(0, m0.size());
|
||||
Assert.assertEquals(0, m1.size());
|
||||
Assert.assertEquals(4, m5.size());
|
||||
Assert.assertEquals(3, m5i.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEmpty()
|
||||
{
|
||||
Assert.assertTrue(m0.isEmpty());
|
||||
Assert.assertFalse(m1.isEmpty());
|
||||
Assert.assertFalse(m5.isEmpty());
|
||||
Assert.assertFalse(m5i.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear()
|
||||
{
|
||||
m0.clear();
|
||||
m1.clear();
|
||||
m5.clear();
|
||||
m5i.clear();
|
||||
Assert.assertTrue(m0.isEmpty());
|
||||
Assert.assertTrue(m1.isEmpty());
|
||||
Assert.assertTrue(m5.isEmpty());
|
||||
Assert.assertTrue(m5i.isEmpty());
|
||||
Assert.assertEquals(null, m1.get("abc"));
|
||||
Assert.assertEquals(null, m5.get("abc"));
|
||||
Assert.assertEquals(null, m5i.get("abc"));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test for Object put(Object, Object)
|
||||
*/
|
||||
@Test
|
||||
public void testPutGet()
|
||||
{
|
||||
Assert.assertEquals("2", m5.get("abc"));
|
||||
Assert.assertEquals(null, m5.get("aBc"));
|
||||
Assert.assertEquals("2", m5i.get("abc"));
|
||||
Assert.assertEquals("2", m5i.get("aBc"));
|
||||
|
||||
m5.put("aBc", "x");
|
||||
m5i.put("AbC", "x");
|
||||
|
||||
StringBuilder buffer=new StringBuilder();
|
||||
buffer.append("aBc");
|
||||
Assert.assertEquals("2", m5.get("abc"));
|
||||
Assert.assertEquals("x", m5.get(buffer));
|
||||
Assert.assertEquals("x", m5i.get((Object)"abc"));
|
||||
Assert.assertEquals("x", m5i.get("aBc"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Test for Object remove(Object)
|
||||
*/
|
||||
@Test
|
||||
public void testRemove()
|
||||
{
|
||||
m0.remove("abc");
|
||||
m1.remove("abc");
|
||||
m5.remove("aBc");
|
||||
m5.remove("bbb");
|
||||
m5i.remove("aBc");
|
||||
|
||||
Assert.assertEquals(0, m0.size());
|
||||
Assert.assertEquals(0, m1.size());
|
||||
Assert.assertEquals(4, m5.size());
|
||||
Assert.assertEquals(2, m5i.size());
|
||||
|
||||
Assert.assertEquals("2", m5.get("abc"));
|
||||
Assert.assertEquals(null, m5.get("bbb"));
|
||||
Assert.assertEquals(null, m5i.get("AbC"));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test for Set entrySet()
|
||||
*/
|
||||
@Test
|
||||
public void testEntrySet()
|
||||
{
|
||||
Set es0=m0.entrySet();
|
||||
Set es1=m1.entrySet();
|
||||
Set es5=m5.entrySet();
|
||||
Assert.assertEquals(0, es0.size());
|
||||
Assert.assertEquals(1, es1.size());
|
||||
Assert.assertEquals(5, es5.size());
|
||||
}
|
||||
|
||||
/*
|
||||
* Test for boolean containsKey(Object)
|
||||
*/
|
||||
@Test
|
||||
public void testContainsKey()
|
||||
{
|
||||
Assert.assertTrue(m5.containsKey("abc"));
|
||||
Assert.assertTrue(!m5.containsKey("aBc"));
|
||||
Assert.assertTrue(m5.containsKey("bbb"));
|
||||
Assert.assertTrue(!m5.containsKey("xyz"));
|
||||
|
||||
Assert.assertTrue(m5i.containsKey("abc"));
|
||||
Assert.assertTrue(m5i.containsKey("aBc"));
|
||||
Assert.assertTrue(m5i.containsKey("ABC"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString()
|
||||
{
|
||||
Assert.assertEquals("{}", m0.toString());
|
||||
Assert.assertEquals("{abc=0}", m1.toString());
|
||||
Assert.assertTrue(m5.toString().indexOf("abc=2") > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreCase()
|
||||
{
|
||||
StringMap<String> map = new StringMap<>(true);
|
||||
map.put("POST","1");
|
||||
map.put("HEAD","2");
|
||||
map.put("PUT","3");
|
||||
map.put("OPTIONS","4");
|
||||
map.put("DELETE","5");
|
||||
map.put("TRACE","6");
|
||||
map.put("CONNECT","7");
|
||||
map.put("Upgrade","8");
|
||||
|
||||
Assert.assertEquals("1", map.get("POST"));
|
||||
Assert.assertEquals("1", map.get("pOST"));
|
||||
Assert.assertEquals("1", map.get("Post"));
|
||||
|
||||
Assert.assertEquals("8", map.get("UPGRADE"));
|
||||
Assert.assertEquals("8", map.get("Upgrade"));
|
||||
Assert.assertEquals("8", map.get("upgrade"));
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue