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 12:19:00 +0900 (금, 13  7월 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 }