HttpRequestRouter.java
/*
* Copyright 2016 The Lannister Project
*
* 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 net.anyflow.lannister.http;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import org.apache.commons.io.IOUtils;
import com.google.common.io.Files;
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.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import net.anyflow.lannister.Application;
import net.anyflow.lannister.Settings;
public class HttpRequestRouter extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(HttpRequestRouter.class);
private boolean isStaticResourcePath(String path) {
String ext = Files.getFileExtension(path);
if (ext == null) { return false; }
return Settings.INSTANCE.webResourceExtensionToMimes().keySet().stream().anyMatch(s -> s.equalsIgnoreCase(ext));
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest rawRequest) throws Exception {
Date now = new Date();
HttpRequest request = new HttpRequest(rawRequest);
if (HttpHeaderValues.WEBSOCKET.toString().equalsIgnoreCase(rawRequest.headers().get(HttpHeaderNames.UPGRADE))
&& HttpHeaderValues.UPGRADE.toString()
.equalsIgnoreCase(rawRequest.headers().get(HttpHeaderNames.CONNECTION))) {
if (ctx.pipeline().get(WebsocketFrameHandler.class) == null) {
logger.error("No WebSocket Handler available");
ctx.channel()
.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN))
.addListener(ChannelFutureListener.CLOSE);
return;
}
ctx.fireChannelRead(rawRequest.retain());
return;
}
if (HttpUtil.is100ContinueExpected(rawRequest)) {
ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));
return;
}
HttpResponse response = HttpResponse.createServerDefault(request.headers().get(HttpHeaderNames.COOKIE));
String requestPath = new URI(request.uri()).getPath();
if (isStaticResourcePath(requestPath)) {
handleStaticResource(response, requestPath);
}
else {
try {
handleDynamicResource(request, response);
}
catch (URISyntaxException e) {
set404Response(response);
}
catch (Exception e) {
response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
logger.error("Unknown exception was thrown in business logic handler.\r\n{}", e.getMessage(), e);
}
}
setDefaultHeaders(rawRequest, response);
if ("true".equalsIgnoreCase(Settings.INSTANCE.getProperty("webserver.logging.writeHttpRequest"))) {
logger.info(request.toString(now));
}
if ("true".equalsIgnoreCase(Settings.INSTANCE.getProperty("webserver.logging.writeHttpResponse"))) {
logger.info(response.toString());
}
ctx.write(response);
}
private void set404Response(HttpResponse response) {
response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
response.setStatus(HttpResponseStatus.NOT_FOUND);
response.setContent(HttpResponseStatus.NOT_FOUND.toString());
}
private void handleStaticResource(HttpResponse response, String webResourceRequestPath) throws IOException {
String requestPath = webResourceRequestPath;
if (requestPath.startsWith("/")) {
requestPath = requestPath.substring(1, requestPath.length());
}
InputStream is = Application.class.getClassLoader().getResourceAsStream(requestPath);
try {
if (is == null) {
set404Response(response);
}
else {
response.content().writeBytes(IOUtils.toByteArray(is));
String ext = Files.getFileExtension(requestPath);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,
Settings.INSTANCE.webResourceExtensionToMimes().get(ext));
}
}
finally {
if (is != null) {
is.close();
}
}
}
private void handleDynamicResource(HttpRequest request, HttpResponse response)
throws InstantiationException, IllegalAccessException, IOException, URISyntaxException {
HttpRequestHandler.MatchedCriterion mc = HttpRequestHandler.findRequestHandler(new URI(request.uri()).getPath(),
request.method().toString());
if (mc.requestHandlerClass() == null) {
set404Response(response);
}
else {
HttpRequestHandler handler = mc.requestHandlerClass().newInstance();
String webResourcePath = handler.getClass().getAnnotation(HttpRequestHandler.Handles.class)
.webResourcePath();
if (HttpRequestHandler.NO_WEB_RESOURCE_PATH.equals(webResourcePath)) {
response.setContent(
handler.initialize(request.pathParameters(mc.pathParameters()), response).service());
}
else {
handleStaticResource(response, webResourcePath);
}
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error(cause.getMessage(), cause);
ctx.close();
}
protected static void setDefaultHeaders(FullHttpRequest request, HttpResponse response) {
response.headers().add(HttpHeaderNames.SERVER,
"lannister " + net.anyflow.lannister.Settings.INSTANCE.version());
boolean keepAlive = HttpHeaderValues.KEEP_ALIVE.toString()
.equals(request.headers().get(HttpHeaderNames.CONNECTION));
if (keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
if (Settings.INSTANCE.getProperty("webserver.allowCrossDomain", "false").equalsIgnoreCase("true")) {
response.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
response.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, DELETE");
response.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGARUNER");
response.headers().add(HttpHeaderNames.ACCESS_CONTROL_MAX_AGE, "1728000");
}
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
}
}