PageRenderTime 69ms CodeModel.GetById 56ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/sitebricks-async/src/main/java/com/google/sitebricks/SitebricksAsyncHandler.java

http://github.com/dhanji/sitebricks
Java | 149 lines | 113 code | 18 blank | 18 comment | 11 complexity | dc8dbb49adbb185611f434530d06d621 MD5 | raw file
  1package com.google.sitebricks;
  2
  3import com.google.common.collect.Maps;
  4import com.google.inject.Inject;
  5import com.google.inject.Key;
  6import com.google.inject.Provider;
  7import com.google.inject.Singleton;
  8import com.google.inject.servlet.ServletScopes;
  9import com.google.sitebricks.headless.NettyReplyMaker;
 10import com.google.sitebricks.headless.Reply;
 11import com.google.sitebricks.headless.Request;
 12import com.google.sitebricks.routing.RoutingDispatcher;
 13import com.google.sitebricks.routing.RoutingDispatcher.Events;
 14import org.jboss.netty.buffer.ChannelBuffers;
 15import org.jboss.netty.channel.ChannelFuture;
 16import org.jboss.netty.channel.ChannelFutureListener;
 17import org.jboss.netty.channel.ChannelHandlerContext;
 18import org.jboss.netty.channel.MessageEvent;
 19import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
 20import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
 21import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
 22import org.jboss.netty.handler.codec.http.HttpRequest;
 23import org.jboss.netty.handler.codec.http.HttpResponseStatus;
 24import org.jboss.netty.handler.codec.http.HttpVersion;
 25import org.jboss.netty.handler.codec.http.websocket.WebSocketFrame;
 26import org.jboss.netty.util.CharsetUtil;
 27
 28import java.io.IOException;
 29import java.util.Map;
 30import java.util.concurrent.Callable;
 31
 32/**
 33 * @author dhanji@gmail.com (Dhanji R. Prasanna)
 34 */
 35@Singleton
 36class SitebricksAsyncHandler extends SimpleChannelUpstreamHandler {
 37  private static final Key<HttpRequest> HTTP_REQUEST_KEY = Key.get(HttpRequest.class);
 38  private final RoutingDispatcher dispatcher;
 39  private final Provider<Bootstrapper> bootstrapper;
 40  private final Provider<Shutdowner> teardowner;
 41  private final Provider<Request> requestProvider;
 42  private final NettyReplyMaker replyMaker;
 43
 44  @Inject
 45  SitebricksAsyncHandler(RoutingDispatcher dispatcher, Provider<Bootstrapper> bootstrapper,
 46                         Provider<Shutdowner> teardowner,
 47                         Provider<Request> requestProvider,
 48                         NettyReplyMaker replyMaker) {
 49    this.dispatcher = dispatcher;
 50    this.bootstrapper = bootstrapper;
 51    this.teardowner = teardowner;
 52    this.replyMaker = replyMaker;
 53    this.requestProvider = requestProvider;
 54  }
 55
 56  private Config config;
 57
 58  public void startup(Config config) {
 59    this.config = config;
 60    bootstrapper.get().start();
 61  }
 62
 63  public void shutdown() {
 64    teardowner.get().shutdown();
 65  }
 66
 67  @Override
 68  public void messageReceived(final ChannelHandlerContext ctx, MessageEvent e)
 69      throws Exception {
 70    Object message = e.getMessage();
 71    if (message instanceof HttpRequest) {
 72      // Handle normal request.
 73      HttpRequest request = (HttpRequest) message;
 74
 75      // Scope this request using Guice thread-local scopes.
 76      Map<Key<?>,Object> seedMap = Maps.newHashMap();
 77      seedMap.put(HTTP_REQUEST_KEY, request);
 78
 79      ServletScopes.scopeRequest(new Callable<Object>() {
 80            @Override
 81            public Object call() throws Exception {
 82              // Close channel once we're done with request.
 83              handleHttpRequest(ctx).addListener(new ChannelFutureListener() {
 84                @Override
 85                public void operationComplete(ChannelFuture future) throws Exception {
 86                  try {
 87                    dispatcher.dispatch(requestProvider.get(), Events.AFTER);
 88                  } finally {
 89                    ChannelFutureListener.CLOSE.operationComplete(future);
 90                  }
 91                }
 92              });
 93              return null;
 94            }
 95          }, seedMap).call();
 96
 97    } else if (message instanceof WebSocketFrame) {
 98      // Handle websocket frame.
 99    }
100  }
101
102  private ChannelFuture handleHttpRequest(ChannelHandlerContext ctx) throws IOException {
103    // Because this method executes within a request scope, we can obtain the Sitebricks
104    // request directly from its provider.
105    Object respondObject = dispatcher.dispatch(this.requestProvider.get(), Events.DURING);
106
107    //was there any matching page? (if it was a headless response, we don't need to do anything).
108    // Also we do not do anything if the page elected to do nothing.
109    if (null != respondObject) {
110
111      // Only use the string rendering pipeline if this is not a headless request.
112      if (respondObject instanceof Respond) {
113        Respond respond = (Respond) respondObject;
114
115        //do we need to redirect or was this a successful render?
116        final String redirect = respond.getRedirect();
117        if (null != redirect) {
118          // A redirect is called for...
119          DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
120              HttpResponseStatus.TEMPORARY_REDIRECT);
121          response.setHeader(Names.LOCATION, redirect);
122          return ctx.getChannel().write(response);
123        } else { //successful render
124
125          // by checking if a content type was set, we allow users to override content-type
126          //  on an arbitrary basis
127          DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
128              HttpResponseStatus.OK);
129          response.setContent(ChannelBuffers.copiedBuffer(respond.toString(), CharsetUtil.UTF_8));
130          response.setHeader(Names.CONTENT_TYPE, respond.getContentType());
131
132          // Apparently you only need this for a keepAlive connection? Handle them...
133          response.setHeader(Names.CONTENT_LENGTH, response.getContent().readableBytes());
134
135          return ctx.getChannel().write(response);
136        }
137      } else { // It must be a headless Reply. Render the headless response.
138        assert respondObject instanceof Reply; // This just has to be true.
139        return ctx.getChannel().write(replyMaker.populate((Reply<Object>) respondObject));
140      }
141    } else {
142      // Resource not found, reject (TODO we should serve static resources here from the file system)
143      DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
144          HttpResponseStatus.NOT_FOUND);
145      System.out.println("Not found for " + requestProvider.get().path());
146      return ctx.getChannel().write(response);
147    }
148  }
149}