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.http;
    21
    22  import java.nio.ByteBuffer;
    23  import java.nio.charset.Charset;
    24  import java.util.HashMap;
    25  import java.util.Map;
    26  import java.util.regex.Pattern;
    27
    28  import org.apache.mina.codec.ProtocolDecoder;
    29  import org.apache.mina.http.api.HttpContentChunk;
    30  import org.apache.mina.http.api.HttpEndOfContent;
    31  import org.apache.mina.http.api.HttpMethod;
    32  import org.apache.mina.http.api.HttpPdu;
    33  import org.apache.mina.http.api.HttpVersion;
    34  import org.slf4j.Logger;
    35  import org.slf4j.LoggerFactory;
    36
    37  /**
    38   * In charge of decoding received bytes into HTTP message.
    39   *
    40   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    41   */
    42  public class HttpServerDecoder implements ProtocolDecoder<ByteBuffer, HttpPdu, HttpDecoderState> {
    43      private static final Logger LOG = LoggerFactory.getLogger(HttpServerDecoder.class);
    44
    45      /** Regex to parse HttpRequest Request Line */
    46      public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" ");
    47
    48      /** Regex to parse out QueryString from HttpRequest */
    49      public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?");
    50
    51      /** Regex to parse out parameters from query string */
    52      public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;");
    53
    54      /** Regex to parse out key/value pairs */
    55      public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("=");
    56
    57      /** Regex to parse raw headers and body */
    58      public static final Pattern RAW_VALUE_PATTERN = Pattern.compile("\\r\\n\\r\\n");
    59
    60      /** Regex to parse raw headers from body */
    61      public static final Pattern HEADERS_BODY_PATTERN = Pattern.compile("\\r\\n");
    62
    63      /** Regex to parse header name and value */
    64      public static final Pattern HEADER_VALUE_PATTERN = Pattern.compile(":");
    65
    66      /** Regex to split cookie header following RFC6265 Section 5.4 */
    67      public static final Pattern COOKIE_SEPARATOR_PATTERN = Pattern.compile(";");
    68
    69      /**
    70       * {@inheritDoc}
    71       */
    72      @Override
    73      public HttpDecoderState createDecoderState() {
    74          return new HttpDecoderState();
    75      }
    76
    77      /**
    78       * {@inheritDoc}
    79       */
    80      @Override
    81      public HttpPdu decode(ByteBuffer msg, HttpDecoderState context) {
    82          LOG.debug("decode : {}", msg);
    83          if (msg.remaining() <= 0) {
    84              return null;
    85          }
    86          switch (context.getState()) {
    87          case HEAD:
    88              LOG.debug("decoding HEAD");
    89              // concat the old buffer and the new incoming one
    90              msg = ByteBuffer.allocate(context.getPartial().remaining() + msg.remaining()).put(context.getPartial())
    91                      .put(msg);
    92              msg.flip();
    93              // now let's decode like it was a new message
    94
    95          case NEW:
    96              LOG.debug("decoding NEW");
    97              HttpRequestImpl rq = parseHttpRequestHead(msg);
    98
    99              if (rq == null) {
    100                 // we copy the incoming BB because it's going to be recycled by the inner IoProcessor for next reads
    101                 context.setPartial(ByteBuffer.allocate(msg.remaining()));
    102                 context.getPartial().put(msg);
    103                 context.getPartial().flip();
    104             } else {
    105                 return rq;
    106             }
    107             return null;
    108         case BODY:
    109             LOG.debug("decoding BODY");
    110             int chunkSize = msg.remaining();
    111             // send the chunk of body
    112             HttpContentChunk chunk = new HttpContentChunk(msg);
    113             // do we have reach end of body ?
    114             context.setRemainingBytes(context.getRemainingBytes() - chunkSize);
    115
    116             if (context.getRemainingBytes() <= 0) {
    117                 LOG.debug("end of HTTP body");
    118                 context.setState(DecoderState.NEW);
    119                 context.setRemainingBytes(0);
    120                 context.setState(DecoderState.DONE);
    121                 return chunk;
    122
    123             }
    124             break;
    125         case DONE:
    126             return new HttpEndOfContent();
    127         default:
    128             throw new IllegalStateException("Unknonwn decoder state : " + context.getState());
    129         }
    130
    131         return null;
    132     }
    133
    134     private HttpRequestImpl parseHttpRequestHead(ByteBuffer buffer) {
    135         String raw = new String(buffer.array(), 0, buffer.limit(), Charset.forName("ISO-8859-1"));
    136         String[] headersAndBody = RAW_VALUE_PATTERN.split(raw, -1);
    137
    138         if (headersAndBody.length <= 1) {
    139             // we didn't receive the full HTTP head
    140             return null;
    141         }
    142
    143         String[] headerFields = HEADERS_BODY_PATTERN.split(headersAndBody[0]);
    144         headerFields = ArrayUtil.dropFromEndWhile(headerFields, "");
    145
    146         String requestLine = headerFields[0];
    147         Map<String, String> generalHeaders = new HashMap<String, String>();
    148
    149         for (int i = 1; i < headerFields.length; i++) {
    150             String[] header = HEADER_VALUE_PATTERN.split(headerFields[i]);
    151             generalHeaders.put(header[0].toLowerCase(), header[1].trim());
    152         }
    153
    154         String[] elements = REQUEST_LINE_PATTERN.split(requestLine);
    155         HttpMethod method = HttpMethod.valueOf(elements[0]);
    156         HttpVersion version = HttpVersion.fromString(elements[2]);
    157         String[] pathFrags = QUERY_STRING_PATTERN.split(elements[1]);
    158         String requestedPath = pathFrags[0];
    159
    160         // we put the buffer position where we found the beginning of the HTTP body
    161         buffer.position(headersAndBody[0].length() + 4);
    162
    163         return new HttpRequestImpl(version, method, requestedPath, generalHeaders);
    164     }
    165
    166     /**
    167      * {@inheritDoc}
    168      */
    169     @Override
    170     public void finishDecode(HttpDecoderState context) {
    171
    172     }
    173 }