View Javadoc

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one
    3    *  or more contributor license agreements.  See the NOTICE file
    4    *  distributed with this work for additional information
    5    *  regarding copyright ownership.  The ASF licenses this file
    6    *  to you under the Apache License, Version 2.0 (the
    7    *  "License"); you may not use this file except in compliance
    8    *  with the License.  You may obtain a copy of the License at
    9    *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   *  Unless required by applicable law or agreed to in writing,
    13   *  software distributed under the License is distributed on an
    14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   *  KIND, either express or implied.  See the License for the
    16   *  specific language governing permissions and limitations
    17   *  under the License.
    18   *
    19   */
    20  package org.apache.mina.example.httpserver.codec;
    21
    22  import java.io.BufferedReader;
    23  import java.io.IOException;
    24  import java.io.Reader;
    25  import java.io.StringReader;
    26  import java.nio.charset.CharacterCodingException;
    27  import java.nio.charset.CharsetDecoder;
    28  import java.nio.charset.Charset;
    29  import java.util.HashMap;
    30  import java.util.Map;
    31
    32  import org.apache.mina.common.ByteBuffer;
    33  import org.apache.mina.common.IoSession;
    34  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
    35  import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
    36  import org.apache.mina.filter.codec.demux.MessageDecoderResult;
    37
    38  /**
    39   * A {@link org.apache.mina.filter.codec.demux.MessageDecoder} that decodes {@link HttpRequest}.
    40   *
    41   * @author The Apache Directory Project (mina-dev@directory.apache.org)
    42   * @version $Rev: 555855 $, $Date: 2007-07-13 05:19:00 +0200 (Fri, 13 Jul 2007) $
    43   */
    44  public class HttpRequestDecoder extends MessageDecoderAdapter {
    45      private static final byte[] CONTENT_LENGTH = new String("Content-Length:")
    46              .getBytes();
    47
    48      private CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
    49
    50      private HttpRequestMessage request = null;
    51
    52      public HttpRequestDecoder() {
    53      }
    54
    55      public MessageDecoderResult decodable(IoSession session, ByteBuffer in) {
    56          // Return NEED_DATA if the whole header is not read yet.
    57          try {
    58              return messageComplete(in) ? MessageDecoderResult.OK
    59                      : MessageDecoderResult.NEED_DATA;
    60          } catch (Exception ex) {
    61              ex.printStackTrace();
    62          }
    63
    64          return MessageDecoderResult.NOT_OK;
    65      }
    66
    67      public MessageDecoderResult decode(IoSession session, ByteBuffer in,
    68              ProtocolDecoderOutput out) throws Exception {
    69          // Try to decode body
    70          HttpRequestMessage m = decodeBody(in);
    71
    72          // Return NEED_DATA if the body is not fully read.
    73          if (m == null)
    74              return MessageDecoderResult.NEED_DATA;
    75
    76          out.write(m);
    77
    78          return MessageDecoderResult.OK;
    79      }
    80
    81      private boolean messageComplete(ByteBuffer in) throws Exception {
    82          int last = in.remaining() - 1;
    83          if (in.remaining() < 4)
    84              return false;
    85
    86          // to speed up things we check if the Http request is a GET or POST
    87          if (in.get(0) == (byte) 'G' && in.get(1) == (byte) 'E'
    88                  && in.get(2) == (byte) 'T') {
    89              // Http GET request therefore the last 4 bytes should be 0x0D 0x0A 0x0D 0x0A
    90              return (in.get(last) == (byte) 0x0A
    91                      && in.get(last - 1) == (byte) 0x0D
    92                      && in.get(last - 2) == (byte) 0x0A && in.get(last - 3) == (byte) 0x0D);
    93          } else if (in.get(0) == (byte) 'P' && in.get(1) == (byte) 'O'
    94                  && in.get(2) == (byte) 'S' && in.get(3) == (byte) 'T') {
    95              // Http POST request
    96              // first the position of the 0x0D 0x0A 0x0D 0x0A bytes
    97              int eoh = -1;
    98              for (int i = last; i > 2; i--) {
    99                  if (in.get(i) == (byte) 0x0A && in.get(i - 1) == (byte) 0x0D
    100                         && in.get(i - 2) == (byte) 0x0A
    101                         && in.get(i - 3) == (byte) 0x0D) {
    102                     eoh = i + 1;
    103                     break;
    104                 }
    105             }
    106             if (eoh == -1)
    107                 return false;
    108             for (int i = 0; i < last; i++) {
    109                 boolean found = false;
    110                 for (int j = 0; j < CONTENT_LENGTH.length; j++) {
    111                     if (in.get(i + j) != CONTENT_LENGTH[j]) {
    112                         found = false;
    113                         break;
    114                     }
    115                     found = true;
    116                 }
    117                 if (found) {
    118                     // retrieve value from this position till next 0x0D 0x0A
    119                     StringBuilder contentLength = new StringBuilder();
    120                     for (int j = i + CONTENT_LENGTH.length; j < last; j++) {
    121                         if (in.get(j) == 0x0D)
    122                             break;
    123                         contentLength.append(new String(
    124                                 new byte[] { in.get(j) }));
    125                     }
    126                     // if content-length worth of data has been received then the message is complete
    127                     return (Integer.parseInt(contentLength.toString().trim())
    128                             + eoh == in.remaining());
    129                 }
    130             }
    131         }
    132
    133         // the message is not complete and we need more data
    134         return false;
    135     }
    136
    137     private HttpRequestMessage decodeBody(ByteBuffer in) {
    138         request = new HttpRequestMessage();
    139         try {
    140             request.setHeaders(parseRequest(new StringReader(in
    141                     .getString(decoder))));
    142             return request;
    143         } catch (CharacterCodingException ex) {
    144             ex.printStackTrace();
    145         }
    146
    147         return null;
    148     }
    149
    150     private Map parseRequest(Reader is) {
    151         Map<String, String[]> map = new HashMap<String, String[]>();
    152         BufferedReader rdr = new BufferedReader(is);
    153
    154         try {
    155             // Get request URL.
    156             String line = rdr.readLine();
    157             String[] url = line.split(" ");
    158             if (url.length < 3)
    159                 return map;
    160
    161             map.put("URI", new String[] { line });
    162             map.put("Method", new String[] { url[0].toUpperCase() });
    163             map.put("Context", new String[] { url[1].substring(1) });
    164             map.put("Protocol", new String[] { url[2] });
    165             // Read header
    166             while ((line = rdr.readLine()) != null && line.length() > 0) {
    167                 String[] tokens = line.split(": ");
    168                 map.put(tokens[0], new String[] { tokens[1] });
    169             }
    170
    171             // If method 'POST' then read Content-Length worth of data
    172             if (url[0].equalsIgnoreCase("POST")) {
    173                 int len = Integer.parseInt(map.get("Content-Length")[0]);
    174                 char[] buf = new char[len];
    175                 if (rdr.read(buf) == len) {
    176                     line = String.copyValueOf(buf);
    177                 }
    178             } else if (url[0].equalsIgnoreCase("GET")) {
    179                 int idx = url[1].indexOf('?');
    180                 if (idx != -1) {
    181                     map.put("Context",
    182                             new String[] { url[1].substring(1, idx) });
    183                     line = url[1].substring(idx + 1);
    184                 } else {
    185                     line = null;
    186                 }
    187             }
    188             if (line != null) {
    189                 String[] match = line.split("\\&");
    190                 for (int i = 0; i < match.length; i++) {
    191                     String[] params = new String[1];
    192                     String[] tokens = match[i].split("=");
    193                     switch (tokens.length) {
    194                     case 0:
    195                         map.put("@".concat(match[i]), new String[] {});
    196                         break;
    197                     case 1:
    198                         map.put("@".concat(tokens[0]), new String[] {});
    199                         break;
    200                     default:
    201                         String name = "@".concat(tokens[0]);
    202                         if (map.containsKey(name)) {
    203                             params = map.get(name);
    204                             String[] tmp = new String[params.length + 1];
    205                             for (int j = 0; j < params.length; j++)
    206                                 tmp[j] = params[j];
    207                             params = null;
    208                             params = tmp;
    209                         }
    210                         params[params.length - 1] = tokens[1].trim();
    211                         map.put(name, params);
    212                     }
    213                 }
    214             }
    215         } catch (IOException ex) {
    216             ex.printStackTrace();
    217         }
    218
    219         return map;
    220     }
    221 }