๐Ÿ‘ a fluffy Gleam web server
23

Configure Feed

Select the types of activity you want to include in your feed.

parse http1

vshakitskiy (Jul 17, 2026, 5:29 PM +0300) 9ac9217e 67c0abde

+188 -67
+24
dev/serve.gleam
··· 1 + import ewe 2 + import gleam/erlang/process 3 + import gleam/http/request 4 + import gleam/http/response 5 + 6 + pub fn main() -> Nil { 7 + let listener_name = process.new_name("listener_name") 8 + let connection_factory_name = process.new_name("connection_factory_name") 9 + 10 + let assert Ok(_started) = 11 + ewe.new(listener_name:, connection_factory_name:, handler: handle_request) 12 + |> ewe.start 13 + 14 + process.sleep_forever() 15 + } 16 + 17 + fn handle_request( 18 + request: request.Request(ewe.Connection), 19 + ) -> response.Response(ewe.Body) { 20 + echo request 21 + 22 + response.new(200) 23 + |> response.set_body(ewe.Text("Hello, Joe!")) 24 + }
+22 -10
src/ewe.gleam
··· 19 19 import glisten/socket/options 20 20 import glisten/transport 21 21 22 - pub opaque type Connection { 23 - Connection(transport: transport.Transport, socket: socket.Socket) 24 - } 22 + pub type Connection = 23 + connection.Connection 25 24 26 - pub type Body 25 + pub type Body { 26 + Bytes(BitArray) 27 + Text(String) 28 + } 27 29 28 30 pub type IpAddress { 29 31 IpV4(Int, Int, Int, Int) ··· 45 47 address: options.IpAddress, 46 48 ) -> IpAddress 47 49 50 + // glisten.IpAddress and IpAddress are structurally identical. 51 + @external(erlang, "gleam_stdlib", "identity") 52 + fn unsafe_from_internal_ip_address(address: glisten.IpAddress) -> IpAddress 53 + 48 54 /// The address a socket is bound to, or the address of a connected peer. 49 55 pub type SocketAddress { 50 56 TcpSocketAddress(ip_address: IpAddress, port: Int) 51 57 UnixSocketAddress(path: String) 52 58 } 53 59 54 - // SocketAddress and glisten.SocketAddress are structurally identical. 55 - @external(erlang, "gleam_stdlib", "identity") 56 - fn unsafe_from_internal_socket_address( 57 - address: glisten.SocketAddress, 58 - ) -> SocketAddress 60 + // Field order differs between `SocketAddress` and `glisten.SocketAddress`. 61 + fn convert_socket_address(address: glisten.SocketAddress) -> SocketAddress { 62 + case address { 63 + glisten.TcpSocketAddress(port:, ip_address:) -> 64 + TcpSocketAddress( 65 + ip_address: unsafe_from_internal_ip_address(ip_address), 66 + port:, 67 + ) 68 + glisten.UnixSocketAddress(path:) -> UnixSocketAddress(path:) 69 + } 70 + } 59 71 60 72 /// Retrieves the client's socket address from the connection. Returns error if 61 73 /// the socket information is unavailable. ··· 77 89 listener: process.Subject(listener.Message), 78 90 ) -> SocketAddress { 79 91 glisten.get_server_info(listener, 1000) 80 - |> unsafe_from_internal_socket_address 92 + |> convert_socket_address 81 93 } 82 94 83 95 type BindTarget {
-1
src/ewe/internal/connection.gleam
··· 10 10 self: process.Subject(handler.Message(Message)), 11 11 buffer: BitArray, 12 12 ) 13 - Http2 14 13 } 15 14 16 15 pub type Message {
+2 -8
src/ewe/internal/handler.gleam
··· 2 2 import ewe/internal/http1 3 3 import gleam/bit_array 4 4 import gleam/erlang/process 5 + import gleam/http 5 6 import gleam/option 6 7 import glisten 8 + import glisten/transport 7 9 8 10 /// The state of a connection for its entire lifetime. It starts at 9 11 /// `Initialised`, is classified into `Http1` or `Http2` and then stays in that ··· 49 51 NeedMoreData -> glisten.continue(Initialised(buffer:)) 50 52 Http2Preface(_remaining) -> glisten.continue(Http2) 51 53 NotHttp2(buffer:) -> { 52 - let connection = 53 - connection.Http1( 54 - connection.transport, 55 - connection.socket, 56 - connection.subject, 57 - buffer: <<>>, 58 - ) 59 - 60 54 let next = 61 55 http1.State(buffer:, idle_timer: option.None) 62 56 |> http1.handle_message(connection)
+75 -7
src/ewe/internal/http1.gleam
··· 2 2 import gleam/bit_array 3 3 import gleam/erlang/process 4 4 import gleam/http 5 + import gleam/http/request 5 6 import gleam/int 6 7 import gleam/list 7 8 import gleam/option 9 + import glisten 10 + import glisten/transport 11 + import logging 8 12 9 13 pub type State { 10 14 State(buffer: BitArray, idle_timer: option.Option(process.Timer)) ··· 15 19 Close 16 20 } 17 21 18 - pub fn handle_message(state: State, connection: connection.Connection) -> Next { 19 - Continue(state) 22 + pub fn handle_message( 23 + state: State, 24 + connection: glisten.Connection(connection.Message), 25 + ) -> Next { 26 + case parse(state.buffer) { 27 + Ok(Complete(head, _metadata, remaining)) -> { 28 + let scheme = case connection.transport { 29 + transport.Tcp -> http.Http 30 + transport.Ssl -> http.Https 31 + } 32 + 33 + let connection = 34 + connection.Http1( 35 + transport: connection.transport, 36 + socket: connection.socket, 37 + self: connection.subject, 38 + buffer: remaining, 39 + ) 40 + 41 + let request = 42 + request.Request( 43 + method: head.method, 44 + headers: head.headers, 45 + body: connection, 46 + scheme:, 47 + host: head.host, 48 + port: head.port, 49 + path: head.path, 50 + query: head.query, 51 + ) 52 + 53 + echo request 54 + 55 + Continue(State(..state, buffer: remaining)) 56 + } 57 + Ok(Incomplete) -> Continue(state) 58 + Error(error) -> { 59 + logging.log( 60 + logging.Error, 61 + "Failed to parse HTTP/1.x request: " <> error_to_string(error), 62 + ) 63 + Close 64 + } 65 + } 20 66 } 21 67 22 68 pub type Version { ··· 66 112 AmbiguousFraming 67 113 } 68 114 115 + pub fn error_to_string(error: ParseError) -> String { 116 + case error { 117 + RequestLineTooLong -> 118 + "request line exceeds " <> int.to_string(max_request_line) <> " bytes" 119 + BadRequestLine -> "malformed request line" 120 + BadMethod -> "invalid request method" 121 + BadTarget -> "invalid request target" 122 + BadVersion -> "unsupported or malformed HTTP version" 123 + HeaderLineTooLong -> 124 + "header line exceeds " <> int.to_string(max_header_line) <> " bytes" 125 + BadHeader -> "malformed header line" 126 + TooManyHeaders -> 127 + "too many headers (max " <> int.to_string(max_headers) <> ")" 128 + DuplicateContentLength -> "duplicate Content-Length header" 129 + BadContentLength -> "invalid Content-Length value" 130 + DuplicateHost -> "duplicate Host header" 131 + BadHost -> "invalid Host header" 132 + MissingHost -> "missing required Host header" 133 + AmbiguousFraming -> 134 + "conflicting Content-Length and Transfer-Encoding headers" 135 + } 136 + } 137 + 69 138 pub type Parsed { 70 139 Complete(head: Head, metadata: Metadata, remaining: BitArray) 71 140 Incomplete 72 - Failed(ParseError) 73 141 } 74 142 75 143 const max_request_line = 8192 ··· 79 147 const max_headers = 100 80 148 81 149 /// Parses as much of a request head as `buffer` contains. 82 - pub fn parse(buffer: BitArray) -> Parsed { 150 + pub fn parse(buffer: BitArray) -> Result(Parsed, ParseError) { 83 151 let step = { 84 152 use #(method, target, version, remaining) <- try_step(parse_request_line( 85 153 buffer, ··· 109 177 } 110 178 111 179 case step { 112 - Done(parsed) -> parsed 113 - More -> Incomplete 114 - ParseError(error) -> Failed(error) 180 + Done(parsed) -> Ok(parsed) 181 + More -> Ok(Incomplete) 182 + ParseError(error) -> Error(error) 115 183 } 116 184 } 117 185
+65 -41
test/ewe/internal/http1_test.gleam
··· 7 7 "GET /foo?a=1 HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n":utf8, 8 8 >> 9 9 10 - let assert http1.Complete(head, metadata, remaining) = http1.parse(buffer) 10 + let assert Ok(http1.Complete(head, metadata, remaining)) = http1.parse(buffer) 11 11 12 12 assert head.method == http.Get 13 13 assert head.host == "example.com" ··· 25 25 26 26 pub fn host_with_port_test() { 27 27 let buffer = <<"GET / HTTP/1.1\r\nHost: example.com:8080\r\n\r\n":utf8>> 28 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(buffer) 28 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 29 + http1.parse(buffer) 29 30 30 31 assert head.host == "example.com" 31 32 assert head.port == Some(8080) ··· 33 34 34 35 pub fn host_ipv6_with_port_test() { 35 36 let buffer = <<"GET / HTTP/1.1\r\nHost: [::1]:8080\r\n\r\n":utf8>> 36 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(buffer) 37 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 38 + http1.parse(buffer) 37 39 38 40 assert head.host == "[::1]" 39 41 assert head.port == Some(8080) ··· 41 43 42 44 pub fn missing_host_on_http11_rejected_test() { 43 45 let buffer = <<"GET / HTTP/1.1\r\n\r\n":utf8>> 44 - assert http1.parse(buffer) == http1.Failed(http1.MissingHost) 46 + assert http1.parse(buffer) == Error(http1.MissingHost) 45 47 } 46 48 47 49 pub fn missing_host_on_http10_allowed_test() { 48 50 let buffer = <<"GET / HTTP/1.0\r\n\r\n":utf8>> 49 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(buffer) 51 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 52 + http1.parse(buffer) 50 53 51 54 assert head.host == "" 52 55 assert head.port == None ··· 54 57 55 58 pub fn duplicate_host_rejected_test() { 56 59 let buffer = <<"GET / HTTP/1.1\r\nHost: a.com\r\nHost: b.com\r\n\r\n":utf8>> 57 - assert http1.parse(buffer) == http1.Failed(http1.DuplicateHost) 60 + assert http1.parse(buffer) == Error(http1.DuplicateHost) 58 61 } 59 62 60 63 pub fn relative_path_rejected_test() { 61 64 let buffer = <<"GET foo HTTP/1.1\r\nHost: example.com\r\n\r\n":utf8>> 62 - assert http1.parse(buffer) == http1.Failed(http1.BadTarget) 65 + assert http1.parse(buffer) == Error(http1.BadTarget) 63 66 } 64 67 65 68 pub fn asterisk_form_allowed_for_options_test() { 66 69 let buffer = <<"OPTIONS * HTTP/1.1\r\nHost: example.com\r\n\r\n":utf8>> 67 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(buffer) 70 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 71 + http1.parse(buffer) 68 72 69 73 assert head.path == "*" 70 74 } 71 75 72 76 pub fn asterisk_form_rejected_for_get_test() { 73 77 let buffer = <<"GET * HTTP/1.1\r\nHost: example.com\r\n\r\n":utf8>> 74 - assert http1.parse(buffer) == http1.Failed(http1.BadTarget) 78 + assert http1.parse(buffer) == Error(http1.BadTarget) 75 79 } 76 80 77 81 pub fn connect_authority_form_test() { 78 82 let buffer = <<"CONNECT example.com:443 HTTP/1.1\r\n\r\n":utf8>> 79 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(buffer) 83 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 84 + http1.parse(buffer) 80 85 81 86 assert head.host == "example.com" 82 87 assert head.port == Some(443) ··· 86 91 87 92 pub fn connect_without_port_rejected_test() { 88 93 let buffer = <<"CONNECT example.com HTTP/1.1\r\n\r\n":utf8>> 89 - assert http1.parse(buffer) == http1.Failed(http1.BadTarget) 94 + assert http1.parse(buffer) == Error(http1.BadTarget) 90 95 } 91 96 92 97 pub fn mixed_case_and_ows_headers_test() { ··· 94 99 "POST /submit HTTP/1.1\r\nHost: example.com\r\nContent-Length: 13 \r\nConnection: close\r\n\r\nHELLO WORLD!!":utf8, 95 100 >> 96 101 97 - let assert http1.Complete(head, metadata, remaining) = http1.parse(buffer) 102 + let assert Ok(http1.Complete(head, metadata, remaining)) = 103 + http1.parse(buffer) 98 104 99 105 assert head.headers 100 106 == [ ··· 112 118 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Name:\t\tvalue\t\t\r\n\r\n":utf8, 113 119 >> 114 120 115 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(buffer) 121 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 122 + http1.parse(buffer) 116 123 117 124 assert head.headers == [#("host", "example.com"), #("x-name", "value")] 118 125 } ··· 122 129 "PUT /x HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n":utf8, 123 130 >> 124 131 125 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 132 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 133 + http1.parse(buffer) 126 134 127 135 assert metadata.chunked 128 136 } ··· 132 140 "PUT /x HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: gzip, chunked\r\n\r\n":utf8, 133 141 >> 134 142 135 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 143 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 144 + http1.parse(buffer) 136 145 137 146 assert metadata.chunked 138 147 } ··· 142 151 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 5\r\nTransfer-Encoding: chunked\r\n\r\nhello":utf8, 143 152 >> 144 153 145 - assert http1.parse(buffer) == http1.Failed(http1.AmbiguousFraming) 154 + assert http1.parse(buffer) == Error(http1.AmbiguousFraming) 146 155 } 147 156 148 157 pub fn conflicting_transfer_encoding_and_content_length_reversed_order_rejected_test() { ··· 150 159 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\nContent-Length: 5\r\n\r\nhello":utf8, 151 160 >> 152 161 153 - assert http1.parse(buffer) == http1.Failed(http1.AmbiguousFraming) 162 + assert http1.parse(buffer) == Error(http1.AmbiguousFraming) 154 163 } 155 164 156 165 pub fn connection_close_lookalike_is_not_close_test() { ··· 158 167 "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close-enough\r\n\r\n":utf8, 159 168 >> 160 169 161 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 170 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 171 + http1.parse(buffer) 162 172 163 173 assert metadata.keep_alive as "\"close-enough\" is not the \"close\" token" 164 174 } ··· 168 178 "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade, Close\r\n\r\n":utf8, 169 179 >> 170 180 171 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 181 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 182 + http1.parse(buffer) 172 183 173 184 assert !metadata.keep_alive 174 185 } 175 186 176 187 pub fn http11_defaults_to_keep_alive_test() { 177 188 let buffer = <<"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n":utf8>> 178 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 189 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 190 + http1.parse(buffer) 179 191 180 192 assert metadata.keep_alive 181 193 as "HTTP/1.1 without Connection defaults to keep-alive" ··· 183 195 184 196 pub fn http10_defaults_to_close_test() { 185 197 let buffer = <<"GET / HTTP/1.0\r\n\r\n":utf8>> 186 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 198 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 199 + http1.parse(buffer) 187 200 188 201 assert !metadata.keep_alive as "HTTP/1.0 without Connection defaults to close" 189 202 } 190 203 191 204 pub fn http10_explicit_keep_alive_test() { 192 205 let buffer = <<"GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n":utf8>> 193 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 206 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 207 + http1.parse(buffer) 194 208 195 209 assert metadata.keep_alive 196 210 } 197 211 198 212 pub fn http10_explicit_close_stays_close_test() { 199 213 let buffer = <<"GET / HTTP/1.0\r\nConnection: close\r\n\r\n":utf8>> 200 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 214 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 215 + http1.parse(buffer) 201 216 202 217 assert !metadata.keep_alive 203 218 } 204 219 205 220 pub fn custom_method_test() { 206 221 let buffer = <<"PROPFIND /dav HTTP/1.1\r\nHost: example.com\r\n\r\n":utf8>> 207 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(buffer) 222 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 223 + http1.parse(buffer) 208 224 209 225 assert head.method == http.Other("PROPFIND") 210 226 } 211 227 212 228 pub fn incomplete_request_line_test() { 213 229 let buffer = <<"GET /foo HTTP/1.1\r\n":utf8>> 214 - assert http1.parse(buffer) == http1.Incomplete 230 + assert http1.parse(buffer) == Ok(http1.Incomplete) 215 231 } 216 232 217 233 pub fn incomplete_headers_test() { 218 234 let buffer = <<"GET /foo HTTP/1.1\r\nHost: example.com\r\n":utf8>> 219 - assert http1.parse(buffer) == http1.Incomplete 235 + assert http1.parse(buffer) == Ok(http1.Incomplete) 220 236 } 221 237 222 238 pub fn split_across_reads_test() { 223 239 let first = <<"GET / HTTP/1.1\r\nHo":utf8>> 224 - assert http1.parse(first) == http1.Incomplete 240 + assert http1.parse(first) == Ok(http1.Incomplete) 225 241 226 242 let second = <<"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n":utf8>> 227 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(second) 243 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 244 + http1.parse(second) 228 245 229 246 assert head.path == "/" 230 247 } 231 248 232 249 pub fn bad_request_line_test() { 233 250 let buffer = <<"GET /foo HTTP/9.9\r\n\r\n":utf8>> 234 - assert http1.parse(buffer) == http1.Failed(http1.BadVersion) 251 + assert http1.parse(buffer) == Error(http1.BadVersion) 235 252 } 236 253 237 254 pub fn duplicate_content_length_test() { 238 255 let buffer = << 239 256 "GET / HTTP/1.1\r\nContent-Length: 1\r\nContent-Length: 2\r\n\r\n":utf8, 240 257 >> 241 - assert http1.parse(buffer) == http1.Failed(http1.DuplicateContentLength) 258 + assert http1.parse(buffer) == Error(http1.DuplicateContentLength) 242 259 } 243 260 244 261 pub fn multibyte_utf8_header_value_test() { ··· 246 263 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Name: caf\u{00E9}\r\n\r\n":utf8, 247 264 >> 248 265 249 - let assert http1.Complete(head, _metadata, _remaining) = http1.parse(buffer) 266 + let assert Ok(http1.Complete(head, _metadata, _remaining)) = 267 + http1.parse(buffer) 250 268 251 269 assert head.headers == [#("host", "example.com"), #("x-name", "cafรฉ")] 252 270 } ··· 258 276 "\r\n\r\n":utf8, 259 277 >> 260 278 261 - assert http1.parse(buffer) == http1.Failed(http1.BadHeader) 279 + assert http1.parse(buffer) == Error(http1.BadHeader) 262 280 } 263 281 264 282 pub fn bare_lf_rejected_test() { 265 283 let buffer = <<"GET / HTTP/1.1\nHost: example.com\r\n\r\n":utf8>> 266 - assert http1.parse(buffer) == http1.Failed(http1.BadRequestLine) 284 + assert http1.parse(buffer) == Error(http1.BadRequestLine) 267 285 } 268 286 269 287 pub fn no_query_string_test() { 270 288 let buffer = <<"GET /plain HTTP/1.1\r\nHost: example.com\r\n\r\n":utf8>> 271 289 272 290 assert http1.parse(buffer) 273 - == http1.Complete( 291 + == Ok(http1.Complete( 274 292 http1.Head( 275 293 method: http.Get, 276 294 host: "example.com", ··· 287 305 upgrade: None, 288 306 ), 289 307 <<>>, 290 - ) 308 + )) 291 309 } 292 310 293 311 pub fn websocket_upgrade_requested_test() { ··· 295 313 "GET /ws HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n":utf8, 296 314 >> 297 315 298 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 316 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 317 + http1.parse(buffer) 299 318 300 319 assert metadata.upgrade == Some("websocket") 301 320 } ··· 305 324 "GET /ws HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nUpgrade: WebSocket\r\n\r\n":utf8, 306 325 >> 307 326 308 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 327 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 328 + http1.parse(buffer) 309 329 310 330 assert metadata.upgrade == Some("websocket") 311 331 } ··· 315 335 "GET /h2c HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive, Upgrade\r\nUpgrade: h2c\r\n\r\n":utf8, 316 336 >> 317 337 318 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 338 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 339 + http1.parse(buffer) 319 340 320 341 assert metadata.upgrade == Some("h2c") 321 342 } ··· 325 346 "GET /ws HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\n\r\n":utf8, 326 347 >> 327 348 328 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 349 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 350 + http1.parse(buffer) 329 351 330 352 assert metadata.upgrade == None 331 353 as "Upgrade requires Connection: upgrade to be honored (RFC 9110 ยง7.8)" ··· 336 358 "GET /ws HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n":utf8, 337 359 >> 338 360 339 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 361 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 362 + http1.parse(buffer) 340 363 341 364 assert metadata.upgrade == Some("websocket") 342 365 as "order of Upgrade vs. Connection headers shouldn't matter" ··· 344 367 345 368 pub fn no_upgrade_requested_test() { 346 369 let buffer = <<"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n":utf8>> 347 - let assert http1.Complete(_head, metadata, _remaining) = http1.parse(buffer) 370 + let assert Ok(http1.Complete(_head, metadata, _remaining)) = 371 + http1.parse(buffer) 348 372 349 373 assert metadata.upgrade == None 350 374 }