changeset 497:55f9f74f1e55

Http.request is now pure luan
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 17 May 2015 19:25:47 -0600
parents c65df5b25932
children ee55be414a34
files .hgignore core/src/luan/LuanTable.java core/src/luan/impl/ForStmt.java core/src/luan/modules/Html.luan core/src/luan/modules/Table.luan http/src/luan/modules/http/Http.luan http/src/luan/modules/http/HttpServicer.java http/src/luan/modules/http/run.luan http/src/luan/modules/http/shell.luan lucene/src/luan/modules/lucene/Web_search.luan scripts/test.luan website/src/examples/hi2.luan website/src/examples/hi2_simply_html.luan
diffstat 13 files changed, 227 insertions(+), 283 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sat May 16 20:19:05 2015 -0600
+++ b/.hgignore	Sun May 17 19:25:47 2015 -0600
@@ -8,3 +8,4 @@
 mine
 *.bck
 *.n
+go
--- a/core/src/luan/LuanTable.java	Sat May 16 20:19:05 2015 -0600
+++ b/core/src/luan/LuanTable.java	Sun May 17 19:25:47 2015 -0600
@@ -282,7 +282,7 @@
 					if( obj==null )
 						return null;
 					Object[] a = (Object[])obj;
-					if( a.length == 0 )
+					if( a.length == 0 || a[0]==null )
 						return null;
 					return new AbstractMap.SimpleEntry<Object,Object>(a[0],a[1]);
 				} catch(LuanException e) {
@@ -416,9 +416,6 @@
 		hasJava = true;
 	}
 
-
-	// from AbstractLuanTable
-
 	private Map<Object,Object> newMap() {
 		return new LinkedHashMap<Object,Object>();
 	}
@@ -447,4 +444,9 @@
 		return map;
 	}
 
+	public void rawClear() {
+		map = null;
+		list = null;
+	}
+
 }
