eyre: subscribe to apps for responses

This removes the %http-response special case from gall.  In its place,
we implement a subscription regime with the following steps:

- Agent sends %connect to Eyre
- Eyre pokes agent with %handle-http-response, including unique eyre-id
- Agent passes %start-watching to Eyre with eyre-id and unique app-id
- Eyre subscribes to agent on /http-response/app-id
- Agent produces a %http-response-header fact followed by 0 or more
  %http-response-data facts and possibly a %http-response-cancel fact
- Agent produces a %kick to close the subscription, which Eyre
  interprets as completion of the message.

This works when there is data.  There is currently a bug where if the
response has no data in total (as in the case of a naked 404), no
response will be sent.

This also includes lib/http-handler, which implements a convenient
interface for agents that want to respond immediately with all the data.
This lets them avoid carrying extra state to keep track of pending
requests.

This should really have access to your state and the ability to change
it.  Perhaps a more minimalist design would be better: just keep track
of the requests, then hand it off to +on-watch when eyre is ready to
receive responses.  It's not clear how to pass in the request data in
+on-watch.
This commit is contained in:
Philip Monk 2019-11-08 23:31:11 -08:00
parent 85a40a13d0
commit 47e3b260d5
No known key found for this signature in database
GPG Key ID: B66E1F02604E44EC
9 changed files with 267 additions and 43 deletions

64
pkg/arvo/age/clock.hoon Normal file
View File

@ -0,0 +1,64 @@
/+ *server, default-agent, verb
/= tile-js
/^ octs
/; as-octs:mimes:html
/: /===/app/clock/js/tile
/| /js/
/~ ~
==
=, format
::
%+ verb &
^- agent:mall
%+ http-handler
%- require-authorization:app
|= =inbound-request:eyre
^- response:http-handler
=/ request-line (parse-request-line url.request.inbound-request)
=/ back-path (flop site.request-line)
=/ name=@t
=/ back-path (flop site.request-line)
?~ back-path
''
i.back-path
::
?~ back-path
not-found:gen
?: =(name 'tile')
(js-response:gen tile-js)
not-found:gen
::
|_ =bowl:mall
+* this .
def ~(. (default-agent this %|) bowl)
::
++ on-init
^- (quip card:agent:mall _this)
=/ launcha
[%launch-action !>([%clock /tile '/~clock/js/tile.js'])]
:_ this
:~ [%pass / %arvo %e %connect [~ /'~clock'] %clock]
[%pass /clock %agent [our.bowl %launch] %poke launcha]
==
++ on-save on-save:def
++ on-load on-load:def
++ on-poke on-poke:def
++ on-watch
|= =path
^- (quip card:agent:mall _this)
?. =(/tile path)
(on-watch:def path)
[[%give %fact ~ %json !>(*json)]~ this]
::
++ on-leave on-leave:def
++ on-peek on-peek:def
++ on-agent on-agent:def
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card:agent:mall _this)
?. ?=(%bound +<.sign-arvo)
(on-arvo:def wire sign-arvo)
[~ this]
::
++ on-fail on-fail:def
--

View File

@ -8,12 +8,10 @@
`agent
::
++ on-save
~& "extracting empty state for {<dap.bowl>}"
!>(~)
::
++ on-load
|= old-state=vase
~& "updating agent {<dap.bowl>} by throwing away old state"
`agent
::
++ on-poke

View File

