/**
* 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.security.cert.Certificate;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.HttpChannel;
import org.restlet.Response;
import org.restlet.Server;
import org.restlet.data.Header;
import org.restlet.data.Status;
import org.restlet.engine.adapter.ServerCall;
import org.restlet.util.Series;
/**
* Call that is used by the Jetty HTTP server connectors.
*
* @author Jerome Louvel
* @author Tal Liron
*/
public class JettyServerCall extends ServerCall {
/** The wrapped Jetty HTTP channel. */
private final HttpChannel<?> channel;
/** Indicates if the request headers were parsed and added. */
private volatile boolean requestHeadersAdded;
/**
* Constructor.
*
* @param server
* The parent server.
* @param channel
* The wrapped Jetty HTTP channel.
*/
public JettyServerCall(Server server, HttpChannel<?> channel) {
super(server);
this.channel = channel;
this.requestHeadersAdded = false;
}
/**
* Closes the end point.
*/
public boolean abort() {
getChannel().getEndPoint().close();
return true;
}
@Override
public void complete() {
// Flush the response
try {
getChannel().getResponse().flushBuffer();
} catch (IOException e) {
getLogger().log(Level.FINE, "Unable to flush the response", e);
} catch (IllegalStateException e) {
getLogger().log(Level.WARNING, "Unable to flush the response", e);
}
// Fully complete the response
try {
getChannel().getResponse().closeOutput();
} catch (IOException e) {
getLogger().log(Level.FINE, "Unable to complete the response", e);
}
}
@Override
public void flushBuffers() throws IOException {
getChannel().getResponse().flushBuffer();
}
@Override
public List<Certificate> getCertificates() {
final Object certificateArray = getChannel().getRequest().getAttribute(
"javax.servlet.request.X509Certificate");
if (certificateArray instanceof Certificate[])
return Arrays.asList((Certificate[]) certificateArray);
return null;
}
/**
* Returns the wrapped Jetty HTTP channel.
*
* @return The wrapped Jetty HTTP channel.
*/
public HttpChannel<?> getChannel() {
return this.channel;
}
@Override
public String getCipherSuite() {
final Object cipherSuite = getChannel().getRequest().getAttribute(
"javax.servlet.request.cipher_suite");
if (cipherSuite instanceof String)
return (String) cipherSuite;
return null;
}
@Override
public String getClientAddress() {
return getChannel().getRequest().getRemoteAddr();
}
@Override
public int getClientPort() {
return getChannel().getRequest().getRemotePort();
}
/**
* Returns the request method.
*
* @return The request method.
*/
@Override
public String getMethod() {
return getChannel().getRequest().getMethod();
}
public InputStream getRequestEntityStream(long size) {
try {
return getChannel().getRequest().getInputStream();
} catch (IOException e) {
getLogger().log(Level.WARNING,
"Unable to get request entity stream", e);
return null;
}
}
/**
* Returns the list of request headers.
*
* @return The list of request headers.
*/
@Override
public Series<Header> getRequestHeaders() {
final Series<Header> result = super.getRequestHeaders();
if (!this.requestHeadersAdded) {
// Copy the headers from the request object
for (Enumeration<String> names = getChannel().getRequest()
.getHeaderNames(); names.hasMoreElements();) {
final String headerName = names.nextElement();
for (Enumeration<String> values = getChannel().getRequest()
.getHeaders(headerName); values.hasMoreElements();) {
final String headerValue = values.nextElement();
result.add(headerName, headerValue);
}
}
this.requestHeadersAdded = true;
}
return result;
}
public InputStream getRequestHeadStream() {
// Not available
return null;
}
/**
* Returns the URI on the request line (most like a relative reference, but
* not necessarily).
*
* @return The URI on the request line.
*/
@Override
public String getRequestUri() {
return getChannel().getRequest().getUri().toString();
}
/**
* Returns the response stream if it exists.
*
* @return The response stream if it exists.
*/
public OutputStream getResponseEntityStream() {
try {
return getChannel().getResponse().getOutputStream();
} catch (IOException e) {
getLogger().log(Level.WARNING,
"Unable to get response entity stream", e);
return null;
}
}
/**
* Returns the response address.<br>
* Corresponds to the IP address of the responding server.
*
* @return The response address.
*/
@Override
public String getServerAddress() {
return getChannel().getRequest().getLocalAddr();
}
@Override
public Integer getSslKeySize() {
Integer keySize = (Integer) getChannel().getRequest().getAttribute(
"javax.servlet.request.key_size");
if (keySize == null)
keySize = super.getSslKeySize();
return keySize;
}
@Override
public String getSslSessionId() {
final Object sessionId = getChannel().getRequest().getAttribute(
"javax.servlet.request.ssl_session_id");
if (sessionId instanceof String)
return (String) sessionId;
return null;
}
/**
* Indicates if the request was made using a confidential mean.<br>
*
* @return True if the request was made using a confidential mean.<br>
*/
@Override
public boolean isConfidential() {
return getChannel().getRequest().isSecure();
}
@Override
public boolean isConnectionBroken(Throwable exception) {
return (exception instanceof EofException)
|| super.isConnectionBroken(exception);
}
@Override
public void sendResponse(Response response) throws IOException {
// Add call headers
for (Iterator<Header> iter = getResponseHeaders().iterator(); iter
.hasNext();) {
Header header = iter.next();
getChannel().getResponse().addHeader(header.getName(),
header.getValue());
}
// Set the status code in the response. We do this after adding the
// headers because when we have to rely on the 'sendError' method,
// the Servlet containers are expected to commit their response.
if (Status.isError(getStatusCode()) && (response.getEntity() == null)) {
try {
getChannel().getResponse().sendError(getStatusCode(),
getReasonPhrase());
} catch (IOException ioe) {
getLogger().log(Level.WARNING,
"Unable to set the response error status", ioe);
}
} else {
// Send the response entity
getChannel().getResponse().setStatus(getStatusCode());
try {
super.sendResponse(response);
} catch (IllegalStateException e) {
getLogger().log(Level.WARNING, "Unable to set the status", e);
}
}
}
}