org.restexpress.pipeline.DefaultRequestHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of RestExpress Show documentation
Show all versions of RestExpress Show documentation
Internet scale, high-performance RESTful Services in Java
The newest version!
/*
* Copyright 2009, Strategic Gains, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.restexpress.pipeline;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.restexpress.ContentType;
import org.restexpress.Request;
import org.restexpress.Response;
import org.restexpress.exception.DefaultExceptionMapper;
import org.restexpress.exception.ExceptionMapping;
import org.restexpress.exception.ExceptionUtils;
import org.restexpress.exception.ServiceException;
import org.restexpress.response.HttpResponseWriter;
import org.restexpress.route.Action;
import org.restexpress.route.RouteResolver;
import org.restexpress.serialization.SerializationProvider;
import org.restexpress.serialization.SerializationSettings;
import org.restexpress.util.HttpSpecification;
/**
* @author toddf
* @since Nov 13, 2009
*/
@Sharable
public class DefaultRequestHandler
extends SimpleChannelInboundHandler
{
//SECTION: CONSTANTS
private static final AttributeKey CONTEXT_KEY = AttributeKey.valueOf("context");
// SECTION: INSTANCE VARIABLES
private RouteResolver routeResolver;
private SerializationProvider serializationProvider;
private HttpResponseWriter responseWriter;
private List preprocessors = new ArrayList();
private List postprocessors = new ArrayList();
private List finallyProcessors = new ArrayList();
private ExceptionMapping exceptionMap = new DefaultExceptionMapper();
private List messageObservers = new ArrayList();
private boolean shouldEnforceHttpSpec = true;
// SECTION: CONSTRUCTORS
public DefaultRequestHandler(RouteResolver routeResolver, SerializationProvider serializationProvider,
HttpResponseWriter responseWriter, boolean enforceHttpSpec)
{
super();
this.routeResolver = routeResolver;
this.serializationProvider = serializationProvider;
setResponseWriter(responseWriter);
this.shouldEnforceHttpSpec = enforceHttpSpec;
}
// SECTION: MUTATORS
public void addMessageObserver(MessageObserver... observers)
{
for (MessageObserver observer : observers)
{
if (!messageObservers.contains(observer))
{
messageObservers.add(observer);
}
}
}
public DefaultRequestHandler mapException(Class from, Class to)
{
exceptionMap.map(from, to);
return this;
}
public DefaultRequestHandler setExceptionMap(ExceptionMapping map)
{
this.exceptionMap = map;
return this;
}
public HttpResponseWriter getResponseWriter()
{
return this.responseWriter;
}
public void setResponseWriter(HttpResponseWriter writer)
{
this.responseWriter = writer;
}
// SECTION: SIMPLE-CHANNEL-UPSTREAM-HANDLER
@Override
public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest event)
throws Exception
{
MessageContext context = createInitialContext(ctx, event);
try
{
// Process the request
processRequest(ctx, context);
}
catch(Throwable t)
{
handleRestExpressException(ctx, t);
}
finally
{
notifyComplete(context);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception{
ctx.flush();
super.channelReadComplete(ctx);
}
private void processRequest(ChannelHandlerContext ctx, MessageContext context)
throws Throwable
{
notifyReceived(context);
resolveRoute(context);
resolveResponseProcessor(context);
invokePreprocessors(preprocessors, context.getRequest());
Object result = context.getAction().invoke(context.getRequest(), context.getResponse());
if (result != null)
{
context.getResponse().setBody(result);
}
invokePostprocessors(postprocessors, context.getRequest(), context.getResponse());
serializeResponse(context, false);
enforceHttpSpecification(context);
invokeFinallyProcessors(finallyProcessors, context.getRequest(), context.getResponse());
writeResponse(ctx, context);
notifySuccess(context);
}
private void resolveResponseProcessor(MessageContext context)
{
SerializationSettings s = serializationProvider.resolveResponse(context.getRequest(), context.getResponse(), false);
context.setSerializationSettings(s);
}
/**
* @param context
*/
private void enforceHttpSpecification(MessageContext context)
{
if (shouldEnforceHttpSpec)
{
HttpSpecification.enforce(context.getResponse());
}
}
private void handleRestExpressException(ChannelHandlerContext ctx, Throwable cause)
throws Exception
{
MessageContext context = (MessageContext) ctx.attr(CONTEXT_KEY).get();
Throwable rootCause = mapServiceException(cause);
if (rootCause != null) // was/is a ServiceException
{
context.setHttpStatus(((ServiceException) rootCause).getHttpStatus());
if (ServiceException.class.isAssignableFrom(rootCause.getClass()))
{
((ServiceException) rootCause).augmentResponse(context.getResponse());
}
}
else
{
rootCause = ExceptionUtils.findRootCause(cause);
context.setHttpStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
context.setException(rootCause);
notifyException(context);
serializeResponse(context, true);
invokeFinallyProcessors(finallyProcessors, context.getRequest(), context.getResponse());
writeResponse(ctx, context);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable)
throws Exception
{
try
{
MessageContext messageContext = (MessageContext) ctx.attr(CONTEXT_KEY).get();
if (messageContext != null)
{
messageContext.setException(throwable.getCause()!=null?throwable.getCause():throwable);
notifyException(messageContext);
}
}
catch(Throwable t)
{
System.err.print("DefaultRequestHandler.exceptionCaught() threw an exception.");
t.printStackTrace();
}
finally
{
ctx.channel().close();
}
}
private MessageContext createInitialContext(ChannelHandlerContext ctx, FullHttpRequest httpRequest)
{
Request request = createRequest(httpRequest, ctx);
Response response = createResponse();
MessageContext context = new MessageContext(request, response);
ctx.attr(CONTEXT_KEY).set(context);
return context;
}
private void resolveRoute(MessageContext context)
{
Action action = routeResolver.resolve(context.getRequest());
context.setAction(action);
}
private void notifyReceived(MessageContext context)
{
for (MessageObserver observer : messageObservers)
{
observer.onReceived(context.getRequest(), context.getResponse());
}
}
private void notifyComplete(MessageContext context)
{
for (MessageObserver observer : messageObservers)
{
observer.onComplete(context.getRequest(), context.getResponse());
}
}
// SECTION: UTILITY -- PRIVATE
private void notifyException(MessageContext context)
{
Throwable exception = context.getException();
for (MessageObserver observer : messageObservers)
{
observer.onException(exception, context.getRequest(), context.getResponse());
}
}
private void notifySuccess(MessageContext context)
{
for (MessageObserver observer : messageObservers)
{
observer.onSuccess(context.getRequest(), context.getResponse());
}
}
public void addPreprocessor(Preprocessor handler)
{
if (!preprocessors.contains(handler))
{
preprocessors.add(handler);
}
}
public void addPostprocessor(Postprocessor handler)
{
if (!postprocessors.contains(handler))
{
postprocessors.add(handler);
}
}
public void addFinallyProcessor(Postprocessor handler)
{
if (!finallyProcessors.contains(handler))
{
finallyProcessors.add(handler);
}
}
private void invokePreprocessors(List processors, Request request)
{
for (Preprocessor handler : processors)
{
handler.process(request);
}
request.getBody().resetReaderIndex();
}
private void invokePostprocessors(List processors, Request request, Response response)
{
for (Postprocessor handler : processors)
{
handler.process(request, response);
}
}
private void invokeFinallyProcessors(List processors, Request request, Response response)
{
for (Postprocessor handler : processors)
{
try
{
handler.process(request, response);
}
catch(Throwable t)
{
t.printStackTrace(System.err);
}
}
}
/**
* Uses the exceptionMap to map a Throwable to a ServiceException, if possible.
*
* @param cause
* @return Either a ServiceException or the root cause of the exception.
*/
private Throwable mapServiceException(Throwable cause)
{
if (ServiceException.isAssignableFrom(cause))
{
return cause;
}
return exceptionMap.getExceptionFor(cause);
}
private Request createRequest(FullHttpRequest request, ChannelHandlerContext context)
{
try
{
return new Request((InetSocketAddress) context.channel().remoteAddress(), request, routeResolver, serializationProvider);
}
catch(Throwable t)
{
return new Request(request, routeResolver, serializationProvider);
}
}
private Response createResponse()
{
return new Response();
}
private void writeResponse(ChannelHandlerContext ctx, MessageContext context)
{
getResponseWriter().write(ctx, context.getRequest(), context.getResponse());
}
private void serializeResponse(MessageContext context, boolean force)
{
Response response = context.getResponse();
if (HttpSpecification.isContentTypeAllowed(response))
{
SerializationSettings settings = null;
if (response.hasSerializationSettings())
{
settings = response.getSerializationSettings();
}
else if (force)
{
settings = serializationProvider.resolveResponse(context.getRequest(), response, force);
}
if (settings != null)
{
if (response.isSerialized())
{
ByteBuffer serialized = settings.serialize(response);
if (serialized != null)
{
response.setBody(Unpooled.wrappedBuffer(serialized));
if (!response.hasHeader(HttpHeaders.Names.CONTENT_TYPE))
{
response.setContentType(settings.getMediaType());
}
}
}
}
if (!response.hasHeader(HttpHeaders.Names.CONTENT_TYPE))
{
response.setContentType(ContentType.TEXT_PLAIN);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy