diff src/luan/webserver/RequestHeadParser.java @ 1137:c123ee15f99b

add webserver
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 29 Jan 2018 18:49:59 -0700
parents
children 3bf5190b3c77
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/webserver/RequestHeadParser.java	Mon Jan 29 18:49:59 2018 -0700
@@ -0,0 +1,138 @@
+package luan.webserver;
+
+import luan.lib.parser.Parser;
+import luan.lib.parser.ParseException;
+
+
+final class RequestHeadParser {
+
+	static Request parse(String text) throws ParseException {
+		RequestHeadParser rhp = new RequestHeadParser(text);
+		rhp.parse();
+		return rhp.request;
+	}
+
+	private final Request request = new Request();
+	private final Parser parser;
+
+	private RequestHeadParser(String text) {
+		this.parser = new Parser(text);
+		request.rawHead = text;
+	}
+
+	private void parse() throws ParseException {
+		parseRequestLine();
+		while( !parser.match("\r\n") ) {
+			parserHeaderField();
+		}
+	}
+
+
+	private void parseRequestLine() throws ParseException {
+		parseMethod();
+		require( parser.match(' ') );
+		parsePath();
+		require( parser.match(' ') );
+		parseProtocol();
+		require( parser.match("\r\n") );
+	}
+
+	private void parseMethod() throws ParseException {
+		int start = parser.currentIndex();
+		if( !methodChar() )
+			throw new ParseException(parser,"no method");
+		while( methodChar() );
+		request.method = parser.textFrom(start);
+	}
+
+	private boolean methodChar() {
+		return parser.inCharRange('A','Z');
+	}
+
+	private void parsePath() throws ParseException {
+		int start = parser.currentIndex();
+		if( !parser.match('/') )
+			throw new ParseException(parser,"bad path");
+		while(
+			parser.inCharRange('A','Z')
+			|| parser.inCharRange('a','z')
+			|| parser.inCharRange('0','9')
+			|| parser.anyOf("-._~:/?#[]@!$&'()*+,;=`.")
+		);
+		request.path = parser.textFrom(start);
+	}
+
+	private void parseProtocol() throws ParseException {
+		int start = parser.currentIndex();
+		if( !(
+			parser.match("HTTP/")
+			&& parser.inCharRange('0','9')
+			&& parser.match('.')
+			&& parser.inCharRange('0','9')
+		) )
+			throw new ParseException(parser,"bad protocol");
+		request.protocol = parser.textFrom(start);
+	}
+
+
+	private void parserHeaderField() throws ParseException {
+		String name = parseName();
+		require( parser.match(':') );
+		while( parser.anyOf(" \t") );
+		String value = parseValue();
+		while( parser.anyOf(" \t") );
+		require( parser.match("\r\n") );
+		request.headers.put(name,value);
+	}
+
+	private String parseName() throws ParseException {
+		StringBuilder buf = new StringBuilder();
+		boolean cap = true;
+		require( tokenChar() );
+		do {
+			char c = parser.lastChar();
+			if( c == '-' ) {
+				cap = true;
+			} else if( cap ) {
+				c = Character.toUpperCase(c);
+				cap = false;
+			} else {
+				c = Character.toLowerCase(c);
+			}
+			buf.append(c);
+		} while( tokenChar() );
+		return buf.toString();
+	}
+
+	private String parseValue() {
+		int start = parser.currentIndex();
+		while( !testEndOfValue() )
+			parser.anyChar();
+		return parser.textFrom(start);
+	}
+
+	private boolean testEndOfValue() {
+		parser.begin();
+		while( parser.anyOf(" \t") );
+		boolean b = parser.endOfInput() || parser.anyOf("\r\n");
+		parser.failure();  // rollback
+		return b;
+	}
+
+	private void require(boolean b) throws ParseException {
+		if( !b )
+			throw new ParseException(parser,"failed");
+	}
+
+	boolean tokenChar() {
+		if( parser.endOfInput() )
+			return false;
+		char c = parser.currentChar();
+		if( 32 <= c && c <= 126 && "()<>@,;:\\\"/[]?={} \t\r\n".indexOf(c) == -1 ) {
+			parser.anyChar();
+			return true;
+		} else {
+			return false;
+		}
+	}
+}