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 org.jboss.netty.example.http.upload;
17  
18  import org.jboss.netty.buffer.ChannelBuffer;
19  import org.jboss.netty.buffer.ChannelBuffers;
20  import org.jboss.netty.channel.Channel;
21  import org.jboss.netty.channel.ChannelFuture;
22  import org.jboss.netty.channel.ChannelFutureListener;
23  import org.jboss.netty.channel.ChannelHandlerContext;
24  import org.jboss.netty.channel.ChannelStateEvent;
25  import org.jboss.netty.channel.Channels;
26  import org.jboss.netty.channel.ExceptionEvent;
27  import org.jboss.netty.channel.MessageEvent;
28  import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
29  import org.jboss.netty.handler.codec.http.Cookie;
30  import org.jboss.netty.handler.codec.http.CookieDecoder;
31  import org.jboss.netty.handler.codec.http.CookieEncoder;
32  import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
33  import org.jboss.netty.handler.codec.http.HttpChunk;
34  import org.jboss.netty.handler.codec.http.HttpHeaders;
35  import org.jboss.netty.handler.codec.http.HttpRequest;
36  import org.jboss.netty.handler.codec.http.HttpResponse;
37  import org.jboss.netty.handler.codec.http.HttpResponseStatus;
38  import org.jboss.netty.handler.codec.http.HttpVersion;
39  import org.jboss.netty.handler.codec.http.QueryStringDecoder;
40  import org.jboss.netty.handler.codec.http.multipart.Attribute;
41  import org.jboss.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
42  import org.jboss.netty.handler.codec.http.multipart.DiskAttribute;
43  import org.jboss.netty.handler.codec.http.multipart.DiskFileUpload;
44  import org.jboss.netty.handler.codec.http.multipart.FileUpload;
45  import org.jboss.netty.handler.codec.http.multipart.HttpDataFactory;
46  import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
47  import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
48  import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
49  import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.IncompatibleDataDecoderException;
50  import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
51  import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData;
52  import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
53  import org.jboss.netty.logging.InternalLogger;
54  import org.jboss.netty.logging.InternalLoggerFactory;
55  import org.jboss.netty.util.CharsetUtil;
56  
57  import java.io.IOException;
58  import java.net.URI;
59  import java.util.Collections;
60  import java.util.List;
61  import java.util.Map;
62  import java.util.Map.Entry;
63  import java.util.Set;
64  
65  public class HttpUploadServerHandler extends SimpleChannelUpstreamHandler {
66  
67      private static final InternalLogger logger =
68          InternalLoggerFactory.getInstance(HttpUploadServerHandler.class);
69  
70      private HttpRequest request;
71  
72      private boolean readingChunks;
73  
74      private final StringBuilder responseContent = new StringBuilder();
75  
76      private static final HttpDataFactory factory = new DefaultHttpDataFactory(
77              DefaultHttpDataFactory.MINSIZE); // Disk if size exceed MINSIZE
78  
79      private HttpPostRequestDecoder decoder;
80      static {
81          DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file
82                                                           // on exit (in normal
83                                                           // exit)
84          DiskFileUpload.baseDirectory = null; // system temp directory
85          DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on
86                                                          // exit (in normal exit)
87          DiskAttribute.baseDirectory = null; // system temp directory
88      }
89  
90      @Override
91      public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
92              throws Exception {
93          if (decoder != null) {
94              decoder.cleanFiles();
95          }
96      }
97  
98      @Override
99      public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
100         if (!readingChunks) {
101             // clean previous FileUpload if Any
102             if (decoder != null) {
103                 decoder.cleanFiles();
104                 decoder = null;
105             }
106 
107             HttpRequest request = this.request = (HttpRequest) e.getMessage();
108             URI uri = new URI(request.getUri());
109             if (!uri.getPath().startsWith("/form")) {
110                 // Write Menu
111                 writeMenu(e);
112                 return;
113             }
114             responseContent.setLength(0);
115             responseContent.append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
116             responseContent.append("===================================\r\n");
117 
118             responseContent.append("VERSION: " +
119                     request.getProtocolVersion().getText() + "\r\n");
120 
121             responseContent.append("REQUEST_URI: " + request.getUri() +
122                     "\r\n\r\n");
123             responseContent.append("\r\n\r\n");
124 
125             // new method
126             List<Entry<String, String>> headers = request.getHeaders();
127             for (Entry<String, String> entry: headers) {
128                 responseContent.append("HEADER: " + entry.getKey() + '=' +
129                         entry.getValue() + "\r\n");
130             }
131             responseContent.append("\r\n\r\n");
132 
133             // new method
134             Set<Cookie> cookies;
135             String value = request.getHeader(HttpHeaders.Names.COOKIE);
136             if (value == null) {
137                 cookies = Collections.emptySet();
138             } else {
139                 CookieDecoder decoder = new CookieDecoder();
140                 cookies = decoder.decode(value);
141             }
142             for (Cookie cookie: cookies) {
143                 responseContent.append("COOKIE: " + cookie.toString() + "\r\n");
144             }
145             responseContent.append("\r\n\r\n");
146 
147             QueryStringDecoder decoderQuery = new QueryStringDecoder(request
148                     .getUri());
149             Map<String, List<String>> uriAttributes = decoderQuery
150                     .getParameters();
151             for (Entry<String, List<String>> attr: uriAttributes.entrySet()) {
152                 for (String attrVal: attr.getValue()) {
153                     responseContent.append("URI: " + attr.getKey() + '=' + attrVal + "\r\n");
154                 }
155             }
156             responseContent.append("\r\n\r\n");
157 
158             // if GET Method: should not try to create a HttpPostRequestDecoder
159             try {
160                 decoder = new HttpPostRequestDecoder(factory, request);
161             } catch (ErrorDataDecoderException e1) {
162                 e1.printStackTrace();
163                 responseContent.append(e1.getMessage());
164                 writeResponse(e.getChannel());
165                 Channels.close(e.getChannel());
166                 return;
167             } catch (IncompatibleDataDecoderException e1) {
168                 // GET Method: should not try to create a HttpPostRequestDecoder
169                 // So OK but stop here
170                 responseContent.append(e1.getMessage());
171                 responseContent.append("\r\n\r\nEND OF GET CONTENT\r\n");
172                 writeResponse(e.getChannel());
173                 return;
174             }
175 
176             responseContent.append("Is Chunked: " + request.isChunked() +
177                     "\r\n");
178             responseContent.append("IsMultipart: " + decoder.isMultipart() +
179                     "\r\n");
180             if (request.isChunked()) {
181                 // Chunk version
182                 responseContent.append("Chunks: ");
183                 readingChunks = true;
184             } else {
185                 // Not chunk version
186                 readHttpDataAllReceive(e.getChannel());
187                 responseContent
188                         .append("\r\n\r\nEND OF NOT CHUNKED CONTENT\r\n");
189                 writeResponse(e.getChannel());
190             }
191         } else {
192             // New chunk is received
193             HttpChunk chunk = (HttpChunk) e.getMessage();
194             try {
195                 decoder.offer(chunk);
196             } catch (ErrorDataDecoderException e1) {
197                 e1.printStackTrace();
198                 responseContent.append(e1.getMessage());
199                 writeResponse(e.getChannel());
200                 Channels.close(e.getChannel());
201                 return;
202             }
203             responseContent.append('o');
204             // example of reading chunk by chunk (minimize memory usage due to Factory)
205             readHttpDataChunkByChunk(e.getChannel());
206             // example of reading only if at the end
207             if (chunk.isLast()) {
208                 readHttpDataAllReceive(e.getChannel());
209                 writeResponse(e.getChannel());
210                 readingChunks = false;
211             }
212         }
213     }
214 
215     /**
216      * Example of reading all InterfaceHttpData from finished transfer
217      *
218      * @param channel
219      */
220     private void readHttpDataAllReceive(Channel channel) {
221         List<InterfaceHttpData> datas = null;
222         try {
223             datas = decoder.getBodyHttpDatas();
224         } catch (NotEnoughDataDecoderException e1) {
225             // Should not be!
226             e1.printStackTrace();
227             responseContent.append(e1.getMessage());
228             writeResponse(channel);
229             Channels.close(channel);
230             return;
231         }
232         for (InterfaceHttpData data: datas) {
233             writeHttpData(data);
234         }
235         responseContent.append("\r\n\r\nEND OF CONTENT AT FINAL END\r\n");
236     }
237 
238     /**
239      * Example of reading request by chunk and getting values from chunk to
240      * chunk
241      *
242      * @param channel
243      */
244     private void readHttpDataChunkByChunk(Channel channel) {
245         try {
246             while (decoder.hasNext()) {
247                 InterfaceHttpData data = decoder.next();
248                 if (data != null) {
249                     // new value
250                     writeHttpData(data);
251                 }
252             }
253         } catch (EndOfDataDecoderException e1) {
254             // end
255             responseContent
256                     .append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n");
257         }
258     }
259 
260     private void writeHttpData(InterfaceHttpData data) {
261         if (data.getHttpDataType() == HttpDataType.Attribute) {
262             Attribute attribute = (Attribute) data;
263             String value;
264             try {
265                 value = attribute.getValue();
266             } catch (IOException e1) {
267                 // Error while reading data from File, only print name and error
268                 e1.printStackTrace();
269                 responseContent.append("\r\nBODY Attribute: " +
270                         attribute.getHttpDataType().name() + ": " +
271                         attribute.getName() + " Error while reading value: " +
272                         e1.getMessage() + "\r\n");
273                 return;
274             }
275             if (value.length() > 100) {
276                 responseContent.append("\r\nBODY Attribute: " +
277                         attribute.getHttpDataType().name() + ": " +
278                         attribute.getName() + " data too long\r\n");
279             } else {
280                 responseContent.append("\r\nBODY Attribute: " +
281                         attribute.getHttpDataType().name() + ": " +
282                         attribute.toString() + "\r\n");
283             }
284         } else {
285             responseContent.append("\r\nBODY FileUpload: " +
286                     data.getHttpDataType().name() + ": " + data.toString() +
287                     "\r\n");
288             if (data.getHttpDataType() == HttpDataType.FileUpload) {
289                 FileUpload fileUpload = (FileUpload) data;
290                 if (fileUpload.isCompleted()) {
291                     if (fileUpload.length() < 10000) {
292                         responseContent.append("\tContent of file\r\n");
293                         try {
294                             responseContent.append(fileUpload.getString(fileUpload.getCharset()));
295                         } catch (IOException e1) {
296                             // do nothing for the example
297                             e1.printStackTrace();
298                         }
299                         responseContent.append("\r\n");
300                     } else {
301                         responseContent
302                                 .append("\tFile too long to be printed out:" +
303                                         fileUpload.length() + "\r\n");
304                     }
305                     // fileUpload.isInMemory();// tells if the file is in Memory
306                     // or on File
307                     // fileUpload.renameTo(dest); // enable to move into another
308                     // File dest
309                     // decoder.removeFileUploadFromClean(fileUpload); //remove
310                     // the File of to delete file
311                 } else {
312                     responseContent
313                             .append("\tFile to be continued but should not!\r\n");
314                 }
315             }
316         }
317     }
318 
319     private void writeResponse(Channel channel) {
320         // Convert the response content to a ChannelBuffer.
321         ChannelBuffer buf = ChannelBuffers.copiedBuffer(responseContent
322                 .toString(), CharsetUtil.UTF_8);
323         responseContent.setLength(0);
324 
325         // Decide whether to close the connection or not.
326         boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request
327                 .getHeader(HttpHeaders.Names.CONNECTION)) ||
328                 request.getProtocolVersion().equals(HttpVersion.HTTP_1_0) &&
329                 !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request
330                         .getHeader(HttpHeaders.Names.CONNECTION));
331 
332         // Build the response object.
333         HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
334                 HttpResponseStatus.OK);
335         response.setContent(buf);
336         response.setHeader(HttpHeaders.Names.CONTENT_TYPE,
337                 "text/plain; charset=UTF-8");
338 
339         if (!close) {
340             // There's no need to add 'Content-Length' header
341             // if this is the last response.
342             response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String
343                     .valueOf(buf.readableBytes()));
344         }
345 
346         Set<Cookie> cookies;
347         String value = request.getHeader(HttpHeaders.Names.COOKIE);
348         if (value == null) {
349             cookies = Collections.emptySet();
350         } else {
351             CookieDecoder decoder = new CookieDecoder();
352             cookies = decoder.decode(value);
353         }
354         if (!cookies.isEmpty()) {
355             // Reset the cookies if necessary.
356             CookieEncoder cookieEncoder = new CookieEncoder(true);
357             for (Cookie cookie: cookies) {
358                 cookieEncoder.addCookie(cookie);
359                 response.addHeader(HttpHeaders.Names.SET_COOKIE, cookieEncoder
360                         .encode());
361                 cookieEncoder = new CookieEncoder(true);
362             }
363         }
364         // Write the response.
365         ChannelFuture future = channel.write(response);
366         // Close the connection after the write operation is done if necessary.
367         if (close) {
368             future.addListener(ChannelFutureListener.CLOSE);
369         }
370     }
371 
372     private void writeMenu(MessageEvent e) {
373         // print several HTML forms
374         // Convert the response content to a ChannelBuffer.
375         responseContent.setLength(0);
376 
377         // create Pseudo Menu
378         responseContent.append("<html>");
379         responseContent.append("<head>");
380         responseContent.append("<title>Netty Test Form</title>\r\n");
381         responseContent.append("</head>\r\n");
382         responseContent
383                 .append("<body bgcolor=white><style>td{font-size: 12pt;}</style>");
384 
385         responseContent.append("<table border=\"0\">");
386         responseContent.append("<tr>");
387         responseContent.append("<td>");
388         responseContent.append("<h1>Netty Test Form</h1>");
389         responseContent.append("Choose one FORM");
390         responseContent.append("</td>");
391         responseContent.append("</tr>");
392         responseContent.append("</table>\r\n");
393 
394         // GET
395         responseContent
396                 .append("<CENTER>GET FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
397         responseContent.append("<FORM ACTION=\"/formget\" METHOD=\"GET\">");
398         responseContent
399                 .append("<input type=hidden name=getform value=\"GET\">");
400         responseContent.append("<table border=\"0\">");
401         responseContent
402                 .append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
403         responseContent
404                 .append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
405         responseContent
406                 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
407         responseContent.append("</td></tr>");
408         responseContent
409                 .append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
410         responseContent
411                 .append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
412         responseContent.append("</table></FORM>\r\n");
413         responseContent
414                 .append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
415 
416         // POST
417         responseContent
418                 .append("<CENTER>POST FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
419         responseContent.append("<FORM ACTION=\"/formpost\" METHOD=\"POST\">");
420         responseContent
421                 .append("<input type=hidden name=getform value=\"POST\">");
422         responseContent.append("<table border=\"0\">");
423         responseContent
424                 .append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
425         responseContent
426                 .append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
427         responseContent
428                 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
429         responseContent
430                 .append("<tr><td>Fill with file (only file name will be transmitted): <br> " +
431                         "<input type=file name=\"myfile\">");
432         responseContent.append("</td></tr>");
433         responseContent
434                 .append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
435         responseContent
436                 .append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
437         responseContent.append("</table></FORM>\r\n");
438         responseContent
439                 .append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
440 
441         // POST with enctype="multipart/form-data"
442         responseContent
443                 .append("<CENTER>POST MULTIPART FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
444         responseContent
445                 .append("<FORM ACTION=\"/formpostmultipart\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">");
446         responseContent
447                 .append("<input type=hidden name=getform value=\"POST\">");
448         responseContent.append("<table border=\"0\">");
449         responseContent
450                 .append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
451         responseContent
452                 .append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
453         responseContent
454                 .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
455         responseContent
456                 .append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">");
457         responseContent.append("</td></tr>");
458         responseContent
459                 .append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
460         responseContent
461                 .append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
462         responseContent.append("</table></FORM>\r\n");
463         responseContent
464                 .append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
465 
466         responseContent.append("</body>");
467         responseContent.append("</html>");
468 
469         ChannelBuffer buf = ChannelBuffers.copiedBuffer(responseContent
470                 .toString(), CharsetUtil.UTF_8);
471         // Build the response object.
472         HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
473                 HttpResponseStatus.OK);
474         response.setContent(buf);
475         response.setHeader(HttpHeaders.Names.CONTENT_TYPE,
476                 "text/html; charset=UTF-8");
477         response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(buf
478                 .readableBytes()));
479         // Write the response.
480         e.getChannel().write(response);
481     }
482 
483     @Override
484     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
485             throws Exception {
486         logger.error(responseContent.toString(), e.getCause());
487         e.getChannel().close();
488     }
489 }