Urbit Developers
  • Lightning Tutorials

    • Introduction
    • Build a Groups App
    • Build a Chat App
    • Build a Voting App
    • Core Curriculum

      • Hoon School

        • Introduction
        • 1. Hoon Syntax
        • 2. Azimuth (Urbit ID)
        • 3. Gates (Functions)
        • 4. Molds (Types)
        • 5. Cores
        • 6. Trees and Addressing
        • 7. Libraries
        • 8. Testing Code
        • 9. Text Processing I
        • 10. Cores and Doors
        • 11. Data Structures
        • 12. Type Checking
        • 13. Conditional Logic
        • 14. Subject-Oriented Programming
        • 15. Text Processing II
        • 16. Functional Programming
        • 17. Text Processing III
        • 18. Generic and Variant Cores
        • 19. Mathematics
        • App School I

          • Introduction
          • 1. Arvo
          • 2. The Agent Core
          • 3. Imports and Aliases
          • 4. Lifecycle
          • 5. Cards
          • 6. Pokes
          • 7. Structures and Marks
          • 8. Subscriptions
          • 9. Vanes
          • 10. Scries
          • 11. Failure
          • 12. Next Steps
          • Appendix: Types
          • App School II (Full-Stack)

            • Introduction
            • 1. Types
            • 2. Agent
            • 3. JSON
            • 4. Marks
            • 5. Eyre
            • 6. React app setup
            • 7. React app logic
            • 8. Desk and glob
            • 9. Summary
          • Environment Setup
          • Additional Guides

            • Hoon Workbook

              • Competitive Programming
              • Gleichniszahlenreihe
              • Rhonda Numbers
              • Roman Numerals
              • Solitaire Cipher
              • App Workbook

                • %ahoy Ship Monitoring
                • %dbug Debugging Wrapper
                • %flap JS Client
                • %feature Page Hosting
                • Threads

                  • Fundamentals
                  • Bind
                  • Input
                  • Output
                  • Summary
                • Aqua Tests
                • Command-Line Apps
                • HTTP API
                • JSON
                • Parsing Text
                • Sail (HTML)
                • Software Distribution
                • Strings
                • Unit Tests
                • Vases
                Urbit Developers
                • Lightning Tutorials

                  • Introduction
                  • Build a Groups App
                  • Build a Chat App
                  • Build a Voting App
                  • Core Curriculum

                    • Hoon School

                      • Introduction
                      • 1. Hoon Syntax
                      • 2. Azimuth (Urbit ID)
                      • 3. Gates (Functions)
                      • 4. Molds (Types)
                      • 5. Cores
                      • 6. Trees and Addressing
                      • 7. Libraries
                      • 8. Testing Code
                      • 9. Text Processing I
                      • 10. Cores and Doors
                      • 11. Data Structures
                      • 12. Type Checking
                      • 13. Conditional Logic
                      • 14. Subject-Oriented Programming
                      • 15. Text Processing II
                      • 16. Functional Programming
                      • 17. Text Processing III
                      • 18. Generic and Variant Cores
                      • 19. Mathematics
                      • App School I

                        • Introduction
                        • 1. Arvo
                        • 2. The Agent Core
                        • 3. Imports and Aliases
                        • 4. Lifecycle
                        • 5. Cards
                        • 6. Pokes
                        • 7. Structures and Marks
                        • 8. Subscriptions
                        • 9. Vanes
                        • 10. Scries
                        • 11. Failure
                        • 12. Next Steps
                        • Appendix: Types
                        • App School II (Full-Stack)

                          • Introduction
                          • 1. Types
                          • 2. Agent
                          • 3. JSON
                          • 4. Marks
                          • 5. Eyre
                          • 6. React app setup
                          • 7. React app logic
                          • 8. Desk and glob
                          • 9. Summary
                        • Environment Setup
                        • Additional Guides

                          • Hoon Workbook

                            • Competitive Programming
                            • Gleichniszahlenreihe
                            • Rhonda Numbers
                            • Roman Numerals
                            • Solitaire Cipher
                            • App Workbook

                              • %ahoy Ship Monitoring
                              • %dbug Debugging Wrapper
                              • %flap JS Client
                              • %feature Page Hosting
                              • Threads

                                • Fundamentals
                                • Bind
                                • Input
                                • Output
                                • Summary
                              • Aqua Tests
                              • Command-Line Apps
                              • HTTP API
                              • JSON
                              • Parsing Text
                              • Sail (HTML)
                              • Software Distribution
                              • Strings
                              • Unit Tests
                              • Vases
                              Guides/Additional Guides/App Workbook

                              %feature Page Hosting

                              %feature Page Hosting

                              %feature by ~hanfel-dovned hosts a simple HTML page from an Urbit ship at an associated URL. This tutorial examines how it uses the middleware %schooner library by Quartus to return a web page when contacted by a web browser. You will learn how a basic site hosting app can handle HTTP requests and render a page using an %html mark.

                              %feature presents a web page from /app/feature-ui at /apps/feature/feature-ui. These paths are both configurable by the developer.

                              /sur Structure Files

                              Our primary event in this case is simply an %action to create a page.

                              /sur/feature.hoon:

                              |%
                              +$ action
                              $% [%new-page html=@t]
                              ==
                              --

                              No special mark files are necessary for %feature other than %html.

                              /app Agent Files

                              The agent only maintains a state containing the page contents as a cord.

                              The system only handles pokes: there are no subscriptions or Arvo calls except for the Eyre binding.

                              /app/feature.hoon:

                              Click to expand

                              /- feature
                              /+ dbug, default-agent, server, schooner
                              /* feature-ui %html /app/feature-ui/html
                              |%
                              +$ versioned-state
                              $% state-0
                              ==
                              +$ state-0 [%0 page=@t]
                              +$ card card:agent:gall
                              --
                              %- agent:dbug
                              ^- agent:gall
                              =| state-0
                              =* state -
                              |_ =bowl:gall
                              +* this .
                              def ~(. (default-agent this %.n) bowl)
                              ++ on-init
                              ^- (quip card _this)
                              :_ this(page 'Hello World')
                              :~
                              :* %pass /eyre/connect %arvo %e
                              %connect `/apps/feature %feature
                              ==
                              ==
                              ::
                              ++ on-save
                              ^- vase
                              !>(state)
                              ::
                              ++ on-load
                              |= old-state=vase
                              ^- (quip card _this)
                              =/ old !<(versioned-state old-state)
                              ?- -.old
                              %0 `this(state old)
                              ==
                              ::
                              ++ on-poke
                              |= [=mark =vase]
                              ^- (quip card _this)
                              |^
                              ?+ mark (on-poke:def mark vase)
                              %handle-http-request
                              ?> =(src.bowl our.bowl)
                              =^ cards state
                              (handle-http !<([@ta =inbound-request:eyre] vase))
                              [cards this]
                              ==
                              ++ handle-http
                              |= [eyre-id=@ta =inbound-request:eyre]
                              ^- (quip card _state)
                              =/ ,request-line:server
                              (parse-request-line:server url.request.inbound-request)
                              =+ send=(cury response:schooner eyre-id)
                              ::
                              ?+ method.request.inbound-request
                              [(send [405 ~ [%stock ~]]) state]
                              ::
                              %'POST'
                              ?. authenticated.inbound-request
                              :_ state
                              %- send
                              [302 ~ [%login-redirect './apps/feature']]
                              ?~ body.request.inbound-request
                              [(send [405 ~ [%stock ~]]) state]
                              =/ json (de-json:html q.u.body.request.inbound-request)
                              =/ action (dejs-action +.json)
                              (handle-action action)
                              ::
                              %'GET'
                              ?+ site
                              :_ state
                              (send [404 ~ [%plain "404 - Not Found"]])
                              ::
                              [%apps %feature %public ~]
                              :_ state
                              %- send
                              :+ 200 ~
                              :- %html page
                              ::
                              [%apps %feature ~]
                              ?. authenticated.inbound-request
                              :_ state
                              %- send
                              [302 ~ [%login-redirect './apps/feature']]
                              :_ state
                              %- send
                              :+ 200 ~
                              :- %html feature-ui
                              ==
                              ==
                              ::
                              ++ dejs-action
                              =, dejs:format
                              |= jon=json
                              ^- action:feature
                              %. jon
                              %- of
                              :~ new-page+so
                              ==
                              ::
                              ++ handle-action
                              |= =action:feature
                              ^- (quip card _state)
                              ?- -.action
                              %new-page
                              ?> =(src.bowl our.bowl)
                              `state(page html:action)
                              ==
                              --
                              ++ on-peek on-peek:def
                              ++ on-watch
                              |= =path
                              ^- (quip card _this)
                              ?+ path (on-watch:def path)
                              [%http-response *]
                              `this
                              ==
                              ::
                              ++ on-leave on-leave:def
                              ++ on-agent on-agent:def
                              ++ on-arvo on-arvo:def
                              ++ on-fail on-fail:def
                              --

                              Pokes

                              ++on-poke only responds to %handle-http-request, which is dealt with in a |^ barket core.

                              The most interesting part of the whole app is the ++handle-http arm:

                              Click to expand

                              ++ handle-http
                              |= [eyre-id=@ta =inbound-request:eyre]
                              ^- (quip card _state)
                              =/ ,request-line:server
                              (parse-request-line:server url.request.inbound-request)
                              =+ send=(cury response:schooner eyre-id)
                              ::
                              ?+ method.request.inbound-request
                              [(send [405 ~ [%stock ~]]) state]
                              ::
                              %'POST'
                              ?. authenticated.inbound-request
                              :_ state
                              %- send
                              [302 ~ [%login-redirect './apps/feature']]
                              ?~ body.request.inbound-request
                              [(send [405 ~ [%stock ~]]) state]
                              =/ json (de-json:html q.u.body.request.inbound-request)
                              =/ action (dejs-action +.json)
                              (handle-action action)
                              ::
                              %'GET'
                              ?+ site
                              :_ state
                              (send [404 ~ [%plain "404 - Not Found"]])
                              ::
                              [%apps %feature %public ~]
                              :_ state
                              %- send
                              :+ 200 ~
                              :- %html page
                              ::
                              [%apps %feature ~]
                              ?. authenticated.inbound-request
                              :_ state
                              %- send
                              [302 ~ [%login-redirect './apps/feature']]
                              :_ state
                              %- send
                              :+ 200 ~
                              :- %html feature-ui
                              ::
                              :: [%apps %feature %state ~]
                              :: :_ state
                              :: %- send
                              :: :+ 200 ~
                              :: [%json (enjs-state +.state)]
                              ==
                              ==

                              This arm uses the server library and schooner to produce a response of a server state and associated data. HTTP requests to /app/feature are checked for login authentication, while /app/feature/public is not.

                              POST

                              In response to a POST request, the default page in the state can be changed. This is the only state change supported by the agent.

                              GET

                              A GET request defaults to a 404 error.

                              • /apps/feature/public returns 200 success and the default page in the state.
                              • /apps/feature returns 200 success and the target page, statically compiled on agent build.

                              /lib/schooner

                              The Schooner library simplifies raw HTTP handling for Gall agents, in particular for MIME returns.

                              <-

                              %flap JS Client

                              Edit this page on GitHub

                              Last modified March 17, 2023