Co-authored-by: Ludovic Orban <lorban@bitronix.be> Co-authored-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
263274891a
commit
7a1c165677
|
@ -291,6 +291,7 @@ public class HttpExchange implements CyclicTimeouts.Expirable
|
|||
{
|
||||
responseState = State.PENDING;
|
||||
responseFailure = null;
|
||||
response.clearHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -411,9 +411,8 @@ public abstract class HttpReceiver
|
|||
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
|
||||
notifier.notifySuccess(listeners, response);
|
||||
|
||||
// Special case for 100 Continue that cannot
|
||||
// be handled by the ContinueProtocolHandler.
|
||||
if (exchange.getResponse().getStatus() == HttpStatus.CONTINUE_100)
|
||||
// Interim responses do not terminate the exchange.
|
||||
if (HttpStatus.isInterim(exchange.getResponse().getStatus()))
|
||||
return true;
|
||||
|
||||
// Mark atomically the response as terminated, with
|
||||
|
|
|
@ -87,6 +87,11 @@ public class HttpResponse implements Response
|
|||
return headers.asImmutable();
|
||||
}
|
||||
|
||||
public void clearHeaders()
|
||||
{
|
||||
headers.clear();
|
||||
}
|
||||
|
||||
public HttpResponse addHeader(HttpField header)
|
||||
{
|
||||
headers.add(header);
|
||||
|
|
|
@ -187,6 +187,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
}
|
||||
else if (read == 0)
|
||||
{
|
||||
assert networkBuffer.isEmpty();
|
||||
releaseNetworkBuffer();
|
||||
fillInterested();
|
||||
return;
|
||||
|
@ -245,18 +246,21 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
this.method = null;
|
||||
if (getHttpChannel().isTunnel(method, status))
|
||||
return true;
|
||||
|
||||
if (networkBuffer.isEmpty())
|
||||
return false;
|
||||
|
||||
if (!HttpStatus.isInformational(status))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Discarding unexpected content after response {}: {}", status, networkBuffer);
|
||||
networkBuffer.clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (networkBuffer.isEmpty())
|
||||
return false;
|
||||
|
||||
if (complete)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Discarding unexpected content after response: {}", networkBuffer);
|
||||
networkBuffer.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,7 +376,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
}
|
||||
|
||||
int status = exchange.getResponse().getStatus();
|
||||
if (status != HttpStatus.CONTINUE_100)
|
||||
if (!HttpStatus.isInterim(status))
|
||||
{
|
||||
inMessages.increment();
|
||||
complete = true;
|
||||
|
|
|
@ -385,15 +385,21 @@ public class HttpGenerator
|
|||
|
||||
// Handle 1xx and no content responses
|
||||
int status = info.getStatus();
|
||||
if (status >= 100 && status < 200)
|
||||
if (HttpStatus.isInformational(status))
|
||||
{
|
||||
_noContentResponse = true;
|
||||
|
||||
if (status != HttpStatus.SWITCHING_PROTOCOLS_101)
|
||||
switch (status)
|
||||
{
|
||||
header.put(HttpTokens.CRLF);
|
||||
_state = State.COMPLETING_1XX;
|
||||
return Result.FLUSH;
|
||||
case HttpStatus.SWITCHING_PROTOCOLS_101:
|
||||
break;
|
||||
case HttpStatus.EARLY_HINT_103:
|
||||
generateHeaders(header, content, last);
|
||||
_state = State.COMPLETING_1XX;
|
||||
return Result.FLUSH;
|
||||
default:
|
||||
header.put(HttpTokens.CRLF);
|
||||
_state = State.COMPLETING_1XX;
|
||||
return Result.FLUSH;
|
||||
}
|
||||
}
|
||||
else if (status == HttpStatus.NO_CONTENT_204 || status == HttpStatus.NOT_MODIFIED_304)
|
||||
|
|
|
@ -88,6 +88,7 @@ public enum HttpHeader
|
|||
AGE("Age"),
|
||||
ALT_SVC("Alt-Svc"),
|
||||
ETAG("ETag"),
|
||||
LINK("Link"),
|
||||
LOCATION("Location"),
|
||||
PROXY_AUTHENTICATE("Proxy-Authenticate"),
|
||||
RETRY_AFTER("Retry-After"),
|
||||
|
|
|
@ -25,6 +25,7 @@ public class HttpStatus
|
|||
public static final int CONTINUE_100 = 100;
|
||||
public static final int SWITCHING_PROTOCOLS_101 = 101;
|
||||
public static final int PROCESSING_102 = 102;
|
||||
public static final int EARLY_HINT_103 = 103;
|
||||
|
||||
public static final int OK_200 = 200;
|
||||
public static final int CREATED_201 = 201;
|
||||
|
@ -103,6 +104,7 @@ public class HttpStatus
|
|||
CONTINUE(CONTINUE_100, "Continue"),
|
||||
SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"),
|
||||
PROCESSING(PROCESSING_102, "Processing"),
|
||||
EARLY_HINT(EARLY_HINT_103, "Early Hint"),
|
||||
|
||||
OK(OK_200, "OK"),
|
||||
CREATED(CREATED_201, "Created"),
|
||||
|
@ -339,6 +341,17 @@ public class HttpStatus
|
|||
return ((100 <= code) && (code <= 199));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the status code is informational but not {@code 101 Switching Protocols}.
|
||||
*
|
||||
* @param code the code to test
|
||||
* @return whether the status code is informational but not {@code 101 Switching Protocols}
|
||||
*/
|
||||
public static boolean isInterim(int code)
|
||||
{
|
||||
return isInformational(code) && code != HttpStatus.SWITCHING_PROTOCOLS_101;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple test against an code to determine if it falls into the
|
||||
* <code>Success</code> message category as defined in the <a
|
||||
|
|
|
@ -120,8 +120,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.
|
|||
if (responseHeaders(exchange))
|
||||
{
|
||||
int status = response.getStatus();
|
||||
boolean informational = HttpStatus.isInformational(status) && status != HttpStatus.SWITCHING_PROTOCOLS_101;
|
||||
if (frame.isEndStream() || informational)
|
||||
if (frame.isEndStream() || HttpStatus.isInterim(status))
|
||||
responseSuccess(exchange);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -98,8 +98,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod());
|
||||
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
|
||||
int status = response.getStatus();
|
||||
boolean interimResponse = status == HttpStatus.CONTINUE_100 || status == HttpStatus.PROCESSING_102;
|
||||
if (interimResponse)
|
||||
if (HttpStatus.isInterim(status))
|
||||
{
|
||||
// Must not commit interim responses.
|
||||
if (hasContent)
|
||||
|
|
|
@ -52,9 +52,11 @@ public class HTTP3StreamClient extends HTTP3Stream implements Stream.Client
|
|||
MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
||||
boolean valid;
|
||||
if (response.getStatus() == HttpStatus.CONTINUE_100)
|
||||
valid = validateAndUpdate(EnumSet.of(FrameState.INITIAL), FrameState.CONTINUE);
|
||||
valid = validateAndUpdate(EnumSet.of(FrameState.INITIAL), FrameState.INFORMATIONAL);
|
||||
else if (response.getStatus() == HttpStatus.EARLY_HINT_103)
|
||||
valid = validateAndUpdate(EnumSet.of(FrameState.INITIAL, FrameState.HEADER, FrameState.INFORMATIONAL), FrameState.INFORMATIONAL);
|
||||
else
|
||||
valid = validateAndUpdate(EnumSet.of(FrameState.INITIAL, FrameState.CONTINUE), FrameState.HEADER);
|
||||
valid = validateAndUpdate(EnumSet.of(FrameState.INITIAL, FrameState.INFORMATIONAL), FrameState.HEADER);
|
||||
if (valid)
|
||||
{
|
||||
notIdle();
|
||||
|
|
|
@ -315,7 +315,7 @@ public abstract class HTTP3Stream implements Stream, CyclicTimeouts.Expirable, A
|
|||
|
||||
protected enum FrameState
|
||||
{
|
||||
INITIAL, CONTINUE, HEADER, DATA, TRAILER, FAILED
|
||||
INITIAL, INFORMATIONAL, HEADER, DATA, TRAILER, FAILED
|
||||
}
|
||||
|
||||
private enum CloseState
|
||||
|
|
|
@ -486,7 +486,12 @@ public abstract class HTTP3StreamConnection extends AbstractConnection
|
|||
else if (metaData.isResponse())
|
||||
{
|
||||
MetaData.Response response = (MetaData.Response)metaData;
|
||||
if (response.getStatus() != HttpStatus.CONTINUE_100)
|
||||
if (HttpStatus.isInformational(response.getStatus()))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("staying in parserDataMode=false for response {} on {}", metaData, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Expect DATA frames now.
|
||||
parserDataMode = true;
|
||||
|
@ -494,11 +499,6 @@ public abstract class HTTP3StreamConnection extends AbstractConnection
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("switching to parserDataMode=true for response {} on {}", metaData, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("staying in parserDataMode=false for response {} on {}", metaData, this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -89,8 +89,7 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Client
|
|||
if (responseHeaders(exchange))
|
||||
{
|
||||
int status = response.getStatus();
|
||||
boolean informational = HttpStatus.isInformational(status) && status != HttpStatus.SWITCHING_PROTOCOLS_101;
|
||||
if (frame.isLast() || informational)
|
||||
if (frame.isLast() || HttpStatus.isInterim(status))
|
||||
responseSuccess(exchange);
|
||||
else
|
||||
stream.demand();
|
||||
|
|
|
@ -71,8 +71,7 @@ public class HttpTransportOverHTTP3 implements HttpTransport
|
|||
boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod());
|
||||
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
|
||||
int status = response.getStatus();
|
||||
boolean interimResponse = status == HttpStatus.CONTINUE_100 || status == HttpStatus.PROCESSING_102;
|
||||
if (interimResponse)
|
||||
if (HttpStatus.isInterim(status))
|
||||
{
|
||||
// Must not commit interim responses.
|
||||
if (hasContent)
|
||||
|
|
|
@ -1044,10 +1044,10 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
_combinedListener.onResponseBegin(_request);
|
||||
_request.onResponseCommit();
|
||||
|
||||
// wrap callback to process 100 responses
|
||||
// wrap callback to process informational responses
|
||||
final int status = response.getStatus();
|
||||
final Callback committed = (status < HttpStatus.OK_200 && status >= HttpStatus.CONTINUE_100)
|
||||
? new Send100Callback(callback)
|
||||
final Callback committed = HttpStatus.isInformational(status)
|
||||
? new Send1XXCallback(callback)
|
||||
: new SendCallback(callback, content, true, complete);
|
||||
|
||||
// committing write
|
||||
|
@ -1477,9 +1477,9 @@ public abstract class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
}
|
||||
}
|
||||
|
||||
private class Send100Callback extends SendCallback
|
||||
private class Send1XXCallback extends SendCallback
|
||||
{
|
||||
private Send100Callback(Callback callback)
|
||||
private Send1XXCallback(Callback callback)
|
||||
{
|
||||
super(callback, null, false, false);
|
||||
}
|
||||
|
|
|
@ -470,6 +470,7 @@ public class Response implements HttpServletResponse
|
|||
* <p>In addition to the servlet standard handling, this method supports some additional codes:</p>
|
||||
* <dl>
|
||||
* <dt>102</dt><dd>Send a partial PROCESSING response and allow additional responses</dd>
|
||||
* <dt>103</dt><dd>Send a partial EARLY_HINT response as per <a href="https://datatracker.ietf.org/doc/html/rfc8297">RFC8297</a></dd>
|
||||
* <dt>-1</dt><dd>Abort the HttpChannel and close the connection/stream</dd>
|
||||
* </dl>
|
||||
* @param code The error code
|
||||
|
@ -490,6 +491,9 @@ public class Response implements HttpServletResponse
|
|||
case HttpStatus.PROCESSING_102:
|
||||
sendProcessing();
|
||||
break;
|
||||
case HttpStatus.EARLY_HINT_103:
|
||||
sendEarlyHint();
|
||||
break;
|
||||
default:
|
||||
_channel.getState().sendError(code, message);
|
||||
break;
|
||||
|
@ -498,9 +502,8 @@ public class Response implements HttpServletResponse
|
|||
|
||||
/**
|
||||
* Sends a 102-Processing response.
|
||||
* If the connection is an HTTP connection, the version is 1.1 and the
|
||||
* request has a Expect header starting with 102, then a 102 response is
|
||||
* sent. This indicates that the request still be processed and real response
|
||||
* If the request had an Expect header starting with 102, then
|
||||
* a 102 response is sent. This indicates that the request still be processed and real response
|
||||
* can still be sent. This method is called by sendError if it is passed 102.
|
||||
*
|
||||
* @throws IOException if unable to send the 102 response
|
||||
|
@ -514,6 +517,22 @@ public class Response implements HttpServletResponse
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a 103 Early Hint response.
|
||||
*
|
||||
* Send a 103 response as per <a href="https://datatracker.ietf.org/doc/html/rfc8297">RFC8297</a>
|
||||
* This method is called by sendError if it is passed 103.
|
||||
*
|
||||
* @throws IOException if unable to send the 103 response
|
||||
* @see javax.servlet.http.HttpServletResponse#sendError(int)
|
||||
*/
|
||||
public void sendEarlyHint() throws IOException
|
||||
{
|
||||
if (!isCommitted())
|
||||
_channel.sendResponse(new MetaData.Response(_channel.getRequest().getHttpVersion(), HttpStatus.EARLY_HINT_103,
|
||||
_channel.getResponse()._fields.asImmutable()), null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a response with one of the 300 series redirection codes.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.HttpConversation;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.ProtocolHandler;
|
||||
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.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import static org.eclipse.jetty.http.client.Transport.FCGI;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class InformationalResponseTest extends AbstractTest<TransportScenario>
|
||||
{
|
||||
@Override
|
||||
public void init(Transport transport) throws IOException
|
||||
{
|
||||
// Skip FCGI for now, not much interested in its server-side behavior.
|
||||
Assumptions.assumeTrue(transport != FCGI);
|
||||
setScenario(new TransportScenario(transport));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(TransportProvider.class)
|
||||
public void test102Processing(Transport transport) throws Exception
|
||||
{
|
||||
init(transport);
|
||||
scenario.start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
jettyRequest.setHandled(true);
|
||||
response.sendError(HttpStatus.PROCESSING_102);
|
||||
response.sendError(HttpStatus.PROCESSING_102);
|
||||
response.setStatus(200);
|
||||
response.getOutputStream().print("OK");
|
||||
}
|
||||
});
|
||||
long idleTimeout = 10000;
|
||||
scenario.setRequestIdleTimeout(idleTimeout);
|
||||
|
||||
scenario.client.getProtocolHandlers().put(new ProtocolHandler()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "Processing";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(org.eclipse.jetty.client.api.Request request, Response response)
|
||||
{
|
||||
return response.getStatus() == HttpStatus.PROCESSING_102;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response.Listener getResponseListener()
|
||||
{
|
||||
return new Response.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
var request = response.getRequest();
|
||||
HttpConversation conversation = ((HttpRequest)request).getConversation();
|
||||
// Reset the conversation listeners, since we are going to receive another response code
|
||||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
if (exchange != null && response.getStatus() == HttpStatus.PROCESSING_102)
|
||||
{
|
||||
// All good, continue.
|
||||
exchange.resetResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
response.abort(new IllegalStateException("should not have accepted"));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
CountDownLatch complete = new CountDownLatch(1);
|
||||
AtomicReference<Response> response = new AtomicReference<>();
|
||||
BufferingResponseListener listener = new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
response.set(result.getResponse());
|
||||
complete.countDown();
|
||||
}
|
||||
};
|
||||
scenario.client.newRequest(scenario.newURI())
|
||||
.method("GET")
|
||||
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.PROCESSING))
|
||||
.timeout(10, TimeUnit.SECONDS)
|
||||
.send(listener);
|
||||
|
||||
assertTrue(complete.await(10, TimeUnit.SECONDS));
|
||||
assertThat(response.get().getStatus(), is(200));
|
||||
assertThat(listener.getContentAsString(), is("OK"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(TransportProvider.class)
|
||||
public void test103EarlyHint(Transport transport) throws Exception
|
||||
{
|
||||
init(transport);
|
||||
scenario.start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
jettyRequest.setHandled(true);
|
||||
response.setHeader("Hint", "one");
|
||||
response.sendError(HttpStatus.EARLY_HINT_103);
|
||||
response.setHeader("Hint", "two");
|
||||
response.sendError(HttpStatus.EARLY_HINT_103);
|
||||
response.setHeader("Hint", "three");
|
||||
response.setStatus(200);
|
||||
response.getOutputStream().print("OK");
|
||||
}
|
||||
});
|
||||
long idleTimeout = 10000;
|
||||
scenario.setRequestIdleTimeout(idleTimeout);
|
||||
|
||||
List<String> hints = new CopyOnWriteArrayList<>();
|
||||
scenario.client.getProtocolHandlers().put(new ProtocolHandler()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "EarlyHint";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(org.eclipse.jetty.client.api.Request request, Response response)
|
||||
{
|
||||
return response.getStatus() == HttpStatus.EARLY_HINT_103;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response.Listener getResponseListener()
|
||||
{
|
||||
return new Response.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
var request = response.getRequest();
|
||||
HttpConversation conversation = ((HttpRequest)request).getConversation();
|
||||
// Reset the conversation listeners, since we are going to receive another response code
|
||||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
if (exchange != null && response.getStatus() == HttpStatus.EARLY_HINT_103)
|
||||
{
|
||||
// All good, continue.
|
||||
hints.add(response.getHeaders().get("Hint"));
|
||||
exchange.resetResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
response.abort(new IllegalStateException("should not have accepted"));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
CountDownLatch complete = new CountDownLatch(1);
|
||||
AtomicReference<Response> response = new AtomicReference<>();
|
||||
BufferingResponseListener listener = new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
hints.add(result.getResponse().getHeaders().get("Hint"));
|
||||
response.set(result.getResponse());
|
||||
complete.countDown();
|
||||
}
|
||||
};
|
||||
scenario.client.newRequest(scenario.newURI())
|
||||
.method("GET")
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send(listener);
|
||||
|
||||
assertTrue(complete.await(5, TimeUnit.SECONDS));
|
||||
assertThat(response.get().getStatus(), is(200));
|
||||
assertThat(listener.getContentAsString(), is("OK"));
|
||||
assertThat(hints, contains("one", "two", "three"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue