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