shithub: mc

Download patch

ref: a603374dcbe55fff12484acdc6ae3f2736280d1e
parent: 6dcd37d6483c01449c9ded6147b0487feca92744
author: Ori Bernstein <ori@eigenstate.org>
date: Tue Jun 28 12:28:31 EDT 2016

Lots of new features.

    - Fixed URL parsing/params.
    - Added header support
    - Add simple and request based client side
    - Add more features to the cli client

--- a/lib/http/main.myr
+++ b/lib/http/main.myr
@@ -2,7 +2,7 @@
 use http
 
 const main = {args
-	var data, method, hdr
+	var data, method, showhdr, hdrs
 	var s, u, r
 	var cmd
 
@@ -12,17 +12,20 @@
 			[.opt='m', .arg="method", .desc="http method to use"],
 			[.opt='d', .arg="data", .desc="data to put in request body"],
 			[.opt='H', .desc="show headers"],
+			[.opt='D', .arg="hdr", .desc="define custom header"]
 		][:]
 	])
 
-	hdr = false
+	showhdr = false
 	method = "get"
 	data = ""
+	hdrs = [][:]
 	for opt in cmd.opts
 		match opt
 		| ('m', m):	method = m
 		| ('d', d):	data = d
-		| ('H', ""):	hdr = true
+		| ('H', ""):	showhdr = true
+		| ('D', def):	parsedef(&hdrs, def)
 		| _:	std.die("unreachable")
 		;;
 	;;
@@ -33,22 +36,22 @@
 		s = std.try(http.mksession(u.schema, u.host, u.port))
 
 		match method
-		| "get":	r = http.get(s, u.path)
-		| "head":	r = http.head(s, u.path)
-		| "delete":	r = http.delete(s, u.path)
-		| "trace":	r = http.trace(s, u.path)
-		| "options":	r = http.options(s, u.path)
-		| "put":	r = http.put(s, u.path, data)
-		| "post":	r = http.post(s, u.path, data)
+		| "get":	r = http.getreq(s, &[.url=u, .hdrs=hdrs])
+		| "head":	r = http.headreq(s, &[.url=u, .hdrs=hdrs])
+		| "delete":	r = http.deletereq(s, &[.url=u, .hdrs=hdrs])
+		| "trace":	r = http.tracereq(s, &[.url=u, .hdrs=hdrs])
+		| "options":	r = http.optionsreq(s, &[.url=u, .hdrs=hdrs])
+		| "put":	r = http.putreq(s, &[.url=u, .hdrs=hdrs], data)
+		| "post":	r = http.postreq(s, &[.url=u, .hdrs=hdrs], data)
 		| unknown:	std.fatal("unknown method '{}'\n", unknown)
 		;;
 
 		match r
 		| `std.Ok resp:
-			std.put("status: {}\n", resp.status)
-			if hdr
-				for h in resp.hdrs
-					std.put("{}\n", h)
+			if showhdr
+				std.put("status: {}\n", resp.status)
+				for (k, v) in resp.hdrs
+					std.put("{}: {}\n", k, v)
 				;;
 			;;
 			std.put("{}\n", resp.body)
@@ -57,6 +60,19 @@
 			std.put("{}\n", e)
 		;;
 		http.urlfree(u)
+	;;
+}
+
+const parsedef = {hdrs, hdr
+	var key, val
+
+	match std.strfind(hdr, ":")
+	| `std.None:
+		std.fatal("bad header string {}\n", hdr)
+	| `std.Some idx:
+		key = std.sldup(std.strstrip(hdr[:idx]))
+		val = std.sldup(std.strstrip(hdr[idx+1:]))
+		std.slpush(hdrs, (key, val))
 	;;
 }
 
--- a/lib/http/parse.myr
+++ b/lib/http/parse.myr
@@ -4,11 +4,45 @@
 use "types"
 
 pkg http =
+	pkglocal const parsereq		: (s : session#, r : req# -> bool)
 	pkglocal const parseresp	: (s : session#, r : resp# -> bool)
 	pkglocal const parsechunksz	: (s : session# -> std.result(std.size, err))
 	pkglocal const parsenumber	: (str : byte[:]#, base : int -> std.option(int))
 ;;
 
+const parsereq = {s, r
+	-> false
+	/*
+	match bio.readln(s.f)
+	| `bio.Err e:	r.err = `std.Some `Econn
+	| `bio.Eof:	r.err = `std.Some `Econn
+	| `bio.Ok ln:
+		if !parsereqstatus(s, r, ln)
+			std.slfree(ln)
+			-> false
+		;;
+		std.slfree(ln)
+	;;
+
+	while true
+		match bio.readln(s.f)
+		| `bio.Err e:	r.err = `std.Some `Econn
+		| `bio.Eof:	r.err = `std.Some `Econn
+		| `bio.Ok ln:
+			if std.strstrip(ln).len == 0
+				std.slfree(ln)
+				break
+			;;
+			if !parsehdr(s, r, ln)
+				std.slfree(ln)
+				-> false
+			;;
+			std.slfree(ln)
+		;;
+	;;
+	*/
+}
+
 const parseresp = {s, r
 	match bio.readln(s.f)
 	| `bio.Err e:	r.err = `std.Some `Econn
