View Javadoc

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