/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.ext.jetty.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Uniform;
import org.restlet.data.Header;
import org.restlet.data.Protocol;
import org.restlet.data.Status;
import org.restlet.engine.adapter.ClientCall;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.ext.jetty.HttpClientHelper;
import org.restlet.representation.Representation;
import org.restlet.util.Series;
/**
* HTTP client connector call based on Jetty's HttpRequest class.
*
* @author Jerome Louvel
* @author Tal Liron
*/
public class JettyClientCall extends ClientCall {
/**
* The associated HTTP client.
*/
private final HttpClientHelper clientHelper;
/**
* The wrapped HTTP request.
*/
private final HttpRequest httpRequest;
/**
* The wrapped HTTP response.
*/
private volatile org.eclipse.jetty.client.api.Response httpResponse;
/**
* The wrapped input stream response listener.
*/
private volatile InputStreamResponseListener inputStreamResponseListener;
/**
* Indicates if the response headers were added.
*/
private volatile boolean responseHeadersAdded;
/**
* Constructor.
*
* @param helper
* The parent HTTP client helper.
* @param method
* The method name.
* @param requestUri
* The request URI.
* @throws IOException
*/
public JettyClientCall(HttpClientHelper helper, final String method,
final String requestUri) throws IOException {
super(helper, method, requestUri);
this.clientHelper = helper;
if (requestUri.startsWith("http")) {
this.httpRequest = (HttpRequest) helper.getHttpClient().newRequest(
requestUri);
this.httpRequest.method(method);
setConfidential(this.httpRequest.getURI().getScheme()
.equalsIgnoreCase(Protocol.HTTPS.getSchemeName()));
} else {
throw new IllegalArgumentException(
"Only HTTP or HTTPS resource URIs are allowed here");
}
}
/**
* Returns the HTTP request.
*
* @return The HTTP request.
*/
public HttpRequest getHttpRequest() {
return this.httpRequest;
}
/**
* Returns the HTTP response.
*
* @return The HTTP response.
*/
public org.eclipse.jetty.client.api.Response getHttpResponse() {
return this.httpResponse;
}
/**
* Returns the input stream response listener.
*
* @return The input stream response listener.
*/
public InputStreamResponseListener getInputStreamResponseListener() {
return this.inputStreamResponseListener;
}
/**
* Returns the response reason phrase.
*
* @return The response reason phrase.
*/
@Override
public String getReasonPhrase() {
final org.eclipse.jetty.client.api.Response httpResponse = getHttpResponse();
return httpResponse == null ? null : httpResponse.getReason();
}
public WritableByteChannel getRequestEntityChannel() {
return null;
}
public OutputStream getRequestEntityStream() {
return null;
}
public OutputStream getRequestHeadStream() {
return null;
}
public ReadableByteChannel getResponseEntityChannel(long size) {
return null;
}
public InputStream getResponseEntityStream(long size) {
final InputStreamResponseListener inputStreamResponseListener = getInputStreamResponseListener();
return inputStreamResponseListener == null ? null
: inputStreamResponseListener.getInputStream();
}
/**
* Returns the response entity if available. Note that no metadata is
* associated by default, you have to manually set them from your headers.
*
* As jetty client decode the input stream on the fly in
* {@link org.eclipse.jetty.client.HttpReceiver#responseContent(org.eclipse.jetty.client.HttpExchange, java.nio.ByteBuffer, org.eclipse.jetty.util.Callback)}
* we have to clear the
* {@link org.restlet.representation.Representation#getEncodings()} to avoid
* decoding the input stream another time.
*
* @param response
* the Response to get the entity from
* @return The response entity if available.
*/
@Override
public Representation getResponseEntity(Response response) {
Representation responseEntity = super.getResponseEntity(response);
if (responseEntity != null && !responseEntity.getEncodings().isEmpty()) {
responseEntity.getEncodings().clear();
}
return responseEntity;
}
/**
* Returns the modifiable list of response headers.
*
* @return The modifiable list of response headers.
*/
@Override
public Series<Header> getResponseHeaders() {
final Series<Header> result = super.getResponseHeaders();
if (!this.responseHeadersAdded) {
final org.eclipse.jetty.client.api.Response httpResponse = getHttpResponse();
if (httpResponse != null) {
final HttpFields headers = httpResponse.getHeaders();
if (headers != null) {
for (HttpField header : headers)
result.add(header.getName(), header.getValue());
}
}
this.responseHeadersAdded = true;
}
return result;
}
/**
* Returns the response address.<br>
* Corresponds to the IP address of the responding server.
*
* @return The response address.
*/
@Override
public String getServerAddress() {
return this.httpRequest.getURI().getHost();
}
/**
* Returns the response status code.
*
* @return The response status code.
*/
@Override
public int getStatusCode() {
final org.eclipse.jetty.client.api.Response httpResponse = getHttpResponse();
return httpResponse == null ? null : httpResponse.getStatus();
}
/**
* Sends the request to the client. Commits the request line, headers and
* optional entity and send them over the network.
*
* @param request
* The high-level request.
* @return The result status.
*/
@Override
public Status sendRequest(Request request) {
Status result = null;
try {
final Representation entity = request.getEntity();
// Request entity
if (entity != null && entity.isAvailable())
this.httpRequest.content(new InputStreamContentProvider(entity
.getStream()));
// Set the request headers
for (Header header : getRequestHeaders()) {
final String name = header.getName();
switch (name) {
case HeaderConstants.HEADER_CONTENT_LENGTH:
// skip this header
break;
case HeaderConstants.HEADER_USER_AGENT:
this.httpRequest.agent(header.getValue());
break;
default:
this.httpRequest.header(name, header.getValue());
break;
}
}
// Ensure that the connection is active
this.inputStreamResponseListener = new InputStreamResponseListener();
this.httpRequest.send(this.inputStreamResponseListener);
this.httpResponse = this.inputStreamResponseListener.get(
clientHelper.getIdleTimeout(), TimeUnit.MILLISECONDS);
result = new Status(getStatusCode(), getReasonPhrase());
} catch (IOException e) {
this.clientHelper.getLogger().log(Level.WARNING,
"An error occurred while reading the request entity.", e);
result = new Status(Status.CONNECTOR_ERROR_INTERNAL, e);
// Release the connection
getHttpRequest().abort(e);
} catch (TimeoutException e) {
this.clientHelper.getLogger().log(Level.WARNING,
"The HTTP request timed out.", e);
result = new Status(Status.CONNECTOR_ERROR_COMMUNICATION, e);
// Release the connection
getHttpRequest().abort(e);
} catch (InterruptedException e) {
this.clientHelper.getLogger().log(Level.WARNING,
"The HTTP request thread was interrupted.", e);
result = new Status(Status.CONNECTOR_ERROR_COMMUNICATION, e);
// Release the connection
getHttpRequest().abort(e);
} catch (ExecutionException e) {
this.clientHelper.getLogger().log(Level.WARNING,
"An error occurred while processing the HTTP request.", e);
result = new Status(Status.CONNECTOR_ERROR_COMMUNICATION, e);
// Release the connection
getHttpRequest().abort(e);
}
return result;
}
@Override
public void sendRequest(Request request, Response response, Uniform callback)
throws Exception {
sendRequest(request);
final Uniform getOnSent = request.getOnSent();
if (getOnSent != null)
getOnSent.handle(request, response);
if (callback != null)
// Transmit to the callback, if any
callback.handle(request, response);
}
}