1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.DefaultHttpResponse;
32 import org.apache.mina.http.api.HttpEndOfContent;
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 HttpClientDecoder implements ProtocolDecoder {
39 private static final Logger LOG = LoggerFactory.getLogger(HttpClientCodec.class);
40
41
42 private static final String DECODER_STATE_ATT = "http.ds";
43
44
45 private static final String PARTIAL_HEAD_ATT = "http.ph";
46
47
48 private static final String BODY_REMAINING_BYTES = "http.brb";
49
50
51 private static final String BODY_CHUNKED = "http.ckd";
52
53
54 public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" ");
55
56
57 public static final Pattern RESPONSE_LINE_PATTERN = Pattern.compile(" ");
58
59
60 public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?");
61
62
63 public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;");
64
65
66 public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("=");
67
68
69 public static final Pattern RAW_VALUE_PATTERN = Pattern.compile("\\r\\n\\r\\n");
70
71
72 public static final Pattern HEADERS_BODY_PATTERN = Pattern.compile("\\r\\n");
73
74
75 public static final Pattern HEADER_VALUE_PATTERN = Pattern.compile(": ");
76
77
78 public static final Pattern COOKIE_SEPARATOR_PATTERN = Pattern.compile(";");
79
80 public void decode(final IoSession session, final IoBuffer msg, final ProtocolDecoderOutput out) {
81 DecoderState state = (DecoderState)session.getAttribute(DECODER_STATE_ATT);
82 if (null == state) {
83 session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW);
84 state = (DecoderState)session.getAttribute(DECODER_STATE_ATT);
85 }
86 switch (state) {
87 case HEAD:
88 LOG.debug("decoding HEAD");
89
90 final ByteBuffer oldBuffer = (ByteBuffer)session.getAttribute(PARTIAL_HEAD_ATT);
91
92 IoBuffer.allocate(oldBuffer.remaining() + msg.remaining()).put(oldBuffer).put(msg).flip();
93
94
95 case NEW:
96 LOG.debug("decoding NEW");
97 final DefaultHttpResponse rp = parseHttpReponseHead(msg.buf());
98
99 if (rp == null) {
100
101 final ByteBuffer partial = ByteBuffer.allocate(msg.remaining());
102 partial.put(msg.buf());
103 partial.flip();
104
105 session.setAttribute(PARTIAL_HEAD_ATT, partial);
106 session.setAttribute(DECODER_STATE_ATT, DecoderState.HEAD);
107 } else {
108 out.write(rp);
109
110 LOG.debug("response with content");
111 session.setAttribute(DECODER_STATE_ATT, DecoderState.BODY);
112
113 final String contentLen = rp.getHeader("content-length");
114
115 if (contentLen != null) {
116 LOG.debug("found content len : {}", contentLen);
117 session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(contentLen));
118 } else if ("chunked".equalsIgnoreCase(rp.getHeader("transfer-encoding"))) {
119 LOG.debug("no content len but chunked");
120 session.setAttribute(BODY_CHUNKED, Boolean.TRUE);
121 } else if ("close".equalsIgnoreCase(rp.getHeader("connection"))) {
122 session.close(true);
123 } else {
124 throw new HttpException(HttpStatus.CLIENT_ERROR_LENGTH_REQUIRED, "no content length !");
125 }
126 }
127
128 break;
129
130 case BODY:
131 LOG.debug("decoding BODY: {} bytes", msg.remaining());
132 final int chunkSize = msg.remaining();
133
134 if (chunkSize != 0) {
135 final IoBuffer wb = IoBuffer.allocate(msg.remaining());
136 wb.put(msg);
137 wb.flip();
138 out.write(wb);
139 }
140 msg.position(msg.limit());
141
142
143 int remaining = 0;
144
145
146 if( session.getAttribute(BODY_CHUNKED) != null ) {
147 remaining = chunkSize;
148 } else {
149
150 remaining = (Integer) session.getAttribute(BODY_REMAINING_BYTES);
151 remaining -= chunkSize;
152 }
153
154 if (remaining <= 0 ) {
155 LOG.debug("end of HTTP body");
156 session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW);
157 session.removeAttribute(BODY_REMAINING_BYTES);
158 if( session.getAttribute(BODY_CHUNKED) != null ) {
159 session.removeAttribute(BODY_CHUNKED);
160 }
161 out.write(new HttpEndOfContent());
162 } else {
163 if( session.getAttribute(BODY_CHUNKED) == null ) {
164 session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(remaining));
165 }
166 }
167
168 break;
169
170 default:
171 throw new HttpException(HttpStatus.SERVER_ERROR_INTERNAL_SERVER_ERROR, "Unknonwn decoder state : " + state);
172 }
173 }
174
175 public void finishDecode(final IoSession session, final ProtocolDecoderOutput out) throws Exception {
176 }
177
178 public void dispose(final IoSession session) throws Exception {
179 }
180
181 private DefaultHttpResponse parseHttpReponseHead(final ByteBuffer buffer) {
182
183 final String raw = new String(buffer.array(), 0, buffer.limit());
184 final String[] headersAndBody = RAW_VALUE_PATTERN.split(raw, -1);
185 if (headersAndBody.length <= 1) {
186
187 return null;
188 }
189
190 String[] headerFields = HEADERS_BODY_PATTERN.split(headersAndBody[0]);
191 headerFields = ArrayUtil.dropFromEndWhile(headerFields, "");
192
193 final String requestLine = headerFields[0];
194 final Map<String, String> generalHeaders = new HashMap<String, String>();
195
196 for (int i = 1; i < headerFields.length; i++) {
197 final String[] header = HEADER_VALUE_PATTERN.split(headerFields[i]);
198 generalHeaders.put(header[0].toLowerCase(), header[1]);
199 }
200
201 final String[] elements = RESPONSE_LINE_PATTERN.split(requestLine);
202 HttpStatus status = null;
203 final int statusCode = Integer.valueOf(elements[1]);
204 for (int i = 0; i < HttpStatus.values().length; i++) {
205 status = HttpStatus.values()[i];
206 if (statusCode == status.code()) {
207 break;
208 }
209 }
210 final HttpVersion version = HttpVersion.fromString(elements[0]);
211
212
213 buffer.position(headersAndBody[0].length() + 4);
214
215 return new DefaultHttpResponse(version, status, generalHeaders);
216 }
217 }