blob: 27a5947b728021c5d60bf8342b7ce0318f62f790 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 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.http.client;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.ContinueProtocolHandler;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
public class HttpClientContinueTest extends AbstractTest
{
public HttpClientContinueTest(Transport transport)
{
// Skip FCGI for now.
super(transport == Transport.FCGI ? null : transport);
}
@Test
public void test_Expect100Continue_WithOneContent_Respond100Continue() throws Exception
{
test_Expect100Continue_Respond100Continue("data1".getBytes(StandardCharsets.UTF_8));
}
@Test
public void test_Expect100Continue_WithMultipleContents_Respond100Continue() throws Exception
{
test_Expect100Continue_Respond100Continue("data1".getBytes(StandardCharsets.UTF_8), "data2".getBytes(StandardCharsets.UTF_8), "data3".getBytes(StandardCharsets.UTF_8));
}
private void test_Expect100Continue_Respond100Continue(byte[]... contents) throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and copy the content back
IO.copy(request.getInputStream(), response.getOutputStream());
}
});
ContentResponse response = client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(contents))
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
int index = 0;
byte[] responseContent = response.getContent();
for (byte[] content : contents)
{
for (byte b : content)
{
Assert.assertEquals(b, responseContent[index++]);
}
}
}
@Test
public void test_Expect100Continue_WithChunkedContent_Respond100Continue() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and copy the content back
ServletInputStream input = request.getInputStream();
// Make sure we chunk the response too
response.flushBuffer();
IO.copy(input, response.getOutputStream());
}
});
byte[] content1 = new byte[10240];
byte[] content2 = new byte[16384];
ContentResponse response = client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content1, content2)
{
@Override
public long getLength()
{
return -1;
}
})
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
int index = 0;
byte[] responseContent = response.getContent();
for (byte b : content1)
Assert.assertEquals(b, responseContent[index++]);
for (byte b : content2)
Assert.assertEquals(b, responseContent[index++]);
}
@Test
public void test_Expect100Continue_WithContent_Respond417ExpectationFailed() throws Exception
{
test_Expect100Continue_WithContent_RespondError(417);
}
@Test
public void test_Expect100Continue_WithContent_Respond413RequestEntityTooLarge() throws Exception
{
test_Expect100Continue_WithContent_RespondError(413);
}
private void test_Expect100Continue_WithContent_RespondError(final int error) throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.sendError(error);
}
});
byte[] content1 = new byte[10240];
byte[] content2 = new byte[16384];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content1, content2))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNotNull(result.getRequestFailure());
Assert.assertNull(result.getResponseFailure());
byte[] content = getContent();
Assert.assertNotNull(content);
Assert.assertTrue(content.length > 0);
Assert.assertEquals(error, result.getResponse().getStatus());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Expect100Continue_WithContent_WithRedirect() throws Exception
{
final String data = "success";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (request.getRequestURI().endsWith("/done"))
{
response.getOutputStream().print(data);
}
else
{
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
// Send a redirect
response.sendRedirect("/done");
}
}
});
byte[] content = new byte[10240];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI())
.method(HttpMethod.POST)
.path("/continue")
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertFalse(result.isFailed());
Assert.assertEquals(200, result.getResponse().getStatus());
Assert.assertEquals(data, getContentAsString());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Redirect_WithExpect100Continue_WithContent() throws Exception
{
// A request with Expect: 100-Continue cannot receive non-final responses like 3xx
final String data = "success";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (request.getRequestURI().endsWith("/done"))
{
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
response.getOutputStream().print(data);
}
else
{
// Send a redirect
response.sendRedirect("/done");
}
}
});
byte[] content = new byte[10240];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI())
.method(HttpMethod.POST)
.path("/redirect")
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNotNull(result.getRequestFailure());
Assert.assertNull(result.getResponseFailure());
Assert.assertEquals(302, result.getResponse().getStatus());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Slow
@Test
public void test_Expect100Continue_WithContent_WithResponseFailure_Before100Continue() throws Exception
{
final long idleTimeout = 1000;
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
try
{
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
client.setIdleTimeout(idleTimeout);
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNotNull(result.getRequestFailure());
Assert.assertNotNull(result.getResponseFailure());
latch.countDown();
}
});
Assert.assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Slow
@Test
public void test_Expect100Continue_WithContent_WithResponseFailure_After100Continue() throws Exception
{
final long idleTimeout = 1000;
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
try
{
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
client.setIdleTimeout(idleTimeout);
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNull(result.getRequestFailure());
Assert.assertNotNull(result.getResponseFailure());
latch.countDown();
}
});
Assert.assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Test
public void test_Expect100Continue_WithContent_WithResponseFailure_During100Continue() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
}
});
client.getProtocolHandlers().clear();
client.getProtocolHandlers().put(new ContinueProtocolHandler()
{
@Override
public Response.Listener getResponseListener()
{
final Response.Listener listener = super.getResponseListener();
return new Response.Listener.Adapter()
{
@Override
public void onBegin(Response response)
{
response.abort(new Exception());
}
@Override
public void onFailure(Response response, Throwable failure)
{
listener.onFailure(response, failure);
}
};
}
});
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNotNull(result.getRequestFailure());
Assert.assertNotNull(result.getResponseFailure());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Slow
@Test
public void test_Expect100Continue_WithDeferredContent_Respond100Continue() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and echo the content
IO.copy(request.getInputStream(), response.getOutputStream());
}
});
final byte[] chunk1 = new byte[]{0, 1, 2, 3};
final byte[] chunk2 = new byte[]{4, 5, 6, 7};
final byte[] data = new byte[chunk1.length + chunk2.length];
System.arraycopy(chunk1, 0, data, 0, chunk1.length);
System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
final CountDownLatch latch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider();
client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(content)
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertArrayEquals(data, getContent());
latch.countDown();
}
});
Thread.sleep(1000);
content.offer(ByteBuffer.wrap(chunk1));
Thread.sleep(1000);
content.offer(ByteBuffer.wrap(chunk2));
content.close();
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Slow
@Test
public void test_Expect100Continue_WithInitialAndDeferredContent_Respond100Continue() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and echo the content
IO.copy(request.getInputStream(), response.getOutputStream());
}
});
final byte[] chunk1 = new byte[]{0, 1, 2, 3};
final byte[] chunk2 = new byte[]{4, 5, 6, 7};
final byte[] data = new byte[chunk1.length + chunk2.length];
System.arraycopy(chunk1, 0, data, 0, chunk1.length);
System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
final CountDownLatch latch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1));
client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(content)
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertArrayEquals(data, getContent());
latch.countDown();
}
});
Thread.sleep(1000);
content.offer(ByteBuffer.wrap(chunk2));
content.close();
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Expect100Continue_WithConcurrentDeferredContent_Respond100Continue() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and echo the content
IO.copy(request.getInputStream(), response.getOutputStream());
}
});
final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
final DeferredContentProvider content = new DeferredContentProvider();
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.onRequestHeaders(request ->
{
content.offer(ByteBuffer.wrap(data));
content.close();
})
.content(content)
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertArrayEquals(data, getContent());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Expect100Continue_WithInitialAndConcurrentDeferredContent_Respond100Continue() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and echo the content
IO.copy(request.getInputStream(), response.getOutputStream());
}
});
final byte[] chunk1 = new byte[]{0, 1, 2, 3};
final byte[] chunk2 = new byte[]{4, 5, 6};
final byte[] data = new byte[chunk1.length + chunk2.length];
System.arraycopy(chunk1, 0, data, 0, chunk1.length);
System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
final DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1));
client.getProtocolHandlers().put(new ContinueProtocolHandler()
{
@Override
public Response.Listener getResponseListener()
{
return new ContinueListener()
{
@Override
public void onHeaders(Response response)
{
super.onHeaders(response);
content.offer(ByteBuffer.wrap(chunk2));
content.close();
}
};
}
});
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest(newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(content)
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertArrayEquals(data, getContent());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Expect100Continue_WithTwoResponsesInOneRead() throws Exception
{
Assume.assumeThat(transport, Matchers.isOneOf(Transport.HTTP, Transport.HTTPS));
// There is a chance that the server replies with the 100 Continue response
// and immediately after with the "normal" response, say a 200 OK.
// These may be read by the client in a single read, and must be handled correctly.
startClient();
try (ServerSocket server = new ServerSocket())
{
server.bind(new InetSocketAddress("localhost", 0));
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", server.getLocalPort())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(new byte[]{0}))
.send(result ->
{
Assert.assertTrue(result.toString(), result.isSucceeded());
Assert.assertEquals(200, result.getResponse().getStatus());
latch.countDown();
});
try (Socket socket = server.accept())
{
// Read the request headers.
InputStream input = socket.getInputStream();
int crlfs = 0;
while (true)
{
int read = input.read();
if (read == '\r' || read == '\n')
++crlfs;
else
crlfs = 0;
if (crlfs == 4)
break;
}
OutputStream output = socket.getOutputStream();
String responses = "" +
"HTTP/1.1 100 Continue\r\n" +
"\r\n" +
"HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"10\r\n" +
"0123456789ABCDEF\r\n";
output.write(responses.getBytes(StandardCharsets.UTF_8));
output.flush();
Thread.sleep(1000);
String content = "" +
"10\r\n" +
"0123456789ABCDEF\r\n" +
"0\r\n" +
"\r\n";
output.write(content.getBytes(StandardCharsets.UTF_8));
output.flush();
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
}
}