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)