@ -0,0 +1,93 @@
:: wrap an http handler without having to worry about subscriptions
::
|%
+$ response simple-payload:http
+$ handler $-(inbound-request:eyre response)
--
|= [=handler =agent:mall]
=| state=[count=@ud map=(map app-id=@ud response)]
^- agent:mall
|_ =bowl:mall
+* this .
ag ~(. agent bowl)
::
++ on-init
^- (quip card:agent:mall agent:mall)
=^ cards agent on-init:ag
[cards this]
::
++ on-save
^- vase
!>([on-save:ag state])
::
++ on-load
|= old-state=vase
^- (quip card:agent:mall agent:mall)
=^ old state !<([vase _state] old-state)
=^ cards agent (on-load:ag old)
[cards this]
::
++ on-poke
|= [=mark =vase]
^- (quip card:agent:mall agent:mall)
?. ?=(%handle-http-request mark)
=^ cards agent (on-poke:ag mark vase)
[cards this]
=+ !<([eyre-id=@ud =inbound-request:eyre] vase)
=/ response (handler inbound-request)
=/ app-id count.state
=: count.state +(count.state)
map.state (~(put by map.state) app-id response)
==
:_ this :_ ~
[%pass / %arvo %e %start-watching eyre-id app-id]
::
++ on-watch
|= =path
^- (quip card:agent:mall agent:mall)
?. ?=([%http-response @ ~] path)
=^ cards agent (on-watch:ag path)
[cards this]
=/ app-id (slav %ud i.t.path)
=/ response (~(get by map.state) app-id)
:_ this(map.state (~(del by map.state) app-id))
?~ response
^- (list card:agent:mall)
:~ [%give %fact `path %http-response-cancel !>(~)]
[%give %kick `path ~]
==
^- (list card:agent:mall)
:~ [%give %fact `path %http-response-header !>(response-header.u.response)]
[%give %fact `path %http-response-data !>(data.u.response)]
[%give %kick `path ~]
==
::
++ on-leave
|= =path
^- (quip card:agent:mall agent:mall)
=^ cards agent (on-leave:ag path)
[cards this]
::
++ on-peek
|= =path
^- (unit (unit cage))
(on-peek:ag path)
::
++ on-agent
|= [=wire =sign:agent:mall]
^- (quip card:agent:mall agent:mall)
=^ cards agent (on-agent:ag wire sign)
[cards this]
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card:agent:mall agent:mall)
=^ cards agent (on-arvo:ag wire sign-arvo)
[cards this]
::
++ on-fail
|= [=term =tang]
^- (quip card:agent:mall agent:mall)
=^ cards agent (on-fail:ag term tang)
[cards this]
--

View File

@ -1,3 +1,4 @@
/+ http-handler
=, eyre
|%
::
@ -24,22 +25,19 @@
:: +require-authorization: redirect to the login page when unauthenticated
::
++ require-authorization
|* this=*
|= handler=$-(inbound-request:eyre (quip card:agent:mall _this))
|= handler=$-(inbound-request:eyre simple-payload:http)
|= =inbound-request:eyre
^- (quip card:agent:mall _this)
^- simple-payload:http
::
?: authenticated.inbound-request
~! this
~! +:*handler
(handler inbound-request)
::
:_ this
^- (list card:agent:mall)
=/ redirect=cord
%- crip
"/~/login?redirect={(trip url.request.inbound-request)}"
[%give [%http-response %start [307 ['location' redirect]~] ~ %.y]]~
[[307 ['location' redirect]~] ~]
::
++ html-response
|= oct-html=octs

View File

@ -447,7 +447,6 @@
+>.$
(dump:(crud %reap u.p.p.+>.sih) %logo ~)
$fact pump:(from ;;(dill-blit q:`vase`+>+>.sih))
$http-response !!
==
::
{$c $note *}

View File

@ -102,6 +102,11 @@
:: the :binding into a (map (unit @t) (trie knot =action)).
::
bindings=(list [=binding =duct =action])
:: starting: new http connections waiting for app to send %start-watching
::
:: Ducts should be keys of connections.
::
starting=[count=@ud map=(map eyre-id=@ud [=duct app=term])]
:: connections: open http connections not fully complete
::
connections=(map duct outstanding-connection)
@ -816,6 +821,12 @@
^- [(list move) server-state]
::
=/ act [%app app=%lens]
=/ eyre-id count.starting.state
=: count.starting.state +(count.starting.state)
map.starting.state
(~(put by map.starting.state) eyre-id [duct app.act])
==
::
=/ connection=outstanding-connection
[act [& secure address request] ~ 0]
::
@ -831,7 +842,7 @@
^- task:agent:mall
:* %poke
%handle-http-request
!>(inbound-request.connection)
!>([eyre-id inbound-request.connection])
==
:: +request: starts handling an inbound http request
::
@ -866,6 +877,12 @@
[%$ %noun !>([authenticated request])]
::
%app
=/ eyre-id count.starting.state
=: count.starting.state +(count.starting.state)
map.starting.state
(~(put by map.starting.state) eyre-id [duct app.action])
==
::
:_ state
:_ ~
:^ duct %pass /run-app-request/[app.action]
@ -878,7 +895,7 @@
^- task:agent:mall
:* %poke
%handle-http-request
!>(inbound-request.connection)
!>([eyre-id inbound-request.connection])
==
::
%authentication
@ -891,6 +908,23 @@
%^ return-static-data-on-duct 404 'text/html'
(error-page 404 authenticated url.request ~)
==
:: +start-watching: start watching app for response
::
++ start-watching
|= [eyre-id=@ud app-id=@ud]
^- [(list move) server-state]
=/ start=(unit [duct=^duct app=term])
(~(get by map.starting.state) eyre-id)
?~ start
~& [%invalid-starting-connection eyre-id]
[~ state]
::
:_ state(map.starting (~(del by map.starting.state) eyre-id))
:_ ~
:* duct.u.start %pass /watch-response
%m %deal [our our] app.u.start
%watch /http-response/(scot %ud app-id)
==
:: +cancel-request: handles a request being externally aborted
::
++ cancel-request
@ -1513,19 +1547,19 @@
:: +on-gall-response: turns a gall response into an event
::
++ on-gall-response
|= [channel-id=@t request-id=@ud =unto:mall]
|= [channel-id=@t request-id=@ud =sign:agent:mall]
^- [(list move) server-state]
::
?+ -.unto ~|([%invalid-gall-response -.unto] !!)
?- -.sign
%poke-ack
=/ =json
=, enjs:format
%- pairs :~
['response' [%s 'poke']]
['id' (numb request-id)]
?~ p.unto
?~ p.sign
['ok' [%s 'ok']]
['err' (wall (render-tang-to-wall 100 u.p.unto))]
['err' (wall (render-tang-to-wall 100 u.p.sign))]
==
::
(emit-event channel-id [(en-json:html json)]~)
@ -1537,8 +1571,8 @@
['response' [%s 'diff']]
['id' (numb request-id)]
:- 'json'
?> =(%json p.cage.unto)
;;(json q.q.cage.unto)
?> =(%json p.cage.sign)
;;(json q.q.cage.sign)
==
::
(emit-event channel-id [(en-json:html json)]~)
@ -1560,9 +1594,9 @@
%- pairs :~
['response' [%s 'subscribe']]
['id' (numb request-id)]
?~ p.unto
?~ p.sign
['ok' [%s 'ok']]
['err' (wall (render-tang-to-wall 100 u.p.unto))]
['err' (wall (render-tang-to-wall 100 u.p.sign))]
==
::
(emit-event channel-id [(en-json:html json)]~)
@ -1805,6 +1839,8 @@
%cancel
:: todo: log this differently from an ise.
::
:: maybe should also send %leave to app?
::
error-connection
==
::
@ -2110,6 +2146,10 @@
%request-local
=^ moves server-state.ax (request-local:server +.task)
[moves http-server-gate]
::
%start-watching
=^ moves server-state.ax (start-watching:server +.task)
[moves http-server-gate]
::
%cancel-request
=^ moves server-state.ax cancel-request:server
@ -2169,6 +2209,7 @@
~|([%bad-take-wire wire] !!)
::
%run-app-request run-app-request
%watch-response watch-response
%run-app-cancel run-app-cancel
%run-build run-build
%channel channel
@ -2180,24 +2221,62 @@
?> ?=([%m %unto *] sign)
::
::
?: ?=([%poke-ack *] p.sign)
?> ?=([%poke-ack *] p.sign)
?~ p.p.sign
:: received a positive acknowledgment: take no action
::
[~ http-server-gate]
:: we have an error; propagate it to the client
::
=/ event-args [[our eny duct now scry-gate] server-state.ax]
=/ handle-gall-error
handle-gall-error:(per-server-event event-args)
=^ moves server-state.ax
(handle-gall-error u.p.p.sign)
[moves http-server-gate]
::
++ watch-response
::
=/ event-args [[our eny duct now scry-gate] server-state.ax]
::
?: ?=([%m %unto %watch-ack *] sign)
?~ p.p.sign
:: received a positive acknowledgment: take no action
::
[~ http-server-gate]
:: we have an error; propagate it to the client
::
=/ event-args [[our eny duct now scry-gate] server-state.ax]
=/ handle-gall-error
handle-gall-error:(per-server-event event-args)
=^ moves server-state.ax (handle-gall-error u.p.p.sign)
[moves http-server-gate]
::
?> ?=([%m %unto %http-response *] sign)
?: ?=([%m %unto %kick ~] sign)
=/ handle-response handle-response:(per-server-event event-args)
=^ moves server-state.ax
(handle-response %continue ~ &)
[moves http-server-gate]
::
=/ event-args [[our eny duct now scry-gate] server-state.ax]
?> ?=([%m %unto %fact *] sign)
=/ =mark p.cage.p.sign
=/ =vase q.cage.p.sign
?. ?= ?(%http-response-header %http-response-data %http-response-cancel)
mark
=/ handle-gall-error
handle-gall-error:(per-server-event event-args)
=^ moves server-state.ax
(handle-gall-error leaf+"eyre bad mark {<mark>}" ~)
[moves http-server-gate]
::
=/ =http-event:http
?- mark
%http-response-header [%start !<(response-header:http vase) ~ |]
%http-response-data [%continue !<((unit octs) vase) |]
%http-response-cancel [%cancel ~]
==
=/ handle-response handle-response:(per-server-event event-args)
=^ moves server-state.ax (handle-response http-event.p.sign)
=^ moves server-state.ax
(handle-response http-event)
[moves http-server-gate]
::
++ run-app-cancel

View File

@ -535,8 +535,7 @@
::
[%m %unto *]
?- +>-.hin
$kick ~|([%jael-unexpected-quit tea hin] !!)
$http-response ~|([%jael-unexpected-http-response tea hin] !!)
$kick ~|([%jael-unexpected-quit tea hin] !!)
$poke-ack
?~ p.p.+>.hin
+>.$

View File

@ -683,17 +683,17 @@
mo-core
::
?> ?=([%m %unto *] sign-arvo)
=/ =unto:agent +>.sign-arvo
=/ =sign:agent +>.sign-arvo
::
?- -.unto
?- -.sign
%poke-ack
(mo-give %mack p.unto)
(mo-give %mack p.sign)
::
%fact
=/ sys-path [%sys %red t.path]
=/ =note-arvo
=/ path [%m %gh dap ~]
=/ noun [num %d p.cage.unto q.q.cage.unto]
=/ noun [num %d p.cage.sign q.q.cage.sign]
[%a %want him path noun]
(mo-pass sys-path note-arvo)
::
@ -706,10 +706,7 @@
(mo-pass sys-path note-arvo)
::
%watch-ack
(mo-give %mack p.unto)
::
%http-response
!!
(mo-give %mack p.sign)
==
:: +mo-handle-sys-val: inbound validate.
::
@ -774,8 +771,7 @@
::
=. app (ap-generic-take:app t.t.path sign-arvo)
ap-abet:app
=/ =unto +>.sign-arvo
?< ?=(%http-response -.unto)
=/ =sign:agent +>.sign-arvo
=/ app
?> ?=([%out @ @ *] t.t.path)
=/ =term i.path
@ -783,7 +779,7 @@
=/ =routes [disclosing=~ attributing=ship]
(ap-abed:ap term routes)
=. app
(ap-specific-take:app t.t.path unto)
(ap-specific-take:app t.t.path sign)
ap-abet:app
:: +mo-clear-queue: clear blocked tasks from the specified running agent.
::

View File

@ -922,6 +922,9 @@
:: starts handling an backdoor http request
::
[%request-local secure=? =address =request:http]
:: initiates a subscription to get response
::
[%start-watching our-id=@ud app-id=@ud]
:: cancels a previous request
::
[%cancel-request ~]
@ -1876,7 +1879,7 @@
$% {$mass p/mass} :: memory usage
{$onto p/(each suss tang)} :: about agent
{$rend p/path q/*} :: network request
{$unto p/unto} ::
{$unto p/sign:agent} ::
{$mack p/(unit tang)} :: message ack
== ::
++ task :: incoming request
@ -1928,10 +1931,6 @@
[%pump ~]
task:agent
==
+$ unto
$% [%http-response =http-event:http]
sign:agent
==
::
:: +agent: app core
::
@ -1956,7 +1955,6 @@
[%kick path=(unit path) ship=(unit ship)]
[%watch-ack p=(unit tang)]
[%poke-ack p=(unit tang)]
[%http-response =http-event:http]
==
+$ sign
$% [%poke-ack p=(unit tang)]