package org.deephacks.westty.internal.jaxrs;
import org.apache.commons.codec.binary.Base64;
import org.codehaus.jackson.map.DeserializationConfig;
import org.deephacks.westty.spi.HttpHandler;
import org.jboss.netty.buffer.ChannelBufferInputStream;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.resteasy.core.SynchronousDispatcher;
import org.jboss.resteasy.core.ThreadLocalResteasyProviderFactory;
import org.jboss.resteasy.plugins.providers.jackson.ResteasyJacksonProvider;
import org.jboss.resteasy.plugins.server.embedded.SecurityDomain;
import org.jboss.resteasy.plugins.server.netty.NettyHttpRequest;
import org.jboss.resteasy.plugins.server.netty.NettyHttpResponse;
import org.jboss.resteasy.plugins.server.netty.NettySecurityContext;
import org.jboss.resteasy.specimpl.ResteasyHttpHeaders;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.ResteasyUriInfo;
import org.jboss.resteasy.util.HttpHeaderNames;
import org.jboss.resteasy.util.HttpResponseCodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.RuntimeDelegate;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
import java.util.Map;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
@Singleton
public class ResteasyHttpHandler extends HttpHandler {
private static final Logger log = LoggerFactory.getLogger(ResteasyHttpHandler.class);
public static final String JAXRS_CONTEXT_URI = "/jaxrs";
private ResteasyDeployment deployment;
/** TODO: fix security */
protected SecurityDomain domain;
@Override
public boolean accept(String uri) {
return uri.startsWith(JAXRS_CONTEXT_URI);
}
@Inject
public ResteasyHttpHandler(JaxrsApplication jaxrsApps) {
this.deployment = new ResteasyDeployment();
deployment.setApplication(jaxrsApps);
ResteasyJacksonProvider provider = new ResteasyJacksonProvider();
provider.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
ResteasyProviderFactory resteasyFactory = ResteasyProviderFactory.getInstance();
resteasyFactory.registerProviderInstance(provider);
deployment.setProviderFactory(resteasyFactory);
deployment.start();
log.info("RestEasy started.");
}
public void messageReceived(ChannelHandlerContext ctx, MessageEvent event, HttpRequest request)
throws Exception {
boolean keepAlive = org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive(request);
NettyHttpResponse nettyResponse = new NettyHttpResponse(event.getChannel(), keepAlive);
ResteasyHttpHeaders headers = null;
ResteasyUriInfo uriInfo = null;
try {
headers = NettyUtil.extractHttpHeaders(request);
uriInfo = NettyUtil.extractUriInfo(request, JaxrsApplication.JAXRS_CONTEXT_URI,
"http");
NettyHttpRequest restEasyRequest = new NettyHttpRequest(headers, uriInfo, request
.getMethod().getName(), ((SynchronousDispatcher) deployment.getDispatcher()),
nettyResponse,
org.jboss.netty.handler.codec.http.HttpHeaders.is100ContinueExpected(request));
ChannelBufferInputStream is = new ChannelBufferInputStream(request.getContent());
restEasyRequest.setInputStream(is);
if (restEasyRequest.is100ContinueExpected()) {
send100Continue(event);
}
try {
service(restEasyRequest, nettyResponse, true);
} catch (Failure e1) {
nettyResponse.reset();
nettyResponse.setStatus(e1.getErrorCode());
return;
} catch (Exception ex) {
nettyResponse.reset();
nettyResponse.setStatus(500);
log.error("Unexpected", ex);
return;
}
// Build the response object.
HttpResponseStatus status = HttpResponseStatus.valueOf(nettyResponse.getStatus());
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
for (Map.Entry<String, List<Object>> entry : nettyResponse.getOutputHeaders()
.entrySet()) {
String key = entry.getKey();
for (Object value : entry.getValue()) {
RuntimeDelegate.HeaderDelegate delegate = deployment.getProviderFactory()
.createHeaderDelegate(value.getClass());
if (delegate != null) {
response.addHeader(key, delegate.toString(value));
} else {
response.setHeader(key, value.toString());
}
}
}
nettyResponse.getOutputStream().flush();
response.setContent(nettyResponse.getBuffer());
if (nettyResponse.isKeepAlive()) {
// Add content length and connection header if needed
response.setHeader(Names.CONTENT_LENGTH, response.getContent().readableBytes());
response.setHeader(Names.CONNECTION, Values.KEEP_ALIVE);
}
// Write the response.
ChannelFuture future = event.getChannel().write(response);
// Close the non-keep-alive connection after the write operation is done.
if (!request.isKeepAlive()) {
future.addListener(ChannelFutureListener.CLOSE);
}
} catch (Exception e) {
nettyResponse.sendError(400);
// made it warn so that people can filter this.
log.warn("Failed to parse request.", e);
}
}
public void service(org.jboss.resteasy.spi.HttpRequest request,
org.jboss.resteasy.spi.HttpResponse response, boolean handleNotFound)
throws IOException {
try {
ResteasyProviderFactory defaultInstance = ResteasyProviderFactory.getInstance();
if (defaultInstance instanceof ThreadLocalResteasyProviderFactory) {
ThreadLocalResteasyProviderFactory.push(deployment.getProviderFactory());
}
SecurityContext securityContext;
if (domain != null) {
securityContext = basicAuthentication(request, response);
if (securityContext == null) // not authenticated
{
return;
}
} else {
securityContext = new NettySecurityContext();
}
try {
ResteasyProviderFactory.pushContext(SecurityContext.class, securityContext);
if (handleNotFound) {
((SynchronousDispatcher) deployment.getDispatcher()).invoke(request, response);
} else {
((SynchronousDispatcher) deployment.getDispatcher()).invokePropagateNotFound(
request, response);
}
} finally {
ResteasyProviderFactory.clearContextData();
}
} finally {
ResteasyProviderFactory defaultInstance = ResteasyProviderFactory.getInstance();
if (defaultInstance instanceof ThreadLocalResteasyProviderFactory) {
ThreadLocalResteasyProviderFactory.pop();
}
}
}
private SecurityContext basicAuthentication(org.jboss.resteasy.spi.HttpRequest request,
org.jboss.resteasy.spi.HttpResponse response) throws IOException {
List<String> headers = request.getHttpHeaders().getRequestHeader(
HttpHeaderNames.AUTHORIZATION);
if (!headers.isEmpty()) {
String auth = headers.get(0);
if (auth.length() > 5) {
String type = auth.substring(0, 5);
type = type.toLowerCase();
if ("basic".equals(type)) {
String cookie = auth.substring(6);
cookie = new String(Base64.decodeBase64(cookie.getBytes()));
String[] split = cookie.split(":");
Principal user = null;
try {
user = domain.authenticate(split[0], split[1]);
return new NettySecurityContext(user, domain, "BASIC", true);
} catch (SecurityException e) {
response.sendError(HttpResponseCodes.SC_UNAUTHORIZED);
return null;
}
} else {
response.sendError(HttpResponseCodes.SC_UNAUTHORIZED);
return null;
}
}
}
return null;
}
public SecurityDomain getDomain() {
return domain;
}
private void send100Continue(MessageEvent e) {
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE);
e.getChannel().write(response);
}
}