com.facebook.presto.jdbc.internal.jetty.client.HttpReceiver Maven / Gradle / Ivy
//
// ========================================================================
// 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 com.facebook.presto.jdbc.internal.jetty.client;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import com.facebook.presto.jdbc.internal.jetty.client.api.Response;
import com.facebook.presto.jdbc.internal.jetty.client.api.Result;
import com.facebook.presto.jdbc.internal.jetty.http.HttpField;
import com.facebook.presto.jdbc.internal.jetty.http.HttpHeader;
import com.facebook.presto.jdbc.internal.jetty.util.BufferUtil;
import com.facebook.presto.jdbc.internal.jetty.util.log.Log;
import com.facebook.presto.jdbc.internal.jetty.util.log.Logger;
/**
* {@link HttpReceiver} provides the abstract code to implement the various steps of the receive of HTTP responses.
*
* {@link HttpReceiver} maintains a state machine that is updated when the steps of receiving a response are executed.
*
* Subclasses must handle the transport-specific details, for example how to read from the raw socket and how to parse
* the bytes read from the socket. Then they have to call the methods defined in this class in the following order:
*
* - {@link #responseBegin(HttpExchange)}, when the HTTP response data containing the HTTP status code
* is available
* - {@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available
* - {@link #responseHeaders(HttpExchange)}, when all HTTP headers are available
* - {@link #responseContent(HttpExchange, ByteBuffer)}, when HTTP content is available; this is the only method
* that may be invoked multiple times with different buffers containing different content
* - {@link #responseSuccess(HttpExchange)}, when the response is complete
*
* At any time, subclasses may invoke {@link #responseFailure(Throwable)} to indicate that the response has failed
* (for example, because of I/O exceptions).
* At any time, user threads may abort the response which will cause {@link #responseFailure(Throwable)} to be
* invoked.
*
* The state machine maintained by this class ensures that the response steps are not executed by an I/O thread
* if the response has already been failed.
*
* @see HttpSender
*/
public abstract class HttpReceiver
{
protected static final Logger LOG = Log.getLogger(HttpReceiver.class);
private final AtomicReference responseState = new AtomicReference<>(ResponseState.IDLE);
private final HttpChannel channel;
private volatile ContentDecoder decoder;
protected HttpReceiver(HttpChannel channel)
{
this.channel = channel;
}
protected HttpChannel getHttpChannel()
{
return channel;
}
protected HttpExchange getHttpExchange()
{
return channel.getHttpExchange();
}
protected HttpDestination getHttpDestination()
{
return channel.getHttpDestination();
}
/**
* Method to be invoked when the response status code is available.
*
* Subclasses must have set the response status code on the {@link Response} object of the {@link HttpExchange}
* prior invoking this method.
*
* This method takes case of notifying {@link com.facebook.presto.jdbc.internal.jetty.client.api.Response.BeginListener}s.
*
* @param exchange the HTTP exchange
* @return whether the processing should continue
*/
protected boolean responseBegin(HttpExchange exchange)
{
if (!updateResponseState(ResponseState.IDLE, ResponseState.BEGIN))
return false;
HttpConversation conversation = exchange.getConversation();
HttpResponse response = exchange.getResponse();
// Probe the protocol handlers
HttpDestination destination = getHttpDestination();
HttpClient client = destination.getHttpClient();
ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.getRequest(), response);
Response.Listener handlerListener = null;
if (protocolHandler != null)
{
handlerListener = protocolHandler.getResponseListener();
LOG.debug("Found protocol handler {}", protocolHandler);
}
exchange.getConversation().updateResponseListeners(handlerListener);
LOG.debug("Response begin {}", response);
ResponseNotifier notifier = destination.getResponseNotifier();
notifier.notifyBegin(conversation.getResponseListeners(), response);
return true;
}
/**
* Method to be invoked when a response HTTP header is available.
*
* Subclasses must not have added the header to the {@link Response} object of the {@link HttpExchange}
* prior invoking this method.
*
* This method takes case of notifying {@link com.facebook.presto.jdbc.internal.jetty.client.api.Response.HeaderListener}s and storing cookies.
*
* @param exchange the HTTP exchange
* @param field the response HTTP field
* @return whether the processing should continue
*/
protected boolean responseHeader(HttpExchange exchange, HttpField field)
{
out: while (true)
{
ResponseState current = responseState.get();
switch (current)
{
case BEGIN:
case HEADER:
{
if (updateResponseState(current, ResponseState.HEADER))
break out;
break;
}
default:
{
return false;
}
}
}
HttpResponse response = exchange.getResponse();
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
boolean process = notifier.notifyHeader(exchange.getConversation().getResponseListeners(), response, field);
if (process)
{
response.getHeaders().add(field);
HttpHeader fieldHeader = field.getHeader();
if (fieldHeader != null)
{
switch (fieldHeader)
{
case SET_COOKIE:
case SET_COOKIE2:
{
storeCookie(exchange.getRequest().getURI(), field);
break;
}
default:
{
break;
}
}
}
}
return true;
}
protected void storeCookie(URI uri, HttpField field)
{
try
{
String value = field.getValue();
if (value != null)
{
Map> header = new HashMap<>(1);
header.put(field.getHeader().asString(), Collections.singletonList(value));
getHttpDestination().getHttpClient().getCookieManager().put(uri, header);
}
}
catch (IOException x)
{
LOG.debug(x);
}
}
/**
* Method to be invoked after all response HTTP headers are available.
*
* This method takes case of notifying {@link com.facebook.presto.jdbc.internal.jetty.client.api.Response.HeadersListener}s.
*
* @param exchange the HTTP exchange
* @return whether the processing should continue
*/
protected boolean responseHeaders(HttpExchange exchange)
{
out: while (true)
{
ResponseState current = responseState.get();
switch (current)
{
case BEGIN:
case HEADER:
{
if (updateResponseState(current, ResponseState.HEADERS))
break out;
break;
}
default:
{
return false;
}
}
}
HttpResponse response = exchange.getResponse();
if (LOG.isDebugEnabled())
LOG.debug("Response headers {}{}{}", response, System.getProperty("line.separator"), response.getHeaders().toString().trim());
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response);
Enumeration contentEncodings = response.getHeaders().getValues(HttpHeader.CONTENT_ENCODING.asString(), ",");
if (contentEncodings != null)
{
for (ContentDecoder.Factory factory : getHttpDestination().getHttpClient().getContentDecoderFactories())
{
while (contentEncodings.hasMoreElements())
{
if (factory.getEncoding().equalsIgnoreCase(contentEncodings.nextElement()))
{
this.decoder = factory.newContentDecoder();
break;
}
}
}
}
return true;
}
/**
* Method to be invoked when response HTTP content is available.
*
* This method takes case of decoding the content, if necessary, and notifying {@link com.facebook.presto.jdbc.internal.jetty.client.api.Response.ContentListener}s.
*
* @param exchange the HTTP exchange
* @param buffer the response HTTP content buffer
* @return whether the processing should continue
*/
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer)
{
out: while (true)
{
ResponseState current = responseState.get();
switch (current)
{
case HEADERS:
case CONTENT:
{
if (updateResponseState(current, ResponseState.CONTENT))
break out;
break;
}
default:
{
return false;
}
}
}
HttpResponse response = exchange.getResponse();
if (LOG.isDebugEnabled())
LOG.debug("Response content {}{}{}", response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer));
ContentDecoder decoder = this.decoder;
if (decoder != null)
{
buffer = decoder.decode(buffer);
if (LOG.isDebugEnabled())
LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer));
}
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer);
return true;
}
/**
* Method to be invoked when the response is successful.
*
* This method takes case of notifying {@link com.facebook.presto.jdbc.internal.jetty.client.api.Response.SuccessListener}s and possibly
* {@link com.facebook.presto.jdbc.internal.jetty.client.api.Response.CompleteListener}s (if the exchange is completed).
*
* @param exchange the HTTP exchange
* @return whether the response was processed as successful
*/
protected boolean responseSuccess(HttpExchange exchange)
{
// Mark atomically the response as completed, with respect
// to concurrency between response success and response failure.
boolean completed = exchange.responseComplete();
if (!completed)
return false;
// Reset to be ready for another response
reset();
// Mark atomically the response as terminated and succeeded,
// with respect to concurrency between request and response.
// If there is a non-null result, then both sender and
// receiver are reset and ready to be reused, and the
// connection closed/pooled (depending on the transport).
Result result = exchange.terminateResponse(null);
HttpResponse response = exchange.getResponse();
LOG.debug("Response success {}", response);
List listeners = exchange.getConversation().getResponseListeners();
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
notifier.notifySuccess(listeners, response);
if (result != null)
{
boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
if (!ordered)
channel.exchangeTerminated(result);
LOG.debug("Request/Response succeeded {}", response);
notifier.notifyComplete(listeners, result);
if (ordered)
channel.exchangeTerminated(result);
}
return true;
}
/**
* Method to be invoked when the response is failed.
*
* This method takes care of notifying {@link com.facebook.presto.jdbc.internal.jetty.client.api.Response.FailureListener}s.
*
* @param failure the response failure
* @return whether the response was processed as failed
*/
protected boolean responseFailure(Throwable failure)
{
HttpExchange exchange = getHttpExchange();
// In case of a response error, the failure has already been notified
// and it is possible that a further attempt to read in the receive
// loop throws an exception that reenters here but without exchange;
// or, the server could just have timed out the connection.
if (exchange == null)
return false;
// Mark atomically the response as completed, with respect
// to concurrency between response success and response failure.
boolean completed = exchange.responseComplete();
if (!completed)
return false;
// Dispose to avoid further responses
dispose();
// Mark atomically the response as terminated and failed,
// with respect to concurrency between request and response.
Result result = exchange.terminateResponse(failure);
HttpResponse response = exchange.getResponse();
LOG.debug("Response failure {} {}", response, failure);
List listeners = exchange.getConversation().getResponseListeners();
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
notifier.notifyFailure(listeners, response, failure);
if (result != null)
{
boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
if (!ordered)
channel.exchangeTerminated(result);
LOG.debug("Request/Response failed {}", response);
notifier.notifyComplete(listeners, result);
if (ordered)
channel.exchangeTerminated(result);
}
return true;
}
/**
* Resets this {@link HttpReceiver} state.
*
* Subclasses should override (but remember to call {@code super}) to reset their own state.
*
* Either this method or {@link #dispose()} is called.
*/
protected void reset()
{
decoder = null;
responseState.set(ResponseState.IDLE);
}
/**
* Disposes this {@link HttpReceiver} state.
*
* Subclasses should override (but remember to call {@code super}) to dispose their own state.
*
* Either this method or {@link #reset()} is called.
*/
protected void dispose()
{
decoder = null;
responseState.set(ResponseState.FAILURE);
}
public boolean abort(Throwable cause)
{
return responseFailure(cause);
}
private boolean updateResponseState(ResponseState from, ResponseState to)
{
boolean updated = responseState.compareAndSet(from, to);
if (!updated)
LOG.debug("State update failed: {} -> {}: {}", from, to, responseState.get());
return updated;
}
/**
* The request states {@link HttpReceiver} goes through when receiving a response.
*/
private enum ResponseState
{
/**
* The response is not yet received, the initial state
*/
IDLE,
/**
* The response status code has been received
*/
BEGIN,
/**
* The response headers are being received
*/
HEADER,
/**
* All the response headers have been received
*/
HEADERS,
/**
* The response content is being received
*/
CONTENT,
/**
* The response is failed
*/
FAILURE
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy