org.restexpress.pipeline.DefaultRequestHandler Maven / Gradle / Ivy
/*
* 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