--- a/core/src/luan/impl/ForStmt.java	Sat May 16 20:19:05 2015 -0600
+++ b/core/src/luan/impl/ForStmt.java	Sun May 17 19:25:47 2015 -0600
@@ -32,7 +32,7 @@
 					break;
 				if( vals instanceof Object[] ) {
 					Object[] a = (Object[])vals;
-					if( a.length==0 )
+					if( a.length==0 || a[0]==null )
 						break;
 					for( int i=0; i<nVars; i++ ) {
 						luan.stackSet( iVars+i, i < a.length ? a[i] : null );
--- a/core/src/luan/modules/Html.luan	Sat May 16 20:19:05 2015 -0600
+++ b/core/src/luan/modules/Html.luan	Sun May 17 19:25:47 2015 -0600
@@ -13,6 +13,11 @@
 local ipairs = Luan.ipairs
 local type = Luan.type
 local Io = require "luan:Io"
+local URLEncoder = require "java:java.net.URLEncoder"
+
+function url_encode(s)
+	return URLEncoder.encode(s,"UTF-8")
+end
 
 function process_url_tags(html)
 	for i, v in ipairs(html) do
--- a/core/src/luan/modules/Table.luan	Sat May 16 20:19:05 2015 -0600
+++ b/core/src/luan/modules/Table.luan	Sun May 17 19:25:47 2015 -0600
@@ -10,3 +10,11 @@
 sort = TableLuan.sort
 sub_list = TableLuan.sub_list
 unpack = TableLuan.unpack
+
+
+local Luan = require "luan:Luan"
+local pairs = Luan.pairs
+
+function is_empty(t)
+	return pairs(t)() == nil
+end
--- a/http/src/luan/modules/http/Http.luan	Sat May 16 20:19:05 2015 -0600
+++ b/http/src/luan/modules/http/Http.luan	Sun May 17 19:25:47 2015 -0600
@@ -1,15 +1,87 @@
+local Luan = require "luan:Luan"
+local ipairs = Luan.ipairs
+local pairs = Luan.pairs
+local set_metatable = Luan.set_metatable
 local Io = require "luan:Io"
+local Html = require "luan:Html"
+local url_encode = Html.url_encode
 
 
+local singular_metatable = {}
+
+function singular_metatable.__index(table,key)
+	local list = table.__plural[key]
+	return list and list[1]
+end
+
+function singular_metatable.__new_index(table,key,value)
+	table.__plural[key] = {value}
+end
+
+function singular_metatable.__pairs(table)
+	local iter = pairs(table.__plural)
+	return function()
+		local key, value = iter()
+		return key, value and value[1]
+	end
+end
+
+
+local function new_common(this)
+	this = this or {}
+	this.headers = {}
+	this.header = {__plural=this.headers}
+	set_metatable(this.header,singular_metatable)
+	return this
+end
+
+
+function new_request(this)
+	this = new_common(this)
+	this.method = "GET"  -- default
+	-- this.path
+	-- this.protocol
+	this.scheme = "http"  -- default
+	this.parameters = {}
+	this.parameter = {__plural=this.parameters}
+	set_metatable(this.parameter,singular_metatable)
+	this.cookie = {}
+
+	function this.url()
+		local string_uri = Io.uri "string:"
+		local out = string_uri.text_writer()
+
+		out.write( this.scheme, "://", this.header.host, this.path )
+		if this.method ~= "POST" then
+			local and_char = "?"
+			for name, values in pairs(this.parameters) do
+				for _, value in ipairs(values) do
+					out.write( and_char, url_encode(name), "=", url_encode(value) )
+					and_char = "&"
+				end
+			end
+		end
+
+		out.close()
+		return string_uri.read_text()
+	end
+
+	return this
+end
+
+-- request = new_request{}  -- filled in by HttpServicer
+
 
 
 function init_for_test()
 
-	welcome_file = "index.html"
+	test = test or {}
+
+	test.welcome_file = "index.html"
 
 	function get_page(path)
-		if welcome_file ~= nil and path.matches ".*/" then
-			path = path .. welcome_file
+		if test.welcome_file ~= nil and path.matches ".*/" then
+			path = path .. test.welcome_file
 		end
 		local old_out = Io.stdout
 		local mod = require("site:"..path)
@@ -19,12 +91,10 @@
 		return result.read_text()
 	end
 
-	cookies = cookies or {}
+	test.cookies = test.cookies or {}
 
-	request = {
-		parameters = {};
-	}
-	request.cookies = cookies
+	request = new_request{}
+	request.cookies = test.cookies
 
 	response = {
 
@@ -35,11 +105,11 @@
 		end;
 
 		set_cookie = function(name,value)
-			cookies[name] = value
+			test.cookies[name] = value
 		end;
 
 		remove_cookie = function(name)
-			cookies[name] = nil
+			test.cookies[name] = nil
 		end;
 
 		send_redirect = function(url)
--- a/http/src/luan/modules/http/HttpServicer.java	Sat May 16 20:19:05 2015 -0600
+++ b/http/src/luan/modules/http/HttpServicer.java	Sun May 17 19:25:47 2015 -0600
@@ -74,145 +74,38 @@
 		}
 
 		LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http");
-		HttpServicer lib = new HttpServicer(request,response);
-		try {
-			module.put( luan, "request", lib.requestTable() );
-			module.put( luan, "response", lib.responseTable() );
-		} catch(NoSuchMethodException e) {
-			throw new RuntimeException(e);
+
+		// request
+		LuanTable requestTbl = (LuanTable)luan.call( (LuanFunction)module.rawGet("new_request") );
+		module.rawPut("request",requestTbl);
+		requestTbl.rawPut("java",request);
+		requestTbl.rawPut("method",request.getMethod());
+		requestTbl.rawPut("path",request.getRequestURI());
+		requestTbl.rawPut("protocol",request.getProtocol());
+		requestTbl.rawPut("scheme",request.getScheme());
+
+		LuanTable headersTbl = (LuanTable)requestTbl.rawGet("headers");
+		for( Enumeration<String> enKeys = request.getHeaderNames(); enKeys.hasMoreElements(); ) {
+			String key = enKeys.nextElement();
+			key = key.toLowerCase().replace('-','_');
+			LuanTable values = new LuanTable();
+			for( Enumeration<String> en = request.getHeaders(key); en.hasMoreElements(); ) {
+				values.rawPut(values.rawLength()+1,en.nextElement());
+			}
+			headersTbl.rawPut(key,values);
 		}
 
-		luan.call(fn,"<http>");
-		return true;
-	}
-
-	private static LuanFunction getService(LuanState luan,LuanTable tbl)
-		throws LuanException
-	{
-		Object respond = tbl.get(luan,"respond");
-		if( respond == null )
-			throw luan.exception( "function 'respond' is not defined" );
-		if( !(respond instanceof LuanFunction) )
-			throw luan.exception( "'respond' must be a function but is a " + Luan.type(respond) );
-		return (LuanFunction)respond;
-	}
-
-
-	private final HttpServletRequest request;
-	private final HttpServletResponse response;
-//	private PrintWriter writer = null;
-//	private ServletOutputStream sos = null;
-
-	private HttpServicer(HttpServletRequest request,HttpServletResponse response) {
-		this.request = request;
-		this.response = response;
-	}
-
-	private LuanTable requestTable() throws NoSuchMethodException {
-		LuanTable tbl = LuanPropertyMeta.INSTANCE.newTable();
-		LuanTable getters = LuanPropertyMeta.INSTANCE.getters(tbl);
-		tbl.rawPut("java",request);
-		LuanTable parameters = new NameMeta() {
-
-			@Override Object get(String name) {
-				return request.getParameter(name);
-			}
-
-			@Override protected Iterator keys(LuanTable tbl) {
-				return new EnumerationIterator(request.getParameterNames());
-			}
-
-			@Override protected String type(LuanTable tbl) {
-				return "request.parameters";
-			}
-
-		}.newTable();
-		tbl.rawPut( "parameters", parameters );
-		add( tbl, "get_parameter_values", String.class );
-		LuanTable headers = new NameMeta() {
-
-			@Override Object get(String name) {
-				return request.getHeader(name);
-			}
-
-			@Override protected Iterator keys(LuanTable tbl) {
-				return new EnumerationIterator(request.getHeaderNames());
-			}
-
-			@Override protected String type(LuanTable tbl) {
-				return "request.headers";
+		LuanTable parametersTbl = (LuanTable)requestTbl.rawGet("parameters");
+		String contentType = request.getContentType();
+		if( contentType==null || !contentType.startsWith("multipart/form-data") ) {
+			for( Map.Entry<String,String[]> entry : request.getParameterMap().entrySet() ) {
+				parametersTbl.rawPut(entry.getKey(),new LuanTable(Arrays.asList(entry.getValue())));
 			}
-
-		}.newTable();
-		tbl.rawPut( "headers", headers );
-		getters.rawPut( "method", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getMethod" ), request
-		) );
-		getters.rawPut( "path", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getRequestURI" ), request
-		) );
-		getters.rawPut( "server_name", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getServerName" ), request
-		) );
-		getters.rawPut( "url", new LuanJavaFunction(
-			HttpServicer.class.getMethod( "getURL" ), this
-		) );
-		getters.rawPut( "query_string", new LuanJavaFunction(
-			HttpServicer.class.getMethod( "getQueryString" ), this
-		) );
-		getters.rawPut( "remote_address", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getRemoteAddr" ), request
-		) );
-		getters.rawPut( "protocol", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getProtocol" ), request
-		) );
-		getters.rawPut( "scheme", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getScheme" ), request
-		) );
-		getters.rawPut( "is_secure", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "isSecure" ), request
-		) );
-		LuanTable cookies = new LuanMeta() {
-
-			@Override public Object __index(LuanState luan,LuanTable tbl,Object key) {
-				if( !(key instanceof String) )
-					return null;
-				String name = (String)key;
-				return getCookieValue(request,name);
-			}
-
-			@Override protected Iterator<Object> keys(LuanTable tbl) {
-				return new Iterator<Object>() {
-					final Cookie[] cookies = request.getCookies();
-					int i = 0;
-	
-					@Override public boolean hasNext() {
-						return i < cookies.length;
-					}
-					@Override public Object next() {
-						return cookies[i++].getName();
-					}
-					@Override public void remove() {
-						throw new UnsupportedOperationException();
-					}
-				};
-			}
-
-			@Override protected String type(LuanTable tbl) {
-				return "request.cookies";
-			}
-
-		}.newTable();
-		tbl.rawPut( "cookies", cookies );
-
-		String contentType = request.getContentType();
-		if( contentType!=null && contentType.startsWith("multipart/form-data") ) {
+		} else {  // multipart
 			try {
 				InputStream in = new BufferedInputStream(request.getInputStream());
 				final MultiPartInputStream mpis = new MultiPartInputStream(in,contentType,null,null);
 				mpis.setDeleteOnExit(true);
-				parameters = new LuanTable();
-				final Map map = new HashMap();
 				for( Part p : mpis.getParts() ) {
 					final MultiPartInputStream.MultiPart part = (MultiPartInputStream.MultiPart)p;
 					String name = part.getName();
@@ -238,26 +131,13 @@
 						} );
 						value = partTbl;
 					}
