/** * */ package org.minnal.core; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponseStatus; import java.net.URI; import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.glassfish.jersey.internal.MapPropertiesDelegate; import org.glassfish.jersey.server.ApplicationHandler; import org.glassfish.jersey.server.ContainerRequest; import org.glassfish.jersey.server.ContainerResponse; import org.glassfish.jersey.server.ResourceConfig; import org.minnal.core.config.ApplicationConfiguration; import org.minnal.core.server.MessageContext; import org.minnal.utils.http.HttpUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.util.concurrent.Futures; /** * @author ganeshs * */ public class Router { private ApplicationMapping applicationMapping; private RouterListener listener; private Map<Application<? extends ApplicationConfiguration>, ApplicationHandler> handlers = new HashMap<Application<? extends ApplicationConfiguration>, ApplicationHandler>(); private static final Logger logger = LoggerFactory.getLogger(Router.class); /** * @param applicationMapping */ public Router(ApplicationMapping applicationMapping) { this.applicationMapping = applicationMapping; } /** * @param context */ public void route(MessageContext context) { logger.trace("Routing the context {}", context); Application<ApplicationConfiguration> application = applicationMapping.resolve(context.getRequest()); if (application == null) { throw new NotFoundException("Request path not found"); } context.setApplication(application); if (listener != null) { listener.onApplicationResolved(context); } ApplicationHandler handler = getApplicationHandler(application); ContainerRequest containerRequest = createContainerRequest(context); ByteBuf buffer = Unpooled.buffer(); ContainerResponse response = null; try { response = Futures.getUnchecked(handler.apply(containerRequest, new ByteBufOutputStream(buffer))); } catch (Exception e) { logger.debug("Failed while handling the request - " + containerRequest, e); response = new ContainerResponse(containerRequest, Response.serverError().build()); } UriInfo uriInfo = containerRequest.getUriInfo(); List<String> matchedUris = uriInfo.getMatchedURIs(); if (matchedUris != null && ! matchedUris.isEmpty()) { context.setMatchedRoute(matchedUris.get(0)); } FullHttpResponse httpResponse = createHttpResponse(context, response, buffer); context.setResponse(httpResponse); } /** * Returns the handler for the given application * * @param application * @return */ protected ApplicationHandler getApplicationHandler(Application<ApplicationConfiguration> application) { ApplicationHandler handler = handlers.get(application); if (handler == null) { handler = createApplicationHandler(application.getResourceConfig()); handlers.put(application, handler); } return handler; } /** * @param resourceConfig * @return */ protected ApplicationHandler createApplicationHandler(ResourceConfig resourceConfig) { return new ApplicationHandler(resourceConfig); } /** * Creates the container request from the http request * * @param httpRequest * @return */ protected ContainerRequest createContainerRequest(MessageContext context) { Application<ApplicationConfiguration> application = context.getApplication(); FullHttpRequest httpRequest = context.getRequest(); URI baseUri = URI.create(context.getBaseUri().resolve(application.getPath()) + "/"); URI requestUri = HttpUtil.createURI(httpRequest.getUri()); ContainerRequest containerRequest = new ContainerRequest(baseUri, requestUri, httpRequest.getMethod().name(), null, new MapPropertiesDelegate()); // containerRequest.setProperty(REQUEST_PROPERTY_REMOTE_ADDR, context.getRequest().channel().remoteAddress()); containerRequest.setEntityStream(new ByteBufInputStream(httpRequest.content())); for (Map.Entry<String, String> headerEntry : httpRequest.headers()) { containerRequest.getHeaders().add(headerEntry.getKey(), headerEntry.getValue()); } return containerRequest; } /** * Creates the http response from container response</p> * * <li> Sets the content length to the http response from the container response. If content length is not available, computes from the response entity * <li> * * @param context * @param containerResponse * @param buffer * @return */ protected FullHttpResponse createHttpResponse(MessageContext context, ContainerResponse containerResponse, ByteBuf buffer) { FullHttpRequest httpRequest = context.getRequest(); DefaultFullHttpResponse httpResponse = new DefaultFullHttpResponse(httpRequest.getProtocolVersion(), HttpResponseStatus.valueOf(containerResponse.getStatus()), buffer); int length = containerResponse.getLength(); // FIXME Hack. is there a better way? if (length == -1 && containerResponse.getEntity() instanceof String) { final String entity = (String) containerResponse.getEntity(); final byte[] encodedBytes = entity.getBytes(Charset.forName("UTF-8")); length = encodedBytes.length; } if (! containerResponse.getHeaders().containsKey(HttpHeaders.Names.CONTENT_LENGTH)) { HttpHeaders.setContentLength(httpResponse, length); logger.trace("Writing response status and headers {}, length {}", containerResponse, containerResponse.getLength()); } for (Map.Entry<String, List<Object>> headerEntry : containerResponse.getHeaders().entrySet()) { HttpHeaders.addHeader(httpResponse, headerEntry.getKey(), Joiner.on(", ").join(headerEntry.getValue())); } return httpResponse; } protected void registerListener(RouterListener listener) { this.listener = listener; } }