main.bend

emerging-ezhttp@0.10.0.0 · 193 lines · 0 definitions

1# ezhttp: HTTP/1.1 client and server for Bend 2, with auth, cookie and CORS helpers.
2#
3# Shared message types, BYO JSON via encode/decode. TLS for the client stays
4# at the wire/runtime layer.
5import Base
6import emerging-ezhttp@0.10.0.0/src/url.bend as Url
7import emerging-ezhttp@0.10.0.0/src/pct.bend as Pct
8import emerging-ezhttp@0.10.0.0/src/http.bend as Http
9import emerging-ezhttp@0.10.0.0/src/body.bend as Body
10import emerging-ezhttp@0.10.0.0/src/client.bend as Client
11import emerging-ezhttp@0.10.0.0/src/server.bend as Server
12import emerging-ezhttp@0.10.0.0/src/cookie.bend as Cookie
13import emerging-ezhttp@0.10.0.0/src/cache.bend as Cache
14import emerging-ezhttp@0.10.0.0/src/cors.bend as Cors
15
16# URL parse re-export
17def parse_url(url: String) -> Url.Loc:
18  Url.parse(url)
19
20# percent-encode a path (RFC 3986 §2.1), as Python's urllib.parse.quote(text):
21# unreserved characters and "/" stay, every other character becomes %XX per
22# UTF-8 byte, upper-case hex (SPEC.md HTTP-URL)
23def quote_path(text: String) -> String:
24  Pct.quote(True{}, text)
25
26# percent-encode a path segment, query key or value (RFC 3986 §2.1), as
27# urllib.parse.quote(text, safe=""): like quote_path, but "/" becomes %2F
28def quote_component(text: String) -> String:
29  Pct.quote(False{}, text)
30
31# response parse re-export
32def parse_response(raw: String) -> Http.Reply:
33  Http.parse(raw)
34
35# request parse re-export (server inbound)
36def parse_request(raw: String) -> Http.Request:
37  Http.ask(raw)
38
39# format a response message (server outbound)
40def format_response(status: U32, headers: List<&2, Http.Header>, body: String)
41  -> String:
42  Http.respond(status, headers, body)
43
44# empty request body
45def empty_body() -> Body.Body:
46  Body.body.empty()
47
48# UTF-8 text request body
49def text_body(text: String) -> Body.Body:
50  Body.body.text(text)
51
52# octet-list request body
53def octets_body(bs: List<&2, U32>) -> Body.Body:
54  Body.body.octets(bs)
55
56# BYO JSON (or any codec): encode a value with the caller's to_text
57def encode(~A: Data, ~to_text: A -> String, value: A) -> Body.Body:
58  Body.body.encode(A, to_text, value)
59
60# BYO JSON (or any codec): decode body text with the caller's from_text
61def decode(
62  ~A: Data,
63  ~from_text: String -> Maybe<&2, A>,
64  text: String
65) -> Maybe<&2, A>:
66  Body.body.decode(A, from_text, text)
67
68# Bearer token header helper
69def bearer(token: String) -> Http.Header:
70  Client.client.bearer(token)
71
72# Basic credentials header (RFC 7617)
73def basic(user: String, password: String) -> Http.Header:
74  Client.client.basic(user, password)
75
76# Set-Cookie header from a parsed cookie (RFC 6265)
77def set_cookie(cookie: Cookie.Cookie) -> Http.Header:
78  Cookie.cookie.set(cookie)
79
80# parse one Set-Cookie field value
81def parse_cookie(line: String) -> Maybe<&2, Cookie.Cookie>:
82  Cookie.cookie.parse(line)
83
84# Cookie request header for a list of cookies
85def cookie_header(cs: List<&2, Cookie.Cookie>) -> Http.Header:
86  Cookie.cookie.request(cs)
87
88# parse a Cache-Control field value (RFC 9111)
89def cache_control(value: String) -> Cache.Cc:
90  Cache.cache.parse(value)
91
92# freshness of a Cache-Control value at an age in seconds
93def fresh(value: String, age: Nat) -> Bool:
94  Cache.cache.fresh(Cache.cache.parse(value), age)
95
96# CORS preflight or simple headers on a handler reply
97def cors_reply(cfg: Cors.Cfg, req: Http.Request, reply: Http.Reply) -> Http.Reply:
98  Cors.cors.on(cfg, req, reply)
99
100# GET with no extra headers
101def http.get(url: String) -> IO(Client.Response):
102  Client.client.get(url, [])
103
104# GET with headers
105def http.get_with(url: String, headers: List<&2, Http.Header>)
106  -> IO(Client.Response):
107  Client.client.get(url, headers)
108
109# HEAD with no extra headers
110def http.head(url: String) -> IO(Client.Response):
111  Client.client.head(url, [])
112
113# HEAD with headers
114def http.head_with(url: String, headers: List<&2, Http.Header>)
115  -> IO(Client.Response):
116  Client.client.head(url, headers)
117
118# POST with a body and no extra headers
119def http.post(url: String, body: Body.Body) -> IO(Client.Response):
120  Client.client.post(url, [], body)
121
122# POST with headers and a body
123def http.post_with(
124  url: String,
125  headers: List<&2, Http.Header>,
126  body: Body.Body
127) -> IO(Client.Response):
128  Client.client.post(url, headers, body)
129
130# PUT with a body and no extra headers
131def http.put(url: String, body: Body.Body) -> IO(Client.Response):
132  Client.client.put(url, [], body)
133
134# PUT with headers and a body
135def http.put_with(url: String, headers: List<&2, Http.Header>, body: Body.Body)
136  -> IO(Client.Response):
137  Client.client.put(url, headers, body)
138
139# DELETE with a body and no extra headers
140def http.delete(url: String, body: Body.Body) -> IO(Client.Response):
141  Client.client.delete(url, [], body)
142
143# DELETE with headers and a body
144def http.delete_with(
145  url: String,
146  headers: List<&2, Http.Header>,
147  body: Body.Body
148) -> IO(Client.Response):
149  Client.client.delete(url, headers, body)
150
151# OPTIONS with no extra headers
152def http.options(url: String) -> IO(Client.Response):
153  Client.client.options(url, [])
154
155# OPTIONS with headers
156def http.options_with(url: String, headers: List<&2, Http.Header>)
157  -> IO(Client.Response):
158  Client.client.options(url, headers)
159
160# listen on host:port and serve up to `limit` connections
161def http.serve(
162  ~handle: Http.Request -> Http.Reply,
163  host: String,
164  port: U32,
165  limit: U32
166) -> IO(Unit):
167  Server.server.serve(handle, host, port, limit)
168
169# listen on host:port and serve up to `limit` connections with a handler that
170# does IO; a failed connection is logged and the next one is served
171def http.serve_io(
172  ~handle: Http.Request -> IO(Http.Reply),
173  host: String,
174  port: U32,
175  limit: U32
176) -> IO(Unit):
177  Server.server.serve_io(handle, host, port, limit)
178
179# listen on host:port and serve with an IO handler until the process stops
180@unsafe def http.serve_forever(
181  ~handle: Http.Request -> IO(Http.Reply),
182  host: String,
183  port: U32
184) -> IO(Unit):
185  Server.server.serve_forever(handle, host, port)
186
187# listen on host:port, serve one connection, then stop
188def http.serve_once(
189  ~handle: Http.Request -> Http.Reply,
190  host: String,
191  port: U32
192) -> IO(Unit):
193  Server.server.once(handle, host, port)