changeset 1144:ae0a048f3bc7

webserver - handle POST params
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 31 Jan 2018 00:29:50 -0700
parents 3bf5190b3c77
children 12ececf30597
files src/luan/webserver/Connection.java src/luan/webserver/Request.java src/luan/webserver/RequestHeadParser.java src/luan/webserver/RequestParser.java src/luan/webserver/examples/post.html
diffstat 5 files changed, 264 insertions(+), 226 deletions(-) [+]
line wrap: on
line diff
--- a/src/luan/webserver/Connection.java	Tue Jan 30 23:53:28 2018 -0700
+++ b/src/luan/webserver/Connection.java	Wed Jan 31 00:29:50 2018 -0700
@@ -26,39 +26,68 @@
 
 	private void handle() {
 		try {
-			InputStream in = socket.getInputStream();
-			byte[] a = new byte[8192];
-			int endOfHeader;
-			int size = 0;
-			int left = a.length;
-			outer: while(true) {
-				int n = in.read(a,size,left);
-				if( n == -1 ) {
-					if( size == 0 ) {
-						socket.close();
-						return;
+			Request request = new Request();
+			{
+				InputStream in = socket.getInputStream();
+				byte[] a = new byte[8192];
+				int endOfHeader;
+				int size = 0;
+				int left = a.length;
+				outer: while(true) {
+					int n = in.read(a,size,left);
+					if( n == -1 ) {
+						if( size == 0 ) {
+							socket.close();
+							return;
+						}
+						throw new IOException("unexpected end of input at "+size);
 					}
-					throw new IOException("unexpected end of input at "+size);
-				}
-				size += n;
-				for( int i=0; i<=size-4; i++ ) {
-					if( a[i]=='\r' && a[i+1]=='\n' && a[i+2]=='\r' && a[i+3]=='\n' ) {
-						endOfHeader = i + 4;
-						break outer;
+					size += n;
+					for( int i=0; i<=size-4; i++ ) {
+						if( a[i]=='\r' && a[i+1]=='\n' && a[i+2]=='\r' && a[i+3]=='\n' ) {
+							endOfHeader = i + 4;
+							break outer;
+						}
+					}
+					left -= n;
+					if( left == 0 ) {
+						byte[] a2 = new byte[2*a.length];
+						System.arraycopy(a,0,a2,0,size);
+						a = a2;
+						left = a.length - size;
 					}
 				}
-				left -= n;
-				if( left == 0 ) {
-					byte[] a2 = new byte[2*a.length];
-					System.arraycopy(a,0,a2,0,size);
-					a = a2;
-					left = a.length - size;
+				String rawHead = new String(a,0,endOfHeader);
+//System.out.println(rawHead);
+				request.rawHead = rawHead;
+				RequestParser parser = new RequestParser(request);
+				parser.parseHead();
+	
+				String lenStr = request.headers.get("Content-Length");
+				if( lenStr != null ) {
+					int len = Integer.parseInt(lenStr);
+					byte[] body = new byte[len];
+					size -= endOfHeader;
+					System.arraycopy(a,endOfHeader,body,0,size);
+					while( size < len ) {
+						int n = in.read(body,size,len-size);
+						if( n == -1 ) {
+							throw new IOException("unexpected end of input at "+size);
+						}
+						size += n;
+					}
+					request.body = new String(body);
+//System.out.println(request.body);
+				}
+	
+				if( request.method.equals("POST") ) {
+					if( request.body == null ) {
+						logger.error("post body is null");
+					} else {
+						parser.parsePost();
+					}
 				}
 			}
-			String rawRequest = new String(a,0,endOfHeader);
-//System.out.println(rawRequest);
-			Request request = RequestHeadParser.parse(rawRequest);
-//System.out.println(request.headers);
 
 			Response response = server.handler.handle(request);
 			response.headers.put("Connection","close");
@@ -73,7 +102,7 @@
 		} catch(IOException e) {
 			logger.info("",e);
 		} catch(ParseException e) {
-			logger.info("",e);
+			logger.warn("",e);
 		}
 	}
 
--- a/src/luan/webserver/Request.java	Tue Jan 30 23:53:28 2018 -0700
+++ b/src/luan/webserver/Request.java	Wed Jan 31 00:29:50 2018 -0700
@@ -13,4 +13,5 @@
 	public volatile String protocol;  // only HTTP/1.1 is accepted
 	public final Map<String,String> headers = Collections.synchronizedMap(new LinkedHashMap<String,String>());
 	public final Map<String,Object> parameters = Collections.synchronizedMap(new LinkedHashMap<String,Object>());
+	public volatile String body;
 }
--- a/src/luan/webserver/RequestHeadParser.java	Tue Jan 30 23:53:28 2018 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-package luan.webserver;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.util.List;
-import java.util.ArrayList;
-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(' ') );
-		parseRawPath();
-		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 parseRawPath() throws ParseException {
-		int start = parser.currentIndex();
-		parsePath();
-		if( parser.match('?') )
-			parseQuery();
-		request.rawPath = parser.textFrom(start);
-	}
-
-	private void parsePath() throws ParseException {
-		int start = parser.currentIndex();
-		if( !parser.match('/') )
-			throw new ParseException(parser,"bad path");
-		while( safePathChar() || parser.anyOf("&=") );
-		request.path = decode( parser.textFrom(start) );
-	}
-
-	private void parseQuery() throws ParseException {
-		while(true) {
-			while( parser.match('&') );
-			int start = parser.currentIndex();
-			if( !queryChar() )
-				return;
-			while( queryChar() );
-			String name = decode( parser.textFrom(start) );
-			String value;
-			if( parser.match('=') ) {
-				start = parser.currentIndex();
-				while( queryChar() );
-				value = decode( parser.textFrom(start) );
-			} else {
-				value = "";
-			}
-			Object current = request.parameters.get(name);
-			if( current == null ) {
-				request.parameters.put(name,value);
-			} else if( current instanceof List ) {
-				List list = (List)current;
-				list.add(value);
-			} else {
-				List list = new ArrayList();
-				list.add(current);
-				list.add(value);
-				request.parameters.put(name,list);
-			}
-		}
-	}
-
-	private boolean queryChar() {
-		return safePathChar() || parser.anyOf("?");
-	}
-
-	// where did I get this?
-	private boolean safePathChar() {
-		return parser.inCharRange('A','Z')
-			|| parser.inCharRange('a','z')
-			|| parser.inCharRange('0','9')
-			|| parser.anyOf("-._~:/[]@!$'()*+,;`.%")
-		;
-	}
-
-	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;
-		}
-	}
-
-	private static String decode(String s) {
-		try {
-			return URLDecoder.decode(s,"UTF-8");
-		} catch(UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/webserver/RequestParser.java	Wed Jan 31 00:29:50 2018 -0700
@@ -0,0 +1,195 @@
+package luan.webserver;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.List;
+import java.util.ArrayList;
+import luan.lib.parser.Parser;
+import luan.lib.parser.ParseException;
+
+
+final class RequestParser {
+	private final Request request;
+	private Parser parser;
+
+	RequestParser(Request request) {
+		this.request = request;
+	}
+
+	void parsePost() throws ParseException {
+		this.parser = new Parser(request.body);
+		parseQuery();
+		require( parser.endOfInput() );
+	}
+
+	void parseHead() throws ParseException {
+		this.parser = new Parser(request.rawHead);
+		parseRequestLine();
+		while( !parser.match("\r\n") ) {
+			parserHeaderField();
+		}
+	}
+
+	private void parseRequestLine() throws ParseException {
+		parseMethod();
+		require( parser.match(' ') );
+		parseRawPath();
+		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 parseRawPath() throws ParseException {
+		int start = parser.currentIndex();
+		parsePath();
+		if( parser.match('?') )
+			parseQuery();
+		request.rawPath = parser.textFrom(start);
+	}
+
+	private void parsePath() throws ParseException {
+		int start = parser.currentIndex();
+		if( !parser.match('/') )
+			throw new ParseException(parser,"bad path");
+		while( safePathChar() || parser.anyOf("&=") );
+		request.path = decode( parser.textFrom(start) );
+	}
+
+	private void parseQuery() throws ParseException {
+		while(true) {
+			while( parser.match('&') );
+			int start = parser.currentIndex();
+			if( !queryChar() )
+				return;
+			while( queryChar() );
+			String name = decode( parser.textFrom(start) );
+			String value;
+			if( parser.match('=') ) {
+				start = parser.currentIndex();
+				while( queryChar() );
+				value = decode( parser.textFrom(start) );
+			} else {
+				value = "";
+			}
+			Object current = request.parameters.get(name);
+			if( current == null ) {
+				request.parameters.put(name,value);
+			} else if( current instanceof List ) {
+				List list = (List)current;
+				list.add(value);
+			} else {
+				List list = new ArrayList();
+				list.add(current);
+				list.add(value);
+				request.parameters.put(name,list);
+			}
+		}
+	}
+
+	private boolean queryChar() {
+		return safePathChar() || parser.anyOf("?");
+	}
+
+	// where did I get this?
+	private boolean safePathChar() {
+		return parser.inCharRange('A','Z')
+			|| parser.inCharRange('a','z')
+			|| parser.inCharRange('0','9')
+			|| parser.anyOf("-._~:/[]@!$'()*+,;`.%")
+		;
+	}
+
+	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;
+		}
+	}
+
+	private static String decode(String s) {
+		try {
+			return URLDecoder.decode(s,"UTF-8");
+		} catch(UnsupportedEncodingException e) {
+			throw new RuntimeException(e);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/webserver/examples/post.html	Wed Jan 31 00:29:50 2018 -0700
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+	<body>
+		<form action=/params method=post>
+			<p>a <input name=a></p>
+			<p>b <input name=b></p>
+			<p><input type=submit></p>
+		</form>
+	</body>
+</html>