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.util.HashMap;
24  import java.util.Map;
25  import java.util.regex.Pattern;
26  
27  import org.apache.mina.core.buffer.IoBuffer;
28  import org.apache.mina.core.session.IoSession;
29  import org.apache.mina.filter.codec.ProtocolDecoder;
30  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
31  import org.apache.mina.http.api.HttpEndOfContent;
32  import org.apache.mina.http.api.HttpMethod;
33  import org.apache.mina.http.api.HttpStatus;
34  import org.apache.mina.http.api.HttpVersion;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  public class HttpServerDecoder implements ProtocolDecoder {
39      private static final Logger LOG = LoggerFactory.getLogger(HttpServerCodec.class);
40  
41      /** Key for decoder current state */
42      private static final String DECODER_STATE_ATT = "http.ds";
43  
44      /** Key for the partial HTTP requests head */
45      private static final String PARTIAL_HEAD_ATT = "http.ph";
46  
47      /** Key for the number of bytes remaining to read for completing the body */
48      private static final String BODY_REMAINING_BYTES = "http.brb";
49  
50      /** Regex to parse HttpRequest Request Line */
51      public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" ");
52  
53      /** Regex to parse out QueryString from HttpRequest */
54      public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?");
55  
56      /** Regex to parse out parameters from query string */
57      public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;");
58  
59      /** Regex to parse out key/value pairs */
60      public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("=");
61  
62      /** Regex to parse raw headers and body */
63      public static final Pattern RAW_VALUE_PATTERN = Pattern.compile("\\r\\n\\r\\n");
64  
65      /** Regex to parse raw headers from body */
66      public static final Pattern HEADERS_BODY_PATTERN = Pattern.compile("\\r\\n");
67  
68      /** Regex to parse header name and value */
69      public static final Pattern HEADER_VALUE_PATTERN = Pattern.compile(": ");
70  
71      /** Regex to split cookie header following RFC6265 Section 5.4 */
72      public static final Pattern COOKIE_SEPARATOR_PATTERN = Pattern.compile(";");
73  
74      public void decode(final IoSession session, final IoBuffer msg, final ProtocolDecoderOutput out) {
75          DecoderState state = (DecoderState)session.getAttribute(DECODER_STATE_ATT);
76          if (null == state) {
77          	session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW);
78          	state = (DecoderState)session.getAttribute(DECODER_STATE_ATT);
79          }
80          switch (state) {
81          case HEAD:
82              LOG.debug("decoding HEAD");
83              // grab the stored a partial HEAD request
84              final ByteBuffer oldBuffer = (ByteBuffer)session.getAttribute(PARTIAL_HEAD_ATT);
85              // concat the old buffer and the new incoming one
86              IoBuffer.allocate(oldBuffer.remaining() + msg.remaining()).put(oldBuffer).put(msg).flip();
87              // now let's decode like it was a new message
88  
89          case NEW:
90              LOG.debug("decoding NEW");
91              final HttpRequestImpl rq = parseHttpRequestHead(msg.buf());
92  
93              if (rq == null) {
94                  // we copy the incoming BB because it's going to be recycled by the inner IoProcessor for next reads
95                  final ByteBuffer partial = ByteBuffer.allocate(msg.remaining());
96                  partial.put(msg.buf());
97                  partial.flip();
98                  // no request decoded, we accumulate
99                  session.setAttribute(PARTIAL_HEAD_ATT, partial);
100                 session.setAttribute(DECODER_STATE_ATT, DecoderState.HEAD);
101             } else {
102                 out.write(rq);
103                 // is it a request with some body content ?
104                 if (rq.getMethod() == HttpMethod.POST || rq.getMethod() == HttpMethod.PUT) {
105                     LOG.debug("request with content");
106                     session.setAttribute(DECODER_STATE_ATT, DecoderState.BODY);
107 
108                     final String contentLen = rq.getHeader("content-length");
109 
110                     if (contentLen != null) {
111                         LOG.debug("found content len : {}", contentLen);
112                         session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(contentLen));
113                     } else {
114                         throw new HttpException(HttpStatus.CLIENT_ERROR_LENGTH_REQUIRED, "no content length !");
115                     }
116                 } else {
117                     LOG.debug("request without content");
118                     session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW);
119                     out.write(new HttpEndOfContent());
120                 }
121             }
122 
123             break;
124 
125         case BODY:
126             LOG.debug("decoding BODY: {} bytes", msg.remaining());
127             final int chunkSize = msg.remaining();
128             // send the chunk of body
129             if (chunkSize != 0) {
130             	final IoBuffer wb = IoBuffer.allocate(msg.remaining());
131     			wb.put(msg);
132     			wb.flip();
133     			out.write(wb);
134             }
135             msg.position(msg.limit());
136             // do we have reach end of body ?
137             int remaining = (Integer) session.getAttribute(BODY_REMAINING_BYTES);
138             remaining -= chunkSize;
139 
140             if (remaining <= 0) {
141                 LOG.debug("end of HTTP body");
142                 session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW);
143                 session.removeAttribute(BODY_REMAINING_BYTES);
144                 out.write(new HttpEndOfContent());
145             } else {
146                 session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(remaining));
147             }
148 
149             break;
150 
151         default:
152             throw new HttpException(HttpStatus.CLIENT_ERROR_BAD_REQUEST, "Unknonwn decoder state : " + state);
153         }
154     }
155 
156     public void finishDecode(final IoSession session, final ProtocolDecoderOutput out) throws Exception {
157     }
158 
159     public void dispose(final IoSession session) throws Exception {
160     }
161 
162     private HttpRequestImpl parseHttpRequestHead(final ByteBuffer buffer) {
163     	// Java 6 >> String raw = new String(buffer.array(), 0, buffer.limit(), Charset.forName("UTF-8"));
164         final String raw = new String(buffer.array(), 0, buffer.limit());
165         final String[] headersAndBody = RAW_VALUE_PATTERN.split(raw, -1);
166 
167         if (headersAndBody.length <= 1) {
168             // we didn't receive the full HTTP head
169             return null;
170         }
171 
172         String[] headerFields = HEADERS_BODY_PATTERN.split(headersAndBody[0]);
173         headerFields = ArrayUtil.dropFromEndWhile(headerFields, "");
174 
175         final String requestLine = headerFields[0];
176         final Map<String, String> generalHeaders = new HashMap<String, String>();
177 
178         for (int i = 1; i < headerFields.length; i++) {
179             final String[] header = HEADER_VALUE_PATTERN.split(headerFields[i]);
180             generalHeaders.put(header[0].toLowerCase(), header[1]);
181         }
182 
183         final String[] elements = REQUEST_LINE_PATTERN.split(requestLine);
184         final HttpMethod method = HttpMethod.valueOf(elements[0]);
185         final HttpVersion version = HttpVersion.fromString(elements[2]);
186         final String[] pathFrags = QUERY_STRING_PATTERN.split(elements[1]);
187         final String requestedPath = pathFrags[0];
188         final String queryString = pathFrags.length == 2 ? pathFrags[1] : "";
189 
190         // we put the buffer position where we found the beginning of the HTTP body
191         buffer.position(headersAndBody[0].length() + 4);
192 
193         return new HttpRequestImpl(version, method, requestedPath, queryString, generalHeaders);
194     }
195 }