import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipelineCoverage; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; /** * Ultra simple HTTP decoder for Netty that parses out the header and then * conveniently removes itself from the pipeline. */ @ChannelPipelineCoverage("one") public class SimpleHttpResponseUpstreamHandler extends SimpleChannelUpstreamHandler { // public static final Pattern REGEX_STATUS = Pattern.compile("(.+)\\s+(\\d+)\\s+(.+)", Pattern.CASE_INSENSITIVE) , REGEX_HEADER = Pattern.compile("([^:]+):\\s*(.+)", Pattern.CASE_INSENSITIVE) ; public static final byte CR = (byte)13 , LF = (byte)10 ; public static final int STATUS_CONTINUE = 100 , STATUS_SWITCHING_PROTOCOLS = 101 , STATUS_OK = 200 , STATUS_CREATED = 201 , STATUS_ACCEPTED = 202 , STATUS_NON_AUTHORITATIVE_INFORMATION = 203 , STATUS_NO_CONTENT = 204 , STATUS_RESET_CONTENT = 205 , STATUS_PARTIAL_CONTENT = 206 , STATUS_MULTIPLE_CHOICES = 300 , STATUS_MOVED_PERMANENTLY = 301 , STATUS_FOUND = 302 , STATUS_SEE_OTHER = 303 , STATUS_NOT_MODIFIED = 304 , STATUS_USE_PROXY = 305 , STATUS_TEMPORARY_REDIRECT = 307 , STATUS_BAD_REQUEST = 400 , STATUS_UNAUTHORIZED = 401 , STATUS_PAYMENT_REQUIRED = 402 , STATUS_FORBIDDEN = 403 , STATUS_NOT_FOUND = 404 , STATUS_NOT_ALLOWED = 405 , STATUS_NOT_ACCEPTABLE = 406 , STATUS_PROXY_AUTHENTICATION_REQUIRED = 407 , STATUS_REQUEST_TIMEOUT = 408 , STATUS_CONFLICT = 409 , STATUS_GONE = 410 , STATUS_LENGTH_REQUIRED = 411 , STATUS_PRECONDITION_FAILED = 412 , STATUS_REQUEST_ENTITY_TOO_LARGE = 413 , STATUS_REQUEST_URI_TOO_LONG = 414 , STATUS_UNSUPPORTED_MEDIA_TYPE = 415 , STATUS_REQUESTED_RANGE_NOT_SATISFIABLE = 416 , STATUS_EXPECTATION_FAILED = 417 , STATUS_INTERNAL_SERVER_ERROR = 500 , STATUS_NOT_IMPLEMENTED = 501 , STATUS_BAD_GATEWAY = 502 , STATUS_SERVICE_UNAVAILABLE = 503 , STATUS_GATEWAY_TIMEOUT = 504 , STATUS_HTTP_VERSION_NOT_SUPPORTED = 505 ; // // private boolean readHeaders = false; private boolean readCR = false; private String statusLine = null; private String httpVersion = null; private String httpStatus = null; private String httpStatusMsg = null; private int status = STATUS_OK; private StringBuilder sb = new StringBuilder(64); private Map headers = new HashMap(5); // // public final int getHttpStatus() { return status; } public final String getHttpVersion() { return httpVersion; } public final String getHttpStatusMessage() { return httpStatusMsg; } public final String getHttpStatusLine() { return statusLine; } public final Map getHttpHeaders() { return headers; } // // private String readLine(ChannelBuffer buffer) { byte byteVal; while(buffer.readable()) { byteVal = buffer.readByte(); if (byteVal == CR) { readCR = true; continue; } else if (readCR && byteVal == LF) { readCR = false; String line = sb.toString(); sb = new StringBuilder(64); return line; } sb.append((char)byteVal); } return null; } // // protected ChannelBuffer processHeaders(Channel channel, ChannelBuffer buffer) throws Exception { //Allows subclasses to process header info if they want to return buffer; } // // @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { Object m = e.getMessage(); if (!(m instanceof ChannelBuffer)) { ctx.sendUpstream(e); return; } ChannelBuffer buffer = (ChannelBuffer) m; if (!buffer.readable()) { return; } if (readHeaders) { ctx.sendUpstream(e); return; } String line; while((line = readLine(buffer)) != null) { if (statusLine == null) { statusLine = line; final Matcher matcher = REGEX_STATUS.matcher(statusLine); if (!matcher.matches() || matcher.groupCount() != 3) throw new IllegalArgumentException("Invalid status line: " + line); httpVersion = matcher.group(1); httpStatus = matcher.group(2); httpStatusMsg = matcher.group(3); status = Integer.parseInt(httpStatus); } else { if (!"".equals(line)) { //Add header final Matcher matcher = REGEX_HEADER.matcher(line); if (!matcher.matches() || matcher.groupCount() != 2) throw new IllegalArgumentException("Invalid header: " + line); headers.put(matcher.group(1), matcher.group(2)); } else { sb = null; readHeaders = true; //Start of content according to HTTP spec //Take this decoder out of the pipeline - skip directly to next guy in the pipeline from now on ctx.getPipeline().remove(this); //Send this upstream Channels.fireMessageReceived(ctx.getChannel(), processHeaders(ctx.getChannel(), buffer)); return; } } } } // }