use of com.linkedin.r2.transport.common.bridge.common.TransportResponse in project rest.li by linkedin.
the class TestHttp2NettyStreamClient method testChannelReusedAfterRequestTimeout.
/**
* When a request fails due to {@link TimeoutException}, connection should not be destroyed.
* @throws Exception
*/
@Test(timeOut = TEST_TIMEOUT)
public void testChannelReusedAfterRequestTimeout() throws Exception {
final HttpServerBuilder.HttpServerStatsProvider statsProvider = new HttpServerBuilder.HttpServerStatsProvider();
final HttpServerBuilder serverBuilder = new HttpServerBuilder();
final Server server = serverBuilder.serverStatsProvider(statsProvider).stopTimeout(0).build();
final HttpClientBuilder clientBuilder = new HttpClientBuilder(_eventLoop, _scheduler);
final Http2NettyStreamClient client = clientBuilder.setRequestTimeout(1000).buildHttp2StreamClient();
final TransportResponse<StreamResponse> response1;
final TransportResponse<StreamResponse> response2;
try {
server.start();
final StreamRequestBuilder builder1 = new StreamRequestBuilder(new URI(URL));
final StreamRequest request1 = builder1.setMethod(METHOD).setHeader(HttpHeaderNames.HOST.toString(), HOST_NAME.toString()).build(EntityStreams.newEntityStream(new TimeoutWriter()));
final FutureTransportCallback<StreamResponse> callback1 = new FutureTransportCallback<>();
client.streamRequest(request1, new RequestContext(), new HashMap<>(), callback1);
response1 = callback1.get();
final StreamRequestBuilder builder2 = new StreamRequestBuilder(new URI(URL));
final StreamRequest request2 = builder2.setMethod(METHOD).setHeader(HttpHeaderNames.HOST.toString(), HOST_NAME.toString()).build(EntityStreams.newEntityStream(new ByteStringWriter(ByteString.copy(new byte[REQUEST_SIZE]))));
final FutureTransportCallback<StreamResponse> callback2 = new FutureTransportCallback<>();
client.streamRequest(request2, new RequestContext(), new HashMap<>(), callback2);
response2 = callback2.get();
} finally {
server.stop();
}
// The 1st request should be failed with timeout
Assert.assertNotNull(response1);
Assert.assertTrue(response1.hasError());
Assert.assertNotNull(response1.getError());
ExceptionTestUtil.verifyCauseChain(response1.getError(), TimeoutException.class);
// The 2nd request should succeed
Assert.assertNotNull(response2);
Assert.assertFalse(response2.hasError());
response2.getResponse().getEntityStream().setReader(new DrainReader());
// The server should have seen 2 requests but establishes only 1 connection with the client
Assert.assertEquals(statsProvider.requestCount(), 3);
Assert.assertEquals(statsProvider.clientConnections().size(), 1);
}
use of com.linkedin.r2.transport.common.bridge.common.TransportResponse in project rest.li by linkedin.
the class AbstractAsyncR2Servlet method service.
@Override
public void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
RequestContext requestContext = ServletHelper.readRequestContext(req);
RestRequest restRequest;
try {
restRequest = readFromServletRequest(req);
} catch (URISyntaxException e) {
writeToServletError(resp, RestStatus.BAD_REQUEST, e.toString());
return;
}
final AsyncContext ctx = req.startAsync(req, resp);
ctx.setTimeout(_timeout);
ctx.addListener(new AsyncListener() {
@Override
public void onTimeout(AsyncEvent event) throws IOException {
AsyncContext ctx = event.getAsyncContext();
writeToServletError((HttpServletResponse) ctx.getResponse(), RestStatus.INTERNAL_SERVER_ERROR, "Server Timeout");
ctx.complete();
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
// Nothing to do here
}
@Override
public void onError(AsyncEvent event) throws IOException {
writeToServletError((HttpServletResponse) event.getSuppliedResponse(), RestStatus.INTERNAL_SERVER_ERROR, "Server Error");
ctx.complete();
}
@Override
public void onComplete(AsyncEvent event) throws IOException {
Object exception = req.getAttribute(TRANSPORT_CALLBACK_IOEXCEPTION);
if (exception != null)
throw new IOException((IOException) exception);
}
});
TransportCallback<RestResponse> callback = new TransportCallback<RestResponse>() {
@Override
public void onResponse(final TransportResponse<RestResponse> response) {
// TransportCallback is usually invoked by non-servlet threads; hence we cannot assume that it's ok to
// do blocking IO there. As a result, we should use AsyncContext.start() to do blocking IO using the
// container/servlet threads. This still maintains the advantage of Async, meaning servlet thread is not
// blocking-wait when the response is not ready.
ctx.start(new Runnable() {
@Override
public void run() {
try {
writeToServletResponse(response, (HttpServletResponse) ctx.getResponse());
} catch (IOException e) {
req.setAttribute(TRANSPORT_CALLBACK_IOEXCEPTION, e);
} finally {
ctx.complete();
}
}
});
}
};
getDispatcher().handleRequest(restRequest, requestContext, callback);
}
use of com.linkedin.r2.transport.common.bridge.common.TransportResponse in project rest.li by linkedin.
the class AbstractR2Servlet method writeToServletResponse.
protected void writeToServletResponse(TransportResponse<RestResponse> response, HttpServletResponse resp) throws IOException {
Map<String, String> wireAttrs = response.getWireAttributes();
for (Map.Entry<String, String> e : WireAttributeHelper.toWireAttributes(wireAttrs).entrySet()) {
resp.setHeader(e.getKey(), e.getValue());
}
RestResponse restResponse = null;
if (response.hasError()) {
Throwable e = response.getError();
if (e instanceof RestException) {
restResponse = ((RestException) e).getResponse();
}
if (restResponse == null) {
restResponse = RestStatus.responseForError(RestStatus.INTERNAL_SERVER_ERROR, e);
}
} else {
restResponse = response.getResponse();
}
resp.setStatus(restResponse.getStatus());
Map<String, String> headers = restResponse.getHeaders();
for (Map.Entry<String, String> e : headers.entrySet()) {
// TODO multi-valued headers
resp.setHeader(e.getKey(), e.getValue());
}
for (String cookie : restResponse.getCookies()) {
resp.addHeader(HttpConstants.RESPONSE_COOKIE_HEADER_NAME, cookie);
}
final ByteString entity = restResponse.getEntity();
entity.write(resp.getOutputStream());
resp.getOutputStream().close();
}
use of com.linkedin.r2.transport.common.bridge.common.TransportResponse in project rest.li by linkedin.
the class AbstractR2Servlet method service.
@Override
protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
RequestContext requestContext = ServletHelper.readRequestContext(req);
RestRequest restRequest;
try {
restRequest = readFromServletRequest(req);
} catch (URISyntaxException e) {
writeToServletError(resp, RestStatus.BAD_REQUEST, e.toString());
return;
}
final AtomicReference<TransportResponse<RestResponse>> result = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
TransportCallback<RestResponse> callback = new TransportCallback<RestResponse>() {
@Override
public void onResponse(TransportResponse<RestResponse> response) {
result.set(response);
latch.countDown();
}
};
getDispatcher().handleRequest(restRequest, requestContext, callback);
try {
if (latch.await(_timeout, TimeUnit.MILLISECONDS)) {
writeToServletResponse(result.get(), resp);
} else {
writeToServletError(resp, RestStatus.INTERNAL_SERVER_ERROR, "Server Timeout after " + _timeout + "ms.");
}
} catch (InterruptedException e) {
throw new ServletException("Interrupted!", e);
}
}
use of com.linkedin.r2.transport.common.bridge.common.TransportResponse in project rest.li by linkedin.
the class HttpDispatcher method handleRequest.
/**
* handle a {@link com.linkedin.r2.message.stream.StreamRequest} using the given request context.
* @see com.linkedin.r2.transport.common.bridge.server.TransportDispatcher#handleStreamRequest
*
* @param req the request to be handled.
* @param context the request context.
* @param callback the callback to be invoked with the response or error.
*/
public void handleRequest(StreamRequest req, RequestContext context, final TransportCallback<StreamResponse> callback) {
markOnRequestTimings(context);
final Map<String, String> headers = new HashMap<>(req.getHeaders());
final Map<String, String> wireAttrs = WireAttributeHelper.removeWireAttributes(headers);
final BaseConnector connector = new BaseConnector();
try {
MessageType.Type msgType = MessageType.getMessageType(wireAttrs, MessageType.Type.REST);
switch(msgType) {
default:
case REST:
req.getEntityStream().setReader(connector);
StreamRequest newReq = req.builder().build(EntityStreams.newEntityStream(connector));
// decorate the call back so that if response is error or response finishes streaming,
// we cancel the request stream
TransportCallback<StreamResponse> decorateCallback = new TransportCallback<StreamResponse>() {
@Override
public void onResponse(TransportResponse<StreamResponse> response) {
// no need to check StreamException because that's handled by HttpBridge.httpToStreamCallback
if (response.hasError()) {
connector.cancel();
} else {
Observer observer = new Observer() {
@Override
public void onDataAvailable(ByteString data) {
// do nothing
}
@Override
public void onDone() {
connector.cancel();
}
@Override
public void onError(Throwable e) {
connector.cancel();
}
};
response.getResponse().getEntityStream().addObserver(observer);
}
callback.onResponse(response);
}
};
_dispatcher.handleStreamRequest(HttpBridge.toStreamRequest(newReq, headers), wireAttrs, context, HttpBridge.httpToStreamCallback(decorateCallback));
}
} catch (Exception e) {
connector.cancel();
callback.onResponse(TransportResponseImpl.<StreamResponse>error(e, Collections.<String, String>emptyMap()));
}
}
Aggregations