001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.http; 021 022import java.nio.ByteBuffer; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.regex.Pattern; 026 027import org.apache.mina.core.buffer.IoBuffer; 028import org.apache.mina.core.session.IoSession; 029import org.apache.mina.filter.codec.ProtocolDecoder; 030import org.apache.mina.filter.codec.ProtocolDecoderOutput; 031import org.apache.mina.http.api.HttpEndOfContent; 032import org.apache.mina.http.api.HttpMethod; 033import org.apache.mina.http.api.HttpStatus; 034import org.apache.mina.http.api.HttpVersion; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038public class HttpServerDecoder implements ProtocolDecoder { 039 private static final Logger LOG = LoggerFactory.getLogger(HttpServerCodec.class); 040 041 /** Key for decoder current state */ 042 private static final String DECODER_STATE_ATT = "http.ds"; 043 044 /** Key for the partial HTTP requests head */ 045 private static final String PARTIAL_HEAD_ATT = "http.ph"; 046 047 /** Key for the number of bytes remaining to read for completing the body */ 048 private static final String BODY_REMAINING_BYTES = "http.brb"; 049 050 /** Regex to parse HttpRequest Request Line */ 051 public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" "); 052 053 /** Regex to parse out QueryString from HttpRequest */ 054 public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?"); 055 056 /** Regex to parse out parameters from query string */ 057 public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;"); 058 059 /** Regex to parse out key/value pairs */ 060 public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("="); 061 062 /** Regex to parse raw headers and body */ 063 public static final Pattern RAW_VALUE_PATTERN = Pattern.compile("\\r\\n\\r\\n"); 064 065 /** Regex to parse raw headers from body */ 066 public static final Pattern HEADERS_BODY_PATTERN = Pattern.compile("\\r\\n"); 067 068 /** Regex to parse header name and value */ 069 public static final Pattern HEADER_VALUE_PATTERN = Pattern.compile(":"); 070 071 /** Regex to split cookie header following RFC6265 Section 5.4 */ 072 public static final Pattern COOKIE_SEPARATOR_PATTERN = Pattern.compile(";"); 073 074 public void decode(IoSession session, IoBuffer msg, ProtocolDecoderOutput out) { 075 DecoderState state = (DecoderState) session.getAttribute(DECODER_STATE_ATT); 076 if (null == state) { 077 session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW); 078 state = (DecoderState) session.getAttribute(DECODER_STATE_ATT); 079 } 080 switch (state) { 081 case HEAD: 082 LOG.debug("decoding HEAD"); 083 // grab the stored a partial HEAD request 084 final ByteBuffer oldBuffer = (ByteBuffer) session.getAttribute(PARTIAL_HEAD_ATT); 085 // concat the old buffer and the new incoming one 086 // now let's decode like it was a new message 087 msg = IoBuffer.allocate(oldBuffer.remaining() + msg.remaining()).put(oldBuffer).put(msg).flip(); 088 case NEW: 089 LOG.debug("decoding NEW"); 090 HttpRequestImpl rq = parseHttpRequestHead(msg.buf()); 091 092 if (rq == null) { 093 // we copy the incoming BB because it's going to be recycled by the inner IoProcessor for next reads 094 ByteBuffer partial = ByteBuffer.allocate(msg.remaining()); 095 partial.put(msg.buf()); 096 partial.flip(); 097 // no request decoded, we accumulate 098 session.setAttribute(PARTIAL_HEAD_ATT, partial); 099 session.setAttribute(DECODER_STATE_ATT, DecoderState.HEAD); 100 break; 101 } else { 102 out.write(rq); 103 // is it a request with some body content ? 104 String contentLen = rq.getHeader("content-length"); 105 106 if (contentLen != null) { 107 LOG.debug("found content len : {}", contentLen); 108 session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(contentLen)); 109 session.setAttribute(DECODER_STATE_ATT, DecoderState.BODY); 110 // fallthrough, process body immediately 111 } else { 112 LOG.debug("request without content"); 113 session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW); 114 out.write(new HttpEndOfContent()); 115 break; 116 } 117 } 118 119 case BODY: 120 LOG.debug("decoding BODY: {} bytes", msg.remaining()); 121 final int chunkSize = msg.remaining(); 122 // send the chunk of body 123 if (chunkSize != 0) { 124 final IoBuffer wb = IoBuffer.allocate(msg.remaining()); 125 wb.put(msg); 126 wb.flip(); 127 out.write(wb); 128 } 129 msg.position(msg.limit()); 130 // do we have reach end of body ? 131 int remaining = (Integer) session.getAttribute(BODY_REMAINING_BYTES); 132 remaining -= chunkSize; 133 134 if (remaining <= 0) { 135 LOG.debug("end of HTTP body"); 136 session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW); 137 session.removeAttribute(BODY_REMAINING_BYTES); 138 out.write(new HttpEndOfContent()); 139 } else { 140 session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(remaining)); 141 } 142 143 break; 144 145 default: 146 throw new HttpException(HttpStatus.CLIENT_ERROR_BAD_REQUEST, "Unknonwn decoder state : " + state); 147 } 148 } 149 150 public void finishDecode(final IoSession session, final ProtocolDecoderOutput out) throws Exception { 151 } 152 153 public void dispose(final IoSession session) throws Exception { 154 } 155 156 private HttpRequestImpl parseHttpRequestHead(final ByteBuffer buffer) { 157 // Java 6 >> String raw = new String(buffer.array(), 0, buffer.limit(), Charset.forName("UTF-8")); 158 final String raw = new String(buffer.array(), 0, buffer.limit()); 159 final String[] headersAndBody = RAW_VALUE_PATTERN.split(raw, -1); 160 161 if (headersAndBody.length <= 1) { 162 // we didn't receive the full HTTP head 163 return null; 164 } 165 166 String[] headerFields = HEADERS_BODY_PATTERN.split(headersAndBody[0]); 167 headerFields = ArrayUtil.dropFromEndWhile(headerFields, ""); 168 169 final String requestLine = headerFields[0]; 170 final Map<String, String> generalHeaders = new HashMap<String, String>(); 171 172 for (int i = 1; i < headerFields.length; i++) { 173 final String[] header = HEADER_VALUE_PATTERN.split(headerFields[i]); 174 generalHeaders.put(header[0].toLowerCase(), header[1].trim()); 175 } 176 177 final String[] elements = REQUEST_LINE_PATTERN.split(requestLine); 178 final HttpMethod method = HttpMethod.valueOf(elements[0]); 179 final HttpVersion version = HttpVersion.fromString(elements[2]); 180 final String[] pathFrags = QUERY_STRING_PATTERN.split(elements[1]); 181 final String requestedPath = pathFrags[0]; 182 final String queryString = pathFrags.length == 2 ? pathFrags[1] : ""; 183 184 // we put the buffer position where we found the beginning of the HTTP body 185 buffer.position(headersAndBody[0].length() + 4); 186 187 return new HttpRequestImpl(version, method, requestedPath, queryString, generalHeaders); 188 } 189}