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;
}
}
}
}
//
}