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(¶ms, (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")