| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.mina.http; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.charset.Charset; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| import org.apache.mina.codec.ProtocolDecoder; |
| import org.apache.mina.codec.ProtocolDecoderException; |
| import org.apache.mina.http.api.HttpContentChunk; |
| import org.apache.mina.http.api.HttpEndOfContent; |
| import org.apache.mina.http.api.HttpMethod; |
| import org.apache.mina.http.api.HttpPdu; |
| import org.apache.mina.http.api.HttpVersion; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * In charge of decoding received bytes into HTTP message. |
| * |
| * @author <a href="http://mina.apache.org">Apache MINA Project</a> |
| */ |
| public class HttpServerDecoder implements ProtocolDecoder<ByteBuffer, HttpPdu> { |
| private static final Logger LOG = LoggerFactory.getLogger(HttpServerDecoder.class); |
| |
| /** Regex to parse HttpRequest Request Line */ |
| public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" "); |
| |
| /** Regex to parse out QueryString from HttpRequest */ |
| public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?"); |
| |
| /** Regex to parse out parameters from query string */ |
| public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;"); |
| |
| /** Regex to parse out key/value pairs */ |
| public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("="); |
| |
| /** Regex to parse raw headers and body */ |
| public static final Pattern RAW_VALUE_PATTERN = Pattern.compile("\\r\\n\\r\\n"); |
| |
| /** Regex to parse raw headers from body */ |
| public static final Pattern HEADERS_BODY_PATTERN = Pattern.compile("\\r\\n"); |
| |
| /** Regex to parse header name and value */ |
| public static final Pattern HEADER_VALUE_PATTERN = Pattern.compile(": "); |
| |
| /** Regex to split cookie header following RFC6265 Section 5.4 */ |
| public static final Pattern COOKIE_SEPARATOR_PATTERN = Pattern.compile(";"); |
| |
| /** State of the decoder */ |
| private DecoderState state = DecoderState.NEW; |
| |
| /** The previously received buffer, not totally decoded */ |
| private ByteBuffer partial; |
| |
| /** Number of bytes remaining to read for completing the body */ |
| private int remainingBytes; |
| |
| @Override |
| public HttpPdu[] decode(ByteBuffer msg) throws ProtocolDecoderException { |
| LOG.debug("decode : {}", msg); |
| switch (state) { |
| case HEAD: |
| LOG.debug("decoding HEAD"); |
| // concat the old buffer and the new incoming one |
| msg = ByteBuffer.allocate(partial.remaining() + msg.remaining()).put(partial).put(msg); |
| msg.flip(); |
| // now let's decode like it was a new message |
| |
| case NEW: |
| LOG.debug("decoding NEW"); |
| HttpRequestImpl rq = parseHttpRequestHead(msg); |
| |
| if (rq == null) { |
| // we copy the incoming BB because it's going to be recycled by the inner IoProcessor for next reads |
| partial = ByteBuffer.allocate(msg.remaining()); |
| partial.put(msg); |
| partial.flip(); |
| } else { |
| return new HttpPdu[] { rq }; |
| } |
| return null; |
| case BODY: |
| LOG.debug("decoding BODY"); |
| int chunkSize = msg.remaining(); |
| // send the chunk of body |
| HttpContentChunk chunk = new HttpContentChunk(msg); |
| // do we have reach end of body ? |
| remainingBytes -= chunkSize; |
| |
| if (remainingBytes <= 0) { |
| LOG.debug("end of HTTP body"); |
| state = DecoderState.NEW; |
| remainingBytes = 0; |
| return new HttpPdu[] { chunk, new HttpEndOfContent() }; |
| } |
| break; |
| |
| default: |
| throw new RuntimeException("Unknonwn decoder state : " + state); |
| } |
| |
| return null; |
| } |
| |
| private HttpRequestImpl parseHttpRequestHead(ByteBuffer buffer) { |
| String raw = new String(buffer.array(), 0, buffer.limit(), Charset.forName("ISO-8859-1")); |
| String[] headersAndBody = RAW_VALUE_PATTERN.split(raw, -1); |
| |
| if (headersAndBody.length <= 1) { |
| // we didn't receive the full HTTP head |
| return null; |
| } |
| |
| String[] headerFields = HEADERS_BODY_PATTERN.split(headersAndBody[0]); |
| headerFields = ArrayUtil.dropFromEndWhile(headerFields, ""); |
| |
| String requestLine = headerFields[0]; |
| Map<String, String> generalHeaders = new HashMap<String, String>(); |
| |
| for (int i = 1; i < headerFields.length; i++) { |
| String[] header = HEADER_VALUE_PATTERN.split(headerFields[i]); |
| generalHeaders.put(header[0].toLowerCase(), header[1]); |
| } |
| |
| String[] elements = REQUEST_LINE_PATTERN.split(requestLine); |
| HttpMethod method = HttpMethod.valueOf(elements[0]); |
| HttpVersion version = HttpVersion.fromString(elements[2]); |
| String[] pathFrags = QUERY_STRING_PATTERN.split(elements[1]); |
| String requestedPath = pathFrags[0]; |
| |
| // we put the buffer position where we found the beginning of the HTTP body |
| buffer.position(headersAndBody[0].length() + 4); |
| |
| return new HttpRequestImpl(version, method, requestedPath, generalHeaders); |
| } |
| |
| @Override |
| public void finishDecode() { |
| |
| } |
| } |