/**
* 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.engine.adapter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Level;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Encoding;
import org.restlet.data.Header;
import org.restlet.data.Method;
import org.restlet.data.Status;
import org.restlet.engine.connector.ConnectorHelper;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.engine.header.HeaderUtils;
import org.restlet.representation.Representation;
import org.restlet.util.Series;
/**
* Low-level HTTP client call.
*
* @author Jerome Louvel
*/
public abstract class ClientCall extends Call {
/**
* Returns the local IP address or 127.0.0.1 if the resolution fails.
*
* @return The local IP address or 127.0.0.1 if the resolution fails.
*/
public static String getLocalAddress() {
// [ifndef gae,gwt]
try {
return java.net.InetAddress.getLocalHost().getHostAddress();
} catch (java.net.UnknownHostException e) {
// [enddef]
return "127.0.0.1";
// [ifndef gae,gwt]
}
// [enddef]
}
/** The parent HTTP client helper. */
private volatile HttpClientHelper helper;
/**
* Constructor setting the request address to the local host.
*
* @param helper
* The parent HTTP client helper.
* @param method
* The method name.
* @param requestUri
* The request URI.
*/
public ClientCall(HttpClientHelper helper, String method, String requestUri) {
this.helper = helper;
setMethod(method);
setRequestUri(requestUri);
setClientAddress(getLocalAddress());
}
/**
* Returns the content length of the request entity if know,
* {@link Representation#UNKNOWN_SIZE} otherwise.
*
* @return The request content length.
*/
protected long getContentLength() {
return HeaderUtils.getContentLength(getResponseHeaders());
}
/**
* Returns the HTTP client helper.
*
* @return The HTTP client helper.
*/
public HttpClientHelper getHelper() {
return this.helper;
}
// [ifndef gwt] member
/**
* Returns the request entity channel if it exists.
*
* @return The request entity channel if it exists.
*/
public abstract java.nio.channels.WritableByteChannel getRequestEntityChannel();
// [ifndef gwt] member
/**
* Returns the request entity stream if it exists.
*
* @return The request entity stream if it exists.
*/
public abstract OutputStream getRequestEntityStream();
// [ifdef gwt] member uncomment
// /**
// * Returns the request entity string if it exists.
// *
// * @return The request entity string if it exists.
// */
// public abstract String getRequestEntityString();
// [ifndef gwt] member
/**
* Returns the request head stream if it exists.
*
* @return The request head stream if it exists.
*/
public abstract OutputStream getRequestHeadStream();
/**
* Returns the response entity if available. Note that no metadata is
* associated by default, you have to manually set them from your headers.
*
* @param response
* the Response to get the entity from
* @return The response entity if available.
*/
public Representation getResponseEntity(Response response) {
Representation result = null;
// boolean available = false;
long size = Representation.UNKNOWN_SIZE;
// Compute the content length
Series<Header> responseHeaders = getResponseHeaders();
String transferEncoding = responseHeaders.getFirstValue(
HeaderConstants.HEADER_TRANSFER_ENCODING, true);
if ((transferEncoding != null)
&& !Encoding.IDENTITY.getName().equalsIgnoreCase(
transferEncoding)) {
size = Representation.UNKNOWN_SIZE;
} else {
size = getContentLength();
}
if (!getMethod().equals(Method.HEAD.getName())
&& !response.getStatus().isInformational()
&& !response.getStatus()
.equals(Status.REDIRECTION_NOT_MODIFIED)
&& !response.getStatus().equals(Status.SUCCESS_NO_CONTENT)
&& !response.getStatus().equals(Status.SUCCESS_RESET_CONTENT)) {
// Make sure that an InputRepresentation will not be instantiated
// while the stream is closed.
InputStream stream = getUnClosedResponseEntityStream(getResponseEntityStream(size));
// [ifndef gwt] line
java.nio.channels.ReadableByteChannel channel = getResponseEntityChannel(size);
// [ifdef gwt] line uncomment
// InputStream channel = null;
if (stream != null) {
result = getRepresentation(stream);
} else if (channel != null) {
result = getRepresentation(channel);
}
}
if (result != null) {
result.setSize(size);
// Informs that the size has not been specified in the header.
if (size == Representation.UNKNOWN_SIZE) {
getLogger()
.fine("The length of the message body is unknown. The entity must be handled carefully and consumed entirely in order to surely release the connection.");
}
}
result = HeaderUtils.extractEntityHeaders(responseHeaders, result);
return result;
}
// [ifndef gwt] member
/**
* Returns the response channel if it exists.
*
* @param size
* The expected entity size or -1 if unknown.
* @return The response channel if it exists.
*/
public abstract java.nio.channels.ReadableByteChannel getResponseEntityChannel(
long size);
/**
* Returns the response entity stream if it exists.
*
* @param size
* The expected entity size or -1 if unknown.
* @return The response entity stream if it exists.
*/
public abstract InputStream getResponseEntityStream(long size);
/**
* Checks if the given input stream really contains bytes to be read. If so,
* returns the inputStream otherwise returns null.
*
* @param inputStream
* the inputStream to check.
* @return null if the given inputStream does not contain any byte, an
* inputStream otherwise.
*/
private InputStream getUnClosedResponseEntityStream(InputStream inputStream) {
InputStream result = null;
if (inputStream != null) {
try {
if (inputStream.available() > 0) {
result = inputStream;
// [ifndef gwt]
} else {
java.io.PushbackInputStream is = new java.io.PushbackInputStream(
inputStream);
int i = is.read();
if (i >= 0) {
is.unread(i);
result = is;
}
// [enddef]
}
} catch (IOException ioe) {
getLogger().log(Level.FINER, "End of response entity stream.",
ioe);
}
}
return result;
}
@Override
protected boolean isClientKeepAlive() {
return true;
}
@Override
protected boolean isServerKeepAlive() {
return !HeaderUtils.isConnectionClose(getResponseHeaders());
}
// [ifndef gwt] method
/**
* 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 status of the communication
*/
public Status sendRequest(Request request) {
Status result = null;
Representation entity = request.isEntityAvailable() ? request
.getEntity() : null;
// Get the connector service to callback
org.restlet.service.ConnectorService connectorService = ConnectorHelper
.getConnectorService();
if (connectorService != null) {
connectorService.beforeSend(entity);
}
try {
if (entity != null) {
// In order to workaround bug #6472250
// (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6472250),
// it is very important to reuse that exact same "requestStream"
// reference when manipulating the request stream, otherwise
// "insufficient data sent" exceptions will occur in
// "fixedLengthMode"
OutputStream requestStream = getRequestEntityStream();
java.nio.channels.WritableByteChannel requestChannel = getRequestEntityChannel();
if (requestChannel != null) {
entity.write(requestChannel);
requestChannel.close();
} else if (requestStream != null) {
entity.write(requestStream);
requestStream.flush();
requestStream.close();
}
}
// Now we can access the status code, this MUST happen after closing
// any open request stream.
result = new Status(getStatusCode(), getReasonPhrase());
} catch (IOException ioe) {
getHelper()
.getLogger()
.log(Level.FINE,
"An error occurred during the communication with the remote HTTP server.",
ioe);
result = new Status(Status.CONNECTOR_ERROR_COMMUNICATION, ioe);
} finally {
if (entity != null) {
entity.release();
}
// Call-back after writing
if (connectorService != null) {
connectorService.afterSend(entity);
}
}
return result;
}
/**
* 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.
* @param response
* The high-level response.
* @param callback
* The callback invoked upon request completion.
*/
public void sendRequest(Request request, Response response,
org.restlet.Uniform callback) throws Exception {
Context.getCurrentLogger().warning(
"Currently callbacks are not available for this connector.");
}
/**
* Indicates if the request entity should be chunked.
*
* @return True if the request should be chunked
*/
protected boolean shouldRequestBeChunked(Request request) {
return request.isEntityAvailable() && (request.getEntity() != null)
&& !request.getEntity().hasKnownSize();
}
}