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