implement Handler; include "sys.m"; sys: Sys; sprint: import sys; include "arg.m"; arg: Arg; include "bufio.m"; include "string.m"; str: String; include "dial.m"; dial: Dial; include "registries.m"; regs: Registries; Registry: import regs; Attributes: import regs; include "http.m"; http: Http; HConn, Req, Rsp: import http; headers: Headers; HeaderMap: import headers; hostmap: list of (string, string); destaddr: string; init(m: Http, args: list of string) { sys = load Sys Sys->PATH; arg = load Arg Arg->PATH; str = load String String->PATH; dial = load Dial Dial->PATH; http = m; headers = http->headers; regs = load Registries Registries->PATH; arg->init(args); arg->setusage(sprint("%s [addr]", arg->progname())); if(arg->opt() != '\0') arg->usage(); args = arg->argv(); case len args { 0 => regs->init(); if((reg := Registry.new(nil)) == nil) fail("no registry"); readhmap(reg); spawn regwatch(reg, (sync := chan of int)); <-sync; 1 => destaddr = hd args; * => arg->usage(); } } lthost(a, b: (string, string)): int { if(a.t0 + a.t1 < b.t0 + b.t1) return 1; return 0; } insert(host: (string, string), hosts: list of (string, string)): list of (string, string) { if(hosts == nil || lthost(host, hd hosts)) return host :: hosts; return hd hosts :: insert(host, tl hosts); } diff(as, bs: list of (string, string)) { while(as != nil || bs != nil) { if(bs == nil || (as != nil && lthost(hd as, hd bs))) { a := hd as; as = tl as; sys->fprint(sys->fildes(2), "%s: - %s %s\n", arg->progname(), a.t1, a.t0); } else if(as == nil || (bs != nil && lthost(hd bs, hd as))) { b := hd bs; bs = tl bs; sys->fprint(sys->fildes(2), "%s: + %s %s\n", arg->progname(), b.t1, b.t0); } else { as = tl as; bs = tl bs; } } } readhmap(r: ref Registry) { map: list of (string, string); (svcs, err) := r.find(("resource", "http") :: ("hosts", "*") :: nil); if(err != nil) fail("find"); for(; svcs != nil; svcs = tl svcs) { svc := hd svcs; hosts := str->unquoted(svc.attrs.get("hosts")); for(; hosts != nil; hosts = tl hosts) map = insert((str->tolower(hd hosts), svc.addr), map); } diff(hostmap, map); hostmap = map; } regwatch(r: ref Registry, sync: chan of int) { sys->pctl(Sys->NEWFD, 0 :: 1 :: 2 :: nil); r.indexfd = nil; # without this the first call after a reload hangs filename := r.dir + "/event"; if((fd := sys->open(filename, Sys->OREAD)) == nil) fail(sprint("open %s: %r", filename)); sync <-= 69105; buf := array[1] of byte; n: int; while((n = sys->read(fd, buf, len buf)) > 0) readhmap(r); if(n < 0) fail(sprint("read %s: %r", filename)); } hostaddr(s: string): string { for(hs := hostmap; hs != nil; hs = tl hs) { (host, addr) := hd hs; if(host == nil) continue; if(host == s) return addr; } return nil; } fail(msg: string) { (fmsg, nil) := str->splitl(msg, ":"); sys->fprint(sys->fildes(2), "%s: %s\n", arg->progname(), msg); raise "fail:" + fmsg; } kill(pid: int) { name := sprint("/prog/%d/ctl", pid); fd := sys->open(name, Sys->OWRITE); if(fd == nil) return; sys->fprint(fd, "kill"); } toserver(pidch: chan of int, cfd, sfd: ref Sys->FD, h: ref HConn, host: string) { pidch <-= sys->pctl(0, nil); buf := array[Sys->ATOMICIO] of byte; while((n := sys->read(cfd, buf, len buf)) > 0) { if(sys->write(sfd, buf, n) != n) fail(sprint("toserver write %s: %r", host)); h.received += big n; } } toclient(sfd, cfd: ref Sys->FD, h: ref HConn, host: string) { buf := array[Sys->ATOMICIO] of byte; if((n := sys->read(sfd, buf, len buf)) < 0) fail(sprint("toclient read %s: %r", host)); if(sys->write(cfd, buf, n) != n) fail(sprint("toclient write %s: %r", host)); if(n == 0) return; h.sent += big n; # if this fails, the connection was probably hijacked, and the # default 200 OK will be used (rsp, nil) := Rsp.parsestatus(string buf); if(rsp != nil) { h.rsp.version = rsp.version; h.rsp.statuscode = rsp.statuscode; h.rsp.statusmsg = rsp.statusmsg; # don't clear h.rsp.hmap } while((n = sys->read(sfd, buf, len buf)) > 0) { if(sys->write(cfd, buf, n) != n) break; h.sent += big n; } } handle(h: ref HConn) { r := h.req; addr: string; if(r.url.host == nil) { h.err(505, "no host header"); return; } host := str->tolower(r.url.host); if((addr = destaddr) == nil && (addr = hostaddr(host)) == nil) { h.err(503, "registry lookup failed"); return; } sconn := dial->dial(addr, ""); if(sconn == nil) { h.err(503, "dial failed"); return; } sfd := sconn.dfd; (cfd, dbuf) := h.hijack(); # maybe put a Dial->Connection or Dial->Conninfo in h instead info := dial->netinfo(ref Dial->Connection(h.fd, nil, nil)); if(info == nil) { h.err(500, sprint("%s: netinfo: %r", arg->progname())); return; } h.req.hmap.addsep("X-Forwarded-For", info.rsys, ','); rbuf := array of byte r.tostring(h.urlstr, 1); if(sys->write(sfd, rbuf, len rbuf) != len rbuf) fail(sprint("toserver write %s: %r", host)); if(len dbuf > 0 && sys->write(sfd, dbuf, len dbuf) != len dbuf) fail(sprint("toserver write %s: %r", host)); h.received += big len rbuf + big len dbuf; pidch := chan of int; spawn toserver(pidch, cfd, sfd, h, host); pid := <-pidch; toclient(sfd, cfd, h, host); kill(pid); }