-					parameters.rawPut(name,value);
-					Object old = map.get(name);
-					if( old == null ) {
-						map.put(name,value);
-					} else if( old instanceof Object[] ) {
-						Object[] aOld = (Object[])old;
-						Object[] aNew = new Object[aOld.length+1];
-						System.arraycopy(aOld,0,aNew,0,aOld.length);
-						aNew[aOld.length] = value;
-						map.put(name,aNew);
-					} else {
-						map.put(name,new Object[]{old,value});
+					LuanTable list = (LuanTable)parametersTbl.rawGet(name);
+					if( list == null ) {
+						list = new LuanTable();
+						parametersTbl.rawPut(name,list);
 					}
+					parametersTbl.rawPut(parametersTbl.rawLength()+1,value);
 				}
-				tbl.rawPut( "parameters", parameters );
-				tbl.rawPut( "get_parameter_values", new LuanFunction() {
-					@Override public Object call(LuanState luan,Object[] args) throws LuanException {
-						return args.length==0 ? null : map.get(args[0]);
-					}
-				} );
 			} catch(IOException e) {
 				throw new RuntimeException(e);
 			} catch(ServletException e) {
@@ -265,7 +145,40 @@
 			}
 		}
 
-		return tbl;
+		LuanTable cookieTbl = (LuanTable)requestTbl.rawGet("cookie");
+		for( Cookie cookie : request.getCookies() ) {
+			cookieTbl.rawPut( cookie.getName(), unescape(cookie.getValue()) );
+		}
+
+		HttpServicer lib = new HttpServicer(request,response);
+		try {
+			module.put( luan, "response", lib.responseTable() );
+		} catch(NoSuchMethodException e) {
+			throw new RuntimeException(e);
+		}
+
+		luan.call(fn,"<http>");
+		return true;
+	}
+
+	private static LuanFunction getService(LuanState luan,LuanTable tbl)
+		throws LuanException
+	{
+		Object respond = tbl.get(luan,"respond");
+		if( respond == null )
+			throw luan.exception( "function 'respond' is not defined" );
+		if( !(respond instanceof LuanFunction) )
+			throw luan.exception( "'respond' must be a function but is a " + Luan.type(respond) );
+		return (LuanFunction)respond;
+	}
+
+
+	private final HttpServletRequest request;
+	private final HttpServletResponse response;
+
+	private HttpServicer(HttpServletRequest request,HttpServletResponse response) {
+		this.request = request;
+		this.response = response;
 	}
 
 	private LuanTable responseTable() throws NoSuchMethodException {
@@ -346,23 +259,11 @@
 	private void add(LuanTable t,String method,Class<?>... parameterTypes) throws NoSuchMethodException {
 		t.rawPut( method, new LuanJavaFunction(HttpServicer.class.getMethod(method,parameterTypes),this) );
 	}
-/*
-	public void text_write(LuanState luan,Object... args) throws LuanException, IOException {
-		if( writer == null )
-			writer = response.getWriter();
-		for( Object obj : args ) {
-			writer.print( luan.toString(obj) );
-		}
-	}
-*/
+
 	public LuanTable text_writer() throws IOException {
 		return IoLuan.textWriter(response.getWriter());
 	}
 
-	public Object[] get_parameter_values(String name) {
-		return request.getParameterValues(name);
-	}
-
 	public void set_cookie(String name,String value,boolean isPersistent, String domain) {
 		setCookie(request,response,name,value,isPersistent,domain);
 	}
@@ -374,60 +275,6 @@
 
 	// static utils
 
-	public String getQueryString() {
-		return getQueryString(request);
-	}
-
-	public static String getQueryString(HttpServletRequest request) {
-		return getQueryString(request,0);
-	}
-
-	public static String getQueryString(HttpServletRequest request,int maxValueLen) {
-		String method = request.getMethod();
-		if( method.equals("GET") )
-			return request.getQueryString();
-		if( !method.equals("POST") && !method.equals("HEAD") )
-			throw new RuntimeException(method);
-		Enumeration en = request.getParameterNames();
-		StringBuilder queryBuf = new StringBuilder();
-		if( !en.hasMoreElements() )
-			return null;
-		do {
-			String param = (String)en.nextElement();
-			String value = request.getParameter(param);
-			if( maxValueLen > 0 ) {
-				int len = value.length();
-				if( len > maxValueLen )
-					value = value.substring(0,maxValueLen) + "..." + (len-maxValueLen);
-			}
-			queryBuf.append(param);
-			queryBuf.append('=');
-			queryBuf.append(value);
-			queryBuf.append('&');
-		} while( en.hasMoreElements() );
-		queryBuf.deleteCharAt(queryBuf.length() - 1);
-		return queryBuf.toString();
-	}
-
-	public  String getURL() {
-		return getURL(request);
-	}
-
-	public static String getURL(HttpServletRequest request) {
-		return getURL(request,0);
-	}
-
-	public static String getURL(HttpServletRequest request,int maxValueLen) {
-//		StringBuffer buf = HttpUtils.getRequestURL(request);
-		StringBuffer buf = request.getRequestURL();
-		String qStr = getQueryString(request,maxValueLen);
-		if(qStr != null && qStr.length() > 0) {
-			buf.append('?');
-			buf.append(qStr);
-		}
-		return buf.toString();
-	}
-
 	private static String escape(String value) {
 		return value.replaceAll(";", "%3B");
 	}
@@ -447,11 +294,6 @@
 		return null;
 	}
 