@@ -27,6 +61,7 @@
 		| `bio.Eof:	r.err = `std.Some `Econn
 		| `bio.Ok ln:
 			if std.strstrip(ln).len == 0
+				std.slfree(ln)
 				break
 			;;
 			if !parsehdr(s, r, ln)
--- a/lib/http/session.myr
+++ b/lib/http/session.myr
@@ -7,6 +7,10 @@
 	const mksession	: (schema : schema, host : byte[:], port : int -> std.result(session#, err))
 	const freesession	: (s : session# -> void)
 
+	//const setcookie	: (s : session#, name : byte[:], val : byte[:] -> void)
+	//const getcookie	: (s : session#, name : byte[:] -> void)
+	//const clearjar	: (s : session# -> void)
+
 	pkglocal const ioput	: (s : session#, fmt : byte[:], args : ... -> bool)
 	pkglocal const ioflush	: (s : session# -> void)
 ;;
--- a/lib/http/types.myr
+++ b/lib/http/types.myr
@@ -14,9 +14,9 @@
 		port	: int
 		host	: byte[:]
 		path	: byte[:]
+		params	: (byte[:], byte[:])[:]
 	;;
 
-
 	type err = union
 		`Ewat
 		`Eunsupp
@@ -53,8 +53,8 @@
 	;;
 
 	type req = struct
+		url	: url#
 		hdrs	: (byte[:], byte[:])[:]
-		method	: method
 	;;
 
 	type resp = struct
--- a/lib/http/url.myr
+++ b/lib/http/url.myr
@@ -8,8 +8,59 @@
 	const urlfree	: (url : url# -> void)
 ;;
 
+const __init__ = {
+	var u : url#
+
+	u = u
+	std.fmtinstall(std.typeof(u), urlfmt, [
+		("p", false)
+	][:])
+}
+
+const urlfmt = {sb, ap, opts
+	var url : url#
+	var defaultport
+	var sep
+	var showhost 
+
+	showhost = true
+	url = std.vanext(ap)
+	for o in opts
+		match o
+		| ("p", ""):	showhost = false
+		| _:	std.fatal("unknown param\n")
+		;;
+	;;
+
+	if showhost
+		match url.schema
+		| `Http:
+			std.sbputs(sb, "http://")
+			defaultport = 80
+		| `Https:
+			std.sbputs(sb, "https://")
+			defaultport = 443
+		;;
+
+		std.sbputs(sb, url.host)
+		if url.port != defaultport
+			std.sbfmt(sb, ":{}", url.port)
+		;;
+	;;
+
+	std.sbfmt(sb, url.path)
+
+	if url.params.len > 0
+		sep = '?'
+		for (k, v) in url.params
+			std.sbfmt(sb, "{}{}={}", sep, k, v)
+			sep = '&'
+		;;
+	;;
+}
+
 const parseurl = {url
-	var schema, host, path, port
+	var schema, host, port, path, params
 
 	match parseschema(&url)
 	| `std.Ok s:	schema = s
@@ -31,6 +82,11 @@
 	| `std.Fail e:	-> `std.Fail e
 	;;
 
+	match parseparams(&url)
+	| `std.Ok p:	params = p
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
 	/* todo: params */
 	-> `std.Ok std.mk([
 		.schema=schema,
@@ -37,6 +93,7 @@
 		.host=std.sldup(host),
 		.port=port,
 		.path=std.sldup(path),
+		.params=params,
 	])
 }
 
@@ -65,15 +122,49 @@
 }
 
 const parsepath = {url
+	var len, p
+
 	if url#.len == 0
 		-> `std.Ok "/"
+	elif std.decode(url#) == '?'
+		-> `std.Ok "/"
 	elif std.decode(url#) == '/'
-		-> `std.Ok url#
+		match std.strfind(url#, "?")
+		| `std.Some i:	len = i
+		| `std.None:	len = url#.len
+		;;
+		p = url#[:len]
+		url# = url#[len:]
+		-> `std.Ok p
 	else
 		-> `std.Fail `Esyntax
 	;;
 }
 
+const parseparams = {url
+	var kvp : byte[:][2]
+	var params
+
+	if url#.len == 0
+		-> `std.Ok [][:]
+	;;
+
+	match std.decode(url#)
+	| '?':	(_, url#) = std.strstep(url#)
+	| _:	-> `std.Fail `Esyntax
+	;;
+
+	params = [][:]
+	for sp in std.bysplit(url#, "&")
+		if std.bstrsplit(kvp[:], sp, "=").len != 2
+			-> `std.Fail `Esyntax
+		;;
+		std.slpush(&params, (std.sldup(kvp[0]), std.sldup(kvp[1])))
+	;;
+
+	-> `std.Ok params
+}
+
 const hostname = {url
 	var len, host
 
@@ -82,6 +173,7 @@
 		match c
 		| ':':	break
 		| '/':	break
+		| '?':	break
 		| chr:
 			if ishostchar(chr)
 				len += std.charlen(chr)
@@ -136,6 +228,6 @@
 
 const subdelim = {c
 	-> c == '.' || c == '-' || c == '_' || c == '~' || c == '!' || \
-		c == '$' || c == '&' || c == '\'' || c ==  '(' || c ==  ')' \
-		|| c ==  '*' || c ==  '+' || c ==  ',' || c ==  ';' || c ==  '='
+		c == '$' || c == '&' || c == '\'' || c ==  '(' || c ==  ')' || \
+		c ==  '*' || c ==  '+' || c ==  ',' || c ==  ';' || c ==  '='
 }
--- a/lib/http/verbs.myr
+++ b/lib/http/verbs.myr
@@ -6,19 +6,38 @@
 use "parse"
 
 pkg http =
-	const get	: (s : session#, path : byte[:]	-> std.result(resp#, err))
-	const head	: (s : session#, path : byte[:] -> std.result(resp#, err))
-	const put	: (s : session#, path : byte[:], data : byte[:] -> std.result(resp#, err))
-	const post	: (s : session#, path : byte[:], data : byte[:] -> std.result(resp#, err))
-	const delete	: (s : session#, path : byte[:] -> std.result(resp#, err))
-	const options	: (s : session#, path : byte[:] -> std.result(resp#, err))
-	const trace	: (s : session#, path : byte[:] -> std.result(resp#, err))
+	/* simple versions */
+	const get	: (s : session#, r : byte[:] -> std.result(resp#, err))
+	const head	: (s : session#, r : byte[:] -> std.result(resp#, err))
+	const put	: (s : session#, r : byte[:], data : byte[:] -> std.result(resp#, err))
+	const post	: (s : session#, r : byte[:], data : byte[:] -> std.result(resp#, err))
+	const delete	: (s : session#, r : byte[:] -> std.result(resp#, err))
+	const options	: (s : session#, r : byte[:] -> std.result(resp#, err))
+	const trace	: (s : session#, r : byte[:] -> std.result(resp#, err))
 
+	/* request versions */
+	const getreq	: (s : session#, r : req# -> std.result(resp#, err))
+	const headreq	: (s : session#, r : req# -> std.result(resp#, err))
+	const putreq	: (s : session#, r : req#, data : byte[:] -> std.result(resp#, err))
+	const postreq	: (s : session#, r : req#, data : byte[:] -> std.result(resp#, err))
+	const deletereq	: (s : session#, r : req# -> std.result(resp#, err))
+	const optionsreq	: (s : session#, r : req# -> std.result(resp#, err))
+	const tracereq	: (s : session#, r : req# -> std.result(resp#, err))
+
 	const freeresp	: (r : resp# -> void)
 ;;
 
-const get = {s, path
-	match request(s, `Get, path, [][:], `std.None)
+const get = {s, path; -> getreq(s, &[.url=&[.path=path]])}
+const head = {s, path; -> headreq(s, &[.url=&[.path=path]])}
+const put = {s, path, data; -> putreq(s, &[.url=&[.path=path]], data)}
+const post = {s, path, data; -> postreq(s, &[.url=&[.path=path]], data)}
+const delete = {s, path; -> deletereq(s, &[.url=&[.path=path]])}
+const options = {s, path; -> optionsreq(s, &[.url=&[.path=path]])}
+const trace = {s, path; -> tracereq(s, &[.url=&[.path=path]])}
+
+
+const getreq = {s, r
+	match request(s, `Get, r, `std.None)
 	| `std.Ok _:	/* nothing */
 	| `std.Fail e:	-> `std.Fail e
 	;;
@@ -26,8 +45,8 @@
 	-> response(s, true)
 }
 
-const head = {s, path
-	match request(s, `Head, path, [][:], `std.None)
+const headreq = {s, r
+	match request(s, `Head, r, `std.None)
 	| `std.Ok _:	/* nothing */
 	| `std.Fail e:	-> `std.Fail e
 	;;
@@ -35,8 +54,8 @@
 	-> response(s, false)
 }
 
-const put = {s, path, data
-	match request(s, `Put, path, [][:], `std.Some data)
+const putreq = {s, r, data
+	match request(s, `Put, r, `std.Some data)
 	| `std.Ok _:	/* nothing */
 	| `std.Fail e:	-> `std.Fail e
 	;;
@@ -44,8 +63,8 @@
 	-> response(s, true)
 }
 
-const post = {s, path, data
-	match request(s, `Post, path, [][:], `std.Some data)
+const postreq = {s, r, data
+	match request(s, `Post, r, `std.Some data)
 	| `std.Ok _:	/* nothing */
 	| `std.Fail e:	-> `std.Fail e
 	;;
@@ -53,8 +72,8 @@
 	-> response(s, true)
 }
 
-const delete = {s, path
-	match request(s, `Delete, path, [][:], `std.None)
+const deletereq = {s, r
+	match request(s, `Delete, r, `std.None)
 	| `std.Ok _:	/* nothing */
 	| `std.Fail e:	-> `std.Fail e
 	;;
@@ -62,8 +81,8 @@
 	-> response(s, true)
 }
 
-const options = {s, path
-	match request(s, `Options, path, [][:], `std.None)
+const optionsreq = {s, r
+	match request(s, `Options, r, `std.None)
 	| `std.Ok _:	/* nothing */
 	| `std.Fail e:	-> `std.Fail e
 	;;
@@ -71,8 +90,8 @@
 	-> response(s, true)
 }
 
-const trace = {s, path
-	match request(s, `Trace, path, [][:], `std.None)
+const tracereq = {s, r
+	match request(s, `Trace, r, `std.None)
 	| `std.Ok _:	/* nothing */
 	| `std.Fail e:	-> `std.Fail e
 	;;
@@ -110,9 +129,9 @@
 	-> `std.Ok resp
 }
 
-const request = {s, method, path, hdrs : (byte[:], byte[:])[:], data
+const request = {s, method, r, data
 	/* status */
-	ioput(s, "{} {} HTTP/1.1\r\n", method, path)
+	ioput(s, "{} {p} HTTP/1.1\r\n", method, r.url)
 
 	/* headers */
 	ioput(s, "Host: {}\r\n", s.host)
@@ -121,7 +140,7 @@
 	| `std.Some d:	ioput(s, "Content-Length: {}\r\n", d.len)
 	| `std.None:	/* nothing to do */
 	;;
-	for (k, v) in hdrs
+	for (k, v) in r.hdrs
 		ioput(s, "{}: {}\r\n", k, v)
 	;;
 	ioput(s, "\r\n")