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