View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
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.DefaultFullHttpResponse;
26  import io.netty.handler.codec.http.FullHttpResponse;
27  import io.netty.handler.codec.http.HttpContent;
28  import io.netty.handler.codec.http.HttpHeaderNames;
29  import io.netty.handler.codec.http.HttpHeaderUtil;
30  import io.netty.handler.codec.http.HttpHeaderValues;
31  import io.netty.handler.codec.http.HttpMethod;
32  import io.netty.handler.codec.http.HttpObject;
33  import io.netty.handler.codec.http.HttpRequest;
34  import io.netty.handler.codec.http.HttpResponseStatus;
35  import io.netty.handler.codec.http.HttpVersion;
36  import io.netty.handler.codec.http.LastHttpContent;
37  import io.netty.handler.codec.http.QueryStringDecoder;
38  import io.netty.handler.codec.http.ServerCookieDecoder;
39  import io.netty.handler.codec.http.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.HttpDataFactory;
46  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
47  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
48  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
49  import io.netty.handler.codec.http.multipart.InterfaceHttpData;
50  import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
51  import io.netty.util.CharsetUtil;
52  
53  import java.io.IOException;
54  import java.net.URI;
55  import java.util.Collections;
56  import java.util.List;
57  import java.util.Map;
58  import java.util.Map.Entry;
59  import java.util.Set;
60  import java.util.logging.Level;
61  import java.util.logging.Logger;
62  
63  import static io.netty.buffer.Unpooled.*;
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); // Disk if size exceed
77  
78      private HttpPostRequestDecoder decoder;
79      static {
80          DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file
81                                                           // on exit (in normal
82                                                           // exit)
83          DiskFileUpload.baseDirectory = null; // system temp directory
84          DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on
85                                                          // exit (in normal exit)
86          DiskAttribute.baseDirectory = null; // system temp directory
87      }
88  
89      @Override
90      public void channelInactive(ChannelHandlerContext ctx) throws Exception {
91          if (decoder != null) {
92              decoder.cleanFiles();
93          }
94      }
95  
96      @Override
97      public void messageReceived(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
98          if (msg instanceof HttpRequest) {
99              HttpRequest request = this.request = (HttpRequest) msg;
100             URI uri = new URI(request.uri());
101             if (!uri.getPath().startsWith("/form")) {
102                 // Write Menu
103                 writeMenu(ctx);
104                 return;
105             }
106             responseContent.setLength(0);
107             responseContent.append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
108             responseContent.append("===================================\r\n");
109 
110             responseContent.append("VERSION: " + request.protocolVersion().text() + "\r\n");
111 
112             responseContent.append("REQUEST_URI: " + request.uri() + "\r\n\r\n");
113             responseContent.append("\r\n\r\n");
114 
115             // new getMethod
116             for (Entry<CharSequence, CharSequence> entry : request.headers()) {
117                 responseContent.append("HEADER: " + entry.getKey() + '=' + entry.getValue() + "\r\n");
118             }
119             responseContent.append("\r\n\r\n");
120 
121             // new getMethod
122             Set<Cookie> cookies;
123             String value = request.headers().getAndConvert(HttpHeaderNames.COOKIE);
124             if (value == null) {
125                 cookies = Collections.emptySet();
126             } else {
127                 cookies = ServerCookieDecoder.decode(value);
128             }
129             for (Cookie cookie : cookies) {
130                 responseContent.append("COOKIE: " + cookie + "\r\n");
131             }
132             responseContent.append("\r\n\r\n");
133 
134             QueryStringDecoder decoderQuery = new QueryStringDecoder(request.uri());
135             Map<String, List<String>> uriAttributes = decoderQuery.parameters();
136             for (Entry<String, List<String>> attr: uriAttributes.entrySet()) {
137                 for (String attrVal: attr.getValue()) {
138                     responseContent.append("URI: " + attr.getKey() + '=' + attrVal + "\r\n");
139                 }
140             }
141             responseContent.append("\r\n\r\n");
142 
143             // if GET Method: should not try to create a HttpPostRequestDecoder
144             if (request.method().equals(HttpMethod.GET)) {
145                 // GET Method: should not try to create a HttpPostRequestDecoder
146                 // So stop here
147                 responseContent.append("\r\n\r\nEND OF GET CONTENT\r\n");
148                 // Not now: LastHttpContent will be sent writeResponse(ctx.channel());
149                 return;
150             }
151             try {
152                 decoder = new HttpPostRequestDecoder(factory, request);
153             } catch (ErrorDataDecoderException e1) {
154                 e1.printStackTrace();
155                 responseContent.append(e1.getMessage());
156                 writeResponse(ctx.channel());
157                 ctx.channel().close();
158                 return;
159             }
160 
161             readingChunks = HttpHeaderUtil.isTransferEncodingChunked(request);
162             responseContent.append("Is Chunked: " + readingChunks + "\r\n");
163             responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n");
164             if (readingChunks) {
165                 // Chunk version
166                 responseContent.append("Chunks: ");
167                 readingChunks = true;
168             }
169         }
170 
171         // check if the decoder was constructed before
172         // if not it handles the form get
173         if (decoder != null) {
174             if (msg instanceof HttpContent) {
175                 // New chunk is received
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());
183                     ctx.channel().close();
184                     return;
185                 }
186                 responseContent.append('o');
187                 // example of reading chunk by chunk (minimize memory usage due to
188                 // Factory)
189                 readHttpDataChunkByChunk();
190                 // example of reading only if at the end
191                 if (chunk instanceof LastHttpContent) {
192                     writeResponse(ctx.channel());
193                     readingChunks = false;
194 
195                     reset();
196                 }
197             }
198         } else {
199             writeResponse(ctx.channel());
200         }
201     }
202 
203     private void reset() {
204         request = null;
205 
206         // destroy the decoder to release all resources
207         decoder.destroy();
208         decoder = null;
209     }
210 
211     /**
212      * Example of reading request by chunk and getting values from chunk to chunk
213      */
214     private void readHttpDataChunkByChunk() {
215         try {
216             while (decoder.hasNext()) {
217                 InterfaceHttpData data = decoder.next();
218                 if (data != null) {
219                     try {
220                         // new value
221                         writeHttpData(data);
222                     } finally {
223                         data.release();
224                     }
225                 }
226             }
227         } catch (EndOfDataDecoderException e1) {
228             // end
229             responseContent.append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n");
230         }
231     }
232 
233     private void writeHttpData(InterfaceHttpData data) {
234         if (data.getHttpDataType() == HttpDataType.Attribute) {
235             Attribute attribute = (Attribute) data;
236             String value;
237             try {
238                 value = attribute.getValue();
239             } catch (IOException e1) {
240                 // Error while reading data from File, only print name and error
241                 e1.printStackTrace();
242                 responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": "
243                         + attribute.getName() + " Error while reading value: " + e1.getMessage() + "\r\n");
244                 return;
245             }
246             if (value.length() > 100) {
247                 responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": "
248                         + attribute.getName() + " data too long\r\n");
249             } else {
250                 responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": "
251                         + attribute + "\r\n");
252             }
253         } else {
254             responseContent.append("\r\nBODY FileUpload: " + data.getHttpDataType().name() + ": " + data
255                     + "\r\n");
256             if (data.getHttpDataType() == HttpDataType.FileUpload) {
257                 FileUpload fileUpload = (FileUpload) data;
258                 if (fileUpload.isCompleted()) {
259                     if (fileUpload.length() < 10000) {
260                         responseContent.append("\tContent of file\r\n");
261                         try {
262                             responseContent.append(fileUpload.getString(fileUpload.getCharset()));
263                         } catch (IOException e1) {
264                             // do nothing for the example
265                             e1.printStackTrace();
266                         }
267                         responseContent.append("\r\n");
268                     } else {
269                         responseContent.append("\tFile too long to be printed out:" + fileUpload.length() + "\r\n");
270                     }
271                     // fileUpload.isInMemory();// tells if the file is in Memory
272                     // or on File
273                     // fileUpload.renameTo(dest); // enable to move into another
274                     // File dest
275                     // decoder.removeFileUploadFromClean(fileUpload); //remove
276                     // the File of to delete file
277                 } else {
278                     responseContent.append("\tFile to be continued but should not!\r\n");
279                 }
280             }
281         }
282     }
283 
284     private void writeResponse(Channel channel) {
285         // Convert the response content to a ChannelBuffer.
286         ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
287         responseContent.setLength(0);
288 
289         // Decide whether to close the connection or not.
290         boolean close = request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true)
291                 || request.protocolVersion().equals(HttpVersion.HTTP_1_0)
292                 && !request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE, true);
293 
294         // Build the response object.
295         FullHttpResponse response = new DefaultFullHttpResponse(
296                 HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
297         response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
298 
299         if (!close) {
300             // There's no need to add 'Content-Length' header
301             // if this is the last response.
302             response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
303         }
304 
305         Set<Cookie> cookies;
306         String value = request.headers().getAndConvert(HttpHeaderNames.COOKIE);
307         if (value == null) {
308             cookies = Collections.emptySet();
309         } else {
310             cookies = ServerCookieDecoder.decode(value);
311         }
312         if (!cookies.isEmpty()) {
313             // Reset the cookies if necessary.
314             for (Cookie cookie : cookies) {
315                 response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.encode(cookie));
316             }
317         }
318         // Write the response.
319         ChannelFuture future = channel.writeAndFlush(response);
320         // Close the connection after the write operation is done if necessary.
321         if (close) {
322             future.addListener(ChannelFutureListener.CLOSE);
323         }
324     }
325 
326     private void writeMenu(ChannelHandlerContext ctx) {
327         // print several HTML forms
328         // Convert the response content to a ChannelBuffer.
329         responseContent.setLength(0);
330 
331         // create Pseudo Menu
332         responseContent.append("<html>");
333         responseContent.append("<head>");
334         responseContent.append("<title>Netty Test Form</title>\r\n");
335         responseContent.append("</head>\r\n");
336         responseContent.append("<body bgcolor=white><style>td{font-size: 12pt;}</style>");
337 
338         responseContent.append("<table border=\"0\">");
339         responseContent.append("<tr>");
340         responseContent.append("<td>");
341         responseContent.append("<h1>Netty Test Form</h1>");
342         responseContent.append("Choose one FORM");
343         responseContent.append("</td>");
344         responseContent.append("</tr>");
345         responseContent.append("</table>\r\n");
346 
347         // GET
348         responseContent.append("<CENTER>GET FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
349         responseContent.append("<FORM ACTION=\"/formget\" METHOD=\"GET\">");
350         responseContent.append("<input type=hidden name=getform value=\"GET\">");
351         responseContent.append("<table border=\"0\">");
352         responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
353         responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
354         responseContent
355                 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
356         responseContent.append("</td></tr>");
357         responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
358         responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
359         responseContent.append("</table></FORM>\r\n");
360         responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
361 
362         // POST
363         responseContent.append("<CENTER>POST FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
364         responseContent.append("<FORM ACTION=\"/formpost\" METHOD=\"POST\">");
365         responseContent.append("<input type=hidden name=getform value=\"POST\">");
366         responseContent.append("<table border=\"0\">");
367         responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
368         responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
369         responseContent
370                 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
371         responseContent.append("<tr><td>Fill with file (only file name will be transmitted): <br> "
372                 + "<input type=file name=\"myfile\">");
373         responseContent.append("</td></tr>");
374         responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
375         responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
376         responseContent.append("</table></FORM>\r\n");
377         responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
378 
379         // POST with enctype="multipart/form-data"
380         responseContent.append("<CENTER>POST MULTIPART FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
381         responseContent.append("<FORM ACTION=\"/formpostmultipart\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">");
382         responseContent.append("<input type=hidden name=getform value=\"POST\">");
383         responseContent.append("<table border=\"0\">");
384         responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
385         responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
386         responseContent
387                 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
388         responseContent.append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">");
389         responseContent.append("</td></tr>");
390         responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
391         responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
392         responseContent.append("</table></FORM>\r\n");
393         responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
394 
395         responseContent.append("</body>");
396         responseContent.append("</html>");
397 
398         ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
399         // Build the response object.
400         FullHttpResponse response = new DefaultFullHttpResponse(
401                 HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
402 
403         response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
404         response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
405 
406         // Write the response.
407         ctx.channel().writeAndFlush(response);
408     }
409 
410     @Override
411     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
412         logger.log(Level.WARNING, responseContent.toString(), cause);
413         ctx.channel().close();
414     }
415 }