package jj.http.server; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import java.io.IOException; import java.util.regex.Pattern; import javax.inject.Inject; import com.google.inject.AbstractModule; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Provides; import jj.event.Publisher; import jj.http.server.uri.RouteMatch; import jj.http.server.uri.Router; import jj.http.server.websocket.WebSocketConnectionMaker; import jj.http.server.websocket.WebSocketFrameHandlerCreator; import jj.http.server.websocket.WebSocketRequestChecker; import jj.logging.Emergency; /** * Reads incoming http messages and looks for ways to respond * @author jason * * */ public class EngineHttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private static final Pattern HTTP_REPLACER = Pattern.compile("http"); private final ServableResources servables; private final Router router; private final Injector parentInjector; private final WebSocketRequestChecker webSocketRequestChecker; private final Publisher publisher; @Inject EngineHttpHandler( final ServableResources servables, final Router router, final Injector parentInjector, final WebSocketRequestChecker webSocketRequestChecker, final Publisher publisher ) { this.servables = servables; this.router = router; this.parentInjector = parentInjector; this.webSocketRequestChecker = webSocketRequestChecker; this.publisher = publisher; } private Module makeRequestResponseModule(final ChannelHandlerContext ctx, final FullHttpRequest request) { return new AbstractModule() { @Override protected void configure() { bind(ChannelHandlerContext.class).toInstance(ctx); bind(FullHttpRequest.class).toInstance(request); bind(HttpServerRequest.class).to(HttpServerRequestImpl.class); bind(HttpServerResponse.class).to(HttpServerResponseImpl.class); } }; } private Module makeWebSocketHandshakerModule(final FullHttpRequest request) { return new AbstractModule() { @Override protected void configure() { bind(WebSocketConnectionMaker.class); bind(WebSocketFrameHandlerCreator.class); } @Provides protected WebSocketServerHandshakerFactory provideHandshaker() { String uri = HTTP_REPLACER.matcher( request.headers().get(HttpHeaderNames.ORIGIN) + request.uri() ).replaceFirst("ws"); return new WebSocketServerHandshakerFactory(uri, null, false); } }; } @Override protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest request) throws Exception { //long time = System.nanoTime(); // injector creation is split apart here because it's measurably slower to include the websocket bindings if (!request.decoderResult().isSuccess()) { Injector injector = parentInjector.createChildInjector(makeRequestResponseModule(ctx, request)); //System.out.printf("made req/res injector in %s millis%n", MILLISECONDS.convert(System.nanoTime() - time, NANOSECONDS)); injector.getInstance(HttpServerResponse.class).sendError(HttpResponseStatus.BAD_REQUEST); } else if (webSocketRequestChecker.isWebSocketRequest(request)) { Injector injector = parentInjector.createChildInjector(makeRequestResponseModule(ctx, request), makeWebSocketHandshakerModule(request)); //System.out.printf("made websocket handshake injector in %s millis%n", MILLISECONDS.convert(System.nanoTime() - time, NANOSECONDS)); injector.getInstance(WebSocketConnectionMaker.class).handshakeWebsocket(); } else { Injector injector = parentInjector.createChildInjector(makeRequestResponseModule(ctx, request)); //System.out.printf("made req/res injector in %s millis%n", MILLISECONDS.convert(System.nanoTime() - time, NANOSECONDS)); handleHttpRequest(injector.getInstance(HttpServerRequest.class), injector.getInstance(HttpServerResponse.class)); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (!(cause instanceof IOException)) { publisher.publish(new Emergency("engine caught an exception", cause)); try { ctx.writeAndFlush( new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR) ).addListener(ChannelFutureListener.CLOSE); } catch (Exception e) { publisher.publish(new Emergency("additionally, an exception occurred while responding with an error", e)); } } } private void handleHttpRequest(final HttpServerRequest request, final HttpServerResponse response) throws Exception { RouteMatch routeMatch = router.routeRequest(request.method(), request.uriMatch()); RouteProcessor rp = servables.routeProcessor(routeMatch.resourceName()); if (rp != null) { rp.process(routeMatch, request, response); } else { response.sendNotFound(); } } }