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 }