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