-	public static String getCookieValue(HttpServletRequest request,String name) {
-		Cookie cookie = getCookie(request,name);
-		return cookie==null ? null : unescape(cookie.getValue());
-	}
-
 	public static void setCookie(HttpServletRequest request,HttpServletResponse response,String name,String value,boolean isPersistent, String domain) {
 		Cookie cookie = getCookie(request,name);
 		if( cookie==null || !cookie.getValue().equals(value) ) {
@@ -485,26 +327,6 @@
 
 	// util classes
 
-	static final class EnumerationIterator implements Iterator {
-		private final Enumeration en;
-
-		EnumerationIterator(Enumeration en) {
-			this.en = en;
-		}
-
-		@Override public boolean hasNext() {
-			return en.hasMoreElements();
-		}
-
-		@Override public Object next() {
-			return en.nextElement();
-		}
-
-		@Override public void remove() {
-			throw new UnsupportedOperationException();
-		}
-	}
-
 	private static abstract class NameMeta extends LuanMeta {
 		abstract Object get(String name);
 
@@ -517,9 +339,4 @@
 
 	};
 
-	private static String string(Object value) {
-		if( !(value instanceof String) )
-			throw new IllegalArgumentException("value must be string");
-		return (String)value;
-	}
 }
--- a/http/src/luan/modules/http/run.luan	Sat May 16 20:19:05 2015 -0600
+++ b/http/src/luan/modules/http/run.luan	Sun May 17 19:25:47 2015 -0600
@@ -49,12 +49,12 @@
 
 function respond()
 	Io.stdout = Http.response.text_writer()
-	local code = Http.request.parameters.code
+	local code = Http.request.parameter.code
 	if code == nil then
 		form()
 		return
 	end
-	local content_type = Http.request.parameters.content_type
+	local content_type = Http.request.parameter.content_type
 	if content_type ~= nil then
 		Http.response.content_type = content_type
 	end
--- a/http/src/luan/modules/http/shell.luan	Sat May 16 20:19:05 2015 -0600
+++ b/http/src/luan/modules/http/shell.luan	Sun May 17 19:25:47 2015 -0600
@@ -14,10 +14,10 @@
 env = {}
 
 function respond()
-	if Http.request.parameters.clear ~= nil then
+	if Http.request.parameter.clear ~= nil then
 		history = {}
 	else
-		local cmd = Http.request.parameters.cmd
+		local cmd = Http.request.parameter.cmd
 		if cmd ~= nil then
 			Io.stdout = {}
 			function Io.stdout.write(...)
--- a/lucene/src/luan/modules/lucene/Web_search.luan	Sat May 16 20:19:05 2015 -0600
+++ b/lucene/src/luan/modules/lucene/Web_search.luan	Sun May 17 19:25:47 2015 -0600
@@ -121,16 +121,16 @@
 
 function of(index)
 
-	return { service = function()
+	return { respond = function()
 		Io.stdout = Http.response.text_writer()
-		local query_string = Http.request.parameters.query
+		local query_string = Http.request.parameter.query
 		if query_string == nil then
 			form()
 			return
 		end
 		local query = load(query_string,"<query>",{Query=index.query},true)()
-		local rows = Http.request.parameters.rows.to_number()
-		local sort = load(Http.request.parameters.sort,"<sort>",{Query=index.query},true)()
+		local rows = Http.request.parameter.rows.to_number()
+		local sort = load(Http.request.parameter.sort,"<sort>",{Query=index.query},true)()
 		index.Searcher( function(searcher)
 			local results, length, total_hits = searcher.search(query,rows,sort)
 			local headers = {}
--- a/scripts/test.luan	Sat May 16 20:19:05 2015 -0600
+++ b/scripts/test.luan	Sun May 17 19:25:47 2015 -0600
@@ -1,8 +1,9 @@
 local Luan = require "luan:Luan"
-local assert = Luan.assert
+local error = Luan.error
 local range = Luan.range
 local Io = require "luan:Io"
 local Http = require "luan:http/Http"
+local init_for_test = Http.init_for_test
 local Lucene = require "luan:lucene/Lucene"
 local Ab_testing = require "luan:lucene/Ab_testing"
 
@@ -16,15 +17,15 @@
 end
 
 
-Http.init_for_test()
-Http.request.parameters.code = "require('luan:Io').print 'hi'"
+init_for_test()
+Http.request.parameter.code = "require('luan:Io').print 'hi'"
 page = Http.get_page "/run"
-assert( page.trim() == "hi" )
+page.trim() == "hi" or error "failed"
 
-Http.init_for_test()
-Http.request.parameters.cmd = "'ab'..'cd'"
+init_for_test()
+Http.request.parameter.cmd = "'ab'..'cd'"
 page = Http.get_page "/shell"
-assert( page.find "abcd" )
+page.find "abcd" or error "failed"
 
 
 -- lucene
@@ -50,8 +51,48 @@
 	db.save_document(doc)
 end
 
-Http.init_for_test()
+init_for_test()
 ab_testing.web_page{"All","null"}.respond()
 
+local Web_search = require "luan:lucene/Web_search"
+local web_search = Web_search.of(db)
+
+init_for_test()
+web_search.respond()
+
+init_for_test()
+Http.request.parameter.query = "Query.all_docs"
+Http.request.parameter.rows = "100"
+Http.request.parameter.sort = ""
+web_search.respond()
+
+
+-- website
+
+function Io.schemes.site(path,add_extension)
+	return Io.uri( "file:../website/src"..path, add_extension )
+end
+
+init_for_test(); Http.get_page "/"
+init_for_test(); Http.get_page "/docs.html"
+init_for_test(); Http.get_page "/tutorial.html"
+init_for_test(); Http.get_page "/pil.html"
+init_for_test(); Http.get_page "/manual.html"
+init_for_test(); Http.get_page "/diff.html"
+init_for_test(); Http.get_page "/examples/hi"
+init_for_test(); Http.get_page "/examples/hi2"
+init_for_test(); Http.get_page "/examples/hi2_simply_html"
+init_for_test(); Http.get_page "/examples/shell"
+
+init_for_test()
+Http.request.parameter.name = "bob"
+page = Http.get_page "/examples/hi2"
+page.find "bob" or error "failed"
+
+init_for_test()
+Http.request.parameter.name = "bob"
+page = Http.get_page "/examples/hi2_simply_html"
+page.find "bob" or error "failed"
+
 
 print "done"
--- a/website/src/examples/hi2.luan	Sat May 16 20:19:05 2015 -0600
+++ b/website/src/examples/hi2.luan	Sun May 17 19:25:47 2015 -0600
@@ -8,7 +8,7 @@
 	<body>
 		<h1>Hello</h1>
 		<form>
-			What is you name?
+			What is your name?
 			<input name="name">
 			<input type=submit>
 		</form>
@@ -30,7 +30,7 @@
 
 function respond()
 	Io.stdout = Http.response.text_writer()
-	name = Http.request.parameters.name
+	name = Http.request.parameter.name
 	if name == nil then
 		form()
 	else
--- a/website/src/examples/hi2_simply_html.luan	Sat May 16 20:19:05 2015 -0600
+++ b/website/src/examples/hi2_simply_html.luan	Sun May 17 19:25:47 2015 -0600
@@ -5,7 +5,7 @@
 
 local function form() %>
 			<form>
-				<label>What is you name?</label>
+				<label>What is your name?</label>
 				<input name="name" margin-bottom="1em">
 				<input type=submit>
 			</form>
@@ -19,7 +19,7 @@
 
 function respond()
 	Io.stdout = Http.response.text_writer()
-	name = Http.request.parameters.name
+	name = Http.request.parameter.name
 %>
 <html>
 	<head>