diff --git a/lib/pill.hoon b/lib/pill.hoon index a52d5d17f2..c70c296073 100644 --- a/lib/pill.hoon +++ b/lib/pill.hoon @@ -14,6 +14,12 @@ :~ :: sys/zuse: standard library :: [%$ /zuse] + :: sys/vane/http-client: http client + :: + [%http-client /vane/http-client] + :: sys/vane/light: new web + :: + [%l /vane/light] :: sys/vane/ames: network :: [%a /vane/ames] @@ -38,9 +44,6 @@ :: sys/vane/jael: security :: [%j /vane/jael] - :: sys/vane/light: new web - :: - [%l /vane/light] == |= [=term =path] =/ pax (weld sys path) diff --git a/sys/vane/gall.hoon b/sys/vane/gall.hoon index 3ee0136621..8d2e24bd99 100644 --- a/sys/vane/gall.hoon +++ b/sys/vane/gall.hoon @@ -1,5 +1,6 @@ :: :: %gall, agent execution !? 163 +!: :::: |= pit/vase =, gall @@ -842,6 +843,7 @@ =^ tel vel (~(slot wa vel) 3 pec) :_(+>.$ [%& sto %give %diff `cage`[-.q.pec tel]]) :: + ++ ap-move-connect |= {sto/bone vax/vase} ^- {(each cove tang) _+>} @@ -863,7 +865,7 @@ :: TODO: Magic vase validation. I have no idea how malformed checking works. :: :_ +>.$ - [%& sto %give %http-response ((hard raw-http-response:light) q.vax)] + [%& sto %give %http-response ((hard http-event:http) q.vax)] :: ++ ap-move-hiss :: pass %hiss ~/ %hiss diff --git a/sys/vane/http-client.hoon b/sys/vane/http-client.hoon new file mode 100644 index 0000000000..8b2db6ffa2 --- /dev/null +++ b/sys/vane/http-client.hoon @@ -0,0 +1,316 @@ +!: +:: http-client +:: +|= pit=vase +=, http-client +:: +:: +:: internal data structures +:: +=> =~ +:: +:: internal data structures that won't go in zuse +:: +|% ++$ move + :: + $: :: duct: request identifier + :: + =duct + :: + :: + card=(wind note gift:able) + == +:: +note: private request from light to another vane +:: ++$ note _~ +:: +:: + + +-- +:: more structures +:: +|% ++$ axle + $: :: date: date at which light's state was updated to this data structure + :: + date=%~2019.2.8 + :: + :: + =state + == +:: +state:client: state relating to open outbound HTTP connections +:: ++$ state + $: :: next-id: monotonically increasing id number for the next connection + :: + next-id=@ud + :: connection-by-id: open connections to the + :: + connection-by-id=(map @ud [=duct =in-progress-http-request]) + :: outbound-duct: the duct to send outbound requests on + :: + outbound-duct=duct + == +:: +in-progress-http-request: state around an outbound http +:: ++$ in-progress-http-request + $: :: remaining-redirects: http limit of number of redirects before error + :: + remaining-redirects=@ud + :: remaining-retries: number of times to retry the request + :: + remaining-retries=@ud + :: response-header: the response headers from the %start packet + :: + :: We send the response headers with each %http-progress, so we must + :: save them. + :: + response-header=(unit response-header:http) + :: chunks: a list of partial results returned from unix + :: + :: This list of octs must be flopped before it is composed as the + :: final response, as we want to be able to quickly insert. + :: + chunks=(list octs) + :: bytes-read: the sum of the size of the :chunks + :: + bytes-read=@ud + :: expected-size: the expected content-length of the http request + :: + expected-size=(unit @ud) + == +-- +:: +|% +:: +combine-octs: combine multiple octs into one +:: +++ combine-octs + |= a=(list octs) + ^- octs + :- %+ roll a + |= [=octs sum=@ud] + (add sum p.octs) + (can 3 a) +:: +per-client-event: per-event client core +:: +++ per-client-event + |= [[our=@p eny=@ =duct now=@da scry=sley] =state] + |% + :: +request: makes an external web request + :: + ++ request + |= [=request:http =outbound-config] + ^- [(list move) ^state] + :: get the next id for this request + :: + =^ id next-id.state [next-id.state +(next-id.state)] + :: add a new open session + :: + =. connection-by-id.state + %+ ~(put by connection-by-id.state) id + =, outbound-config + [duct [redirects retries ~ ~ 0 ~]] + :: start the download + :: + :: the original eyre keeps track of the duct on %born and then sends a + :: %give on that duct. this seems like a weird inversion of + :: responsibility, where we should instead be doing a pass to unix. the + :: reason we need to manually build ids is because we aren't using the + :: built in duct system. + :: + :: email discussions make it sound like fixing that might be hard, so + :: maybe i should just live with the way it is now? + :: + :- [outbound-duct.state %give %request id request]~ + state + :: +receive: receives a response to an http-request we made + :: + :: TODO: Right now, we are not following redirect and not handling retries + :: correctly. We need to do this. + :: + ++ receive + |= [id=@ud =http-event:http] + ^- [(list move) ^state] + :: ensure that this is a valid receive + :: + ?~ connection=(~(get by connection-by-id.state) id) + ~& [%eyre-unknown-receive id] + [~ state] + :: + ?- -.http-event + %start + :: TODO: Handle redirects and retries here, before we start dispatching + :: back to the application. + :: + :: record data from the http response that only comes from %start + :: + =. connection-by-id.state + %+ ~(jab by connection-by-id.state) id + |= [duct=^duct =in-progress-http-request] + :: + =. expected-size.in-progress-http-request + ?~ str=(get-header:http 'content-length' headers.response-header.http-event) + ~ + :: + (rush u.str dum:ag) + :: + =. response-header.in-progress-http-request + `response-header:http-event + :: + [duct in-progress-http-request] + :: + ?: complete.http-event + (send-finished id data.http-event) + :: + (record-and-send-progress id data.http-event) + :: + %continue + ?: complete.http-event + (send-finished id data.http-event) + :: + (record-and-send-progress id data.http-event) + :: + %cancel + ~& [%eyre-received-cancel id] + [~ state] + == + :: +record-and-send-progress: save incoming data and send progress report + :: + ++ record-and-send-progress + |= [id=@ud data=(unit octs)] + ^- [(list move) ^state] + :: + =. connection-by-id.state + %+ ~(jab by connection-by-id.state) id + |= [duct=^duct =in-progress-http-request] + :: record the data chunk and size, if it exists + :: + =? chunks.in-progress-http-request + ?=(^ data) + [u.data chunks.in-progress-http-request] + =? bytes-read.in-progress-http-request + ?=(^ data) + (add bytes-read.in-progress-http-request p.u.data) + :: + [duct in-progress-http-request] + :: + =/ connection (~(got by connection-by-id.state) id) + :_ state + ^- (list move) + :_ ~ + :* duct.connection + %give + %progress + (need response-header.in-progress-http-request.connection) + bytes-read.in-progress-http-request.connection + expected-size.in-progress-http-request.connection + data + == + :: +send-finished: sends the %finished, cleans up the session state + :: + ++ send-finished + |= [id=@ud data=(unit octs)] + ^- [(list move) ^state] + :: + =/ connection (~(got by connection-by-id.state) id) + :: reassemble the octs that we've received into their final form + :: + =/ data=octs + %- combine-octs + %- flop + :: + ?~ data + chunks.in-progress-http-request.connection + [u.data chunks.in-progress-http-request.connection] + :: + =/ response-header=response-header:http + (need response-header.in-progress-http-request.connection) + :: + =/ mime=@t + ?~ mime-type=(get-header:http 'content-type' headers.response-header) + 'application/octet-stream' + u.mime-type + :- :~ :* duct.connection + %give + %finished + response-header + ?:(=(0 p.data) ~ `[mime data]) + == == + state(connection-by-id (~(del by connection-by-id.state) id)) + -- +-- +:: end the =~ +:: +. == +:: begin with a default +axle as a blank slate +:: +=| ax=axle +:: a vane is activated with current date, entropy, and a namespace function +:: +|= [our=ship now=@da eny=@uvJ scry-gate=sley] +:: allow jets to be registered within this core +:: +~% %http-client ..is ~ +|% +++ call + |= [=duct type=* wrapped-task=(hobo task:able)] + ^- [(list move) _light-gate] + :: + =/ task=task:able + ?. ?=(%soft -.wrapped-task) + wrapped-task + ~| [%p-wrapped-task p.wrapped-task] + ((hard task:able) p.wrapped-task) + :: + =/ event-args [[our eny duct now scry-gate] state.ax] + =/ client (per-client-event event-args) + ?- -.task + :: + %born + ~& %todo-http-client-born + :: TODO: reset the next-id for client state here. + :: + :: send requests on the duct passed in with born. + :: + =. outbound-duct.state.ax duct + [~ light-gate] + :: + %request + =^ moves state.ax (request:client +.task) + [moves light-gate] + :: + %cancel-request + ~& %todo-cancel-request + [~ light-gate] + :: + %receive + =^ moves state.ax (receive:client +.task) + [moves light-gate] + == +:: http-client issues no requests to other vanes +:: +++ take + |= [=wire =duct wrapped-sign=*] + ^- [(list move) _light-gate] + !! +:: +++ light-gate ..$ +:: +load: migrate old state to new state (called on vane reload) +:: +++ load + |= old=axle + ^+ ..^$ + :: + ~! %loading + ..^$(ax old) +:: +stay: produce current state +:: +++ stay `axle`ax +:: +scry: request a path in the urbit namespace +:: +++ scry + |= * + [~ ~] +-- diff --git a/sys/vane/light.hoon b/sys/vane/light.hoon index ca8ab9cca7..c4a4e4f7dd 100644 --- a/sys/vane/light.hoon +++ b/sys/vane/light.hoon @@ -115,7 +115,7 @@ :: We send the response headers with each %http-progress, so we must :: save them. :: - response-headers=(unit http-response-header) + response-headers=(unit response-header:http) :: chunks: a list of partial results returned from unix :: :: This list of octs must be flopped before it is composed as the @@ -166,12 +166,9 @@ :: inbound-request: the original request which caused this connection :: =inbound-request - :: code: the status code, if sent + :: response-header: set when we get our first %start :: - code=(unit @ud) - :: headers: the headers, if sent - :: - headers=(unit header-list) + response-header=(unit response-header:http) :: bytes-sent: the total bytes sent in response :: bytes-sent=@ud @@ -657,19 +654,6 @@ ?. =(i.prefix i.full) %.n $(prefix t.prefix, full t.full) -:: +get-header: returns the value for :header, if it exists in :header-list -:: -++ get-header - |= [header=@t =header-list] - ^- (unit @t) - :: - ?~ header-list - ~ - :: - ?: =(key.i.header-list header) - `value.i.header-list - :: - $(header-list t.header-list) :: +simplified-url-parser: returns [(each @if @t) (unit port=@ud)] :: ++ simplified-url-parser @@ -687,155 +671,6 @@ (easy ~) == == -:: +per-client-event: per-event client core -:: -++ per-client-event - |= [[our=@p eny=@ =duct now=@da scry=sley] state=state:client] - |% - :: +request: makes an external web request - :: - ++ request - |= [=http-request =outbound-config] - ^- [(list move) state:client] - :: get the next id for this request - :: - =^ id next-id.state [next-id.state +(next-id.state)] - :: add a new open session - :: - =. connection-by-id.state - %+ ~(put by connection-by-id.state) id - =, outbound-config - [duct [redirects retries ~ ~ 0 ~]] - :: start the download - :: - :: the original eyre keeps track of the duct on %born and then sends a - :: %give on that duct. this seems like a weird inversion of - :: responsibility, where we should instead be doing a pass to unix. the - :: reason we need to manually build ids is because we aren't using the - :: built in duct system. - :: - :: email discussions make it sound like fixing that might be hard, so - :: maybe i should just live with the way it is now? - :: - :- [outbound-duct.state %give %http-client %request id http-request]~ - state - :: +receive: receives a response to an http-request we made - :: - :: TODO: Right now, we are not following redirect and not handling retries - :: correctly. We need to do this. - :: - ++ receive - |= [id=@ud =raw-http-response] - ^- [(list move) state:client] - :: ensure that this is a valid receive - :: - ?~ connection=(~(get by connection-by-id.state) id) - ~& [%eyre-unknown-receive id] - [~ state] - :: - ?- -.raw-http-response - %start - :: TODO: Handle redirects and retries here, before we start dispatching - :: back to the application. - :: - :: record data from the http response that only comes from %start - :: - =. connection-by-id.state - %+ ~(jab by connection-by-id.state) id - |= [duct=^duct =in-progress-http-request:client] - :: - =. expected-size.in-progress-http-request - ?~ str=(get-header 'content-length' headers.raw-http-response) - ~ - :: - (rush u.str dum:ag) - :: - =. response-headers.in-progress-http-request - `[status-code headers]:raw-http-response - :: - [duct in-progress-http-request] - :: - ?: complete.raw-http-response - (send-finished id data.raw-http-response) - :: - (record-and-send-progress id data.raw-http-response) - :: - %continue - ?: complete.raw-http-response - (send-finished id data.raw-http-response) - :: - (record-and-send-progress id data.raw-http-response) - :: - %cancel - ~& [%eyre-received-cancel id] - [~ state] - == - :: +record-and-send-progress: save incoming data and send progress report - :: - ++ record-and-send-progress - |= [id=@ud data=(unit octs)] - ^- [(list move) state:client] - :: - =. connection-by-id.state - %+ ~(jab by connection-by-id.state) id - |= [duct=^duct =in-progress-http-request:client] - :: record the data chunk and size, if it exists - :: - =? chunks.in-progress-http-request - ?=(^ data) - [u.data chunks.in-progress-http-request] - =? bytes-read.in-progress-http-request - ?=(^ data) - (add bytes-read.in-progress-http-request p.u.data) - :: - [duct in-progress-http-request] - :: - =/ connection (~(got by connection-by-id.state) id) - :_ state - ^- (list move) - :_ ~ - :* duct.connection - %give - %http-client - %progress - (need response-headers.in-progress-http-request.connection) - bytes-read.in-progress-http-request.connection - expected-size.in-progress-http-request.connection - data - == - :: +send-finished: sends the %finished, cleans up the session state - :: - ++ send-finished - |= [id=@ud data=(unit octs)] - ^- [(list move) state:client] - :: - =/ connection (~(got by connection-by-id.state) id) - :: reassemble the octs that we've received into their final form - :: - =/ data=octs - %- combine-octs - %- flop - :: - ?~ data - chunks.in-progress-http-request.connection - [u.data chunks.in-progress-http-request.connection] - :: - =/ response-headers=http-response-header - (need response-headers.in-progress-http-request.connection) - :: - =/ mime=@t - ?~ mime-type=(get-header 'content-type' headers.response-headers) - 'application/octet-stream' - u.mime-type - :- :~ :* duct.connection - %give - %http-client - %finished - response-headers - ?:(=(0 p.data) ~ `[mime data]) - == == - state(connection-by-id (~(del by connection-by-id.state) id)) - -- :: +per-server-event: per-event server core :: ++ per-server-event @@ -846,17 +681,17 @@ :: +request: starts handling an inbound http request :: ++ request - |= [secure=? =address =http-request] + |= [secure=? =address =request:http] ^- [(list move) server-state] :: - =+ host=(get-header 'host' header-list.http-request) - =+ action=(get-action-for-binding host url.http-request) + =+ host=(get-header:http 'host' header-list.request) + =+ action=(get-action-for-binding host url.request) :: - =/ authenticated (request-is-logged-in:authentication http-request) + =/ authenticated (request-is-logged-in:authentication request) :: record that we started an asynchronous response :: =/ connection=outstanding-connection - [action [authenticated secure address http-request] ~ ~ 0] + [action [authenticated secure address request] ~ 0] =. connections.state (~(put by connections.state) duct connection) :: @@ -875,7 +710,7 @@ :: prelude with the arguments passed in. :: [%$ %noun !>([[now=now eny=eny bek=[our desk.generator.action [%da now]]] ~ ~])] - [%$ %noun !>([authenticated http-request])] + [%$ %noun !>([authenticated request])] :: %app :_ state @@ -895,14 +730,14 @@ == :: %authentication - (handle-request:authentication secure address http-request) + (handle-request:authentication secure address request) :: %channel - (handle-request:by-channel secure authenticated address http-request) + (handle-request:by-channel secure authenticated address request) :: %four-oh-four %^ return-static-data-on-duct 404 'text/html' - (file-not-found-page url.http-request) + (file-not-found-page url.request) == :: +cancel-request: handles a request being externally aborted :: @@ -958,7 +793,7 @@ :: %- handle-response :* %start - status-code=code + :- status-code=code ^= headers :~ ['content-type' content-type] ['content-length' (crip (format-ud-as-integer p.data))] @@ -977,32 +812,32 @@ :: +handle-request: handles an http request for the :: ++ handle-request - |= [secure=? =address =http-request] + |= [secure=? =address =request:http] ^- [(list move) server-state] :: :: if we received a simple get, just return the page :: - ?: =('GET' method.http-request) + ?: =('GET' method.request) :: parse the arguments out of request uri :: - =+ request-line=(parse-request-line url.http-request) + =+ request-line=(parse-request-line url.request) %^ return-static-data-on-duct 200 'text/html' - (login-page (get-header 'redirect' args.request-line)) + (login-page (get-header:http 'redirect' args.request-line)) :: if we are not a post, return an error :: - ?. =('POST' method.http-request) + ?. =('POST' method.request) (return-static-data-on-duct 400 'text/html' (login-page ~)) :: we are a post, and must process the body type as form data :: - ?~ body.http-request + ?~ body.request (return-static-data-on-duct 400 'text/html' (login-page ~)) :: =/ parsed=(unit (list [key=@t value=@t])) - (rush q.u.body.http-request yquy:de-purl:html) + (rush q.u.body.request yquy:de-purl:html) ?~ parsed (return-static-data-on-duct 400 'text/html' (login-page ~)) :: - ?~ password=(get-header 'password' u.parsed) + ?~ password=(get-header:http 'password' u.parsed) (return-static-data-on-duct 400 'text/html' (login-page ~)) :: check that the password is correct :: @@ -1026,13 +861,13 @@ "urbauth={}; Path=/; Max-Age=86400" :: =/ new-location=@t - ?~ redirect=(get-header 'redirect' u.parsed) + ?~ redirect=(get-header:http 'redirect' u.parsed) '/' u.redirect :: %- handle-response :* %start - status-code=307 + :- status-code=307 ^= headers :~ ['location' new-location] ['set-cookie' cookie-line] @@ -1042,18 +877,18 @@ == :: +request-is-logged-in: checks to see if the request is authenticated :: - :: We are considered logged in if this http-request has an urbauth + :: We are considered logged in if this request has an urbauth :: Cookie which is not expired. :: ++ request-is-logged-in - |= =http-request + |= =request:http ^- ? :: are there cookies passed with this request? :: :: TODO: In HTTP2, the client is allowed to put multiple 'Cookie' :: headers. :: - ?~ cookie-header=(get-header 'cookie' header-list.http-request) + ?~ cookie-header=(get-header:http 'cookie' header-list.request) %.n :: is the cookie line is valid? :: @@ -1061,7 +896,7 @@ %.n :: is there an urbauth cookie? :: - ?~ urbauth=(get-header 'urbauth' u.cookies) + ?~ urbauth=(get-header:http 'urbauth' u.cookies) %.n :: is this formatted like a valid session cookie? :: @@ -1098,7 +933,7 @@ :: +handle-request: handles an http request for the subscription system :: ++ handle-request - |= [secure=? authenticated=? =address =http-request] + |= [secure=? authenticated=? =address =request:http] ^- [(list move) server-state] :: if we're not authenticated error, but don't redirect. :: @@ -1110,16 +945,16 @@ :: TODO: Real 400 page. :: %^ return-static-data-on-duct 400 'text/html' - (internal-server-error authenticated url.http-request ~) + (internal-server-error authenticated url.request ~) :: parse out the path key the subscription is on :: - =+ request-line=(parse-request-line url.http-request) + =+ request-line=(parse-request-line url.request) ?. ?=([@t @t @t ~] site.request-line) ~& %bad-request-line :: url is not of the form '/~/channel/' :: %^ return-static-data-on-duct 400 'text/html' - (internal-server-error authenticated url.http-request ~) + (internal-server-error authenticated url.request ~) :: channel-id: unique channel id parsed out of url :: =+ channel-id=i.t.t.site.request-line @@ -1131,13 +966,13 @@ :: (return-static-data-on-duct 200 'application/javascript' channel-js) :: - ?: =('PUT' method.http-request) + ?: =('PUT' method.request) :: PUT methods starts/modifies a channel, and returns a result immediately :: - (on-put-request channel-id http-request) + (on-put-request channel-id request) :: - ?: =('GET' method.http-request) - (on-get-request channel-id http-request) + ?: =('GET' method.request) + (on-get-request channel-id request) :: ~& %session-not-a-put [~ state] @@ -1231,26 +1066,26 @@ :: client in text/event-stream format. :: ++ on-get-request - |= [channel-id=@t =http-request] + |= [channel-id=@t =request:http] ^- [(list move) server-state] :: if there's no channel-id, we must 404 :: ?~ maybe-channel=(~(get by session.channel-state.state) channel-id) %^ return-static-data-on-duct 404 'text/html' - (internal-server-error %.y url.http-request ~) + (internal-server-error %.y url.request ~) :: if there's already a duct listening to this channel, we must 400 :: ?: ?=([%| *] state.u.maybe-channel) %^ return-static-data-on-duct 400 'text/html' - (internal-server-error %.y url.http-request ~) + (internal-server-error %.y url.request ~) :: when opening an event-stream, we must cancel our timeout timer :: =. moves [(cancel-timeout-move channel-id p.state.u.maybe-channel) moves] - :: the http-request may include a 'Last-Event-Id' header + :: the request may include a 'Last-Event-Id' header :: =/ maybe-last-event-id=(unit @ud) - ?~ maybe-raw-header=(get-header 'Last-Event-ID' header-list.http-request) + ?~ maybe-raw-header=(get-header:http 'Last-Event-ID' header-list.request) ~ (rush u.maybe-raw-header dum:ag) :: flush events older than the passed in 'Last-Event-ID' @@ -1274,7 +1109,8 @@ :: =^ http-moves state %- handle-response - :* %start 200 + :* %start + :- 200 :~ ['content-type' 'text/event-stream'] ['cache-control' 'no-cache'] ['connection' 'keep-alive'] @@ -1312,32 +1148,32 @@ :: a set of commands in JSON format in the body of the message. :: ++ on-put-request - |= [channel-id=@t =http-request] + |= [channel-id=@t =request:http] ^- [(list move) server-state] :: error when there's no body :: - ?~ body.http-request + ?~ body.request ~& %no-body %^ return-static-data-on-duct 400 'text/html' - (internal-server-error %.y url.http-request ~) + (internal-server-error %.y url.request ~) :: if the incoming body isn't json, this is a bad request, 400. :: - ?~ maybe-json=(de-json:html q.u.body.http-request) + ?~ maybe-json=(de-json:html q.u.body.request) ~& %no-json %^ return-static-data-on-duct 400 'text/html' - (internal-server-error %.y url.http-request ~) + (internal-server-error %.y url.request ~) :: parse the json into an array of +channel-request items :: ?~ maybe-requests=(parse-channel-request u.maybe-json) ~& [%no-parse u.maybe-json] %^ return-static-data-on-duct 400 'text/html' - (internal-server-error %.y url.http-request ~) + (internal-server-error %.y url.request ~) :: while weird, the request list could be empty :: ?: =(~ u.maybe-requests) ~& %empty-list %^ return-static-data-on-duct 400 'text/html' - (internal-server-error %.y url.http-request ~) + (internal-server-error %.y url.request ~) :: check for the existence of the channel-id :: :: if we have no session, create a new one set to expire in @@ -1362,8 +1198,7 @@ =^ http-moves state %- handle-response :* %start - status-code=200 - headers=~ + [status-code=200 headers=~] data=~ complete=%.y == @@ -1499,8 +1334,10 @@ :: if a client is connected, send this event to them. :: =? moves ?=([%| *] state.channel) + ^- (list move) :_ moves - :^ p.state.channel %give %http-server + :+ p.state.channel %give + ^- gift:able:light :* %response %continue :: ^= data @@ -1569,10 +1406,10 @@ %- handle-response =/ result=mime ((hard mime) q.q.cage) :: - ^- raw-http-response + ^- http-event:http :* %start - 200 - ^- header-list + :- 200 + ^- header-list:http :~ ['content-type' (en-mite:mimes:html p.result)] ['content-length' (crip (format-ud-as-integer p.q.result))] == @@ -1587,7 +1424,7 @@ :: done with. :: ++ handle-response - |= =raw-http-response + |= =http-event:http ^- [(list move) server-state] :: verify that this is a valid response on the duct :: @@ -1597,10 +1434,10 @@ :: |^ ^- [(list move) server-state] :: - ?- -.raw-http-response + ?- -.http-event :: %start - ?^ code.u.connection-state + ?^ response-header.u.connection-state ~& [%http-multiple-start duct] error-connection :: @@ -1608,28 +1445,27 @@ %+ ~(jab by connections.state) duct |= connection=outstanding-connection %_ connection - code `status-code.raw-http-response - headers `headers.raw-http-response - bytes-sent ?~(data.raw-http-response 0 p.u.data.raw-http-response) + response-header `response-header.http-event + bytes-sent ?~(data.http-event 0 p.u.data.http-event) == :: - =? state complete.raw-http-response + =? state complete.http-event log-complete-request :: pass-response :: %continue - ?~ code.u.connection-state + ?~ response-header.u.connection-state ~& [%http-continue-without-start duct] error-connection :: =. connections.state %+ ~(jab by connections.state) duct |= connection=outstanding-connection - =+ size=?~(data.raw-http-response 0 p.u.data.raw-http-response) + =+ size=?~(data.http-event 0 p.u.data.http-event) connection(bytes-sent (add bytes-sent.connection size)) :: - =? state complete.raw-http-response + =? state complete.http-event log-complete-request :: pass-response @@ -1642,7 +1478,7 @@ :: ++ pass-response ^- [(list move) server-state] - [[duct %give %http-server %response raw-http-response]~ state] + [[duct %give %response http-event]~ state] :: ++ log-complete-request :: todo: log the complete request @@ -1663,7 +1499,7 @@ :: respond to outside with %error :: ^- [(list move) server-state] - [[duct %give %http-server %response %cancel ~]~ state] + [[duct %give %response %cancel ~]~ state] -- :: +add-binding: conditionally add a pairing between binding and action :: @@ -1676,7 +1512,7 @@ |- ^- [(list move) server-state] ?~ to-search - :- [duct %give %http-server %bound %.y binding]~ + :- [duct %give %bound %.y binding]~ =. bindings.state :: store in reverse alphabetical order so that longer paths are first :: @@ -1692,7 +1528,7 @@ state :: ?: =(binding binding.i.to-search) - :- [duct %give %http-server %bound %.n binding]~ + :- [duct %give %bound %.n binding]~ state :: $(to-search t.to-search) @@ -1796,11 +1632,9 @@ wrapped-task ~| [%p-wrapped-task p.wrapped-task] ((hard task:able) p.wrapped-task) + :: %init: tells us what our ship name is :: - ?- -.task - :: %init: tells us what our ship name is - :: - %init + ?: ?=(%init -.task) :: initial value for the login handler :: =. bindings.server-state.ax @@ -1808,9 +1642,8 @@ [[~ /~/channel] duct [%channel ~]] == [~ light-gate] - :: %born: new unix process - :: - %born + :: %born: new unix process + ?: ?=(%born -.task) :: ~& [%todo-handle-born p.task] :: TODO: reset the next-id for client state here. @@ -1843,72 +1676,72 @@ ;: weld :: hand back default configuration for now :: - [duct %give %http-server %set-config *http-config]~ + [duct %give %set-config *http-config]~ :: closed-connections == + :: all other commands operate on a per-server-event :: - :: - :: - %http-server - =/ event-args [[our eny duct now scry-gate] server-state.ax] - =/ server (per-server-event event-args) - ?- -.server-task.task - :: - :: %live: no idea what this is for - :: - %live - :: - ~! task - ~& [%todo-live server-task.task] - :: - [~ light-gate] - :: - %request - =^ moves server-state.ax (request:server +.server-task.task) - [moves light-gate] - :: - %cancel-request - =^ moves server-state.ax cancel-request:server - [moves light-gate] - :: - %connect - =^ moves server-state.ax - %+ add-binding:server binding.server-task.task - [%app app.server-task.task] - [moves light-gate] - :: - %serve - =^ moves server-state.ax - %+ add-binding:server binding.server-task.task - [%gen generator.server-task.task] - [moves light-gate] - :: - %disconnect - =. server-state.ax (remove-binding:server binding.server-task.task) - [~ light-gate] - == + =/ event-args [[our eny duct now scry-gate] server-state.ax] + =/ server (per-server-event event-args) + ?- -.task :: + :: %live: no idea what this is for :: - :: - %http-client - =/ event-args [[our eny duct now scry-gate] client-state.ax] - =/ client (per-client-event event-args) - ?- -.client-task.task + %live :: - %request - =^ moves client-state.ax (request:client +.client-task.task) - [moves light-gate] + ~& [%todo-live task] :: - %cancel-request - ~& %todo-cancel-request - [~ light-gate] - :: - %receive - =^ moves client-state.ax (receive:client +.client-task.task) - [moves light-gate] - == + [~ light-gate] + :: + %request + =^ moves server-state.ax (request:server +.task) + [moves light-gate] + :: + %cancel-request + =^ moves server-state.ax cancel-request:server + [moves light-gate] + :: + %connect + =^ moves server-state.ax + %+ add-binding:server binding.task + [%app app.task] + [moves light-gate] + :: + %serve + =^ moves server-state.ax + %+ add-binding:server binding.task + [%gen generator.task] + [moves light-gate] + :: + %disconnect + =. server-state.ax (remove-binding:server binding.task) + [~ light-gate] == + :: :: + :: :: + :: :: + :: %http-client + :: :: TODO: Move me. + :: :: + :: =/ event-args [[our eny duct now scry-gate] client-state.ax] + :: [~ light-gate] + :: =/ client (per-client-event event-args) + :: ?- -.client-task.task + :: :: + :: %request + :: =^ moves client-state.ax (request:client +.client-task.task) + :: [moves light-gate] + :: :: + :: %cancel-request + :: ~& %todo-cancel-request + :: [~ light-gate] + :: :: + :: %receive + :: =^ moves client-state.ax (receive:client +.client-task.task) + :: [moves light-gate] + :: == + ::== :: ++ take |= [=wire =duct wrapped-sign=(hypo sign)] @@ -1940,7 +1773,7 @@ :: =/ event-args [[our eny duct now scry-gate] server-state.ax] =/ handle-response handle-response:(per-server-event event-args) - =^ moves server-state.ax (handle-response raw-http-response.p.sign) + =^ moves server-state.ax (handle-response http-event.p.sign) [moves light-gate] :: ++ run-build diff --git a/sys/zuse.hoon b/sys/zuse.hoon index e4858b0d6c..ceb7dd4911 100644 --- a/sys/zuse.hoon +++ b/sys/zuse.hoon @@ -75,8 +75,6 @@ :: :: TODO: Rename to +mime once the current +mime and +mite are gone. The :: -+$ mime-data - [type=@t data=octs] ++ octs {p/@ud q/@t} :: octet-stream ++ sock {p/ship q/ship} :: outgoing [our his] ::+| @@ -183,6 +181,113 @@ == -- :: :::: +:::: ++http :: + :: :::: +:: http: shared representations of http concepts +:: +++ http ^? + |% + :: +header-list: an ordered list of http headers + :: + +$ header-list + (list [key=@t value=@t]) + :: +method: exhaustive list of http verbs + :: + +$ method + $? %'CONNECT' + %'DELETE' + %'GET' + %'HEAD' + %'OPTIONS' + %'POST' + %'PUT' + %'TRACE' + == + :: +request: a single http request + :: + +$ request + $: :: method: http method + :: + method=method + :: url: the url requested + :: + :: The url is not escaped. There is no escape. + :: + url=@t + :: header-list: headers to pass with this request + :: + =header-list + :: body: optionally, data to send with this request + :: + body=(unit octs) + == + :: +response-header: the status code and header list on an http request + :: + :: We separate these away from the body data because we may not wait for + :: the entire body before we send a %progress to the caller. + :: + +$ response-header + $: :: status: http status code + :: + status-code=@ud + :: headers: http headers + :: + headers=header-list + == + :: +http-event: packetized http + :: + :: Urbit treats Earth's HTTP servers as pipes, where Urbit sends or + :: receives one or more %http-events. The first of these will always be a + :: %start or an %error, and the last will always be %cancel or will have + :: :complete set to %.y to finish the connection. + :: + :: Calculation of control headers such as 'Content-Length' or + :: 'Transfer-Encoding' should be performed at a higher level; this structure + :: is merely for what gets sent to or received from Earth. + :: + +$ http-event + $% :: %start: the first packet in a response + :: + $: %start + :: response-header: first event information + :: + =response-header + :: data: data to pass to the pipe + :: + data=(unit octs) + :: whether this completes the request + :: + complete=? + == + :: %continue: every subsequent packet + :: + $: %continue + :: data: data to pass to the pipe + :: + data=(unit octs) + :: complete: whether this completes the request + :: + complete=? + == + :: %cancel: represents unsuccessful termination + :: + [%cancel ~] + == + :: +get-header: returns the value for :header, if it exists in :header-list + :: + ++ get-header + |= [header=@t =header-list] + ^- (unit @t) + :: + ?~ header-list + ~ + :: + ?: =(key.i.header-list header) + `value.i.header-list + :: + $(header-list t.header-list) + -- +:: :::: :::: ++ames :: (1a) network :: :::: ++ ames ^? @@ -1711,7 +1816,7 @@ {$diff p/cage} :: subscription output {$quit ~} :: close subscription {$reap p/(unit tang)} :: peer result - [%http-response =raw-http-response:light] :: serve http result + [%http-response =http-event:http] :: serve http result == :: ++ culm :: config action $% {$load p/scup} :: load+reload @@ -1987,47 +2092,19 @@ -- :: rights -- :: jael :: -:::: - :: -++ light ^? +++ http-client ^? |% - +| %vane-interface ++ able |% - :: +gift: %light responses + :: +gift: effects the client can emit :: ++ gift - $% [%http-server server-gift] - [%http-client client-gift] - == - :: +server-gift: effects the server can emit - :: - ++ server-gift - $% :: set-config: configures the external http server - :: - :: TODO: We need to actually return a (map (unit @t) http-config) - :: so we can apply configurations on a per-site basis - :: - [%set-config =http-config] - :: response: response to an event from earth - :: - [%response =raw-http-response] - :: response to a %connect or %serve - :: - :: :accepted is whether :binding was valid. Duplicate bindings are - :: not allowed. - :: - [%bound accepted=? =binding] - == - :: +client-gift: effects the client can emit - :: - ++ client-gift $% :: %request: outbound http-request to earth :: :: TODO: id is sort of wrong for this interface; the duct should :: be enough to identify which request we're talking about? :: - [%request id=@ud request=http-request] + [%request id=@ud request=request:http] :: %cancel-request: tell earth to cancel a previous %request :: [%cancel-request id=@ud] @@ -2039,7 +2116,7 @@ :: In case of a redirect chain, this is the target of the :: final redirect. :: - =http-response-header + =response-header:http :: bytes-read: bytes fetched so far :: bytes-read=@ud @@ -2052,7 +2129,70 @@ == :: final response of a download, parsed as mime-data if successful :: - [%finished =http-response-header full-file=(unit mime-data)] + [%finished =response-header:http full-file=(unit mime-data)] + == + :: + ++ task + $% :: system started up; reset open connections + :: + [%born ~] + :: fetches a remote resource + :: + [%request =request:http =outbound-config] + :: cancels a previous fetch + :: + [%cancel-request ~] + :: receives http data from outside + :: + [%receive id=@ud =http-event:http] + == + -- + :: mime-data: externally received but unvalidated mimed data + :: + +$ mime-data + [type=@t data=octs] + :: +outbound-config: configuration for outbound http requests + :: + +$ outbound-config + $: :: number of times to follow a 300 redirect before erroring + :: + :: Common values for this will be 3 (the limit most browsers use), 5 + :: (the limit recommended by the http standard), or 0 (let the + :: requester deal with 300 redirects). + :: + redirects=_5 + :: number of times to retry before failing + :: + :: When we retry, we'll automatically try to use the 'Range' header + :: to resume the download where we left off if we have the + :: 'Accept-Range: bytes' in the original response. + :: + retries=_3 + == + -- +:: +:::: + :: +++ light ^? + |% + ++ able + |% + ++ gift + $% :: set-config: configures the external http server + :: + :: TODO: We need to actually return a (map (unit @t) http-config) + :: so we can apply configurations on a per-site basis + :: + [%set-config =http-config] + :: response: response to an event from earth + :: + [%response =http-event:http] + :: response to a %connect or %serve + :: + :: :accepted is whether :binding was valid. Duplicate bindings are + :: not allowed. + :: + [%bound accepted=? =binding] == :: ++ task @@ -2064,21 +2204,12 @@ :: new unix process :: [%born p=(list host)] - :: task for the http server - :: - [%http-server =server-task] - :: task for the http client - :: - [%http-client =client-task] - == - :: - ++ server-task - $% :: set http ports (?) + :: set http ports (?) :: [%live p=@ud q=(unit @ud)] :: starts handling an inbound http request :: - [%request secure=? =address =http-request] + [%request secure=? =address =request:http] :: cancels a previous request :: [%cancel-request ~] @@ -2096,20 +2227,7 @@ [%disconnect =binding] == :: - ++ client-task - $% :: fetches a remote resource - :: - [%request =http-request =outbound-config] - :: cancels a previous fetch - :: - [%cancel-request ~] - :: receives http data from outside - :: - [%receive id=@ud =raw-http-response] - == -- - :: - +| %bindings :: +binding: A rule to match a path. :: :: A +binding is a system unique mapping for a path to match. A +binding @@ -2146,9 +2264,6 @@ :: args=* == - :: %config: http configuration - :: - +| %config :: +host: http host :: +$ host @@ -2171,115 +2286,6 @@ :: redirect=? == - :: +outbound-config: configuration for outbound http requests - :: - +$ outbound-config - $: :: number of times to follow a 300 redirect before erroring - :: - :: Common values for this will be 3 (the limit most browsers use), 5 - :: (the limit recommended by the http standard), or 0 (let the - :: requester deal with 300 redirects). - :: - redirects=_5 - :: number of times to retry before failing - :: - :: When we retry, we'll automatically try to use the 'Range' header - :: to resume the download where we left off if we have the - :: 'Accept-Range: bytes' in the original response. - :: - retries=_3 - == - :: - +| %http - :: +header-list: an ordered list of http headers - :: - +$ header-list - (list [key=@t value=@t]) - :: +http-method: exhaustive list of http verbs - :: - +$ http-method - $? %'CONNECT' - %'DELETE' - %'GET' - %'HEAD' - %'OPTIONS' - %'POST' - %'PUT' - %'TRACE' - == - :: +http-request: a single http-request - :: - +$ http-request - $: :: http-method: - :: - method=http-method - :: url: the url requested - :: - :: The url is not escaped. There is no escape. - :: - url=@t - :: header-list: headers to pass with this request - :: - =header-list - :: body: optionally, data to send with this request - :: - body=(unit octs) - == - :: +http-response: the status code and header list on an http request - :: - :: We separate these away from the body data because we may not wait for - :: the entire body before we send a %progress to the caller. - :: - +$ http-response-header - $: :: status: http status code - :: - status-code=@ud - :: headers: http headers - :: - headers=header-list - == - :: +raw-http-response: http-response to sent to earth - :: - :: Urbit treats Earth's HTTP servers as pipes, where Urbit sends one or - :: more %http-response replies on the wire. The first of these will - :: always be a %start or an %error, and the last will always be %error - :: or will have :complete set to %.y to finish the connection. - :: - :: Calculation of control headers such as 'Content-Length' or - :: 'Transfer-Encoding' should be performed at a higher level; this structure - :: is merely for what gets sent to Earth. - :: - +$ raw-http-response - $% :: %start: the first packet in a response - :: - $: %start - :: status: http status code - :: - status-code=@ud - :: headers: http headers - :: - headers=header-list - :: data: data to pass to the pipe - :: - data=(unit octs) - :: whether this completes the request - :: - complete=? - == - :: %continue: every subsequent packet - :: - $: %continue - :: data: data to pass to the pipe - :: - data=(unit octs) - :: complete: whether this completes the request - :: - complete=? - == - :: %cancel: whether the connection should terminate unsuccessfully - :: - [%cancel ~] - == :: +address: client IP address :: +$ address @@ -2299,9 +2305,9 @@ :: address: the source address of this request :: =address - :: http-request: the http-request itself + :: request: the http-request itself :: - =http-request + =request:http == -- :: :::: diff --git a/tests/sys/vane/http-client.hoon b/tests/sys/vane/http-client.hoon new file mode 100644 index 0000000000..b93677591a --- /dev/null +++ b/tests/sys/vane/http-client.hoon @@ -0,0 +1,283 @@ +/+ *test +:: +/= http-client-raw /: /===/sys/vane/http-client /!noun/ +:: +!: +:: +=/ test-pit=vase !>(..zuse) +=/ http-client-gate (http-client-raw test-pit) +:: +|% +:: +test-client-request-basic: tests a single request, single reply style http request +:: +++ test-client-request-basic + :: send a %born event to use /initial-born-duct for requests + :: + =^ results1 http-client-gate + %- http-client-call :* + http-client-gate + now=~1111.1.1 + scry=*sley + call-args=[duct=~[/initial-born-duct] ~ [%born ~]] + expected-moves=~ + == + :: + =/ request=request:http + :* %'GET' + 'http://www.example.com' + ~ + ~ + == + :: opens the http channel + :: + =^ results2 http-client-gate + %- http-client-call :* + http-client-gate + now=(add ~1111.1.1 ~s1) + scry=*sley + ^= call-args + :* duct=~[/http-get-request] ~ + %request + request + *outbound-config:http-client + == + ^= expected-moves + ^- (list move:http-client-gate) + :~ :* duct=~[/initial-born-duct] + %give + %request + id=0 + method=%'GET' + url='http://www.example.com' + ~ + ~ + == == == + :: returns the entire payload in one response + :: + =^ results3 http-client-gate + %- http-client-call :* + http-client-gate + now=(add ~1111.1.1 ~s2) + scry=*sley + ^= call-args + :+ duct=~[/initial-born-duct] ~ + ^- task:able:http-client + :* %receive + id=0 + ^- http-event:http + :* %start + :- 200 + :~ ['content-type' 'text/html'] + ['content-length' '34'] + == + :: + :- ~ + %- as-octs:mimes:html + ''' + Response + ''' + :: + complete=%.y + == == + ^= expected-moves + ^- (list move:http-client-gate) + :~ :* duct=~[/http-get-request] + %give + %finished + :: + :- 200 + :~ ['content-type' 'text/html'] + ['content-length' '34'] + == + :: + :- ~ + :- 'text/html' + %- as-octs:mimes:html + ''' + Response + ''' + == == == + :: + ;: weld + results1 + results2 + results3 + == +:: +test-client-request-multiple-cards: tests when complete=%.n +:: +++ test-client-request-multiple-cards + :: send a %born event to use /initial-born-duct for requests + :: + =^ results1 http-client-gate + %- http-client-call :* + http-client-gate + now=~1111.1.1 + scry=*sley + call-args=[duct=~[/initial-born-duct] ~ [%born ~]] + expected-moves=~ + == + :: + =/ request=request:http + :* %'GET' + 'http://www.example.com' + ~ + ~ + == + :: opens the http channel + :: + =^ results2 http-client-gate + %- http-client-call :* + http-client-gate + now=(add ~1111.1.1 ~s1) + scry=*sley + ^= call-args + :* duct=~[/http-get-request] ~ + %request + request + *outbound-config:http-client + == + ^= expected-moves + ^- (list move:http-client-gate) + :~ :* duct=~[/initial-born-duct] + %give + %request + id=0 + method=%'GET' + url='http://www.example.com' + ~ + ~ + == == == + :: returns the first 1/3 of the payload in the first response + :: + =^ results3 http-client-gate + %- http-client-call :* + http-client-gate + now=(add ~1111.1.1 ~s2) + scry=*sley + ^= call-args + :+ duct=~[/initial-born-duct] ~ + ^- task:able:http-client + :* %receive + id=0 + ^- http-event:http + :* %start + :- 200 + :~ ['content-type' 'text/html'] + ['content-length' '34'] + == + [~ (as-octs:mimes:html '')] + complete=%.n + == == + ^= expected-moves + ^- (list move:http-client-gate) + :~ :* duct=~[/http-get-request] + %give + %progress + :: + :- 200 + :~ ['content-type' 'text/html'] + ['content-length' '34'] + == + :: + bytes-read=12 + expected-size=`34 + [~ (as-octs:mimes:html '')] + == == == + :: returns the second 1/3 of the payload + :: + =^ results4 http-client-gate + %- http-client-call :* + http-client-gate + now=(add ~1111.1.1 ~s3) + scry=*sley + ^= call-args + :+ duct=~[/initial-born-duct] ~ + ^- task:able:http-client + :* %receive + id=0 + ^- http-event:http + :* %continue + [~ (as-octs:mimes:html 'Response')] + complete=%.n + == == + ^= expected-moves + ^- (list move:http-client-gate) + :~ :* duct=~[/http-get-request] + %give + %progress + :: + :- 200 + :~ ['content-type' 'text/html'] + ['content-length' '34'] + == + :: + bytes-read=20 + expected-size=`34 + [~ (as-octs:mimes:html 'Response')] + == == == + :: returns the last part + :: + =^ results5 http-client-gate + %- http-client-call :* + http-client-gate + now=(add ~1111.1.1 ~s4) + scry=*sley + ^= call-args + :+ duct=~[/initial-born-duct] ~ + ^- task:able:http-client + :* %receive + id=0 + ^- http-event:http + :* %continue + [~ (as-octs:mimes:html '')] + complete=%.y + == == + ^= expected-moves + ^- (list move:http-client-gate) + :~ :* duct=~[/http-get-request] + %give + %finished + :: + :- 200 + :~ ['content-type' 'text/html'] + ['content-length' '34'] + == + :: + :- ~ + :- 'text/html' + %- as-octs:mimes:html + ''' + Response + ''' + == == == + :: + ;: weld + results1 + results2 + results3 + results4 + results5 + == +:: +++ http-client-call + |= $: http-client-gate=_http-client-gate + now=@da + scry=sley + call-args=[=duct type=* wrapped-task=(hobo task:able:http-client)] + expected-moves=(list move:http-client-gate) + == + ^- [tang _http-client-gate] + :: + =/ http-client-core + (http-client-gate our=~nul now=now eny=`@uvJ`0xdead.beef scry=scry) + :: + =^ moves http-client-gate (call:http-client-core call-args) + :: + =/ output=tang + %+ expect-eq + !> expected-moves + !> moves + :: + [output http-client-gate] +-- + diff --git a/tests/sys/vane/light.hoon b/tests/sys/vane/light.hoon index 5d98095b2f..5eb8284655 100644 --- a/tests/sys/vane/light.hoon +++ b/tests/sys/vane/light.hoon @@ -37,8 +37,8 @@ light-gate now=~1111.1.2 scry=*sley - call-args=[duct=~[/app1] ~ [%http-server %connect [~ /] %app1]] - expected-moves=[duct=~[/app1] %give %http-server %bound %.y [~ /]]~ + call-args=[duct=~[/app1] ~ [%connect [~ /] %app1]] + expected-moves=[duct=~[/app1] %give %bound %.y [~ /]]~ == :: app2 tries to bind to the same path and fails :: @@ -47,8 +47,8 @@ light-gate now=~1111.1.3 scry=*sley - call-args=[duct=~[/app2] ~ [%http-server %connect [~ /] %app2]] - expected-moves=[duct=~[/app2] %give %http-server %bound %.n [~ /]]~ + call-args=[duct=~[/app2] ~ [%connect [~ /] %app2]] + expected-moves=[duct=~[/app2] %give %bound %.n [~ /]]~ == :: ;: weld @@ -74,8 +74,8 @@ light-gate now=~1111.1.2 scry=*sley - call-args=[duct=~[/app1] ~ [%http-server %connect [~ /] %app1]] - expected-moves=[duct=~[/app1] %give %http-server %bound %.y [~ /]]~ + call-args=[duct=~[/app1] ~ [%connect [~ /] %app1]] + expected-moves=[duct=~[/app1] %give %bound %.y [~ /]]~ == :: app1 unbinds :: @@ -84,7 +84,7 @@ light-gate now=~1111.1.3 scry=*sley - call-args=[duct=~[/app1] ~ [%http-server %disconnect [~ /]]] + call-args=[duct=~[/app1] ~ [%disconnect [~ /]]] expected-moves=~ == :: app2 binds successfully @@ -94,8 +94,8 @@ light-gate now=~1111.1.4 scry=*sley - call-args=[duct=~[/app2] ~ [%http-server %connect [~ /] %app2]] - expected-moves=[duct=~[/app2] %give %http-server %bound %.y [~ /]]~ + call-args=[duct=~[/app2] ~ [%connect [~ /] %app2]] + expected-moves=[duct=~[/app2] %give %bound %.y [~ /]]~ == :: ;: weld @@ -122,8 +122,8 @@ light-gate now=~1111.1.2 scry=*sley - call-args=[duct=~[/app1] ~ [%http-server %connect [~ /] %app1]] - expected-moves=[duct=~[/app1] %give %http-server %bound %.y [~ /]]~ + call-args=[duct=~[/app1] ~ [%connect [~ /] %app1]] + expected-moves=[duct=~[/app1] %give %bound %.y [~ /]]~ == :: app2 tries to steal the binding by disconnecting the path :: @@ -132,7 +132,7 @@ light-gate now=~1111.1.3 scry=*sley - call-args=[duct=~[/app2] ~ [%http-server %disconnect [~ /]]] + call-args=[duct=~[/app2] ~ [%disconnect [~ /]]] expected-moves=~ == :: app2 doesn't bind successfully because it couldn't remove app1's binding @@ -142,8 +142,8 @@ light-gate now=~1111.1.4 scry=*sley - call-args=[duct=~[/app2] ~ [%http-server %connect [~ /] %app2]] - expected-moves=[duct=~[/app2] %give %http-server %bound %.n [~ /]]~ + call-args=[duct=~[/app2] ~ [%connect [~ /] %app2]] + expected-moves=[duct=~[/app2] %give %bound %.n [~ /]]~ == :: ;: weld @@ -173,7 +173,6 @@ scry=*sley ^= call-args :* duct=~[/http-blah] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -183,10 +182,9 @@ ^- (list move:light-gate) :~ :* duct=~[/http-blah] %give - %http-server %response %start - 404 + :- 404 :~ ['content-type' 'text/html'] ['content-length' '153'] == @@ -217,8 +215,8 @@ light-gate now=~1111.1.2 scry=*sley - call-args=[duct=~[/app1] ~ [%http-server %connect [~ /] %app1]] - expected-moves=[duct=~[/app1] %give %http-server %bound %.y [~ /]]~ + call-args=[duct=~[/app1] ~ [%connect [~ /] %app1]] + expected-moves=[duct=~[/app1] %give %bound %.y [~ /]]~ == :: outside requests a path that app1 has bound to :: @@ -229,7 +227,6 @@ scry=*sley ^= call-args :* duct=~[/http-blah] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -270,15 +267,15 @@ ^- (hypo sign:light-gate) :- *type :* %g %unto %http-response - %start 200 - ['content-type' 'text/html']~ + %start + [200 ['content-type' 'text/html']~] [~ (as-octs:mimes:html 'Hiya!')] %.y == == ^= expected-move - :~ :* duct=~[/http-blah] %give %http-server %response - [%start 200 ['content-type' 'text/html']~ `[5 'Hiya!'] %.y] + :~ :* duct=~[/http-blah] %give %response + [%start [200 ['content-type' 'text/html']~] `[5 'Hiya!'] %.y] == == == :: ;: weld @@ -305,8 +302,8 @@ light-gate now=~1111.1.2 scry=*sley - call-args=[duct=~[/app1] ~ [%http-server %connect [~ /] %app1]] - expected-moves=[duct=~[/app1] %give %http-server %bound %.y [~ /]]~ + call-args=[duct=~[/app1] ~ [%connect [~ /] %app1]] + expected-moves=[duct=~[/app1] %give %bound %.y [~ /]]~ == :: outside requests a path that app1 has bound to :: @@ -317,7 +314,6 @@ scry=*sley ^= call-args :* duct=~[/http-blah] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -357,14 +353,15 @@ :* wire=/run-app/app1 duct=~[/http-blah] ^- (hypo sign:light-gate) :- *type :* %g %unto %http-response - %start 200 ['content-type' 'text/html']~ + %start + [200 ['content-type' 'text/html']~] [~ (as-octs:mimes:html 'Hi')] %.n == == ^= expected-move - :~ :* duct=~[/http-blah] %give %http-server %response - [%start 200 ['content-type' 'text/html']~ `[2 'Hi'] %.n] + :~ :* duct=~[/http-blah] %give %response + [%start [200 ['content-type' 'text/html']~] `[2 'Hi'] %.n] == == == :: theoretical outside response :: @@ -381,7 +378,7 @@ == == ^= expected-move - :~ :* duct=~[/http-blah] %give %http-server %response + :~ :* duct=~[/http-blah] %give %response [%continue `[3 'ya!'] %.y] == == == :: @@ -412,8 +409,8 @@ light-gate now=~1111.1.2 scry=*sley - call-args=[duct=~[/app1] ~ [%http-server %connect [~ /'~landscape'] %app1]] - expected-moves=[duct=~[/app1] %give %http-server %bound %.y [~ /'~landscape']]~ + call-args=[duct=~[/app1] ~ [%connect [~ /'~landscape'] %app1]] + expected-moves=[duct=~[/app1] %give %bound %.y [~ /'~landscape']]~ == :: outside requests a path that app1 has bound to :: @@ -424,7 +421,6 @@ scry=*sley ^= call-args :* duct=~[/http-blah] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -464,12 +460,12 @@ :* wire=/run-app/app1 duct=~[/http-blah] ^- (hypo sign:light-gate) :- *type :* %g %unto %http-response - [%start 307 ['location' '/~/login?redirect=/~landscape/inner-path']~ ~ %.y] + [%start [307 ['location' '/~/login?redirect=/~landscape/inner-path']~] ~ %.y] == == ^= expected-move - :~ :* duct=~[/http-blah] %give %http-server %response - [%start 307 ['location' '/~/login?redirect=/~landscape/inner-path']~ ~ %.y] + :~ :* duct=~[/http-blah] %give %response + [%start [307 ['location' '/~/login?redirect=/~landscape/inner-path']~] ~ %.y] == == == :: the browser then fetches the login page :: @@ -489,7 +485,6 @@ ^= call-args ^- [=duct type=* wrapped-task=(hobo task:able:light-gate)] :* duct=~[/http-blah] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -557,8 +552,8 @@ light-gate now=~1111.1.2 scry=*sley - call-args=[duct=~[/gen1] ~ [%http-server %serve [~ /] [%home /gen/handler/hoon ~]]] - expected-moves=[duct=~[/gen1] %give %http-server %bound %.y [~ /]]~ + call-args=[duct=~[/gen1] ~ [%serve [~ /] [%home /gen/handler/hoon ~]]] + expected-moves=[duct=~[/gen1] %give %bound %.y [~ /]]~ == :: outside requests a path that app1 has bound to :: @@ -569,7 +564,6 @@ scry=*sley ^= call-args :* duct=~[/http-blah] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -626,9 +620,9 @@ [%cast %mime !>([['text' 'plain' ~] (as-octs:mimes:html 'one two three')])] == ^= expected-move - :~ :* duct=~[/http-blah] %give %http-server %response + :~ :* duct=~[/http-blah] %give %response :* %start - 200 + :- 200 :~ ['content-type' 'text/plain'] ['content-length' '13'] == @@ -752,7 +746,7 @@ scry=*sley ^= call-args :* duct=~[/http-blah] ~ - %http-server %request + %request %.n [%ipv4 .192.168.1.1] [%'PUT' '/~/channel/1234567890abcdef' ~ ~] @@ -761,10 +755,9 @@ ^- (list move:light-gate) :~ :* duct=~[/http-blah] %give - %http-server %response %start - 400 + :- 400 :~ ['content-type' 'text/html'] ['content-length' '206'] == @@ -876,7 +869,6 @@ scry=*sley ^= call-args :* duct=~[/http-get-open] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -889,10 +881,9 @@ ^- (list move:light-gate) :~ :* duct=~[/http-get-open] %give - %http-server %response %start - 200 + :- 200 :~ ['content-type' 'text/event-stream'] ['cache-control' 'no-cache'] ['connection' 'keep-alive'] @@ -928,7 +919,7 @@ light-gate now=(add ~1111.1.2 ~m4) scry=*sley - call-args=[duct=~[/http-get-open] ~ %http-server %cancel-request ~] + call-args=[duct=~[/http-get-open] ~ %cancel-request ~] ^= expected-moves ^- (list move:light-gate) :: closing the channel restarts the timeout timer @@ -966,7 +957,6 @@ scry=*sley ^= call-args :* duct=~[/http-put-request] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -1001,7 +991,7 @@ card.i.moves :: %+ expect-eq - !> [~[/http-put-request] %give %http-server %response %start 200 ~ ~ %.y] + !> [~[/http-put-request] %give %response %start [200 ~] ~ %.y] !> i.t.moves :: %+ expect-eq @@ -1079,7 +1069,6 @@ scry=*sley ^= call-args :* duct=~[/http-get-open] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -1092,10 +1081,9 @@ ^- (list move:light-gate) :~ :* duct=~[/http-get-open] %give - %http-server %response %start - 200 + :- 200 :~ ['content-type' 'text/event-stream'] ['cache-control' 'no-cache'] ['connection' 'keep-alive'] @@ -1138,7 +1126,6 @@ ^- (list move:light-gate) :~ :* duct=~[/http-get-open] %give - %http-server %response %continue :- ~ @@ -1162,7 +1149,6 @@ scry=*sley ^= call-args :* duct=~[/http-put-request] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -1186,7 +1172,7 @@ [%leaf "wrong number of moves: {<(lent moves)>}"]~ :: %+ expect-eq - !> [~[/http-put-request] %give %http-server %response %start 200 ~ ~ %.y] + !> [~[/http-put-request] %give %response %start [200 ~] ~ %.y] !> i.moves == :: the client connection is detected to be broken @@ -1196,7 +1182,7 @@ light-gate now=(add ~1111.1.2 ~m6) scry=*sley - call-args=[duct=~[/http-get-open] ~ %http-server %cancel-request ~] + call-args=[duct=~[/http-get-open] ~ %cancel-request ~] ^= expected-moves ^- (list move:light-gate) :: closing the channel restarts the timeout timer @@ -1233,7 +1219,6 @@ scry=*sley ^= call-args :* duct=~[/http-get-open] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -1246,10 +1231,9 @@ ^- (list move:light-gate) :~ :* duct=~[/http-get-open] %give - %http-server %response %start - 200 + :- 200 :~ ['content-type' 'text/event-stream'] ['cache-control' 'no-cache'] ['connection' 'keep-alive'] @@ -1289,286 +1273,6 @@ results8 results9 == -:: +test-client-request-basic: tests a single request, single reply style http request -:: -++ test-client-request-basic - :: send a %born event to use /initial-born-duct for requests - :: - =^ results1 light-gate - %- light-call :* - light-gate - now=~1111.1.1 - scry=*sley - ^= call-args - :* duct=~[/initial-born-duct] ~ - %born - [[%.n .192.168.1.1] ~] - == - ^= expected-moves - :~ :* duct=~[/initial-born-duct] - %give - %http-server - %set-config - *http-config:light - == == == - :: - =/ request=http-request:light - :* %'GET' - 'http://www.example.com' - ~ - ~ - == - :: opens the http channel - :: - =^ results2 light-gate - %- light-call :* - light-gate - now=(add ~1111.1.1 ~s1) - scry=*sley - ^= call-args - :* duct=~[/http-get-request] ~ - %http-client - %request - request - *outbound-config:light - == - ^= expected-moves - ^- (list move:light-gate) - :~ :* duct=~[/initial-born-duct] - %give - %http-client - %request - id=0 - method=%'GET' - url='http://www.example.com' - ~ - ~ - == == == - :: returns the entire payload in one response - :: - =^ results3 light-gate - %- light-call :* - light-gate - now=(add ~1111.1.1 ~s2) - scry=*sley - ^= call-args - :+ duct=~[/initial-born-duct] ~ - ^- task:able:light - :* %http-client - %receive - id=0 - ^- raw-http-response:light - :* %start - 200 - :~ ['content-type' 'text/html'] - ['content-length' '34'] - == - :: - :- ~ - %- as-octs:mimes:html - ''' - Response - ''' - :: - complete=%.y - == == - ^= expected-moves - ^- (list move:light-gate) - :~ :* duct=~[/http-get-request] - %give - %http-client - %finished - :: - :- 200 - :~ ['content-type' 'text/html'] - ['content-length' '34'] - == - :: - :- ~ - :- 'text/html' - %- as-octs:mimes:html - ''' - Response - ''' - == == == - :: - ;: weld - results1 - results2 - results3 - == -:: +test-client-request-multiple-cards: tests when complete=%.n -:: -++ test-client-request-multiple-cards - :: send a %born event to use /initial-born-duct for requests - :: - =^ results1 light-gate - %- light-call :* - light-gate - now=~1111.1.1 - scry=*sley - ^= call-args - :* duct=~[/initial-born-duct] ~ - %born - [[%.n .192.168.1.1] ~] - == - ^= expected-moves - :~ :* duct=~[/initial-born-duct] - %give - %http-server - %set-config - *http-config:light - == == == - :: - =/ request=http-request:light - :* %'GET' - 'http://www.example.com' - ~ - ~ - == - :: opens the http channel - :: - =^ results2 light-gate - %- light-call :* - light-gate - now=(add ~1111.1.1 ~s1) - scry=*sley - ^= call-args - :* duct=~[/http-get-request] ~ - %http-client - %request - request - *outbound-config:light - == - ^= expected-moves - ^- (list move:light-gate) - :~ :* duct=~[/initial-born-duct] - %give - %http-client - %request - id=0 - method=%'GET' - url='http://www.example.com' - ~ - ~ - == == == - :: returns the first 1/3 of the payload in the first response - :: - =^ results3 light-gate - %- light-call :* - light-gate - now=(add ~1111.1.1 ~s2) - scry=*sley - ^= call-args - :+ duct=~[/initial-born-duct] ~ - ^- task:able:light - :* %http-client - %receive - id=0 - ^- raw-http-response:light - :* %start - 200 - :~ ['content-type' 'text/html'] - ['content-length' '34'] - == - [~ (as-octs:mimes:html '')] - complete=%.n - == == - ^= expected-moves - ^- (list move:light-gate) - :~ :* duct=~[/http-get-request] - %give - %http-client - %progress - :: - :- 200 - :~ ['content-type' 'text/html'] - ['content-length' '34'] - == - :: - bytes-read=12 - expected-size=`34 - [~ (as-octs:mimes:html '')] - == == == - :: returns the second 1/3 of the payload - :: - =^ results4 light-gate - %- light-call :* - light-gate - now=(add ~1111.1.1 ~s3) - scry=*sley - ^= call-args - :+ duct=~[/initial-born-duct] ~ - ^- task:able:light - :* %http-client - %receive - id=0 - ^- raw-http-response:light - :* %continue - [~ (as-octs:mimes:html 'Response')] - complete=%.n - == == - ^= expected-moves - ^- (list move:light-gate) - :~ :* duct=~[/http-get-request] - %give - %http-client - %progress - :: - :- 200 - :~ ['content-type' 'text/html'] - ['content-length' '34'] - == - :: - bytes-read=20 - expected-size=`34 - [~ (as-octs:mimes:html 'Response')] - == == == - :: returns the last part - :: - =^ results5 light-gate - %- light-call :* - light-gate - now=(add ~1111.1.1 ~s4) - scry=*sley - ^= call-args - :+ duct=~[/initial-born-duct] ~ - ^- task:able:light - :* %http-client - %receive - id=0 - ^- raw-http-response:light - :* %continue - [~ (as-octs:mimes:html '')] - complete=%.y - == == - ^= expected-moves - ^- (list move:light-gate) - :~ :* duct=~[/http-get-request] - %give - %http-client - %finished - :: - :- 200 - :~ ['content-type' 'text/html'] - ['content-length' '34'] - == - :: - :- ~ - :- 'text/html' - %- as-octs:mimes:html - ''' - Response - ''' - == == == - :: - ;: weld - results1 - results2 - results3 - results4 - results5 - == :: ++ light-call |= $: light-gate=_light-gate @@ -1727,7 +1431,6 @@ scry=*sley ^= call-args :* duct=~[/http-blah] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -1737,10 +1440,9 @@ ^- (list move:light-gate) :~ :* duct=~[/http-blah] %give - %http-server %response %start - 200 + :- 200 :~ ['content-type' 'text/html'] ['content-length' '348'] == @@ -1757,7 +1459,6 @@ scry=*sley ^= call-args :* duct=~[/http-blah] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -1772,10 +1473,9 @@ ^- (list move:light-gate) :~ :* duct=~[/http-blah] %give - %http-server %response %start - 307 + :- 307 :~ ['location' '/~landscape'] :- 'set-cookie' 'urbauth=0v3.q0p7t.mlkkq.cqtto.p0nvi.2ieea; Path=/; Max-Age=86400' @@ -1820,7 +1520,6 @@ scry=*sley ^= call-args :* duct=~[/http-put-request] ~ - %http-server %request %.n [%ipv4 .192.168.1.1] @@ -1868,7 +1567,7 @@ card.i.t.moves :: %+ expect-eq - !> [~[/http-put-request] %give %http-server %response %start 200 ~ ~ %.y] + !> [~[/http-put-request] %give %response %start [200 ~] ~ %.y] !> i.t.t.moves :: %+ expect-eq