shithub: mc

ref: c66161595cd3e4638122777d550ab61dc100b973
dir: /lib/http/client.myr/

View raw version
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:
		iowrite(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)
	| `std.Err e:	goto shortread
	| `std.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])
			| `std.Err e:
				std.slfree(buf)
				-> `std.Err `Econn
			| `std.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)
	| `std.Err e:	r = `std.Err `Econn
	| `std.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)
}