1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package net.anyflow.lannister.http;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.util.Date;
24
25 import org.apache.commons.io.IOUtils;
26
27 import com.google.common.io.Files;
28
29 import io.netty.channel.ChannelFutureListener;
30 import io.netty.channel.ChannelHandlerContext;
31 import io.netty.channel.SimpleChannelInboundHandler;
32 import io.netty.handler.codec.http.DefaultFullHttpResponse;
33 import io.netty.handler.codec.http.FullHttpRequest;
34 import io.netty.handler.codec.http.HttpHeaderNames;
35 import io.netty.handler.codec.http.HttpHeaderValues;
36 import io.netty.handler.codec.http.HttpResponseStatus;
37 import io.netty.handler.codec.http.HttpUtil;
38 import io.netty.handler.codec.http.HttpVersion;
39 import net.anyflow.lannister.Application;
40 import net.anyflow.lannister.Settings;
41
42 public class HttpRequestRouter extends SimpleChannelInboundHandler<FullHttpRequest> {
43
44 private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(HttpRequestRouter.class);
45
46 private boolean isStaticResourcePath(String path) {
47 String ext = Files.getFileExtension(path);
48 if (ext == null) { return false; }
49
50 return Settings.INSTANCE.webResourceExtensionToMimes().keySet().stream().anyMatch(s -> s.equalsIgnoreCase(ext));
51 }
52
53 @Override
54 protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest rawRequest) throws Exception {
55 Date now = new Date();
56 HttpRequest request = new HttpRequest(rawRequest);
57
58 if (HttpHeaderValues.WEBSOCKET.toString().equalsIgnoreCase(rawRequest.headers().get(HttpHeaderNames.UPGRADE))
59 && HttpHeaderValues.UPGRADE.toString()
60 .equalsIgnoreCase(rawRequest.headers().get(HttpHeaderNames.CONNECTION))) {
61
62 if (ctx.pipeline().get(WebsocketFrameHandler.class) == null) {
63 logger.error("No WebSocket Handler available");
64
65 ctx.channel()
66 .writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN))
67 .addListener(ChannelFutureListener.CLOSE);
68 return;
69 }
70
71 ctx.fireChannelRead(rawRequest.retain());
72 return;
73 }
74
75 if (HttpUtil.is100ContinueExpected(rawRequest)) {
76 ctx.write(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));
77 return;
78 }
79
80 HttpResponse response = HttpResponse.createServerDefault(request.headers().get(HttpHeaderNames.COOKIE));
81
82 String requestPath = new URI(request.uri()).getPath();
83
84 if (isStaticResourcePath(requestPath)) {
85 handleStaticResource(response, requestPath);
86 }
87 else {
88 try {
89 handleDynamicResource(request, response);
90 }
91 catch (URISyntaxException e) {
92 set404Response(response);
93 }
94 catch (Exception e) {
95 response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
96 logger.error("Unknown exception was thrown in business logic handler.\r\n{}", e.getMessage(), e);
97 }
98 }
99
100 setDefaultHeaders(rawRequest, response);
101
102 if ("true".equalsIgnoreCase(Settings.INSTANCE.getProperty("webserver.logging.writeHttpRequest"))) {
103 logger.info(request.toString(now));
104 }
105 if ("true".equalsIgnoreCase(Settings.INSTANCE.getProperty("webserver.logging.writeHttpResponse"))) {
106 logger.info(response.toString());
107 }
108
109 ctx.write(response);
110 }
111
112 private void set404Response(HttpResponse response) {
113 response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
114 response.setStatus(HttpResponseStatus.NOT_FOUND);
115 response.setContent(HttpResponseStatus.NOT_FOUND.toString());
116 }
117
118 private void handleStaticResource(HttpResponse response, String webResourceRequestPath) throws IOException {
119 String requestPath = webResourceRequestPath;
120 if (requestPath.startsWith("/")) {
121 requestPath = requestPath.substring(1, requestPath.length());
122 }
123
124 InputStream is = Application.class.getClassLoader().getResourceAsStream(requestPath);
125
126 try {
127 if (is == null) {
128 set404Response(response);
129 }
130 else {
131 response.content().writeBytes(IOUtils.toByteArray(is));
132
133 String ext = Files.getFileExtension(requestPath);
134 response.headers().set(HttpHeaderNames.CONTENT_TYPE,
135 Settings.INSTANCE.webResourceExtensionToMimes().get(ext));
136 }
137 }
138 finally {
139 if (is != null) {
140 is.close();
141 }
142 }
143 }
144
145 private void handleDynamicResource(HttpRequest request, HttpResponse response)
146 throws InstantiationException, IllegalAccessException, IOException, URISyntaxException {
147 HttpRequestHandler.MatchedCriterion mc = HttpRequestHandler.findRequestHandler(new URI(request.uri()).getPath(),
148 request.method().toString());
149
150 if (mc.requestHandlerClass() == null) {
151 set404Response(response);
152 }
153 else {
154 HttpRequestHandler handler = mc.requestHandlerClass().newInstance();
155
156 String webResourcePath = handler.getClass().getAnnotation(HttpRequestHandler.Handles.class)
157 .webResourcePath();
158
159 if (HttpRequestHandler.NO_WEB_RESOURCE_PATH.equals(webResourcePath)) {
160 response.setContent(
161 handler.initialize(request.pathParameters(mc.pathParameters()), response).service());
162 }
163 else {
164 handleStaticResource(response, webResourcePath);
165 }
166 }
167 }
168
169 @Override
170 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
171 ctx.flush();
172 }
173
174 @Override
175 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
176 logger.error(cause.getMessage(), cause);
177 ctx.close();
178 }
179
180 protected static void setDefaultHeaders(FullHttpRequest request, HttpResponse response) {
181 response.headers().add(HttpHeaderNames.SERVER,
182 "lannister " + net.anyflow.lannister.Settings.INSTANCE.version());
183
184 boolean keepAlive = HttpHeaderValues.KEEP_ALIVE.toString()
185 .equals(request.headers().get(HttpHeaderNames.CONNECTION));
186 if (keepAlive) {
187 response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
188 }
189
190 if (Settings.INSTANCE.getProperty("webserver.allowCrossDomain", "false").equalsIgnoreCase("true")) {
191 response.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
192 response.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, DELETE");
193 response.headers().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGARUNER");
194 response.headers().add(HttpHeaderNames.ACCESS_CONTROL_MAX_AGE, "1728000");
195 }
196
197 response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
198 }
199 }