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