1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.example.http.upload;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.Channel;
20 import io.netty.channel.ChannelFuture;
21 import io.netty.channel.ChannelFutureListener;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.channel.SimpleChannelInboundHandler;
24 import io.netty.handler.codec.http.DefaultFullHttpResponse;
25 import io.netty.handler.codec.http.FullHttpResponse;
26 import io.netty.handler.codec.http.HttpContent;
27 import io.netty.handler.codec.http.HttpHeaderNames;
28 import io.netty.handler.codec.http.HttpUtil;
29 import io.netty.handler.codec.http.HttpHeaderValues;
30 import io.netty.handler.codec.http.HttpMethod;
31 import io.netty.handler.codec.http.HttpObject;
32 import io.netty.handler.codec.http.HttpRequest;
33 import io.netty.handler.codec.http.HttpResponseStatus;
34 import io.netty.handler.codec.http.HttpVersion;
35 import io.netty.handler.codec.http.LastHttpContent;
36 import io.netty.handler.codec.http.QueryStringDecoder;
37 import io.netty.handler.codec.http.cookie.Cookie;
38 import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
39 import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
40 import io.netty.handler.codec.http.multipart.Attribute;
41 import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
42 import io.netty.handler.codec.http.multipart.DiskAttribute;
43 import io.netty.handler.codec.http.multipart.DiskFileUpload;
44 import io.netty.handler.codec.http.multipart.FileUpload;
45 import io.netty.handler.codec.http.multipart.HttpData;
46 import io.netty.handler.codec.http.multipart.HttpDataFactory;
47 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
48 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
49 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
50 import io.netty.handler.codec.http.multipart.InterfaceHttpData;
51 import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
52 import io.netty.util.CharsetUtil;
53
54 import java.io.IOException;
55 import java.net.URI;
56 import java.util.Collections;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Map.Entry;
60 import java.util.Set;
61 import java.util.logging.Level;
62 import java.util.logging.Logger;
63
64 import static io.netty.buffer.Unpooled.*;
65
66 public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObject> {
67
68 private static final Logger logger = Logger.getLogger(HttpUploadServerHandler.class.getName());
69
70 private HttpRequest request;
71
72 private HttpData partialContent;
73
74 private final StringBuilder responseContent = new StringBuilder();
75
76 private static final HttpDataFactory factory =
77 new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
78
79 private HttpPostRequestDecoder decoder;
80
81 static {
82 DiskFileUpload.deleteOnExitTemporaryFile = true;
83
84
85 DiskFileUpload.baseDirectory = null;
86 DiskAttribute.deleteOnExitTemporaryFile = true;
87
88 DiskAttribute.baseDirectory = null;
89 }
90
91 @Override
92 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
93 if (decoder != null) {
94 decoder.cleanFiles();
95 }
96 }
97
98 @Override
99 public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
100 if (msg instanceof HttpRequest) {
101 HttpRequest request = this.request = (HttpRequest) msg;
102 URI uri = new URI(request.uri());
103 if (!uri.getPath().startsWith("/form")) {
104
105 writeMenu(ctx);
106 return;
107 }
108 responseContent.setLength(0);
109 responseContent.append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
110 responseContent.append("===================================\r\n");
111
112 responseContent.append("VERSION: " + request.protocolVersion().text() + "\r\n");
113
114 responseContent.append("REQUEST_URI: " + request.uri() + "\r\n\r\n");
115 responseContent.append("\r\n\r\n");
116
117
118 for (Entry<String, String> entry : request.headers()) {
119 responseContent.append("HEADER: " + entry.getKey() + '=' + entry.getValue() + "\r\n");
120 }
121 responseContent.append("\r\n\r\n");
122
123
124 Set<Cookie> cookies;
125 String value = request.headers().get(HttpHeaderNames.COOKIE);
126 if (value == null) {
127 cookies = Collections.emptySet();
128 } else {
129 cookies = ServerCookieDecoder.STRICT.decode(value);
130 }
131 for (Cookie cookie : cookies) {
132 responseContent.append("COOKIE: " + cookie + "\r\n");
133 }
134 responseContent.append("\r\n\r\n");
135
136 QueryStringDecoder decoderQuery = new QueryStringDecoder(request.uri());
137 Map<String, List<String>> uriAttributes = decoderQuery.parameters();
138 for (Entry<String, List<String>> attr: uriAttributes.entrySet()) {
139 for (String attrVal: attr.getValue()) {
140 responseContent.append("URI: " + attr.getKey() + '=' + attrVal + "\r\n");
141 }
142 }
143 responseContent.append("\r\n\r\n");
144
145
146 if (HttpMethod.GET.equals(request.method())) {
147
148
149 responseContent.append("\r\n\r\nEND OF GET CONTENT\r\n");
150
151 return;
152 }
153 try {
154 decoder = new HttpPostRequestDecoder(factory, request);
155 } catch (ErrorDataDecoderException e1) {
156 e1.printStackTrace();
157 responseContent.append(e1.getMessage());
158 writeResponse(ctx.channel(), true);
159 return;
160 }
161
162 boolean readingChunks = HttpUtil.isTransferEncodingChunked(request);
163 responseContent.append("Is Chunked: " + readingChunks + "\r\n");
164 responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n");
165 if (readingChunks) {
166
167 responseContent.append("Chunks: ");
168 }
169 }
170
171
172
173 if (decoder != null) {
174 if (msg instanceof HttpContent) {
175
176 HttpContent chunk = (HttpContent) msg;
177 try {
178 decoder.offer(chunk);
179 } catch (ErrorDataDecoderException e1) {
180 e1.printStackTrace();
181 responseContent.append(e1.getMessage());
182 writeResponse(ctx.channel(), true);
183 return;
184 }
185 responseContent.append('o');
186
187
188 readHttpDataChunkByChunk();
189
190 if (chunk instanceof LastHttpContent) {
191 writeResponse(ctx.channel());
192
193 reset();
194 }
195 }
196 } else {
197 writeResponse(ctx.channel());
198 }
199 }
200
201 private void reset() {
202 request = null;
203
204
205 decoder.destroy();
206 decoder = null;
207 }
208
209
210
211
212 private void readHttpDataChunkByChunk() {
213 try {
214 while (decoder.hasNext()) {
215 InterfaceHttpData data = decoder.next();
216 if (data != null) {
217
218 if (partialContent == data) {
219 logger.info(" 100% (FinalSize: " + partialContent.length() + ")");
220 partialContent = null;
221 }
222
223 writeHttpData(data);
224 }
225 }
226
227 InterfaceHttpData data = decoder.currentPartialHttpData();
228 if (data != null) {
229 StringBuilder builder = new StringBuilder();
230 if (partialContent == null) {
231 partialContent = (HttpData) data;
232 if (partialContent instanceof FileUpload) {
233 builder.append("Start FileUpload: ")
234 .append(((FileUpload) partialContent).getFilename()).append(" ");
235 } else {
236 builder.append("Start Attribute: ")
237 .append(partialContent.getName()).append(" ");
238 }
239 builder.append("(DefinedSize: ").append(partialContent.definedLength()).append(")");
240 }
241 if (partialContent.definedLength() > 0) {
242 builder.append(" ").append(partialContent.length() * 100 / partialContent.definedLength())
243 .append("% ");
244 logger.info(builder.toString());
245 } else {
246 builder.append(" ").append(partialContent.length()).append(" ");
247 logger.info(builder.toString());
248 }
249 }
250 } catch (EndOfDataDecoderException e1) {
251
252 responseContent.append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n");
253 }
254 }
255
256 private void writeHttpData(InterfaceHttpData data) {
257 if (data.getHttpDataType() == HttpDataType.Attribute) {
258 Attribute attribute = (Attribute) data;
259 String value;
260 try {
261 value = attribute.getValue();
262 } catch (IOException e1) {
263
264 e1.printStackTrace();
265 responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": "
266 + attribute.getName() + " Error while reading value: " + e1.getMessage() + "\r\n");
267 return;
268 }
269 if (value.length() > 100) {
270 responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": "
271 + attribute.getName() + " data too long\r\n");
272 } else {
273 responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": "
274 + attribute + "\r\n");
275 }
276 } else {
277 responseContent.append("\r\nBODY FileUpload: " + data.getHttpDataType().name() + ": " + data
278 + "\r\n");
279 if (data.getHttpDataType() == HttpDataType.FileUpload) {
280 FileUpload fileUpload = (FileUpload) data;
281 if (fileUpload.isCompleted()) {
282 if (fileUpload.length() < 10000) {
283 responseContent.append("\tContent of file\r\n");
284 try {
285 responseContent.append(fileUpload.getString(fileUpload.getCharset()));
286 } catch (IOException e1) {
287
288 e1.printStackTrace();
289 }
290 responseContent.append("\r\n");
291 } else {
292 responseContent.append("\tFile too long to be printed out:" + fileUpload.length() + "\r\n");
293 }
294
295
296
297
298
299
300 } else {
301 responseContent.append("\tFile to be continued but should not!\r\n");
302 }
303 }
304 }
305 }
306
307 private void writeResponse(Channel channel) {
308 writeResponse(channel, false);
309 }
310
311 private void writeResponse(Channel channel, boolean forceClose) {
312
313 ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
314 responseContent.setLength(0);
315
316
317 boolean keepAlive = HttpUtil.isKeepAlive(request) && !forceClose;
318
319
320 FullHttpResponse response = new DefaultFullHttpResponse(
321 HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
322 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
323 response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
324
325 if (!keepAlive) {
326 response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
327 } else if (request.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
328 response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
329 }
330
331 Set<Cookie> cookies;
332 String value = request.headers().get(HttpHeaderNames.COOKIE);
333 if (value == null) {
334 cookies = Collections.emptySet();
335 } else {
336 cookies = ServerCookieDecoder.STRICT.decode(value);
337 }
338 if (!cookies.isEmpty()) {
339
340 for (Cookie cookie : cookies) {
341 response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
342 }
343 }
344
345 ChannelFuture future = channel.writeAndFlush(response);
346
347 if (!keepAlive) {
348 future.addListener(ChannelFutureListener.CLOSE);
349 }
350 }
351
352 private void writeMenu(ChannelHandlerContext ctx) {
353
354
355 responseContent.setLength(0);
356
357
358 responseContent.append("<html>");
359 responseContent.append("<head>");
360 responseContent.append("<title>Netty Test Form</title>\r\n");
361 responseContent.append("</head>\r\n");
362 responseContent.append("<body bgcolor=white><style>td{font-size: 12pt;}</style>");
363
364 responseContent.append("<table border=\"0\">");
365 responseContent.append("<tr>");
366 responseContent.append("<td>");
367 responseContent.append("<h1>Netty Test Form</h1>");
368 responseContent.append("Choose one FORM");
369 responseContent.append("</td>");
370 responseContent.append("</tr>");
371 responseContent.append("</table>\r\n");
372
373
374 responseContent.append("<CENTER>GET FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
375 responseContent.append("<FORM ACTION=\"/formget\" METHOD=\"GET\">");
376 responseContent.append("<input type=hidden name=getform value=\"GET\">");
377 responseContent.append("<table border=\"0\">");
378 responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
379 responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
380 responseContent
381 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
382 responseContent.append("</td></tr>");
383 responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
384 responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
385 responseContent.append("</table></FORM>\r\n");
386 responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
387
388
389 responseContent.append("<CENTER>POST FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
390 responseContent.append("<FORM ACTION=\"/formpost\" METHOD=\"POST\">");
391 responseContent.append("<input type=hidden name=getform value=\"POST\">");
392 responseContent.append("<table border=\"0\">");
393 responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
394 responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
395 responseContent
396 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
397 responseContent.append("<tr><td>Fill with file (only file name will be transmitted): <br> "
398 + "<input type=file name=\"myfile\">");
399 responseContent.append("</td></tr>");
400 responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
401 responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
402 responseContent.append("</table></FORM>\r\n");
403 responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
404
405
406 responseContent.append("<CENTER>POST MULTIPART FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
407 responseContent.append("<FORM ACTION=\"/formpostmultipart\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">");
408 responseContent.append("<input type=hidden name=getform value=\"POST\">");
409 responseContent.append("<table border=\"0\">");
410 responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
411 responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
412 responseContent
413 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
414 responseContent.append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">");
415 responseContent.append("</td></tr>");
416 responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
417 responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
418 responseContent.append("</table></FORM>\r\n");
419 responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
420
421 responseContent.append("</body>");
422 responseContent.append("</html>");
423
424 ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
425
426 FullHttpResponse response = new DefaultFullHttpResponse(
427 HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
428
429 response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
430 response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
431
432
433 boolean keepAlive = HttpUtil.isKeepAlive(request);
434 if (!keepAlive) {
435 response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
436 } else if (request.protocolVersion().equals(HttpVersion.HTTP_1_0)) {
437 response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
438 }
439
440
441 ChannelFuture future = ctx.channel().writeAndFlush(response);
442
443 if (!keepAlive) {
444 future.addListener(ChannelFutureListener.CLOSE);
445 }
446 }
447
448 @Override
449 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
450 logger.log(Level.WARNING, responseContent.toString(), cause);
451 ctx.channel().close();
452 }
453 }