Emmanuel writes: "Can you post your decodable() and doDecode() methods ?"
Below is the code for the request decoder. Any help is super-appreciated - Regards, Brian... --- x8 snip package com.ecobee.communicator.server.mina; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.*; import com.ecobee.communicator.server.IServer; import com.ecobee.foundation.Container; import com.ecobee.foundation.net.*; import com.whatevernot.util.Log; import org.apache.mina.common.IoBuffer; import org.apache.mina.common.IoSession; import org.apache.mina.filter.codec.ProtocolDecoderOutput; import org.apache.mina.filter.codec.demux.MessageDecoderAdapter; import org.apache.mina.filter.codec.demux.MessageDecoderResult; /** * A request decoder, which handles the parsing of the incoming requests. This * is heavily adapted from the mina example code, but contains a number of changes * since we don't restrict communications to request and response. * @author The Apache MINA Project ([email protected]) * @version $Rev: 593479 $, $Date: 2007-11-09 05:21:35 -0500 (Fri, 09 Nov 2007) $ */ public class HttpRequestDecoder extends MessageDecoderAdapter { final private static String DEFAULT_CONTENT_TYPE = ((IServer) Container.getContext().getBean("server")).getDefaultContentType(); final private static byte[] CONTENT_LENGTH = new String("Content-Length:").getBytes(); final private static int CONTENT_LENGTH_LENGTH = CONTENT_LENGTH.length; final private CharsetDecoder DECODER = Charset.defaultCharset().newDecoder(); private class ParseContext { private int offset; private IoBuffer in; public ParseContext(int offset, IoBuffer in) { this.offset = offset; this.in = in; } } /** * Default constructor, as this object is created dynamically. */ public HttpRequestDecoder() { } /** * @see MessageDecoderAdapter#decodable */ public MessageDecoderResult decodable(IoSession session, IoBuffer in) { try { boolean value = messageComplete(in); if(value) { return MessageDecoderResult.OK; } else { // Return NEED_DATA if the whole header is not read yet. return MessageDecoderResult.NEED_DATA; } } catch (Exception ex) { Log.error(this, "decodable", "Exception decoding HTTP request.", ex); return MessageDecoderResult.NOT_OK; } } /** * @see MessageDecoderAdapter#decode */ public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { ParseContext parseContext = new ParseContext(0, in); // Loop, as it's possible that there is more than one message in the buffer. while(true) { IHttpRequest request = parseRequest(parseContext); if(request == null) break; out.write(request); } return MessageDecoderResult.OK; } private boolean messageComplete(IoBuffer in) throws Exception { // We need at least 4 bytes to make any sense of the request. if(in.remaining() < 4) { return false; } // Loop forwards looking for the line separator 0x0D 0x0A 0x0D 0x0A. int lineSeparatorIndex = findSeparator(in); if(isGet(in)) { // If there is a line separator, we have a valid GET request. return (lineSeparatorIndex != -1); } if(isPost(in)) { // If there is no valid separator, then the POST request is invalid. if(lineSeparatorIndex == -1) { return false; } int last = in.remaining() - 1; for (int i = 0; i <= (last - CONTENT_LENGTH_LENGTH); i++) { boolean found = false; for(int j = 0; j < CONTENT_LENGTH_LENGTH; j++) { if(in.get(i + j) != CONTENT_LENGTH[j]) { found = false; break; } found = true; } if(found) { // Retrieve value from this position till next 0x0D 0x0A. StringBuilder contentLength = new StringBuilder(); for(int j = i + CONTENT_LENGTH.length; j < last; j++) { if (in.get(j) == 0x0D) break; contentLength.append(new String(new byte[] { in.get(j) })); } int intContentLength = Integer.parseInt(contentLength.toString().trim()); // If content-length worth of data has been received then the message is complete. return ((lineSeparatorIndex + 4 + intContentLength) <= in.remaining()); } } } // The message is not complete and we need more data. return false; } private IHttpRequest parseRequest(ParseContext parseContext) throws IOException { int offset = parseContext.offset; IoBuffer in = parseContext.in; // Find the line separator. int separatorIndex = findSeparator(in, offset); // If there is no separator, we need more data. if(separatorIndex == -1) return null; // The end of the message is the separator index + size of the separator. int endOfMessage = separatorIndex + 4; // Update the parse context offset to the beginning of the next message, if any. parseContext.offset = endOfMessage; String contents = in.getString(endOfMessage - offset, DECODER); BufferedReader reader = new BufferedReader(new StringReader(contents)); String firstLine = reader.readLine(); if(firstLine == null) return null; String[] url = firstLine.split(" "); if (url.length < 3) return null; String method = url[0].toUpperCase(); String context = url[1]; String protocol = url[2]; Map<String, String> headers = new HashMap<String, String>(); // Parse up the headers. while(true) { String line = reader.readLine(); if((line == null) || (line.length() == 0)) break; String[] tokens = line.split(": "); if(tokens.length == 2) { headers.put(tokens[0], tokens[1]); } else if(tokens.length == 1) { headers.put(tokens[0], ""); } else { Log.error(this, "parseRequest", "Cannot parse partial header: " + line + " firstLine: " + firstLine); return null; } } // Determine if the request is compressed and/or encrypted or not. String contentType = headers.get(IHttpRequest.CONTENT_TYPE_HEADER); if(contentType == null) contentType = DEFAULT_CONTENT_TYPE; IHttpRequest request = makeRequest(contentType); request.setMethod(method); request.setContext(context); request.setProtocol(protocol); Iterator<String> iterator = headers.keySet().iterator(); while(iterator.hasNext()) { String key = iterator.next(); String value = headers.get(key); request.setHeader(key, value); } // Parse up any parameters. int idx = context.indexOf('?'); if(idx != -1) { // The context becomes everything up to the '?' character. request.setContext(context.substring(0, idx)); // The parameter list is everything else. String params = url[1].substring(idx + 1); // Split up the parameter list. String[] match = params.split("\\&"); for(String element : match) { String[] tokens = element.split("="); switch(tokens.length) { case 0: request.setParameter(element, ""); break; case 1: request.setParameter(tokens[0], ""); break; default: request.setParameter(tokens[0], tokens[1]); break; } } } // If method 'POST' then read Content-Length worth of data if(request.getMethod() == IHttpRequest.HttpMethod.POST) { int contentLength = request.getContentLength(); byte[] bodyBytes = new byte[contentLength]; in.get(bodyBytes, 0, contentLength); request.setBodyBytes(bodyBytes); } return request; } private int findSeparator(IoBuffer in) { return findSeparator(in, 0); } private int findSeparator(IoBuffer in, int offset) { int last = in.remaining() - 1; for(int i = offset; i <= last - 3; ++i) { if(isSeparator(in, i)) return i; } return -1; } private boolean isSeparator(IoBuffer in, int i) { return ((in.get(i) == (byte) 0x0D && in.get(i + 1) == (byte) 0x0A && in.get(i + 2) == (byte) 0x0D && in.get(i + 3) == (byte) 0x0A)); } private boolean isGet(IoBuffer in) { return (in.get(0) == (byte) 'G') && (in.get(1) == (byte) 'E') && (in.get(2) == (byte) 'T'); } private boolean isPost(IoBuffer in) { return ((in.get(0) == (byte) 'P') && (in.get(1) == (byte) 'O') && (in.get(2) == (byte) 'S') && (in.get(3) == (byte) 'T')); } private IHttpRequest makeRequest(String contentType) { if(IHttpRequest.AES_CONTENT_TYPE.equals(contentType)) { return new AesInboundHttpRequest(); } else if(IHttpRequest.ZLIB_CONTENT_TYPE.equals(contentType)) { return new ZLibInboundHttpRequest(); } else { return new HttpRequest(); } } }
