Http Response Handler

Hoyt, David hoyt6 at llnl.gov
Sat Sep 26 04:55:48 EDT 2009


In hopes it may be of benefit to the community, I've created a simplified version of the http decoder that simply parses out the header info. and then removes itself from the pipeline once it has reached the message contents. This makes it easier to work with frame decoders upstream. It's not tested all that much and I'm sure it doesn't conform to all of the HTTP spec., but it might save someone else some trouble. I attached it to this message and I'll produce a copy of it below as well.

It's not as robust as the netty-supplied one, but it's much simpler to use (I think). You don't have to deal w/ http chunks whatsoever - after it's done reading in the header, you never have to see it again. If you're interested in seeing the header info., you can extend the class and override the processHeaders() method which is invoked right after we've reached the message contents. For example:

@ChannelPipelineCoverage("one")
private class MyCustomHttpResponseUpstreamHandler extends SimpleHttpResponseUpstreamHandler {
	@Override
	protected ChannelBuffer processHeaders(Channel channel, ChannelBuffer buffer) throws Exception {
		if (getHttpStatus() != STATUS_OK)
			throw new IllegalArgumentException();

		Map<String, String> headers = this.getHttpHeaders();
		if (headers != null && headers.containsKey("Content-Type")) {
			if (!"image/jpg".equalsIgnoreCase(headers.get("Content-Type")))
				throw new IllegalArgumentException();
		}


		return buffer;
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		e.getChannel().close();
	}
}



-------

Here's the class:

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 {
	//<editor-fold defaultstate="collapsed" desc="Constants">
	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
	;
	//</editor-fold>

	//<editor-fold defaultstate="collapsed" desc="Variables">
	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<String, String> headers = new HashMap<String, String>(5);
	//</editor-fold>

	//<editor-fold defaultstate="collapsed" desc="Getters">
	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<String, String> getHttpHeaders() {
		return headers;
	}
	//</editor-fold>

	//<editor-fold defaultstate="collapsed" desc="Helper Methods">
	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;
	}
	//</editor-fold>

	//<editor-fold defaultstate="collapsed" desc="Protected Methods">
	protected ChannelBuffer processHeaders(Channel channel, ChannelBuffer buffer) throws Exception {
		//Allows subclasses to process header info if they want to
		return buffer;
	}
	//</editor-fold>

	//<editor-fold defaultstate="collapsed" desc="Message">
	@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;
				}
			}
		}
	}
	//</editor-fold>
}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: SimpleHttpResponseUpstreamHandler.java
Type: application/octet-stream
Size: 5816 bytes
Desc: SimpleHttpResponseUpstreamHandler.java
Url : http://lists.jboss.org/pipermail/netty-users/attachments/20090926/9d946b9b/attachment-0001.obj 


More information about the netty-users mailing list