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.util.HashMap;
29  import java.util.Map;
30  
31  import org.apache.mina.common.ByteBuffer;
32  import org.apache.mina.common.IoSession;
33  import org.apache.mina.filter.codec.ProtocolDecoderOutput;
34  import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
35  import org.apache.mina.filter.codec.demux.MessageDecoderResult;
36  import org.apache.mina.util.CharsetUtil;
37  
38  /**
39   * A {@link 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 = CharsetUtil.getDefaultCharset()
49              .newDecoder();
50  
51      private HttpRequestMessage request = null;
52  
53      public HttpRequestDecoder() {
54      }
55  
56      public MessageDecoderResult decodable(IoSession session, ByteBuffer in) {
57          // Return NEED_DATA if the whole header is not read yet.
58          try {
59              return messageComplete(in) ? MessageDecoderResult.OK
60                      : MessageDecoderResult.NEED_DATA;
61          } catch (Exception ex) {
62              ex.printStackTrace();
63          }
64  
65          return MessageDecoderResult.NOT_OK;
66      }
67  
68      public MessageDecoderResult decode(IoSession session, ByteBuffer in,
69              ProtocolDecoderOutput out) throws Exception {
70          // Try to decode body
71          HttpRequestMessage m = decodeBody(in);
72  
73          // Return NEED_DATA if the body is not fully read.
74          if (m == null)
75              return MessageDecoderResult.NEED_DATA;
76  
77          out.write(m);
78  
79          return MessageDecoderResult.OK;
80      }
81  
82      private boolean messageComplete(ByteBuffer in) throws Exception {
83          int last = in.remaining() - 1;
84          if (in.remaining() < 4)
85              return false;
86  
87          // to speed up things we check if the Http request is a GET or POST
88          if (in.get(0) == (byte) 'G' && in.get(1) == (byte) 'E'
89                  && in.get(2) == (byte) 'T') {
90              // Http GET request therefore the last 4 bytes should be 0x0D 0x0A 0x0D 0x0A
91              return (in.get(last) == (byte) 0x0A
92                      && in.get(last - 1) == (byte) 0x0D
93                      && in.get(last - 2) == (byte) 0x0A && in.get(last - 3) == (byte) 0x0D);
94          } else if (in.get(0) == (byte) 'P' && in.get(1) == (byte) 'O'
95                  && in.get(2) == (byte) 'S' && in.get(3) == (byte) 'T') {
96              // Http POST request
97              // first the position of the 0x0D 0x0A 0x0D 0x0A bytes
98              int eoh = -1;
99              for (int i = last; i > 2; i--) {
100                 if (in.get(i) == (byte) 0x0A && in.get(i - 1) == (byte) 0x0D
101                         && in.get(i - 2) == (byte) 0x0A
102                         && in.get(i - 3) == (byte) 0x0D) {
103                     eoh = i + 1;
104                     break;
105                 }
106             }
107             if (eoh == -1)
108                 return false;
109             for (int i = 0; i < last; i++) {
110                 boolean found = false;
111                 for (int j = 0; j < CONTENT_LENGTH.length; j++) {
112                     if (in.get(i + j) != CONTENT_LENGTH[j]) {
113                         found = false;
114                         break;
115                     }
116                     found = true;
117                 }
118                 if (found) {
119                     // retrieve value from this position till next 0x0D 0x0A
120                     StringBuilder contentLength = new StringBuilder();
121                     for (int j = i + CONTENT_LENGTH.length; j < last; j++) {
122                         if (in.get(j) == 0x0D)
123                             break;
124                         contentLength.append(new String(
125                                 new byte[] { in.get(j) }));
126                     }
127                     // if content-length worth of data has been received then the message is complete
128                     return (Integer.parseInt(contentLength.toString().trim())
129                             + eoh == in.remaining());
130                 }
131             }
132         }
133 
134         // the message is not complete and we need more data
135         return false;
136     }
137 
138     private HttpRequestMessage decodeBody(ByteBuffer in) {
139         request = new HttpRequestMessage();
140         try {
141             request.setHeaders(parseRequest(new StringReader(in
142                     .getString(decoder))));
143             return request;
144         } catch (CharacterCodingException ex) {
145             ex.printStackTrace();
146         }
147 
148         return null;
149     }
150 
151     private Map parseRequest(Reader is) {
152         Map map = new HashMap();
153         BufferedReader rdr = new BufferedReader(is);
154 
155         try {
156             // Get request URL.
157             String line = rdr.readLine();
158             String[] url = line.split(" ");
159             if (url.length < 3)
160                 return map;
161 
162             map.put("URI", new String[] { line });
163             map.put("Method", new String[] { url[0].toUpperCase() });
164             map.put("Context", new String[] { url[1].substring(1) });
165             map.put("Protocol", new String[] { url[2] });
166             // Read header
167             while ((line = rdr.readLine()) != null && line.length() > 0) {
168                 String[] tokens = line.split(": ");
169                 map.put(tokens[0], new String[] { tokens[1] });
170             }
171 
172             // If method 'POST' then read Content-Length worth of data
173             if (url[0].equalsIgnoreCase("POST")) {
174                 int len = Integer.parseInt(((String[]) map
175                         .get("Content-Length"))[0]);
176                 char[] buf = new char[len];
177                 if (rdr.read(buf) == len) {
178                     line = String.copyValueOf(buf);
179                 }
180             } else if (url[0].equalsIgnoreCase("GET")) {
181                 int idx = url[1].indexOf('?');
182                 if (idx != -1) {
183                     map.put("Context",
184                             new String[] { url[1].substring(1, idx) });
185                     line = url[1].substring(idx + 1);
186                 } else {
187                     line = null;
188                 }
189             }
190             if (line != null) {
191                 String[] match = line.split("\\&");
192                 for (int i = 0; i < match.length; i++) {
193                     String[] params = new String[1];
194                     String[] tokens = match[i].split("=");
195                     switch (tokens.length) {
196                     case 0:
197                         map.put("@".concat(match[i]), new String[] {});
198                         break;
199                     case 1:
200                         map.put("@".concat(tokens[0]), new String[] {});
201                         break;
202                     default:
203                         String name = "@".concat(tokens[0]);
204                         if (map.containsKey(name)) {
205                             params = (String[]) map.get(name);
206                             String[] tmp = new String[params.length + 1];
207                             for (int j = 0; j < params.length; j++)
208                                 tmp[j] = params[j];
209                             params = null;
210                             params = tmp;
211                         }
212                         params[params.length - 1] = tokens[1].trim();
213                         map.put(name, params);
214                     }
215                 }
216             }
217         } catch (IOException ex) {
218             ex.printStackTrace();
219         }
220 
221         return map;
222     }
223 }