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 }