diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 5a40a2c4061..cc83372c437 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -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) { } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java index 6bba6a58205..73c13ad45da 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java @@ -140,8 +140,11 @@ public abstract class PoolingHttpDestination 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()) diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index a6f7de6ec3b..1c33cf0518d 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -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); } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index eed8c892a60..00c6778d5e3 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -69,7 +69,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map 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 promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 262a7c498c9..f14143bfb0c 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -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 fcgiDestination = - (PoolingHttpDestination)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 { diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java index 39619268a7b..e113dd29b78 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java @@ -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; } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index 4ba70b44e22..d7a7dc6604d 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -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); + } } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java index 419536af77d..1f77eaf0ea6 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java @@ -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() diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java index 577219c301c..5739ef32012 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java @@ -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) + { + + } } } diff --git a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index 5f80ed2827c..826ed84b43e 100644 --- a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -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(); diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java index fd62656164e..91459ed7798 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java @@ -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() - { - } } diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index c69253e4ded..2e8187966e0 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -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()); + } + } } } diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java index 103a326eabb..2146f6305be 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java @@ -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()); + } } diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 5a02af5fdbf..bd5486a5936 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -102,6 +102,7 @@ org.eclipse.jetty.osgi.boot;singleton:=true org.eclipse.jetty.osgi.boot.JettyBootstrapActivator + osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer org.eclipse.jetty.*;version="[9.1,10.0)" javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java index dc2a9c65599..ba56bc7d90c 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java @@ -47,32 +47,33 @@ import org.eclipse.jetty.util.log.Logger; //----------------------------------------------------------------------------- /** * CGI Servlet. - *

- * 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. - *
- * Not that this only works for extracted war files as "jar cf" will not reserve the execute permissions on the cgi files. - *

- * 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. - *

- * The "Path" init param is passed to the exec environment as PATH. Note: Must be run unpacked somewhere in the filesystem. - *

- * 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. + *

+ * + * The following init parameters are used to configure this servlet: + *

+ *
cgibinResourceBase
Path to the cgi bin directory if set or it will default to the resource base of the context.
+ *
resourceBase
An alias for cgibinResourceBase.
+ *
cgibinResourceBaseIsRelative
If true then cgibinResourceBase is relative to the webapp (eg "WEB-INF/cgi")
+ *
commandPrefix
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.
+ *
Path
passed to the exec environment as PATH.
+ *
ENV_*
used to set an arbitrary environment variable with the name stripped of the leading ENV_ and using the init parameter value
+ *
useFullPath
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
+ *
+ * */ 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 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 parameterMap = new MultiMap(); - Enumeration names = req.getParameterNames(); + Enumeration 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 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 diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java deleted file mode 100644 index 63f5cd7d9ca..00000000000 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java +++ /dev/null @@ -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 extends AbstractMap -{ - private final TreeMap _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(new Comparator() - { - @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> entrySet() - { - Object o=_map.entrySet(); - return Collections.unmodifiableSet((Set>)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(); - } - -} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java deleted file mode 100644 index c20e9f16269..00000000000 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java +++ /dev/null @@ -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 m0; - StringMap m1; - StringMap m5; - StringMap 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 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")); - - } - -}