summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorOri Bernstein <ori@eigenstate.org>2017-09-10 21:43:59 -0700
committerOri Bernstein <ori@eigenstate.org>2017-09-10 21:43:59 -0700
commit735a7d2932d70b5bf232e2d021e6c0c61e03f61a (patch)
tree10037fc554426b2c4e2774518aad06bcfd9d1d9c /lib
parent38941f5eadd5e3606776d3c0004b997fb53ba399 (diff)
parentdd8235ce6d9c45e7039a821bdc86818c63c2823d (diff)
downloadmc-735a7d2932d70b5bf232e2d021e6c0c61e03f61a.tar.gz
Merge ../libhttp
Diffstat (limited to 'lib')
-rw-r--r--lib/http/bld.sub21
-rw-r--r--lib/http/client.myr281
-rw-r--r--lib/http/h.myr90
-rw-r--r--lib/http/parse.myr287
-rw-r--r--lib/http/serve.myr14
-rw-r--r--lib/http/server.myr110
-rw-r--r--lib/http/session.myr69
-rw-r--r--lib/http/srvdot.myr66
-rw-r--r--lib/http/status.myr67
-rw-r--r--lib/http/types.myr83
-rw-r--r--lib/http/url.myr238
11 files changed, 1326 insertions, 0 deletions
diff --git a/lib/http/bld.sub b/lib/http/bld.sub
new file mode 100644
index 0000000..1b8062a
--- /dev/null
+++ b/lib/http/bld.sub
@@ -0,0 +1,21 @@
+bin h {noinst}=
+ h.myr
+
+ lib http
+;;
+
+bin srvdot {noinst}=
+ srvdot.myr
+
+ lib http
+;;
+
+lib http =
+ parse.myr
+ types.myr
+ url.myr
+ client.myr
+ server.myr
+ session.myr
+ status.myr
+;;
diff --git a/lib/http/client.myr b/lib/http/client.myr
new file mode 100644
index 0000000..ebf0426
--- /dev/null
+++ b/lib/http/client.myr
@@ -0,0 +1,281 @@
+use std
+use bio
+
+use "types"
+use "session"
+use "parse"
+
+pkg http =
+ /* simple versions */
+ const get : (s : session#, r : url# -> std.result(resp#, err))
+ const head : (s : session#, r : url# -> std.result(resp#, err))
+ const put : (s : session#, r : url#, data : byte[:] -> std.result(resp#, err))
+ const post : (s : session#, r : url#, data : byte[:] -> std.result(resp#, err))
+ const delete : (s : session#, r : url# -> std.result(resp#, err))
+ const options : (s : session#, r : url# -> std.result(resp#, err))
+ const trace : (s : session#, r : url# -> std.result(resp#, err))
+
+ /* request based 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; -> getreq(s, &[.url=path])}
+const head = {s, path; -> headreq(s, &[.url=path])}
+const put = {s, path, data; -> putreq(s, &[.url=path], data)}
+const post = {s, path, data; -> postreq(s, &[.url=path], data)}
+const delete = {s, path; -> deletereq(s, &[.url=path])}
+const options = {s, path; -> optionsreq(s, &[.url=path])}
+const trace = {s, path; -> tracereq(s, &[.url=path])}
+
+
+const getreq = {s, r
+ match request(s, `Get, r, `std.None)
+ | `std.Ok _: /* nothing */
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ -> response(s, true)
+}
+
+const headreq = {s, r
+ match request(s, `Head, r, `std.None)
+ | `std.Ok _: /* nothing */
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ -> response(s, false)
+}
+
+const putreq = {s, r, data
+ match request(s, `Put, r, `std.Some data)
+ | `std.Ok _: /* nothing */
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ -> response(s, true)
+}
+
+const postreq = {s, r, data
+ match request(s, `Post, r, `std.Some data)
+ | `std.Ok _: /* nothing */
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ -> response(s, true)
+}
+
+const deletereq = {s, r
+ match request(s, `Delete, r, `std.None)
+ | `std.Ok _: /* nothing */
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ -> response(s, true)
+}
+
+const optionsreq = {s, r
+ match request(s, `Options, r, `std.None)
+ | `std.Ok _: /* nothing */
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ -> response(s, true)
+}
+
+const tracereq = {s, r
+ match request(s, `Trace, r, `std.None)
+ | `std.Ok _: /* nothing */
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ -> response(s, true)
+}
+
+const response = {s, body
+ var resp
+
+ resp = std.mk([
+ .hdrs = [][:],
+ .len = 0,
+ .err = `std.None,
+ .reason = "",
+ .status = 0,
+ .enc = `Length,
+ ])
+
+ if parseresp(s, resp)
+ if !body
+ -> `std.Ok resp
+ else
+ match readbody(s, resp)
+ | `std.Ok buf: resp.body = buf
+ | `std.Err e: -> `std.Err e
+ ;;
+ ;;
+ else
+ match resp.err
+ | `std.Some e: -> `std.Err e
+ | `std.None: -> `std.Err `Ewat
+ ;;
+ ;;
+ -> `std.Ok resp
+}
+
+const request = {s, method, r, data
+ /* status */
+ ioput(s, "{} {p} HTTP/1.1\r\n", method, r.url)
+
+ /* headers */
+ ioput(s, "Host: {}\r\n", s.host)
+ ioput(s, "User-Agent: {}\r\n", s.ua)
+ match data
+ | `std.Some d: ioput(s, "Content-Length: {}\r\n", d.len)
+ | `std.None: /* nothing to do */
+ ;;
+ for (k, v) : r.hdrs
+ ioput(s, "{}: {}\r\n", k, v)
+ ;;
+ ioput(s, "\r\n")
+
+ /* body */
+ match data
+ | `std.None: /* nothing to do */
+ | `std.Some d:
+ ioput(s, d)
+ ioput(s, "\r\n")
+ ;;
+ ioflush(s)
+
+ if s.err
+ -> `std.Err `Econn
+ else
+ -> `std.Ok void
+ ;;
+
+}
+
+const readbody = {s, r -> std.result(byte[:], err)
+ match r.enc
+ | `Length: -> readlenbody(s, r)
+ | `Chunked: -> readchunkedbody(s, r)
+ | badenc: std.fatal("unsupported encoding {}\n", badenc)
+ ;;
+}
+
+const readlenbody = {s, r
+ var buf
+
+ buf = ""
+ if r.len == 0
+ -> `std.Ok buf
+ ;;
+
+ buf = std.slalloc(r.len)
+ match bio.read(s.f, buf)
+ | `bio.Err e: goto shortread
+ | `bio.Eof: goto shortread
+ | `bio.Ok rd:
+ if rd.len != r.len
+ goto shortread
+ ;;
+ ;;
+ -> `std.Ok buf
+:shortread
+ std.slfree(buf)
+ -> `std.Err `Eshort
+}
+
+const __init__ = {
+ var m
+
+ m = `Get
+ std.fmtinstall(std.typeof(m), fmtmethod, [][:])
+}
+
+const readchunkedbody = {s, r
+ var buf, len
+
+ buf = ""
+ len = 0
+ while true
+ match parsechunksz(s)
+ | `std.Err e:
+ std.slfree(buf)
+ -> `std.Err e
+ | `std.Ok 0:
+ break
+ | `std.Ok sz:
+ std.slgrow(&buf, buf.len + sz)
+ match bio.read(s.f, buf[len:len + sz])
+ | `bio.Eof:
+ std.slfree(buf)
+ -> `std.Err `Eshort
+ | `bio.Err e:
+ std.slfree(buf)
+ -> `std.Err `Econn
+ | `bio.Ok str:
+ if str.len != sz
+ std.slfree(buf)
+ -> `std.Err `Eshort
+ ;;
+ len += sz
+ match checkendln(s)
+ | `std.Ok _: /* nothing */
+ | `std.Err e:
+ std.slfree(buf)
+ -> `std.Err e
+ ;;
+ ;;
+ ;;
+ ;;
+ -> `std.Ok buf
+}
+
+const checkendln = {s
+ var r
+
+ match bio.readln(s.f)
+ | `bio.Err e: r = `std.Err `Econn
+ | `bio.Eof: r = `std.Err `Econn
+ | `bio.Ok crlf:
+ if std.strstrip(crlf).len == 0
+ r = `std.Ok void
+ else
+ r = `std.Err `Eproto
+ ;;
+ std.slfree(crlf)
+ ;;
+ -> r
+}
+
+const fmtmethod = {sb, ap, opt
+ var r
+
+ r = std.vanext(ap)
+ match r
+ | `Get: std.sbputs(sb, "GET")
+ | `Head: std.sbputs(sb, "HEAD")
+ | `Put: std.sbputs(sb, "PUT")
+ | `Post: std.sbputs(sb, "POST")
+ | `Delete: std.sbputs(sb, "DELETE")
+ | `Trace: std.sbputs(sb, "TRACE")
+ | `Options: std.sbputs(sb, "OPTIONS")
+ ;;
+}
+
+const freeresp = {r
+ for (k, v) : r.hdrs
+ std.slfree(k)
+ std.slfree(v)
+ ;;
+ std.slfree(r.reason)
+ std.free(r)
+}
diff --git a/lib/http/h.myr b/lib/http/h.myr
new file mode 100644
index 0000000..9a00e2e
--- /dev/null
+++ b/lib/http/h.myr
@@ -0,0 +1,90 @@
+use std
+use http
+
+const main = {args
+ var data, method, showhdr, hdrs, out
+ var s, u, r
+ var cmd
+
+ cmd = std.optparse(args, &[
+ .argdesc = "url...",
+ .minargs = 1,
+ .opts = [
+ [.opt='m', .arg="method", .desc="http method to use"],
+ [.opt='d', .arg="data", .desc="data to put in request body"],
+ [.opt='o', .arg="out", .desc="output file name"],
+ [.opt='H', .desc="show headers"],
+ [.opt='D', .arg="hdr", .desc="define custom header"]
+ ][:]
+ ])
+
+ showhdr = false
+ method = "get"
+ data = ""
+ hdrs = [][:]
+ out = ""
+ for opt : cmd.opts
+ match opt
+ | ('m', m): method = m
+ | ('d', d): data = d
+ | ('o', o): out = o
+ | ('H', ""): showhdr = true
+ | ('D', def): parsedef(&hdrs, def)
+ | _: std.die("unreachable")
+ ;;
+ ;;
+
+ for url : cmd.args
+ if !std.hasprefix(url, "http")
+ url = std.fmt("http://{}", url)
+ ;;
+ u = std.try(http.parseurl(url))
+ s = std.try(http.mksession(u.schema, u.host, u.port))
+
+ match method
+ | "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:
+ if showhdr
+ std.put("status: {}\n", resp.status)
+ for (k, v) : resp.hdrs
+ std.put("{}: {}\n", k, v)
+ ;;
+ ;;
+ if out.len != 0
+ if !std.blat(out, resp.body, 0o644)
+ std.fatal("could not write output: {}\n", out)
+ ;;
+ else
+ std.fblat(std.Out, resp.body)
+ ;;
+ http.freeresp(resp)
+ | `std.Err e:
+ 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))
+ ;;
+}
+
diff --git a/lib/http/parse.myr b/lib/http/parse.myr
new file mode 100644
index 0000000..d9f8b34
--- /dev/null
+++ b/lib/http/parse.myr
@@ -0,0 +1,287 @@
+use std
+use bio
+
+use "types"
+
+pkg http =
+ pkglocal const parsereq : (s : session# -> std.result(req#, err))
+ 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
+ var r, err
+
+ r.hdrs = [][:]
+ r.url = std.mk([
+ .schema=`Http,
+ .host=std.sldup(s.host),
+ .port=s.port,
+ .path="",
+ .params=[][:],
+ ])
+
+ match bio.readln(s.f)
+ | `bio.Err e:
+ err = `Econn
+ goto error
+ | `bio.Eof:
+ err = `Econn
+ goto error
+ | `bio.Ok ln:
+ match parsereqstatus(s, r, ln)
+ | `std.Ok void:
+ | `std.Err e:
+ std.slfree(ln)
+ err = e
+ -> `std.Err e
+ ;;
+ std.slfree(ln)
+ ;;
+
+ while true
+ match bio.readln(s.f)
+ | `bio.Err e:
+ err = `Econn
+ goto error
+ | `bio.Eof:
+ err = `Econn
+ goto error
+ | `bio.Ok ln:
+ if std.strstrip(ln).len == 0
+ std.slfree(ln)
+ break
+ ;;
+ match parsehdr(s, ln)
+ | `std.Ok kvp:
+ std.slpush(&r.hdrs, kvp)
+ | `std.Err e:
+ std.slfree(ln)
+ -> `std.Err e
+ ;;
+ std.slfree(ln)
+ ;;
+ ;;
+ -> `std.Ok std.mk(r)
+:error
+ -> `std.Err err
+}
+
+const parseresp = {s, r : resp#
+ match bio.readln(s.f)
+ | `bio.Err e: r.err = `std.Some `Econn
+ | `bio.Eof: r.err = `std.Some `Econn
+ | `bio.Ok ln:
+ if !parserespstatus(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
+ ;;
+ match parsehdr(s, ln)
+ | `std.Ok kvp:
+ std.slpush(&r.hdrs, kvp)
+ | `std.Err e:
+ r.err = `std.Some e
+ std.slfree(ln)
+ -> false
+ ;;
+ std.slfree(ln)
+ ;;
+ ;;
+
+ match getenc(r)
+ | `std.Ok `Length:
+ r.enc = `Length
+ match getlen(r)
+ | `std.Some n:
+ r.len = n
+ | `std.None:
+ r.err = `std.Some `Eproto
+ -> false
+ ;;
+ | `std.Ok enc:
+ r.enc = enc
+ | `std.Err e:
+ r.err = `std.Some e
+ -> false
+ ;;
+ -> true
+
+}
+
+const parsereqstatus = {s, r, ln
+ match parseword(&ln)
+ | `std.Some "GET": r.method = `Get
+ | `std.Some "HEAD": r.method = `Head
+ | `std.Some "POST": r.method = `Post
+ | `std.Some "DELETE": r.method = `Delete
+ | `std.Some "TRACE": r.method = `Trace
+ | `std.Some "OPTIONS": r.method = `Options
+ | `std.Some _: -> `std.Err `Eproto
+ | `std.None: -> `std.Err `Eproto
+ ;;
+
+ match parseword(&ln)
+ | `std.Some w: r.url.path = std.sldup(w)
+ | `std.None: -> `std.Err `Eproto
+ ;;
+
+ match parseword(&ln)
+ | `std.Some "HTTP/1.1": /* ok */
+ | `std.Some w: std.put("warn: http version '{}'\n", w)
+ | `std.None: -> `std.Err `Eproto
+ ;;
+
+ ln = std.strfstrip(ln)
+ -> `std.Ok void
+}
+
+const parserespstatus = {s, r, ln
+ /* HTTP/1.1 */
+ ln = std.strfstrip(ln)
+ if !std.chomp(&ln, "HTTP")
+ r.err = `std.Some `Eproto
+ -> false
+ ;;
+ if !std.chomp(&ln, "/1.1")
+ r.err = `std.Some `Eproto
+ -> false
+ ;;
+
+ ln = std.strfstrip(ln)
+ match parsenumber(&ln, 10)
+ | `std.Some n:
+ r.status = n
+ | `std.None:
+ r.err = `std.Some `Eproto
+ -> false
+ ;;
+
+ ln = std.strfstrip(ln)
+ r.reason = std.sldup(ln)
+ -> true
+}
+
+const parsehdr = {s, ln
+ var key, val
+
+ match std.strfind(ln, ":")
+ | `std.Some idx:
+ key = std.sldup(std.strstrip(ln[:idx]))
+ val = std.sldup(std.strstrip(ln[idx+1:]))
+ -> `std.Ok (key, val)
+ | `std.None:
+ -> `std.Err `Ehdr
+ ;;
+}
+
+const getlen = {r
+ match findhdr(r, "Content-Length")
+ | `std.Some v:
+ match std.intparsebase(v, 10)
+ | `std.Some n: -> `std.Some n
+ | `std.None: -> `std.None
+ ;;
+ | `std.None:
+ -> `std.None
+ ;;
+}
+
+const parsechunksz = {s
+ var ret, str
+
+ match bio.readln(s.f)
+ | `bio.Eof: ret = `std.Err `Econn
+ | `bio.Err e: ret = `std.Err `Econn
+ | `bio.Ok ln:
+ str = ln
+ match parsenumber(&str, 16)
+ | `std.Some n: ret = `std.Ok (n : std.size)
+ | `std.None:
+ ret = `std.Err `Eproto
+ ;;
+ std.slfree(ln)
+ ;;
+ -> ret
+}
+
+const getenc = {r
+ match findhdr(r, "Transfer-Encoding")
+ | `std.None: -> `std.Ok `Length
+ | `std.Some "chunked": -> `std.Ok `Chunked
+ | `std.Some "compress": -> `std.Ok `Compress
+ | `std.Some "deflate": -> `std.Ok `Deflate
+ | `std.Some "gzip": -> `std.Ok `Gzip
+ | `std.Some unknown: -> `std.Err `Eenc
+ ;;
+}
+
+const findhdr = {r, name
+ for (k, v) : r.hdrs
+ if std.strcaseeq(k, name)
+ -> `std.Some v
+ ;;
+ ;;
+ -> `std.None
+}
+
+const parseword = {ln
+ var w, end
+
+ ln# = std.strfstrip(ln#)
+ end = 0
+ for var i = 0; i < ln#.len; i++
+ if i == ln#.len - 1
+ end = i + 1
+ elif std.isspace(std.decode(ln#[i:]))
+ end = i
+ break
+ ;;
+ ;;
+ if end == 0
+ -> `std.None
+ else
+ w = ln#[:end]
+ ln# = ln#[end:]
+ -> `std.Some w
+ ;;
+}
+
+const parsenumber = {ln, base
+ var n, ok
+ var c, s, dig
+
+ n = 0
+ s = ln#
+ ok = false
+ while true
+ (c, s) = std.strstep(s)
+ dig = std.charval(c, base)
+ if dig >= 0 && dig < base
+ ok = true
+ n *= base
+ n += dig
+ else
+ break
+ ;;
+ ;;
+ ln# = s
+ if ok
+ -> `std.Some n
+ else
+ -> `std.None
+ ;;
+}
+
diff --git a/lib/http/serve.myr b/lib/http/serve.myr
new file mode 100644
index 0000000..359e308
--- /dev/null
+++ b/lib/http/serve.myr
@@ -0,0 +1,14 @@
+use std
+use bio
+
+use "types"
+use "parse"
+
+pkg http =
+ const readreq : (srv : server# -> req#)
+;;
+
+
+const readreq = {srv : server#
+ -> req
+}
diff --git a/lib/http/server.myr b/lib/http/server.myr
new file mode 100644
index 0000000..8b221bd
--- /dev/null
+++ b/lib/http/server.myr
@@ -0,0 +1,110 @@
+use bio
+use std
+use thread
+
+use "types"
+use "session"
+use "parse"
+
+pkg http =
+ const announce : (ds : byte[:] -> std.result(server#, err))
+ const shutdown : (srv : server# -> void)
+ const serve : (srv : server#, fn : (srv : server#, s : session#, req : req# -> void) -> void)
+ const respond : (srv : server#, sess : session#, resp : resp# -> void)
+;;
+
+const announce = {ds
+ var afd
+
+ match std.announce(ds)
+ | `std.Ok f: afd = f
+ | `std.Err e: -> `std.Err `Econn
+ ;;
+
+ match std.listen(afd)
+ | `std.Err e: -> `std.Err `Econn
+ | `std.Ok lfd:
+ std.close(afd)
+ -> `std.Ok std.mk([
+ .refs=1,
+ .lfd=lfd,
+ .quit=false
+ ])
+ ;;
+}
+
+const serve = {srv, fn
+ while !srv.quit
+ match waitconn(srv)
+ | `std.Ok fd:
+ ref(srv)
+ thread.spawn({;communicate(srv, fd, fn)})
+ | `std.Err e: /* eh? */
+ ;;
+ ;;
+ unref(srv)
+}
+
+const communicate = {srv, fd, fn
+ var s
+
+ s = mksrvsession(fd)
+ while !srv.quit
+ match parsereq(s)
+ | `std.Ok req: fn(srv, s, req)
+ | `std.Err e: break
+ ;;
+ ;;
+ std.close(fd)
+ unref(srv)
+}
+
+const respond = {srv, s, resp
+ var sb
+
+ sb = std.mksb()
+ bio.put(s.f, "HTTP/1.1 {} {}\r\n", resp.status, statusstr(resp.status))
+ bio.put(s.f, "Content-Length: {}\r\n", resp.body.len)
+ bio.put(s.f, "Encoding: {}\r\n", resp.enc)
+ for (k, v) : resp.hdrs
+ bio.put(s.f, "{}: {}\r\n", k, v)
+ ;;
+ bio.put(s.f, "\r\n")
+ bio.write(s.f, resp.body)
+ bio.flush(s.f)
+}
+
+const statusstr = {st
+ match st
+ | 200: -> "OK"
+ | 404: -> "Not Found"
+ | 503: -> "Internal Error"
+ | _: -> "Bad State"
+ ;;
+}
+
+const shutdown = {srv
+ std.close(srv.lfd)
+ srv.quit = true
+
+}
+
+
+const waitconn = {srv
+ match std.accept(srv.lfd)
+ | `std.Ok fd: -> `std.Ok fd
+ | `std.Err e: -> `std.Err `Econn
+ ;;
+}
+
+
+const ref = {srv
+ thread.xadd(&srv.refs, 1)
+}
+
+const unref = {srv
+ thread.xadd(&srv.refs, -1)
+ if thread.xget(&srv.refs) == 0
+ std.free(srv)
+ ;;
+}
diff --git a/lib/http/session.myr b/lib/http/session.myr
new file mode 100644
index 0000000..d641cda
--- /dev/null
+++ b/lib/http/session.myr
@@ -0,0 +1,69 @@
+use std
+use bio
+
+use "types"
+
+pkg http =
+ const mksession : (schema : schema, host : byte[:], port : uint16 -> std.result(session#, err))
+ const mksrvsession : (fd : std.fd -> session#)
+ const freesession : (s : session# -> void)
+
+ pkglocal const ioput : (s : session#, fmt : byte[:], args : ... -> bool)
+ pkglocal const ioflush : (s : session# -> void)
+;;
+
+const mksession = {schema, host, port
+ var s, sess
+
+ match schema
+ | `Http: /* nothing */
+ | `Https: std.fatal("unsupported protocol\n")
+ ;;
+
+ s = std.fmt("tcp!{}!{}", host, port)
+ match std.dial(s)
+ | `std.Err e: sess = `std.Err `Econn
+ | `std.Ok fd: sess = `std.Ok std.mk([
+ .err = false,
+ .ua = std.sldup("Myrfoo HTTP"),
+ .host = std.sldup(host),
+ .f = bio.mkfile(fd, bio.Rw)
+ ])
+ ;;
+ std.slfree(s)
+ -> sess
+}
+
+const mksrvsession = {fd
+ -> std.mk([
+ .err = false,
+ .srvname = std.sldup("Myrfoo HTTP Server"),
+ .f = bio.mkfile(fd, bio.Rw),
+ ])
+}
+
+const freesession = {s
+ bio.close(s.f)
+ std.slfree(s.host)
+ std.slfree(s.ua)
+ std.free(s)
+}
+
+const ioput = {s, fmt, args
+ var ap
+
+ if s.err
+ -> false
+ ;;
+ ap = std.vastart(&args)
+ match bio.putv(s.f, fmt, &ap)
+ | `bio.Ok _: /* nothing */
+ | `bio.Err _: s.err = true
+ | `bio.Eof: s.err = true
+ ;;
+ -> s.err
+}
+
+const ioflush = {s
+ bio.flush(s.f)
+}
diff --git a/lib/http/srvdot.myr b/lib/http/srvdot.myr
new file mode 100644
index 0000000..e6c0a21
--- /dev/null
+++ b/lib/http/srvdot.myr
@@ -0,0 +1,66 @@
+use std
+use http
+
+const main = {args
+ var srv, ann, cmd
+
+ cmd = std.optparse(args, &[
+ .maxargs=0,
+ .opts = [[.opt='a', .arg="ann", .desc="announce on `ann`"]][:]
+ ])
+ ann = "tcp!localhost!8080"
+ for opt : cmd.opts
+ match opt
+ | ('a', a): ann = a
+ | _: std.die("unreachable")
+ ;;
+ ;;
+
+ match http.announce(ann)
+ | `std.Ok s: srv = s
+ | `std.Err e: std.fatal("unable to announce: {}\n", e)
+ ;;
+
+ http.serve(srv, route)
+}
+
+const route = {srv, sess, req
+ std.put("Reading path {}\n", req.url.path)
+ match req.url.path
+ | "/ping": respond(srv, sess, 200, "pong")
+ | "/quit": http.shutdown(srv)
+ | fspath: showfile(srv, sess, req.url.path)
+ ;;
+}
+
+const showfile = {srv, sess, path
+ var eb : byte[128]
+ var p
+
+ p = std.pathcat(".", path)
+ match std.slurp(p)
+ | `std.Ok buf:
+ respond(srv, sess, 200, buf)
+ std.slfree(buf)
+ | `std.Err e:
+ respond(srv, sess, 404, std.bfmt(eb[:], "error reading {}: {}\n", p, e))
+ ;;
+ std.slfree(p)
+}
+
+
+const respond = {srv, sess, status, body
+ var resp
+
+ resp = std.mk([
+ .status=status,
+ .hdrs = [][:],
+ .len = 0,
+ .err = `std.None,
+ .reason = "",
+ .body = body,
+ .enc = `http.Length
+ ])
+ http.respond(srv, sess, resp)
+ std.free(resp)
+}
diff --git a/lib/http/status.myr b/lib/http/status.myr
new file mode 100644
index 0000000..df247be
--- /dev/null
+++ b/lib/http/status.myr
@@ -0,0 +1,67 @@
+use "types"
+
+pkg http =
+ const Continue : status = 100 /* RFC 7231, 6.2.1*/
+ const SwitchingProtocols : status = 101 /* RFC 7231, 6.2.2*/
+ const Processing : status = 102 /* RFC 2518, 10.1*/
+
+ const Ok : status = 200 /* RFC 7231, 6.3.1*/
+ const Created : status = 201 /* RFC 7231, 6.3.2*/
+ const Accepted : status = 202 /* RFC 7231, 6.3.3*/
+ const NonAuthoritativeInfo : status = 203 /* RFC 7231, 6.3.4*/
+ const NoContent : status = 204 /* RFC 7231, 6.3.5*/
+ const ResetContent : status = 205 /* RFC 7231, 6.3.6*/
+ const PartialContent : status = 206 /* RFC 7233, 4.1*/
+ const Multi : status = 207 /* RFC 4918, 11.1*/
+ const AlreadyReported : status = 208 /* RFC 5842, 7.1*/
+ const IMUsed : status = 226 /* RFC 3229, 10.4.1*/
+
+ const MultipleChoices : status = 300 /* RFC 7231, 6.4.1*/
+ const MovedPermanently : status = 301 /* RFC 7231, 6.4.2*/
+ const Found : status = 302 /* RFC 7231, 6.4.3*/
+ const SeeOther : status = 303 /* RFC 7231, 6.4.4*/
+ const NotModified : status = 304 /* RFC 7232, 4.1*/
+ const UseProxy : status = 305 /* RFC 7231, 6.4.5*/
+ const TemporaryRedirect : status = 307 /* RFC 7231, 6.4.7*/
+ const PermanentRedirect : status = 308 /* RFC 7538, 3*/
+
+ const BadRequest : status = 400 /* RFC 7231, 6.5.1*/
+ const Unauthorized : status = 401 /* RFC 7235, 3.1*/
+ const PaymentRequired : status = 402 /* RFC 7231, 6.5.2*/
+ const Forbidden : status = 403 /* RFC 7231, 6.5.3*/
+ const NotFound : status = 404 /* RFC 7231, 6.5.4*/
+ const MethodNotAllowed : status = 405 /* RFC 7231, 6.5.5*/
+ const NotAcceptable : status = 406 /* RFC 7231, 6.5.6*/
+ const ProxyAuthRequired : status = 407 /* RFC 7235, 3.2*/
+ const RequestTimeout : status = 408 /* RFC 7231, 6.5.7*/
+ const Conflict : status = 409 /* RFC 7231, 6.5.8*/
+ const Gone : status = 410 /* RFC 7231, 6.5.9*/
+ const LengthRequired : status = 411 /* RFC 7231, 6.5.10*/
+ const PreconditionFailed : status = 412 /* RFC 7232, 4.2*/
+ const RequestEntityTooLarge : status = 413 /* RFC 7231, 6.5.11*/
+ const RequestURITooLong : status = 414 /* RFC 7231, 6.5.12*/
+ const UnsupportedMediaType : status = 415 /* RFC 7231, 6.5.13*/
+ const RangeNotSatisfiable : status = 416 /* RFC 7233, 4.4*/
+ const ExpectationFailed : status = 417 /* RFC 7231, 6.5.14*/
+ const Teapot : status = 418 /* RFC 7168, 2.3.3*/
+ const UnprocessableEntity : status = 422 /* RFC 4918, 11.2*/
+ const Locked : status = 423 /* RFC 4918, 11.3*/
+ const FailedDependency : status = 424 /* RFC 4918, 11.4*/
+ const UpgradeRequired : status = 426 /* RFC 7231, 6.5.15*/
+ const PreconditionRequired : status = 428 /* RFC 6585, 3*/
+ const TooManyRequests : status = 429 /* RFC 6585, 4*/
+ const HeaderFieldsTooLarge : status = 431 /* RFC 6585, 5*/
+ const UnavailableForLegal : status = 451 /* RFC 7725, 3*/
+
+ const InternalServerError : status = 500 /* RFC 7231, 6.6.1*/
+ const NotImplemented : status = 501 /* RFC 7231, 6.6.2*/
+ const BadGateway : status = 502 /* RFC 7231, 6.6.3*/
+ const ServiceUnavailable : status = 503 /* RFC 7231, 6.6.4*/
+ const GatewayTimeout : status = 504 /* RFC 7231, 6.6.5*/
+ const VersionNotSupported : status = 505 /* RFC 7231, 6.6.6*/
+ const VariantAlsoNegotiates : status = 506 /* RFC 2295, 8.1*/
+ const InsufficientStorage : status = 507 /* RFC 4918, 11.5*/
+ const LoopDetected : status = 508 /* RFC 5842, 7.2*/
+ const NotExtended : status = 510 /* RFC 2774, 7*/
+ const NetworkAuthRequired : status = 511 /* RFC 6585, 6*/
+;;
diff --git a/lib/http/types.myr b/lib/http/types.myr
new file mode 100644
index 0000000..357c3fe
--- /dev/null
+++ b/lib/http/types.myr
@@ -0,0 +1,83 @@
+use std
+use bio
+
+pkg http =
+ type status = int
+
+ type session = struct
+ f : bio.file#
+ host : byte[:]
+ port : uint16
+ srvname : byte[:]
+ ua : byte[:]
+ err : bool
+ ;;
+
+
+ type server = struct
+ lfd : std.fd
+ refs : uint32
+ quit : bool
+ ;;
+
+ type url = struct
+ schema : schema
+ port : uint16
+ host : byte[:]
+ path : byte[:]
+ params : (byte[:], byte[:])[:]
+ ;;
+
+ type err = union
+ `Ehttp int /* http error */
+ `Eunsupp /* unsupported feature */
+ `Econn /* connection lost */
+ `Ehdr /* invalid header */
+ `Eproto /* protocol error */
+ `Eshort /* truncated response */
+ `Egarbled /* syntax error */
+ `Eenc /* encoding error */
+ `Ewat /* unknown error */
+ ;;
+
+ type schema = union
+ `Http
+ `Https
+ ;;
+
+ type method = union
+ `Get
+ `Head
+ `Put
+ `Post
+ `Delete
+ `Trace
+ `Options
+ ;;
+
+ type encoding = union
+ `Length
+ `Chunked
+ `Compress
+ `Deflate
+ `Gzip
+ ;;
+
+ type req = struct
+ url : url#
+ hdrs : (byte[:], byte[:])[:]
+ err : std.option(err)
+ method : method
+ ;;
+
+ type resp = struct
+ status : int
+ hdrs : (byte[:], byte[:])[:]
+ len : std.size
+ err : std.option(err)
+ reason : byte[:]
+ body : byte[:]
+ enc : encoding
+ ;;
+
+;;
diff --git a/lib/http/url.myr b/lib/http/url.myr
new file mode 100644
index 0000000..be17d20
--- /dev/null
+++ b/lib/http/url.myr
@@ -0,0 +1,238 @@
+use std
+
+use "types"
+use "parse"
+
+pkg http =
+ const parseurl : (url : byte[:] -> std.result(url#, err))
+ 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 : 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) : url.params
+ std.sbfmt(sb, "{}{}={}", sep, k, v)
+ sep = '&'
+ ;;
+ ;;
+}
+
+const parseurl = {url
+ var schema, host, port, path, params
+
+ match parseschema(&url)
+ | `std.Ok s: schema = s
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ match parsehostname(&url)
+ | `std.Ok h: host = h
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ match parseport(&url)
+ | `std.Ok p: port = p
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ match parsepath(&url)
+ | `std.Ok p: path = p
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ match parseparams(&url)
+ | `std.Ok p: params = p
+ | `std.Err e: -> `std.Err e
+ ;;
+
+ /* todo: params */
+ -> `std.Ok std.mk([
+ .schema=schema,
+ .host=std.sldup(host),
+ .port=port,
+ .path=std.sldup(path),
+ .params=params,
+ ])
+}
+
+const urlfree = {url
+ std.slfree(url.host)
+ std.slfree(url.path)
+ std.free(url)
+}
+
+const parseschema = {url
+ if std.chomp(url, "http://")
+ -> `std.Ok `Http
+ elif std.chomp(url, "https://")
+ -> `std.Err `Eunsupp
+ else
+ -> `std.Err `Eproto
+ ;;
+}
+
+const parsehostname = {url
+ if std.chomp(url, "[")
+ -> ipv6hostname(url)
+ else
+ -> hostname(url)
+ ;;
+}
+
+const parsepath = {url
+ var len, p
+
+ if url#.len == 0
+ -> `std.Ok "/"
+ elif std.decode(url#) == '?'
+ -> `std.Ok "/"
+ elif std.decode(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.Err `Egarbled
+ ;;
+}
+
+const parseparams = {url
+ var kvp : byte[:][2]
+ var params
+
+ if url#.len == 0
+ -> `std.Ok [][:]
+ ;;
+
+ match std.decode(url#)
+ | '?': (_, url#) = std.strstep(url#)
+ | _: -> `std.Err `Egarbled
+ ;;
+
+ params = [][:]
+ for sp : std.bysplit(url#, "&")
+ if std.bstrsplit(kvp[:], sp, "=").len != 2
+ -> `std.Err `Egarbled
+ ;;
+ std.slpush(&params, (std.sldup(kvp[0]), std.sldup(kvp[1])))
+ ;;
+
+ -> `std.Ok params
+}
+
+const hostname = {url
+ var len, host
+
+ len = 0
+ for c : std.bychar(url#)
+ match c
+ | ':': break
+ | '/': break
+ | '?': break
+ | chr:
+ if ishostchar(chr)
+ len += std.charlen(chr)
+ else
+ -> `std.Err `Egarbled
+ ;;
+ ;;
+ ;;
+
+ host = url#[:len]
+ url# = url#[len:]
+ -> `std.Ok host
+}
+
+const ipv6hostname = {url -> std.result(byte[:], err)
+ var ip
+
+ match std.strfind(url#, "]")
+ | `std.None: -> `std.Err `Egarbled
+ | `std.Some idx:
+ ip = url#[:idx]
+ url# = url#[idx+1:]
+ match std.ip6parse(url#[:idx])
+ | `std.Some _: -> `std.Ok ip
+ | `std.None: -> `std.Err `Egarbled
+ ;;
+ ;;
+}
+
+const parseport = {url
+ if std.chomp(url, ":")
+ match parsenumber(url, 10)
+ | `std.None: -> `std.Err `Egarbled
+ | `std.Some n:
+ if n > 65535
+ -> `std.Err `Egarbled
+ else
+ -> `std.Ok (n : uint16)
+ ;;
+ ;;
+ else
+ -> `std.Ok 80
+ ;;
+}
+
+const ishostchar = {c
+ if !std.isascii(c)
+ -> false
+ else
+ -> std.isalnum(c) || unreserved(c) || subdelim(c)
+ ;;
+}
+
+const unreserved = {c
+ -> c == '.' || c == '-' || c == '_' || c == '~'
+}
+
+const subdelim = {c
+ -> c == '.' || c == '-' || c == '_' || c == '~' || c == '!' || \
+ c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || \
+ c == '*' || c == '+' || c == ',' || c == ';' || c == '='
+}