From b585c8f9010936722eb04aca35103cf5818d7317 Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Fri, 21 Jun 2024 12:26:32 +0200 Subject: [PATCH] Kebabify. (#141) --- examples/client-get-tls-custom.toit | 6 +- examples/client-post.toit | 2 +- examples/local-ws-client.toit | 10 +- examples/server-tls.toit | 12 +- examples/server.toit | 14 +- examples/ws-talk-to-self.toit | 48 +- src/chunked.toit | 36 +- src/client.toit | 532 +++++++++--------- src/connection.toit | 264 ++++----- src/headers.toit | 60 +- src/http.toit | 4 +- src/request.toit | 24 +- src/response.toit | 10 +- src/server.toit | 194 +++---- src/status-codes.toit | 322 +++++------ src/web-socket.toit | 448 +++++++-------- tests/cat.toit | 2 +- tests/google-404-test.toit | 6 +- tests/google-test.toit | 10 +- tests/headers-test.toit | 38 +- tests/http-finalizer-test.toit | 58 +- tests/http-overload-test.toit | 50 +- tests/http-standalone-test.toit | 276 ++++----- tests/parse-url-test.toit | 426 +++++++------- tests/redirect-test-httpbin.toit | 114 ++-- tests/websocket-client-test-paused.toit | 26 +- .../websocket-standalone-semaphore-test.toit | 86 +-- tests/websocket-standalone-test.toit | 96 ++-- 28 files changed, 1587 insertions(+), 1587 deletions(-) diff --git a/examples/client-get-tls-custom.toit b/examples/client-get-tls-custom.toit index b2b4d89..cb5f056 100644 --- a/examples/client-get-tls-custom.toit +++ b/examples/client-get-tls-custom.toit @@ -9,15 +9,15 @@ import net.x509 main: network := net.open client := http.Client.tls network - --root_certificates=[SERVER_CERT] + --root-certificates=[SERVER-CERT] response := client.get "localhost:8080" "/json" while data := response.body.read: - print data.to_string + print data.to-string client.close -SERVER_CERT ::= x509.Certificate.parse """ +SERVER-CERT ::= x509.Certificate.parse """ -----BEGIN CERTIFICATE----- MIIDkzCCAnugAwIBAgIUb3nSgGzXBdgsDhg8shods8EHszAwDQYJKoZIhvcNAQEL BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM diff --git a/examples/client-post.toit b/examples/client-post.toit index c14130c..2591e24 100644 --- a/examples/client-post.toit +++ b/examples/client-post.toit @@ -18,6 +18,6 @@ main: "foo": 42, "bar": 499, } - data := json.decode_stream response.body + data := json.decode-stream response.body client.close print data diff --git a/examples/local-ws-client.toit b/examples/local-ws-client.toit index 79eacae..98029a7 100644 --- a/examples/local-ws-client.toit +++ b/examples/local-ws-client.toit @@ -14,24 +14,24 @@ main: network := net.open client := http.Client network - websocket := client.web_socket --host="localhost" --port=8000 + websocket := client.web-socket --host="localhost" --port=8000 task --background:: print "Message received: '$websocket.receive'" - while reader := websocket.start_receiving: + while reader := websocket.start-receiving: size := 0 data := #[] while chunk := reader.read: data += chunk - if reader.is_text: - print "Message received: '$data.to_string'" + if reader.is-text: + print "Message received: '$data.to-string'" else: print "Message received: size $data.size." websocket.send "Hello, World!" websocket.send "Hello, World!" websocket.send "Hello, World!" - writer := websocket.start_sending + writer := websocket.start-sending writer.write "Hello, World!" writer.write "Now is the time for all good men" writer.write "to come to the aid of the party." diff --git a/examples/server-tls.toit b/examples/server-tls.toit index fa4e815..9b11517 100644 --- a/examples/server-tls.toit +++ b/examples/server-tls.toit @@ -14,7 +14,7 @@ ITEMS := ["FOO", "BAR", "BAZ"] main: network := net.open server := http.Server.tls - --certificate=TLS_SERVER_CERT + --certificate=TLS-SERVER-CERT server.listen network 8080:: | request/http.RequestIncoming writer/http.ResponseWriter | if request.path == "/empty": else if request.path == "/json": @@ -27,17 +27,17 @@ main: writer.out.write "hello\n" else if request.path == "/500": writer.headers.set "Content-Type" "text/plain" - writer.write_headers 500 + writer.write-headers 500 writer.out.write "hello\n" else if request.path == "/599": writer.headers.set "Content-Type" "text/plain" - writer.write_headers 599 --message="Dazed and confused" + writer.write-headers 599 --message="Dazed and confused" writer.close // Self-signed certificate with "localhost" Common-Name. -TLS_SERVER_CERT ::= tls.Certificate SERVER_CERT SERVER_KEY +TLS-SERVER-CERT ::= tls.Certificate SERVER-CERT SERVER-KEY -SERVER_CERT ::= x509.Certificate.parse """ +SERVER-CERT ::= x509.Certificate.parse """ -----BEGIN CERTIFICATE----- MIIDkzCCAnugAwIBAgIUb3nSgGzXBdgsDhg8shods8EHszAwDQYJKoZIhvcNAQEL BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM @@ -62,7 +62,7 @@ eLYDrha/bg== -----END CERTIFICATE----- """ -SERVER_KEY ::= """ +SERVER-KEY ::= """ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0uBjd9ybX8chE +cAfuuxZ/Ifhtay1M/UtcgzVnfW+/dxg3KDO3aY2OiHYkB5LrhS3jTcyzhOzS07R diff --git a/examples/server.toit b/examples/server.toit index 5e228b0..7b43392 100644 --- a/examples/server.toit +++ b/examples/server.toit @@ -11,10 +11,10 @@ ITEMS := ["FOO", "BAR", "BAZ"] main: network := net.open // Listen on a free port. - tcp_socket := network.tcp_listen 0 - print "Server on http://localhost:$tcp_socket.local_address.port/" + tcp-socket := network.tcp-listen 0 + print "Server on http://localhost:$tcp-socket.local-address.port/" server := http.Server - server.listen tcp_socket:: | request/http.RequestIncoming writer/http.ResponseWriter | + server.listen tcp-socket:: | request/http.RequestIncoming writer/http.ResponseWriter | resource := request.query.resource if resource == "/empty": else if resource == "/": @@ -36,16 +36,16 @@ main: json.encode ITEMS else if resource == "/headers": writer.headers.set "Http-Test-Header" "going strong" - writer.write_headers 200 + writer.write-headers 200 else if resource == "/500": writer.headers.set "Content-Type" "text/plain" - writer.write_headers 500 + writer.write-headers 500 writer.out.write "Failure\n" else if resource == "/599": writer.headers.set "Content-Type" "text/plain" - writer.write_headers 599 --message="Dazed and confused" + writer.write-headers 599 --message="Dazed and confused" else: writer.headers.set "Content-Type" "text/plain" - writer.write_headers 404 + writer.write-headers 404 writer.out.write "Not found\n" writer.close diff --git a/examples/ws-talk-to-self.toit b/examples/ws-talk-to-self.toit index 6fa880b..5e5943e 100644 --- a/examples/ws-talk-to-self.toit +++ b/examples/ws-talk-to-self.toit @@ -12,58 +12,58 @@ The server simply echos back any incoming message. main: network := net.open - port := start_server network - run_client network port + port := start-server network + run-client network port -run_client network port/int -> none: +run-client network port/int -> none: client := http.Client network - web_socket := client.web_socket --host="localhost" --port=port + web-socket := client.web-socket --host="localhost" --port=port - task --background:: client_reading web_socket + task --background:: client-reading web-socket - client_sending web_socket + client-sending web-socket sleep --ms=200 -client_sending web_socket -> none: - web_socket.send "Hello, World!" - web_socket.send "Hello, World!" - web_socket.send "Hello, World!" - writer := web_socket.start_sending +client-sending web-socket -> none: + web-socket.send "Hello, World!" + web-socket.send "Hello, World!" + web-socket.send "Hello, World!" + writer := web-socket.start-sending writer.write "Hello, World!" writer.write "Now is the time for all good men" writer.write "to come to the aid of the party." writer.close - web_socket.send #[3, 4, 5] + web-socket.send #[3, 4, 5] -client_reading web_socket -> none: +client-reading web-socket -> none: // Each message can come with its own reader, which can be // useful if messages are large. - while reader := web_socket.start_receiving: + while reader := web-socket.start-receiving: size := 0 - text := reader.is_text ? "" : null + text := reader.is-text ? "" : null while ba := reader.read: - if text: text += ba.to_string + if text: text += ba.to-string size += ba.size if text: print "Message received: '$text'" else: print "Message received: size $size." -start_server network -> int: - server_socket := network.tcp_listen 0 - port := server_socket.local_address.port +start-server network -> int: + server-socket := network.tcp-listen 0 + port := server-socket.local-address.port server := http.Server task --background:: - server.listen server_socket:: | request/http.RequestIncoming response_writer/http.ResponseWriter | + server.listen server-socket:: | request/http.RequestIncoming response-writer/http.ResponseWriter | if request.path == "/": - web_socket := server.web_socket request response_writer + web-socket := server.web-socket request response-writer // The server end of the web socket just echoes back what it gets. // Here we don't use a new reader for each message, but just get // the message with a single call to `receive`. - while data := web_socket.receive: + while data := web-socket.receive: print "Got $data" - web_socket.send data + web-socket.send data else: - response_writer.write_headers http.STATUS_NOT_FOUND --message="Not Found" + response-writer.write-headers http.STATUS-NOT-FOUND --message="Not Found" return port diff --git a/src/chunked.toit b/src/chunked.toit index f9c2c9b..eb4f275 100644 --- a/src/chunked.toit +++ b/src/chunked.toit @@ -26,19 +26,19 @@ This is an adapter that converts a chunked stream (RFC 2616) to a stream of class ChunkedReader_ extends io.Reader: connection_/Connection? := null reader_/io.Reader? := ? - left_in_chunk_ := 0 // How much more raw data we are waiting for before the next size line. + left-in-chunk_ := 0 // How much more raw data we are waiting for before the next size line. constructor .connection_ .reader_: /** Returns the underlying reader, which may have buffered up data. - The ChunkedReader is unusable after a called to $detach_reader. + The ChunkedReader is unusable after a called to $detach-reader. Deprecated. */ // TODO(florian): remove already now? - detach_reader -> io.Reader: + detach-reader -> io.Reader: r := reader_ reader_ = null return r @@ -47,29 +47,29 @@ class ChunkedReader_ extends io.Reader: while true: if not connection_: return null - if left_in_chunk_ > 0: - result := reader_.read --max_size=left_in_chunk_ - if not result: throw io.Reader.UNEXPECTED_END_OF_READER - left_in_chunk_ -= result.size - if left_in_chunk_ == 0: + if left-in-chunk_ > 0: + result := reader_.read --max-size=left-in-chunk_ + if not result: throw io.Reader.UNEXPECTED-END-OF-READER + left-in-chunk_ -= result.size + if left-in-chunk_ == 0: expect_ '\r' expect_ '\n' return result - raw_length := reader_.read_bytes_up_to '\r' + raw-length := reader_.read-bytes-up-to '\r' expect_ '\n' - left_in_chunk_ = int.parse raw_length --radix=16 + left-in-chunk_ = int.parse raw-length --radix=16 // End is indicated by a zero hex length. - if left_in_chunk_ == 0: + if left-in-chunk_ == 0: expect_ '\r' expect_ '\n' - connection_.reading_done_ this + connection_.reading-done_ this connection_ = null expect_ byte/int: - b := reader_.peek_byte 0 + b := reader_.peek-byte 0 if b != byte: throw "PROTOCOL_ERROR" reader_.skip 1 @@ -90,13 +90,13 @@ class ChunkedWriter_ extends io.CloseableWriter: constructor .connection_ .writer_: // We don't know the amount of data ahead of time, so it may already be done. - is_done_ -> bool: + is-done_ -> bool: return true - try_write_ data/io.Data from/int to/int -> int: + try-write_ data/io.Data from/int to/int -> int: size := to - from if size == 0: return 0 - write_header_ size + write-header_ size writer_.write data from to // Always writes all data. // Once we've sent the data, the other side might conclude that // they have gotten everything they need, so we don't want to throw @@ -113,10 +113,10 @@ class ChunkedWriter_ extends io.CloseableWriter: writer_.write "0" writer_.write CRLF_ writer_.write CRLF_ - connection_.writing_done_ this + connection_.writing-done_ this connection_ = null - write_header_ length/int: + write-header_ length/int: writer_.write length.stringify 16 writer_.write CRLF_ diff --git a/src/client.toit b/src/client.toit index 500cb7b..14b61fd 100644 --- a/src/client.toit +++ b/src/client.toit @@ -14,8 +14,8 @@ import .headers import .method import .request import .response -import .status_codes -import .web_socket +import .status-codes +import .web-socket /** An HTTP v1.1 client. @@ -84,16 +84,16 @@ class Client: /** The maximum number of redirects to follow if 'follow_redirect' is true for $get and $post requests. */ - static MAX_REDIRECTS /int ::= 20 + static MAX-REDIRECTS /int ::= 20 interface_/tcp.Interface - use_tls_by_default_ ::= false + use-tls-by-default_ ::= false certificate_/tls.Certificate? ::= null - server_name_/string? ::= null - root_certificates_/List ::= [] + server-name_/string? ::= null + root-certificates_/List ::= [] connection_/Connection? := null - security_store_/SecurityStore + security-store_/SecurityStore /** Constructs a new client instance over the given interface. @@ -101,7 +101,7 @@ class Client: overridden by a redirect or a URI specifying a secure scheme. Therefore it can be meaningful to provide certificate roots despite the insecure default. - If the client is used for secure connections, the $root_certificates must + If the client is used for secure connections, the $root-certificates must contain the root certificate of the server. A client will try to keep a connection open to the last server it contacted, in the hope that the next request will connect to the same @@ -114,39 +114,39 @@ class Client: Use `net.open` to obtain an interface. */ constructor .interface_ - --root_certificates/List=[] - --security_store/SecurityStore=SecurityStoreInMemory: - security_store_ = security_store - root_certificates_ = root_certificates - add_finalizer this:: this.finalize_ + --root-certificates/List=[] + --security-store/SecurityStore=SecurityStoreInMemory: + security-store_ = security-store + root-certificates_ = root-certificates + add-finalizer this:: this.finalize_ /** Constructs a new client. The client will default to a secure HTTPS connection, but this can be overridden by a redirect or a URI specifying an insecure scheme. - The $root_certificates must contain the root certificate of the server. + The $root-certificates must contain the root certificate of the server. See the `certificate_roots` package for common roots: https://pkg.toit.io/package/github.com%2Ftoitware%2Ftoit-cert-roots A client $certificate can be specified for the rare case where the client authenticates itself. - The $server_name can be specified for verifying the TLS certificate. This is + The $server-name can be specified for verifying the TLS certificate. This is for the rare case where we wish to verify the TLS connections with a different server name from the one used to establish the connection. */ constructor.tls .interface_ - --root_certificates/List=[] - --server_name/string?=null + --root-certificates/List=[] + --server-name/string?=null --certificate/tls.Certificate?=null - --security_store/SecurityStore=SecurityStoreInMemory: - security_store_ = security_store - use_tls_by_default_ = true - root_certificates_ = root_certificates - server_name_ = server_name + --security-store/SecurityStore=SecurityStoreInMemory: + security-store_ = security-store + use-tls-by-default_ = true + root-certificates_ = root-certificates + server-name_ = server-name certificate_ = certificate - add_finalizer this:: this.finalize_ + add-finalizer this:: this.finalize_ /** - Variant of $(new_request method --host). + Variant of $(new-request method --host). Instead of specifying host and path, this variant lets you specify a $uri, of the form "http://www.example.com:1080/path/to/file#fragment". @@ -154,13 +154,13 @@ class Client: A URI that starts with "http" (no "s") will disable TLS even if the Client was created as a TLS client. */ - new_request method/string -> RequestOutgoing + new-request method/string -> RequestOutgoing --uri/string --headers/Headers?=null: - parsed := parse_ uri --web_socket=false + parsed := parse_ uri --web-socket=false request := null - try_to_reuse_ parsed: | connection | - request = connection.new_request method parsed.path headers + try-to-reuse_ parsed: | connection | + request = connection.new-request method parsed.path headers return request /** @@ -170,25 +170,25 @@ class Client: The returned $RequestOutgoing should be sent with $RequestOutgoing.send. - The $query_parameters argument is used to encode key-value parameters in the + The $query-parameters argument is used to encode key-value parameters in the request path using the ?key=value&key2=value2&... format. - Do not use $query_parameters for a $POST request. See instead $post_form, + Do not use $query-parameters for a $POST request. See instead $post-form, which encodes the key-value pairs in the body as expected for a POST request. */ - new_request method/string -> RequestOutgoing + new-request method/string -> RequestOutgoing --host/string --port/int?=null --path/string="/" - --query_parameters/Map?=null + --query-parameters/Map?=null --headers/Headers?=null - --use_tls/bool?=null: - if method == POST and query_parameters: throw "INVALID_ARGUMENT" - parsed := parse_ host port path query_parameters use_tls --web_socket=false + --use-tls/bool?=null: + if method == POST and query-parameters: throw "INVALID_ARGUMENT" + parsed := parse_ host port path query-parameters use-tls --web-socket=false request := null - try_to_reuse_ parsed: | connection | - request = connection.new_request method parsed.path headers + try-to-reuse_ parsed: | connection | + request = connection.new-request method parsed.path headers return request /** @@ -207,22 +207,22 @@ class Client: If neither is specified then the default port is used. - Deprecated. Use $(new_request method --host) instead. + Deprecated. Use $(new-request method --host) instead. */ - new_request method/string host/string --port/int?=null path/string --headers/Headers?=null -> RequestOutgoing: + new-request method/string host/string --port/int?=null path/string --headers/Headers?=null -> RequestOutgoing: parsed := ParsedUri_.private_ - --scheme=(use_tls_by_default_ ? "https" : "http") + --scheme=(use-tls-by-default_ ? "https" : "http") --host=host --port=port --path=path - --parse_port_in_host=true - if not parsed.scheme.starts_with "http": throw "INVALID_SCHEME" + --parse-port-in-host=true + if not parsed.scheme.starts-with "http": throw "INVALID_SCHEME" request := null - try_to_reuse_ parsed: | connection | - request = connection.new_request method parsed.path headers + try-to-reuse_ parsed: | connection | + request = connection.new-request method parsed.path headers return request - static starts_with_ignore_case_ str/string needle/string -> bool: + static starts-with-ignore-case_ str/string needle/string -> bool: if str.size < needle.size: return false for i := 0; i < needle.size; i++: a := str[i] @@ -244,9 +244,9 @@ class Client: get -> Response --uri/string --headers/Headers?=null - --follow_redirects/bool=true: - parsed := parse_ uri --web_socket=false - return get_ parsed headers --follow_redirects=follow_redirects + --follow-redirects/bool=true: + parsed := parse_ uri --web-socket=false + return get_ parsed headers --follow-redirects=follow-redirects /** Fetches data for $path on the given server ($host, $port) with a GET request. @@ -254,12 +254,12 @@ class Client: If no port is specified then the default port is used. The $host is not parsed for a port number (but see $(get --uri)). - If $follow_redirects is true, follows redirects (when the status code is 3xx). + If $follow-redirects is true, follows redirects (when the status code is 3xx). - The $use_tls argument can be used to override the default TLS usage of the + The $use-tls argument can be used to override the default TLS usage of the client. - The $query_parameters argument is used to encode key-value parameters in the + The $query-parameters argument is used to encode key-value parameters in the request path using the ?key=value&key2=value2&... format. */ get -> Response @@ -267,11 +267,11 @@ class Client: --port/int?=null --path/string="/" --headers/Headers?=null - --query_parameters/Map?=null - --follow_redirects/bool=true - --use_tls/bool?=null: - parsed := parse_ host port path query_parameters use_tls --web_socket=false - return get_ parsed headers --follow_redirects=follow_redirects + --query-parameters/Map?=null + --follow-redirects/bool=true + --use-tls/bool?=null: + parsed := parse_ host port path query-parameters use-tls --web-socket=false + return get_ parsed headers --follow-redirects=follow-redirects /** Fetches data at $path from the given server ($host, $port) using the $GET method. @@ -285,43 +285,43 @@ class Client: If neither is specified then the default port is used. - If $follow_redirects is true, follows redirects (when the status code is 3xx). + If $follow-redirects is true, follows redirects (when the status code is 3xx). */ - get host/string --port/int?=null path/string --headers/Headers?=null --follow_redirects/bool=true --use_tls/bool=use_tls_by_default_ -> Response: + get host/string --port/int?=null path/string --headers/Headers?=null --follow-redirects/bool=true --use-tls/bool=use-tls-by-default_ -> Response: if headers and headers.contains "Transfer-Encoding": throw "INVALID_ARGUMENT" if headers and headers.contains "Host": throw "INVALID_ARGUMENT" parsed := ParsedUri_.private_ - --scheme=(use_tls ? "https" : "http") + --scheme=(use-tls ? "https" : "http") --host=host --port=port --path=path - --parse_port_in_host - return get_ parsed headers --follow_redirects=follow_redirects + --parse-port-in-host + return get_ parsed headers --follow-redirects=follow-redirects - get_ parsed/ParsedUri_ headers/Headers? --follow_redirects/bool -> Response: - MAX_REDIRECTS.repeat: + get_ parsed/ParsedUri_ headers/Headers? --follow-redirects/bool -> Response: + MAX-REDIRECTS.repeat: response/Response? := null - try_to_reuse_ parsed: | connection | - request := connection.new_request GET parsed.path headers + try-to-reuse_ parsed: | connection | + request := connection.new-request GET parsed.path headers response = request.send - if follow_redirects and - (is_regular_redirect_ response.status_code - or response.status_code == STATUS_SEE_OTHER): - parsed = get_location_ response parsed + if follow-redirects and + (is-regular-redirect_ response.status-code + or response.status-code == STATUS-SEE-OTHER): + parsed = get-location_ response parsed continue.repeat else: return response throw "Too many redirects" - get_location_ response/Response previous/ParsedUri_ -> ParsedUri_: + get-location_ response/Response previous/ParsedUri_ -> ParsedUri_: location := response.headers.single "Location" return ParsedUri_.parse_ location --previous=previous /** - Variant of $(web_socket --host). + Variant of $(web-socket --host). Instead of specifying host and path, this variant lets you specify a $uri, of the form "ws://www.example.com:1080/path/to/file#fragment". @@ -329,52 +329,52 @@ class Client: A URI that starts with "ws:" (not "wss:") will disable TLS even if the Client was created as a TLS client. */ - web_socket -> WebSocket + web-socket -> WebSocket --uri/string --headers/Headers?=null - --follow_redirects/bool=true: - parsed := parse_ uri --web_socket - return web_socket_ parsed headers follow_redirects + --follow-redirects/bool=true: + parsed := parse_ uri --web-socket + return web-socket_ parsed headers follow-redirects /** Makes an HTTP/HTTPS connection to the given server ($host, $port), then immediately upgrades to a $WebSocket connection with the given $path. If no port is specified then the default port is used. The $host is not - parsed for a port number (but see $(web_socket --uri)). + parsed for a port number (but see $(web-socket --uri)). - The $use_tls argument can be used to override the default TLS usage of the + The $use-tls argument can be used to override the default TLS usage of the client. - The $query_parameters argument is used to encode key-value parameters in the + The $query-parameters argument is used to encode key-value parameters in the request path using the ?key=value&key2=value2&... format. */ - web_socket -> WebSocket + web-socket -> WebSocket --host/string --port/int?=null --path/string="/" --headers/Headers?=null - --query_parameters/Map?=null - --follow_redirects/bool=true - --use_tls/bool?=null: - parsed := parse_ host port path query_parameters use_tls --web_socket - return web_socket_ parsed headers follow_redirects + --query-parameters/Map?=null + --follow-redirects/bool=true + --use-tls/bool?=null: + parsed := parse_ host port path query-parameters use-tls --web-socket + return web-socket_ parsed headers follow-redirects - web_socket_ parsed/ParsedUri_ headers/Headers? follow_redirects/bool -> WebSocket: + web-socket_ parsed/ParsedUri_ headers/Headers? follow-redirects/bool -> WebSocket: headers = headers ? headers.copy : Headers - MAX_REDIRECTS.repeat: - nonce := WebSocket.add_client_upgrade_headers_ headers + MAX-REDIRECTS.repeat: + nonce := WebSocket.add-client-upgrade-headers_ headers response/Response? := null - try_to_reuse_ parsed: | connection | - request/RequestOutgoing := connection.new_request GET parsed.path headers + try-to-reuse_ parsed: | connection | + request/RequestOutgoing := connection.new-request GET parsed.path headers response = request.send - if follow_redirects and - (is_regular_redirect_ response.status_code - or response.status_code == STATUS_SEE_OTHER): - parsed = get_location_ response parsed + if follow-redirects and + (is-regular-redirect_ response.status-code + or response.status-code == STATUS-SEE-OTHER): + parsed = get-location_ response parsed continue.repeat else: - WebSocket.check_client_upgrade_response_ response nonce + WebSocket.check-client-upgrade-response_ response nonce connection := connection_ connection_ = null // Can't reuse it any more. return WebSocket connection.detach --client @@ -386,7 +386,7 @@ class Client: This includes `Content-Length`, or `Transfer_Encoding`. */ - clear_payload_headers_ headers/Headers: + clear-payload-headers_ headers/Headers: headers.remove "Content-Length" headers.remove "Content-Type" headers.remove "Content-Encoding" @@ -406,10 +406,10 @@ class Client: post data/ByteArray -> Response --uri/string --headers/Headers?=null - --content_type/string?=null - --follow_redirects/bool=true: - parsed := parse_ uri --web_socket=false - return post_ data parsed --headers=headers --content_type=content_type --follow_redirects=follow_redirects + --content-type/string?=null + --follow-redirects/bool=true: + parsed := parse_ uri --web-socket=false + return post_ data parsed --headers=headers --content-type=content-type --follow-redirects=follow-redirects /** Posts data on $path for the given server ($host, $port) using the $POST method. @@ -422,17 +422,17 @@ class Client: parsed for a port number, but this feature will not be in the next major version of this library. See $(post data --uri). - If $content_type is not null, sends the content type header with that value. + If $content-type is not null, sends the content type header with that value. If the content type is given, then the $headers must not contain any "Content-Type" entry. - If $follow_redirects is true, follows redirects (when the status code is 3xx). + If $follow-redirects is true, follows redirects (when the status code is 3xx). - The $use_tls argument can be used to override the default TLS usage of the + The $use-tls argument can be used to override the default TLS usage of the client. # Advanced If the data can be generated dynamically, it's more efficient to create a new - request with $new_request and to set the $RequestOutgoing.body to a reader + request with $new-request and to set the $RequestOutgoing.body to a reader that produces the data only when needed. */ post data/ByteArray -> Response @@ -440,78 +440,78 @@ class Client: --port/int?=null --path/string="/" --headers/Headers?=null - --content_type/string?=null - --follow_redirects/bool=true - --use_tls/bool?=null: - parsed := parse_ host port path null use_tls --web_socket=false - return post_ data parsed --headers=headers --content_type=content_type --follow_redirects=follow_redirects - - parse_ uri/string --web_socket/bool -> ParsedUri_: - default_scheme := use_tls_by_default_ - ? (web_socket ? "wss" : "https") - : (web_socket ? "ws" : "http") - result := ParsedUri_.parse_ uri --default_scheme=default_scheme - if web_socket == true and result.scheme.starts_with "http": throw "INVALID_SCHEME" - if web_socket == false and result.scheme.starts_with "ws": throw "INVALID_SCHEME" + --content-type/string?=null + --follow-redirects/bool=true + --use-tls/bool?=null: + parsed := parse_ host port path null use-tls --web-socket=false + return post_ data parsed --headers=headers --content-type=content-type --follow-redirects=follow-redirects + + parse_ uri/string --web-socket/bool -> ParsedUri_: + default-scheme := use-tls-by-default_ + ? (web-socket ? "wss" : "https") + : (web-socket ? "ws" : "http") + result := ParsedUri_.parse_ uri --default-scheme=default-scheme + if web-socket == true and result.scheme.starts-with "http": throw "INVALID_SCHEME" + if web-socket == false and result.scheme.starts-with "ws": throw "INVALID_SCHEME" return result /// Rather than verbose named args, this private method has the args in the /// order in which they appear in a URI. - parse_ host/string port/int? path/string query_parameters/Map? use_tls/bool? --web_socket/bool -> ParsedUri_: - default_scheme := (use_tls == null ? use_tls_by_default_ : use_tls) - ? (web_socket ? "wss" : "https") - : (web_socket ? "ws" : "http") - if query_parameters and not query_parameters.is_empty: + parse_ host/string port/int? path/string query-parameters/Map? use-tls/bool? --web-socket/bool -> ParsedUri_: + default-scheme := (use-tls == null ? use-tls-by-default_ : use-tls) + ? (web-socket ? "wss" : "https") + : (web-socket ? "ws" : "http") + if query-parameters and not query-parameters.is-empty: path += "?" - path += (url_encode_ query_parameters).to_string + path += (url-encode_ query-parameters).to-string return ParsedUri_.private_ - --scheme=default_scheme + --scheme=default-scheme --host=host --port=port --path=path - --parse_port_in_host=false + --parse-port-in-host=false post_ data/ByteArray parsed/ParsedUri_ -> Response --headers/Headers? - --content_type/string? - --follow_redirects/bool: + --content-type/string? + --follow-redirects/bool: headers = headers ? headers.copy : Headers if headers.single "Transfer-Encoding": throw "INVALID_ARGUMENT" if headers.single "Host": throw "INVALID_ARGUMENT" - if content_type: - existing_content_type := headers.single "Content-Type" - if existing_content_type: + if content-type: + existing-content-type := headers.single "Content-Type" + if existing-content-type: // Keep the existing entry, but check that the content is the same. - if existing_content_type.to_ascii_lower != content_type.to_ascii_lower: + if existing-content-type.to-ascii-lower != content-type.to-ascii-lower: throw "INVALID_ARGUMENT" else: - headers.set "Content-Type" content_type + headers.set "Content-Type" content-type - MAX_REDIRECTS.repeat: + MAX-REDIRECTS.repeat: response := null - try_to_reuse_ parsed: | connection | - request := connection.new_request POST parsed.path headers + try-to-reuse_ parsed: | connection | + request := connection.new-request POST parsed.path headers request.body = io.Reader data response = request.send - if follow_redirects and is_regular_redirect_ response.status_code: - parsed = get_location_ response parsed + if follow-redirects and is-regular-redirect_ response.status-code: + parsed = get-location_ response parsed continue.repeat - else if follow_redirects and response.status_code == STATUS_SEE_OTHER: - parsed = get_location_ response parsed + else if follow-redirects and response.status-code == STATUS-SEE-OTHER: + parsed = get-location_ response parsed headers = headers.copy - clear_payload_headers_ headers - return get_ parsed headers --follow_redirects=true // Switch from POST to GET. + clear-payload-headers_ headers + return get_ parsed headers --follow-redirects=true // Switch from POST to GET. else: return response throw "Too many redirects" /** - Variant of $(post_json object --host). + Variant of $(post-json object --host). Instead of specifying host and path, this variant lets you specify a $uri, of the form "http://www.example.com:1080/path/to/file#fragment". @@ -519,14 +519,14 @@ class Client: A URI that starts with "http" (no "s") will disable TLS even if the Client was created as a TLS client. */ - post_json object/any -> Response + post-json object/any -> Response --uri/string --headers/Headers?=null - --follow_redirects/bool=true: + --follow-redirects/bool=true: // TODO(florian): we should create the json dynamically. encoded := json.encode object - parsed := parse_ uri --web_socket=false - return post_ encoded parsed --headers=headers --content_type="application/json" --follow_redirects=follow_redirects + parsed := parse_ uri --web-socket=false + return post_ encoded parsed --headers=headers --content-type="application/json" --follow-redirects=follow-redirects /** Posts the $object on $path for the given server ($host, $port) using the $POST method. @@ -541,24 +541,24 @@ class Client: If no port is specified then the default port is used. The $host is parsed for a port number, but this feature will not be in the next major - version of this library. See $(post_json object --uri). + version of this library. See $(post-json object --uri). - If $follow_redirects is true, follows redirects (when the status code is 3xx). + If $follow-redirects is true, follows redirects (when the status code is 3xx). */ - post_json object/any -> Response + post-json object/any -> Response --host/string --port/int?=null --path/string="/" --headers/Headers?=null - --follow_redirects/bool=true - --use_tls/bool?=null: + --follow-redirects/bool=true + --use-tls/bool?=null: // TODO(florian): we should create the json dynamically. encoded := json.encode object - parsed := parse_ host port path null use_tls --web_socket=false - return post_ encoded parsed --headers=headers --content_type="application/json" --follow_redirects=follow_redirects + parsed := parse_ host port path null use-tls --web-socket=false + return post_ encoded parsed --headers=headers --content-type="application/json" --follow-redirects=follow-redirects /** - Variant of $(post_form map --host). + Variant of $(post-form map --host). Instead of specifying host and path, this variant lets you specify a $uri, of the form "http://www.example.com:1080/path/to/file#fragment". @@ -566,12 +566,12 @@ class Client: A URI that starts with "http" (no "s") will disable TLS even if the Client was created as a TLS client. */ - post_form map/Map -> Response + post-form map/Map -> Response --uri/string --headers/Headers?=null - --follow_redirects/bool=true: - parsed := parse_ uri --web_socket=false - return post_form_ map parsed --headers=headers --follow_redirects=follow_redirects + --follow-redirects/bool=true: + parsed := parse_ uri --web-socket=false + return post-form_ map parsed --headers=headers --follow-redirects=follow-redirects /** Posts the $map on $path for the given server ($host, $port) using the $POST method. @@ -592,21 +592,21 @@ class Client: If no port is specified then the default port is used. The $host is parsed for a port number, but this feature will not be in the next major - version of this library. See $(post_form map --uri). + version of this library. See $(post-form map --uri). - If $follow_redirects is true, follows redirects (when the status code is 3xx). + If $follow-redirects is true, follows redirects (when the status code is 3xx). */ - post_form map/Map -> Response + post-form map/Map -> Response --host/string --port/int?=null --path/string="/" --headers/Headers?=null - --follow_redirects/bool=true - --use_tls/bool?=null: - parsed := parse_ host port path null use_tls --web_socket=false - return post_form_ map parsed --headers=headers --follow_redirects=follow_redirects + --follow-redirects/bool=true + --use-tls/bool?=null: + parsed := parse_ host port path null use-tls --web-socket=false + return post-form_ map parsed --headers=headers --follow-redirects=follow-redirects - url_encode_ map/Map -> ByteArray: + url-encode_ map/Map -> ByteArray: buffer := io.Buffer first := true map.do: | key value | @@ -625,14 +625,14 @@ class Client: url.encode value return buffer.bytes - post_form_ map/Map parsed/ParsedUri_ -> Response + post-form_ map/Map parsed/ParsedUri_ -> Response --headers/Headers? - --follow_redirects/bool=true: - encoded := url_encode_ map + --follow-redirects/bool=true: + encoded := url-encode_ map - return post_ encoded parsed --headers=headers --content_type="application/x-www-form-urlencoded" --follow_redirects=follow_redirects + return post_ encoded parsed --headers=headers --content-type="application/x-www-form-urlencoded" --follow-redirects=follow-redirects - try_to_reuse_ location/ParsedUri_ [block]: + try-to-reuse_ location/ParsedUri_ [block]: // We try to reuse an existing connection to a server, but a web server can // lose interest in a long-running connection at any time and close it, so // if it fails we need to reconnect. @@ -643,14 +643,14 @@ class Client: // implementation cannot currently fall back from an attempt with session // info to a from-scratch connection attempt. for attempt := 0; attempt < 3; attempt++: - reused := ensure_connection_ location - catch --unwind=(: attempt == 2 or ((not reused or not is_close_exception_ it) and it != "RESUME_FAILED")): + reused := ensure-connection_ location + catch --unwind=(: attempt == 2 or ((not reused or not is-close-exception_ it) and it != "RESUME_FAILED")): sock := connection_.socket_ if sock is tls.Socket and not reused: - tls_socket := sock as tls.Socket - use_stored_session_state_ tls_socket location - tls_socket.handshake - update_stored_session_state_ tls_socket location + tls-socket := sock as tls.Socket + use-stored-session-state_ tls-socket location + tls-socket.handshake + update-stored-session-state_ tls-socket location block.call connection_ success = true return @@ -658,42 +658,42 @@ class Client: connection_.close connection_ = null // Don't try again with session data if the connection attempt failed. - if not reused: security_store_.delete_session_data location.host location.port + if not reused: security-store_.delete-session-data location.host location.port finally: if not success: - security_store_.delete_session_data location.host location.port + security-store_.delete-session-data location.host location.port if connection_: connection_.close connection_ = null - use_stored_session_state_ tls_socket/tls.Socket location/ParsedUri_: - if data := security_store_.retrieve_session_data location.host location.port: - tls_socket.session_state = data + use-stored-session-state_ tls-socket/tls.Socket location/ParsedUri_: + if data := security-store_.retrieve-session-data location.host location.port: + tls-socket.session-state = data - update_stored_session_state_ tls_socket/tls.Socket location/ParsedUri_: - state := tls_socket.session_state + update-stored-session-state_ tls-socket/tls.Socket location/ParsedUri_: + state := tls-socket.session-state if state: - security_store_.store_session_data location.host location.port state + security-store_.store-session-data location.host location.port state else: - security_store_.delete_session_data location.host location.port + security-store_.delete-session-data location.host location.port /// Returns true if the connection was reused. - ensure_connection_ location/ParsedUri_ -> bool: - if connection_ and connection_.is_open_: - if location.can_reuse_connection connection_.location_: + ensure-connection_ location/ParsedUri_ -> bool: + if connection_ and connection_.is-open_: + if location.can-reuse-connection connection_.location_: connection_.drain_ // Remove any remnants of previous requests. return true // Hostname etc. didn't match so we need a new connection. connection_.close connection_ = null - socket/tcp.Socket := interface_.tcp_connect location.host location.port - if location.use_tls: + socket/tcp.Socket := interface_.tcp-connect location.host location.port + if location.use-tls: // Wrap the socket in TLS. socket = tls.Socket.client socket - --server_name=server_name_ or location.host + --server-name=server-name_ or location.host --certificate=certificate_ - --root_certificates=root_certificates_ - connection_ = Connection socket --location=location --host=location.host_with_port + --root-certificates=root-certificates_ + connection_ = Connection socket --location=location --host=location.host-with-port return false /** @@ -704,14 +704,14 @@ class Client: Deprecated. */ - default_port -> int: - return use_tls_by_default_ ? 443 : 80 + default-port -> int: + return use-tls-by-default_ ? 443 : 80 close: if connection_: connection_.close connection_ = null - remove_finalizer this + remove-finalizer this finalize_: // TODO: We should somehow warn people that they forgot to close the @@ -739,9 +739,9 @@ class ParsedUri_: --port/int?=null --.path/string --.fragment=null - --parse_port_in_host/bool=true: - colon := host.index_of ":" - if parse_port_in_host and colon > 0: + --parse-port-in-host/bool=true: + colon := host.index-of ":" + if parse-port-in-host and colon > 0: this.port = int.parse host[colon + 1..] if port and port != this.port: throw "Conflicting ports given" this.host = host[..colon] @@ -749,141 +749,141 @@ class ParsedUri_: this.host = host this.port = port ? port : SCHEMES_[scheme] - use_tls -> bool: + use-tls -> bool: return SCHEMES_[scheme] == 443 - stringify -> string: return "$scheme://$host_with_port$path$(fragment ? "#$fragment" : "")" + stringify -> string: return "$scheme://$host-with-port$path$(fragment ? "#$fragment" : "")" // When redirecting we need to take the old URI into account to interpret the new one. constructor.parse_ uri/string --previous/ParsedUri_?=null: - parsed := ParsedUri_.parse_ uri --default_scheme=null --previous=previous - new_scheme := parsed.scheme - if previous and (new_scheme.starts_with "ws") != (previous.scheme.starts_with "ws"): + parsed := ParsedUri_.parse_ uri --default-scheme=null --previous=previous + new-scheme := parsed.scheme + if previous and (new-scheme.starts-with "ws") != (previous.scheme.starts-with "ws"): throw "INVALID_REDIRECT" // Can't redirect a WebSockets URI to an HTTP URI or vice versa. - scheme = new_scheme + scheme = new-scheme host = parsed.host port = parsed.port path = parsed.path fragment = parsed.fragment or (previous ? previous.fragment : null) - can_reuse_connection previous/ParsedUri_ -> bool: + can-reuse-connection previous/ParsedUri_ -> bool: // The wording of https://www.rfc-editor.org/rfc/rfc6455#section-4.1 seems // to indicate that WebSockets connections should be fresh HTTP // connections, not ones that have previously been used for plain HTTP. // Therefore we require an exact scheme match here, rather than allowing an // upgrade from http to ws or https to wss. This matches what browsers do. - scheme_is_compatible := scheme == previous.scheme + scheme-is-compatible := scheme == previous.scheme return host == previous.host and port == previous.port - and scheme_is_compatible + and scheme-is-compatible /// Returns the hostname, with the port appended if it is non-default. - host_with_port -> string: - default_port := SCHEMES_[scheme] - return default_port == port ? host : "$host:$port" + host-with-port -> string: + default-port := SCHEMES_[scheme] + return default-port == port ? host : "$host:$port" - constructor.parse_ uri/string --default_scheme/string? --previous/ParsedUri_?=null: + constructor.parse_ uri/string --default-scheme/string? --previous/ParsedUri_?=null: // We recognize a scheme if it's either one of the four we support or if it's // followed by colon-slash. This lets us recognize localhost:1080. - colon := uri.index_of ":" + colon := uri.index-of ":" scheme/string? := null // Recognize a prefix like "https:/" if 0 < colon < uri.size - 2: - up_to_colon := uri[..colon] - if is_alpha_ up_to_colon: - lower := up_to_colon.to_ascii_lower + up-to-colon := uri[..colon] + if is-alpha_ up-to-colon: + lower := up-to-colon.to-ascii-lower if SCHEMES_.contains lower or uri[colon + 1] == '/': scheme = lower uri = uri[colon + 1..] - scheme = scheme or default_scheme or (previous and previous.scheme) + scheme = scheme or default-scheme or (previous and previous.scheme) if not scheme: throw "Missing scheme in '$uri'" if not SCHEMES_.contains scheme: throw "Unknown scheme: '$scheme'" // If this is a URI supplied by the library user (no previous), we allow // plain hostnames with no path, but if there is a previous we require a // double slash to indicate a hostname because otherwise it is a relative // URI. - if not previous and uri.contains "/" and not uri.starts_with "//": throw "URI_PARSING_ERROR" + if not previous and uri.contains "/" and not uri.starts-with "//": throw "URI_PARSING_ERROR" host := null port := null path := ? // Named block. - get_host_and_port := : | h p | + get-host-and-port := : | h p | host = h port = p - has_host := not previous // If there's no previous URI we assume there is a hostname. - if uri.starts_with "//": + has-host := not previous // If there's no previous URI we assume there is a hostname. + if uri.starts-with "//": uri = uri[2..] - has_host = true - if has_host: - slash := uri.index_of "/" + has-host = true + if has-host: + slash := uri.index-of "/" if slash < 0: - extract_host_with_optional_port_ scheme uri get_host_and_port + extract-host-with-optional-port_ scheme uri get-host-and-port path = "/" else: - extract_host_with_optional_port_ scheme uri[..slash] get_host_and_port + extract-host-with-optional-port_ scheme uri[..slash] get-host-and-port path = uri[slash..] else: host = previous.host port = previous.port path = uri - hash := path.index_of "#" + hash := path.index-of "#" fragment := null if hash > 0: fragment = path[hash + 1..] path = path[..hash] - if previous and not path.starts_with "/": + if previous and not path.starts-with "/": // Relative path. - path = merge_paths_ previous.path path + path = merge-paths_ previous.path path return ParsedUri_.private_ --scheme=scheme --host=host --port=port --path=path --fragment=fragment - --parse_port_in_host=false + --parse-port-in-host=false - static merge_paths_ old_path/string new_path/string -> string: - assert: old_path.starts_with "/" + static merge-paths_ old-path/string new-path/string -> string: + assert: old-path.starts-with "/" // Conform to note in RFC 3986 section 5.2.4. - query := old_path.index_of "?" - if query > 0: old_path = old_path[..query] - old_parts := old_path.split "/" - old_parts = old_parts[1..old_parts.size - 1] - new_parts := new_path.split "/" - while new_parts.size != 0: - if new_parts[0] == ".": - new_parts = new_parts[1..] - else if new_parts[0] == "..": - if old_parts.size == 0: throw "ILLEGAL_PATH" - old_parts = old_parts[..old_parts.size - 1] - new_parts = new_parts[1..] + query := old-path.index-of "?" + if query > 0: old-path = old-path[..query] + old-parts := old-path.split "/" + old-parts = old-parts[1..old-parts.size - 1] + new-parts := new-path.split "/" + while new-parts.size != 0: + if new-parts[0] == ".": + new-parts = new-parts[1..] + else if new-parts[0] == "..": + if old-parts.size == 0: throw "ILLEGAL_PATH" + old-parts = old-parts[..old-parts.size - 1] + new-parts = new-parts[1..] else: - old_parts += new_parts + old-parts += new-parts break - return "/" + (old_parts.join "/") + return "/" + (old-parts.join "/") - static extract_host_with_optional_port_ scheme/string host/string [block] -> none: + static extract-host-with-optional-port_ scheme/string host/string [block] -> none: // Two cases: // 1) host // 2) host:port // In either case the host may be an IPv6 address that contains colons. port := SCHEMES_[scheme] ipv6 := false - colon := host.index_of --last ":" - if host.starts_with "[": + colon := host.index-of --last ":" + if host.starts-with "[": // either [ipv6-address] or [ipv6-address]:port // This is a little tricky because the IPv6 address contains colons. - square_end := host.index_of "]" - if square_end < 0 or colon > square_end + 1: + square-end := host.index-of "]" + if square-end < 0 or colon > square-end + 1: throw "URI_PARSING_ERROR" - if colon > square_end: + if colon > square-end: port = int.parse host[colon + 1..] - host = host[1..square_end] + host = host[1..square-end] else: - if square_end != host.size - 1: + if square-end != host.size - 1: throw "URI_PARSING_ERROR" - host = host[1..square_end] + host = host[1..square-end] ipv6 = true else: if colon > 0: @@ -914,7 +914,7 @@ class ParsedUri_: block.call host port // Matches /^[a-zA-Z]+$/. - static is_alpha_ str/string -> bool: + static is-alpha_ str/string -> bool: str.do: if not 'a' <= it <= 'z' and not 'A' <= it <= 'Z': return false return true @@ -925,13 +925,13 @@ The interface of an object you can provide to the $Client to store and */ abstract class SecurityStore: /// Store session data (eg a TLS ticket) for a given host and port. - abstract store_session_data host/string port/int data/ByteArray -> none + abstract store-session-data host/string port/int data/ByteArray -> none /// After a failed attempt to use session data we should not try to use it /// again. This method should delete it from the store. - abstract delete_session_data host/string port/int -> none + abstract delete-session-data host/string port/int -> none /// If we have session data stored for a given host and port, this method /// should return it. - abstract retrieve_session_data host/string port/int -> ByteArray? + abstract retrieve-session-data host/string port/int -> ByteArray? /** Default implementation of $SecurityStore that stores the data in an in-memory @@ -940,13 +940,13 @@ Default implementation of $SecurityStore that stores the data in an in-memory interface. */ class SecurityStoreInMemory extends SecurityStore: - session_data_ ::= {:} + session-data_ ::= {:} - store_session_data host/string port/int data/ByteArray -> none: - session_data_["$host:$port"] = data + store-session-data host/string port/int data/ByteArray -> none: + session-data_["$host:$port"] = data - delete_session_data host/string port/int -> none: - session_data_.remove "$host:$port" + delete-session-data host/string port/int -> none: + session-data_.remove "$host:$port" - retrieve_session_data host/string port/int -> ByteArray?: - return session_data_.get "$host:$port" + retrieve-session-data host/string port/int -> ByteArray?: + return session-data_.get "$host:$port" diff --git a/src/connection.toit b/src/connection.toit index 3157845..75d9337 100644 --- a/src/connection.toit +++ b/src/connection.toit @@ -11,49 +11,49 @@ import .client import .headers import .request import .response -import .status_codes +import .status-codes class Connection: socket_/tcp.Socket? := null host_/string? location_/ParsedUri_? := null // These are writers and readers that have been given to API users. - current_writer_/io.CloseableWriter? := null - current_reader_/io.Reader? := null - write_closed_ := false + current-writer_/io.CloseableWriter? := null + current-reader_/io.Reader? := null + write-closed_ := false // For testing. - call_in_finalizer_/Lambda? := null + call-in-finalizer_/Lambda? := null constructor .socket_ --location/ParsedUri_? --host/string?=null: host_ = host location_ = location - add_finalizer this:: this.finalize_ + add-finalizer this:: this.finalize_ - new_request method/string path/string headers/Headers?=null -> RequestOutgoing: + new-request method/string path/string headers/Headers?=null -> RequestOutgoing: headers = headers ? headers.copy : Headers - if current_reader_ or current_writer_: throw "Previous request not completed" + if current-reader_ or current-writer_: throw "Previous request not completed" return RequestOutgoing.private_ this method path headers - is_open_ -> bool: + is-open_ -> bool: return socket_ != null close -> none: if socket_: socket_.close - if current_writer_: - current_writer_.close + if current-writer_: + current-writer_.close socket_ = null - remove_finalizer this - write_closed_ = true - current_reader_ = null - current_writer_ = null + remove-finalizer this + write-closed_ = true + current-reader_ = null + current-writer_ = null finalize_: // TODO: We should somehow warn people that they forgot to close the // connection. It releases the memory earlier than relying on the // finalizer, so it can avoid some out-of-memory situations. - if call_in_finalizer_ and socket_: call_in_finalizer_.call this + if call-in-finalizer_ and socket_: call-in-finalizer_.call this close @@ -64,15 +64,15 @@ class Connection: drain_ drain_ -> none: - if write_closed_: - current_reader_ = null + if write-closed_: + current-reader_ = null close - if current_reader_: - current_reader_.drain - current_reader_ = null - if current_writer_: - current_writer_.close - current_writer_ = null + if current-reader_: + current-reader_.drain + current-reader_ = null + if current-writer_: + current-writer_.close + current-writer_ = null /** Indicates to the other side that we won't be writing any more on this @@ -83,8 +83,8 @@ class Connection: Deprecated. */ - close_write -> none: - close_write_ + close-write -> none: + close-write_ /** Indicates to the other side that we won't be writing any more on this @@ -93,148 +93,148 @@ class Connection: completely closed. Otherwise the connection will be closed on completing the current read. */ - close_write_ -> none: - if not current_reader_: + close-write_ -> none: + if not current-reader_: close else if socket_: socket_.out.close - write_closed_ = true + write-closed_ = true /** Sends the given $headers. - If $content_length is not given, it will be extracted from the headers. - If neither $content_length nor a Content-Length header is given, the body + If $content-length is not given, it will be extracted from the headers. + If neither $content-length nor a Content-Length header is given, the body will be sent in a chunked way. - If both $content_length and a Content-Length header is given, they must + If both $content-length and a Content-Length header is given, they must match. */ - send_headers -> io.CloseableWriter + send-headers -> io.CloseableWriter status/string headers/Headers - --is_client_request/bool - --content_length/int? - --has_body/bool: + --is-client-request/bool + --content-length/int? + --has-body/bool: writer := socket_.out - if current_writer_: throw "Previous request not completed" - body_writer/io.CloseableWriter := ? - needs_to_write_chunked_header := false - - if has_body: - content_length_header := headers.single "Content-Length" - if content_length_header: - header_length := int.parse content_length_header - if not content_length: content_length = header_length - if content_length != header_length: - throw "Content-Length header ($header_length) does not match content length ($content_length)" - else if content_length: - headers.set "Content-Length" "$content_length" - - if content_length: - body_writer = ContentLengthWriter_ this writer content_length + if current-writer_: throw "Previous request not completed" + body-writer/io.CloseableWriter := ? + needs-to-write-chunked-header := false + + if has-body: + content-length-header := headers.single "Content-Length" + if content-length-header: + header-length := int.parse content-length-header + if not content-length: content-length = header-length + if content-length != header-length: + throw "Content-Length header ($header-length) does not match content length ($content-length)" + else if content-length: + headers.set "Content-Length" "$content-length" + + if content-length: + body-writer = ContentLengthWriter_ this writer content-length else: - needs_to_write_chunked_header = true - body_writer = ChunkedWriter_ this writer + needs-to-write-chunked-header = true + body-writer = ChunkedWriter_ this writer else: // Return a writer that doesn't accept any data. - body_writer = ContentLengthWriter_ this writer 0 + body-writer = ContentLengthWriter_ this writer 0 if not headers.matches "Connection" "Upgrade": headers.set "Content-Length" "0" // Set this before doing blocking operations on the socket, so that we // don't let another task start another request on the same connection. - if has_body: current_writer_ = body_writer - socket_.no_delay = false + if has-body: current-writer_ = body-writer + socket_.no-delay = false writer.write status - headers.write_to writer - if is_client_request and host_: + headers.write-to writer + if is-client-request and host_: writer.write "Host: $host_\r\n" - if needs_to_write_chunked_header: + if needs-to-write-chunked-header: writer.write "Transfer-Encoding: chunked\r\n" writer.write "\r\n" - socket_.no_delay = true - return body_writer + socket_.no-delay = true + return body-writer // Gets the next request from the client. If the client closes the // connection, returns null. - read_request -> RequestIncoming?: + read-request -> RequestIncoming?: // In theory HTTP/1.1 can support pipelining, but it causes issues // with many servers, so nobody uses it. - if current_reader_: throw "Previous response not completed" + if current-reader_: throw "Previous response not completed" if not socket_: return null reader := socket_.in if not reader.try-ensure-buffered 1: - if write_closed_: close + if write-closed_: close return null - index_of_first_space := reader.index_of ' ' --throw-if-absent - method := reader.read_string (index_of_first_space) + index-of-first-space := reader.index-of ' ' --throw-if-absent + method := reader.read-string (index-of-first-space) reader.skip 1 - path := reader.read_string (reader.index_of ' ' --throw-if-absent) + path := reader.read-string (reader.index-of ' ' --throw-if-absent) reader.skip 1 - version := reader.read_string (reader.index_of '\r' --throw-if-absent) + version := reader.read-string (reader.index-of '\r' --throw-if-absent) reader.skip 1 - if reader.read_byte != '\n': throw "FORMAT_ERROR" + if reader.read-byte != '\n': throw "FORMAT_ERROR" - headers := read_headers_ - content_length_str := headers.single "Content-Length" - content_length := content_length_str and (int.parse content_length_str) - current_reader_ = body_reader_ headers --request=true content_length + headers := read-headers_ + content-length-str := headers.single "Content-Length" + content-length := content-length-str and (int.parse content-length-str) + current-reader_ = body-reader_ headers --request=true content-length - body_reader := current_reader_ or ContentLengthReader_ this reader 0 - return RequestIncoming.private_ this body_reader method path version headers + body-reader := current-reader_ or ContentLengthReader_ this reader 0 + return RequestIncoming.private_ this body-reader method path version headers detach -> tcp.Socket: if not socket_: throw "ALREADY_CLOSED" socket := socket_ socket_ = null - remove_finalizer this + remove-finalizer this return socket - read_response -> Response: + read-response -> Response: reader := socket_.in - if current_reader_: throw "Previous response not completed" + if current-reader_: throw "Previous response not completed" headers := null try: - version := reader.read_string (reader.index_of ' ' --throw-if-absent) + version := reader.read-string (reader.index-of ' ' --throw-if-absent) reader.skip 1 - status_code := int.parse (reader.read_string (reader.index_of ' ' --throw-if-absent)) + status-code := int.parse (reader.read-string (reader.index-of ' ' --throw-if-absent)) reader.skip 1 - status_message := reader.read_string (reader.index_of '\r' --throw-if-absent) + status-message := reader.read-string (reader.index-of '\r' --throw-if-absent) reader.skip 1 - if reader.read_byte != '\n': throw "FORMAT_ERROR" + if reader.read-byte != '\n': throw "FORMAT_ERROR" - headers = read_headers_ - content_length_str := headers.single "Content-Length" - content_length := content_length_str and (int.parse content_length_str) - current_reader_ = body_reader_ headers --request=false --status_code=status_code content-length + headers = read-headers_ + content-length-str := headers.single "Content-Length" + content-length := content-length-str and (int.parse content-length-str) + current-reader_ = body-reader_ headers --request=false --status-code=status-code content-length - body_reader := current_reader_ or ContentLengthReader_ this reader 0 - return Response this version status_code status_message headers body_reader + body-reader := current-reader_ or ContentLengthReader_ this reader 0 + return Response this version status-code status-message headers body-reader finally: if not headers: close - body_reader_ headers/Headers --request/bool --status_code/int?=null content_length/int? -> io.Reader?: + body-reader_ headers/Headers --request/bool --status-code/int?=null content-length/int? -> io.Reader?: reader := socket_.in - if content_length: - if content_length == 0: return null // No read is needed to drain this response. - return ContentLengthReader_ this reader content_length + if content-length: + if content-length == 0: return null // No read is needed to drain this response. + return ContentLengthReader_ this reader content-length // The only transfer encodings we support are 'identity' and 'chunked', // which are both required by HTTP/1.1. - T_E ::= "Transfer-Encoding" - if headers.single T_E: - if headers.matches T_E "chunked": + T-E ::= "Transfer-Encoding" + if headers.single T-E: + if headers.matches T-E "chunked": return ChunkedReader_ this reader - else if not headers.matches T_E "identity": - throw "No support for $T_E: $(headers.single T_E)" + else if not headers.matches T-E "identity": + throw "No support for $T-E: $(headers.single T-E)" - if request or status_code == STATUS_NO_CONTENT: + if request or status-code == STATUS-NO-CONTENT: // For requests (we are the server) a missing Content-Length means a zero // length body. We also do this as client if the server has explicitly // stated that there is no content. We return a null reader, which means @@ -251,44 +251,44 @@ class Connection: return UnknownContentLengthReader_ this reader // Optional whitespace is spaces and tabs. - is_whitespace_ char: + is-whitespace_ char: return char == ' ' or char == '\t' - read_headers_: + read-headers_: reader := socket_.in headers := Headers while (reader.peek-byte 0) != '\r': - if is_whitespace_ (reader.peek-byte 0): + if is-whitespace_ (reader.peek-byte 0): // Line folded headers are deprecated in RFC 7230 and we don't support // them. throw "FOLDED_HEADER" - key := reader.read_string (reader.index_of ':') + key := reader.read-string (reader.index-of ':') reader.skip 1 - while is_whitespace_ (reader.peek-byte 0): reader.skip 1 + while is-whitespace_ (reader.peek-byte 0): reader.skip 1 - value := reader.read_string (reader.index_of '\r') + value := reader.read-string (reader.index-of '\r') reader.skip 1 - if reader.read_byte != '\n': throw "FORMAT_ERROR" + if reader.read-byte != '\n': throw "FORMAT_ERROR" headers.add key value reader.skip 1 - if reader.read_byte != '\n': throw "FORMAT_ERROR" + if reader.read-byte != '\n': throw "FORMAT_ERROR" return headers - reading_done_ reader/io.Reader: - if current_reader_: - if reader != current_reader_: throw "Read from reader that was already done" - current_reader_ = null - if write_closed_: close + reading-done_ reader/io.Reader: + if current-reader_: + if reader != current-reader_: throw "Read from reader that was already done" + current-reader_ = null + if write-closed_: close - writing_done_ writer/io.Writer: - if current_writer_: - if writer != current_writer_: throw "Close of a writer that was already done" - current_writer_ = null + writing-done_ writer/io.Writer: + if current-writer_: + if writer != current-writer_: throw "Close of a writer that was already done" + current-writer_ = null /** Deprecated for public use. Use the type $io.Reader instead. @@ -310,17 +310,17 @@ class ContentLengthReader_ extends io.Reader: /** Deprecated. Use $content-size instead. */ - content_length -> int: + content-length -> int: return content-size read_ -> ByteArray?: if read-from-wrapped_ >= content-size: - connection_.reading_done_ this + connection_.reading-done_ this return null - data := reader_.read --max_size=(content-size - processed) + data := reader_.read --max-size=(content-size - processed) if not data: connection_.close - throw io.Reader.UNEXPECTED_END_OF_READER + throw io.Reader.UNEXPECTED-END-OF-READER read-from-wrapped_ += data.size return data @@ -350,7 +350,7 @@ Deprecated for public use. Use the type $io.CloseableWriter instead. */ interface BodyWriter: write data -> int - is_done -> bool + is-done -> bool close -> none /** @@ -358,32 +358,32 @@ Deprecated for public use. Use the type $io.CloseableWriter instead. This class will be made private in the future. */ class ContentLengthWriter extends ContentLengthWriter_: - constructor connection/Connection writer/io.Writer content_length/int: - super connection writer content_length + constructor connection/Connection writer/io.Writer content-length/int: + super connection writer content-length class ContentLengthWriter_ extends io.CloseableWriter implements BodyWriter: connection_/Connection? := null writer_/io.Writer - content_length_/int := ? + content-length_/int := ? written-to-wrapped_/int := 0 - constructor .connection_ .writer_ .content_length_: + constructor .connection_ .writer_ .content-length_: - is_done -> bool: - return written-to-wrapped_ >= content_length_ + is-done -> bool: + return written-to-wrapped_ >= content-length_ - try_write_ data/io.Data from/int to/int -> int: - result := writer_.try_write data from to + try-write_ data/io.Data from/int to/int -> int: + result := writer_.try-write data from to written-to-wrapped_ += result return result close_ -> none: if connection_: - connection_.writing_done_ this + connection_.writing-done_ this connection_ = null -is_close_exception_ exception -> bool: - return exception == io.Reader.UNEXPECTED_END_OF_READER +is-close-exception_ exception -> bool: + return exception == io.Reader.UNEXPECTED-END-OF-READER or exception == "Broken pipe" or exception == "Connection reset by peer" or exception == "NOT_CONNECTED" diff --git a/src/headers.toit b/src/headers.toit index e408581..385a88e 100644 --- a/src/headers.toit +++ b/src/headers.toit @@ -9,7 +9,7 @@ class Headers: constructor: - constructor.from_map map/Map: + constructor.from-map map/Map: map.do: | key/string value | if value is string: // Go through the $add function so that the key is normalized. @@ -36,9 +36,9 @@ class Headers: */ single key -> string?: if not headers_: return null - values := headers_.get key --if_absent=: - key = ascii_normalize_ key - headers_.get key --if_absent=: + values := headers_.get key --if-absent=: + key = ascii-normalize_ key + headers_.get key --if-absent=: return null if values is string: return values return values[values.size - 1] @@ -47,19 +47,19 @@ class Headers: Does ASCII case independent match of whether a key has a value. */ matches key/string value/string -> bool: - from_headers := single key - if not from_headers: return false - return from_headers == value or (ascii_normalize_ from_headers) == (ascii_normalize_ value) + from-headers := single key + if not from-headers: return false + return from-headers == value or (ascii-normalize_ from-headers) == (ascii-normalize_ value) /** Does ASCII case independent match of whether a header value starts with a prefix. Returns false if the header is not present. Only checks the last header if there are several of the same name. */ - starts_with key/string prefix/string -> bool: - from_headers := single key - if not from_headers: return false - return from_headers.starts_with prefix or (ascii_normalize_ from_headers).starts_with (ascii_normalize_ prefix) + starts-with key/string prefix/string -> bool: + from-headers := single key + if not from-headers: return false + return from-headers.starts-with prefix or (ascii-normalize_ from-headers).starts-with (ascii-normalize_ prefix) /** Removes the given header. @@ -79,9 +79,9 @@ class Headers: */ get key/string -> List?: if not headers_: return null - value := headers_.get key --if_absent=: - key = ascii_normalize_ key - headers_.get key --if_absent=: + value := headers_.get key --if-absent=: + key = ascii-normalize_ key + headers_.get key --if-absent=: return null if value is string: // Make it in to a one-element list in case the caller wants to modify @@ -98,7 +98,7 @@ class Headers: */ set key/string value/string -> none: if not headers_: headers_ = {:} - headers_[ascii_normalize_ key] = value + headers_[ascii-normalize_ key] = value /** Adds a new $value to the $key. @@ -107,9 +107,9 @@ class Headers: value to the list. */ add key/string value/string -> none: - key = ascii_normalize_ key + key = ascii-normalize_ key if not headers_: headers_ = {:} - headers_.update key --if_absent=(: value): | old | + headers_.update key --if-absent=(: value): | old | if old is string: [old, value] else: @@ -120,10 +120,10 @@ class Headers: contains key/string -> bool: if not headers_: return false if headers_.contains key: return true - key = ascii_normalize_ key + key = ascii-normalize_ key return headers_.contains key - write_to writer -> none: + write-to writer -> none: if not headers_: return headers_.do: | key values | block := : | value | @@ -138,8 +138,8 @@ class Headers: stringify -> string: buffer := io.Buffer - write_to buffer - return buffer.to_string + write-to buffer + return buffer.to-string /** Creates a copy of this instance. @@ -160,26 +160,26 @@ class Headers: // Camel-case a string. Only works for ASCII in accordance with the HTTP // standard. If the string is already camel cased (the norm) then no // allocation occurs. - static ascii_normalize_ str/string -> string: + static ascii-normalize_ str/string -> string: alpha := false // Was the previous character an alphabetic (ASCII) letter. bytes/ByteArray? := null // Allocate byte array later if needed. str.size.repeat: char := str.at --raw it - problem := alpha ? (is_ascii_upper_case_ char) : (is_ascii_lower_case_ char) + problem := alpha ? (is-ascii-upper-case_ char) : (is-ascii-lower-case_ char) if problem and not bytes: bytes = ByteArray str.size - str[..it].write_to_byte_array bytes + str[..it].write-to-byte-array bytes if bytes: bytes[it] = problem ? (char ^ 32) : char - alpha = is_ascii_alpha_ char + alpha = is-ascii-alpha_ char if not bytes: return str - return bytes.to_string + return bytes.to-string - static is_ascii_upper_case_ char/int -> bool: + static is-ascii-upper-case_ char/int -> bool: return 'A' <= char <= 'Z' - static is_ascii_lower_case_ char/int -> bool: + static is-ascii-lower-case_ char/int -> bool: return 'a' <= char <= 'z' - static is_ascii_alpha_ char/int -> bool: - return is_ascii_lower_case_ char or is_ascii_upper_case_ char + static is-ascii-alpha_ char/int -> bool: + return is-ascii-lower-case_ char or is-ascii-upper-case_ char diff --git a/src/http.toit b/src/http.toit index 6db7dc4..fe86664 100644 --- a/src/http.toit +++ b/src/http.toit @@ -8,8 +8,8 @@ import .request import .response import .headers import .method -import .status_codes -import .web_socket +import .status-codes +import .web-socket export * diff --git a/src/request.toit b/src/request.toit index 7c4824a..4c4a67a 100644 --- a/src/request.toit +++ b/src/request.toit @@ -20,7 +20,7 @@ abstract class Request: abstract body -> io.Reader? body= value/io.Reader: throw "NOT_IMPLEMENTED" send -> Response: throw "NOT_IMPLEMENTED" - content_length -> int?: throw "NOT_IMPLEMENTED" + content-length -> int?: throw "NOT_IMPLEMENTED" abstract drain -> none /// Outgoing request to an HTTP server, we are acting like a client. @@ -44,20 +44,20 @@ class RequestOutgoing extends Request: constructor.private_ .connection_ .method .path .headers: send -> Response: - has_body := body != null - content_length := has_body ? body.content-size : null - slash := (path.starts_with "/") ? "" : "/" - body_writer := connection_.send_headers + has-body := body != null + content-length := has-body ? body.content-size : null + slash := (path.starts-with "/") ? "" : "/" + body-writer := connection_.send-headers "$method $slash$path HTTP/1.1\r\n" headers - --is_client_request=true - --content_length=content_length - --has_body=has_body + --is-client-request=true + --content-length=content-length + --has-body=has-body if body: while data := body.read: - body_writer.write data - body_writer.close - return connection_.read_response + body-writer.write data + body-writer.close + return connection_.read-response drain: body.drain @@ -91,7 +91,7 @@ class RequestIncoming extends Request: /** The length of the body, if known. */ - content_length -> int?: + content-length -> int?: return body.content-size drain: diff --git a/src/response.toit b/src/response.toit index d877da9..62903c1 100644 --- a/src/response.toit +++ b/src/response.toit @@ -14,8 +14,8 @@ class Response: connection_/Connection headers ::= Headers version/string - status_code/int - status_message/string + status-code/int + status-message/string /** A reader that can be used to read the body of the response. @@ -24,15 +24,15 @@ class Response: */ body/io.Reader - constructor .connection_ .version .status_code .status_message .headers .body: + constructor .connection_ .version .status-code .status-message .headers .body: /** The length of the response body, if known. */ - content_length -> int?: + content-length -> int?: return body.content-size - stringify: return "$status_code: $status_message" + stringify: return "$status-code: $status-message" // Return a reader & writer object, used to send raw data on the connection. detach -> tcp.Socket: diff --git a/src/server.toit b/src/server.toit index 78f7ec8..941a1ae 100644 --- a/src/server.toit +++ b/src/server.toit @@ -14,49 +14,49 @@ import .chunked import .connection import .headers import .request -import .status_codes -import .web_socket +import .status-codes +import .web-socket class Server: - static DEFAULT_READ_TIMEOUT/Duration ::= Duration --s=30 + static DEFAULT-READ-TIMEOUT/Duration ::= Duration --s=30 - read_timeout/Duration + read-timeout/Duration logger_/log.Logger - use_tls_/bool ::= false + use-tls_/bool ::= false certificate_/tls.Certificate? ::= null - root_certificates_/List ::= [] + root-certificates_/List ::= [] semaphore_/monitor.Semaphore? ::= null // For testing. - call_in_finalizer_/Lambda? := null + call-in-finalizer_/Lambda? := null - constructor --.read_timeout=DEFAULT_READ_TIMEOUT --max_tasks/int=1 --logger=log.default: + constructor --.read-timeout=DEFAULT-READ-TIMEOUT --max-tasks/int=1 --logger=log.default: logger_ = logger - if max_tasks > 1: semaphore_ = monitor.Semaphore --count=max_tasks + if max-tasks > 1: semaphore_ = monitor.Semaphore --count=max-tasks constructor.tls - --.read_timeout=DEFAULT_READ_TIMEOUT - --max_tasks/int=1 + --.read-timeout=DEFAULT-READ-TIMEOUT + --max-tasks/int=1 --logger=log.default --certificate/tls.Certificate - --root_certificates/List=[]: + --root-certificates/List=[]: logger_ = logger - use_tls_ = true + use-tls_ = true certificate_ = certificate - root_certificates_ = root_certificates - if max_tasks > 1: semaphore_ = monitor.Semaphore --count=max_tasks + root-certificates_ = root-certificates + if max-tasks > 1: semaphore_ = monitor.Semaphore --count=max-tasks /** Sets up an HTTP server on the given $network and port. - Use $(listen server_socket handler) if you want to let the system + Use $(listen server-socket handler) if you want to let the system pick a free port. The handler is called for each incoming request with two arguments: The $Request and a $ResponseWriter. */ listen network/tcp.Interface port/int handler/Lambda -> none: - server_socket := network.tcp_listen port - listen server_socket handler + server-socket := network.tcp-listen port + listen server-socket handler /** Sets up an HTTP server on the given TCP server socket. @@ -82,91 +82,91 @@ class Server: writer.close ``` */ - listen server_socket/tcp.ServerSocket handler/Lambda -> none: + listen server-socket/tcp.ServerSocket handler/Lambda -> none: while true: - parent_task_semaphore := null + parent-task-semaphore := null if semaphore_: - parent_task_semaphore = semaphore_ + parent-task-semaphore = semaphore_ // Down the semaphore before the accept, so we just don't accept // connections if we are at the limit. semaphore_.down try: // A try to ensure the semaphore is upped. - accepted := server_socket.accept + accepted := server-socket.accept if not accepted: continue socket := accepted - if use_tls_: + if use-tls_: socket = tls.Socket.server socket --certificate=certificate_ - --root_certificates=root_certificates_ + --root-certificates=root-certificates_ connection := Connection --location=null socket - address := socket.peer_address - logger := logger_.with_tag "peer" address + address := socket.peer-address + logger := logger_.with-tag "peer" address logger.debug "client connected" // This code can be run in the current task or in a child task. - handle_connection_closure := :: + handle-connection-closure := :: try: // A try to ensure the semaphore is upped in the child task. detached := false - e := catch --trace=(: not is_close_exception_ it and it != DEADLINE_EXCEEDED_ERROR): - detached = run_connection_ connection handler logger - connection.close_write_ - close_logger := e ? logger.with_tag "reason" e : logger + e := catch --trace=(: not is-close-exception_ it and it != DEADLINE-EXCEEDED-ERROR): + detached = run-connection_ connection handler logger + connection.close-write_ + close-logger := e ? logger.with-tag "reason" e : logger if detached: - close_logger.debug "client socket detached" + close-logger.debug "client socket detached" else: - close_logger.debug "connection ended" + close-logger.debug "connection ended" finally: if semaphore_: semaphore_.up // Up the semaphore when the task ends. // End of code that can be run in the current task or in a child task. - parent_task_semaphore = null // We got this far, the semaphore is ours. + parent-task-semaphore = null // We got this far, the semaphore is ours. if semaphore_: - task --background handle_connection_closure + task --background handle-connection-closure else: // For the single-task case, just run the connection in the current task. - handle_connection_closure.call + handle-connection-closure.call finally: // Up the semaphore if we threw before starting the task. - if parent_task_semaphore: parent_task_semaphore.up + if parent-task-semaphore: parent-task-semaphore.up - web_socket request/RequestIncoming response_writer/ResponseWriter -> WebSocket?: - nonce := WebSocket.check_server_upgrade_request_ request response_writer + web-socket request/RequestIncoming response-writer/ResponseWriter -> WebSocket?: + nonce := WebSocket.check-server-upgrade-request_ request response-writer if nonce == null: return null - response_writer.write_headers STATUS_SWITCHING_PROTOCOLS - return WebSocket response_writer.detach --no-client + response-writer.write-headers STATUS-SWITCHING-PROTOCOLS + return WebSocket response-writer.detach --no-client // Returns true if the connection was detached, false if it was closed. - run_connection_ connection/Connection handler/Lambda logger/log.Logger -> bool: - if call_in_finalizer_: connection.call_in_finalizer_ = call_in_finalizer_ + run-connection_ connection/Connection handler/Lambda logger/log.Logger -> bool: + if call-in-finalizer_: connection.call-in-finalizer_ = call-in-finalizer_ while true: request/RequestIncoming? := null - with_timeout read_timeout: - request = connection.read_request + with-timeout read-timeout: + request = connection.read-request if not request: return false // Client closed connection. - request_logger := logger + request-logger := logger if request.method != "GET": - request_logger = request_logger.with_tag "method" request.method - request_logger = request_logger.with_tag "path" request.path - request_logger.debug "incoming request" - writer ::= ResponseWriter connection request request_logger - unwind_block := : | exception | + request-logger = request-logger.with-tag "method" request.method + request-logger = request-logger.with-tag "path" request.path + request-logger.debug "incoming request" + writer ::= ResponseWriter connection request request-logger + unwind-block := : | exception | // If there's an error we can either send a 500 error message or close // the connection. This depends on whether we had already sent the // headers - can't send a 500 if we already sent a success header. - closed := writer.close_on_exception_ "Internal Server error - $exception" + closed := writer.close-on-exception_ "Internal Server error - $exception" closed // Unwind if the connection is dead. if request.method == "HEAD": - writer.write_headers STATUS_METHOD_NOT_ALLOWED --message="HEAD not implemented" + writer.write-headers STATUS-METHOD-NOT-ALLOWED --message="HEAD not implemented" else: - catch --trace --unwind=unwind_block: + catch --trace --unwind=unwind-block: handler.call request writer // Calls the block passed to listen. if writer.detached_: return true if request.body.read: // The request (eg. a POST request) was not fully read - should have // been closed and return null from read. - closed := writer.close_on_exception_ "Internal Server error - request not fully read" + closed := writer.close-on-exception_ "Internal Server error - request not fully read" assert: closed throw "request not fully read: $request.path" writer.close @@ -178,25 +178,25 @@ class ResponseWriter extends Object with io.OutMixin: request_/RequestIncoming logger_/log.Logger headers_/Headers - body_writer_/io.CloseableWriter? := null - content_length_/int? := null + body-writer_/io.CloseableWriter? := null + content-length_/int? := null detached_/bool := false constructor .connection_ .request_ .logger_: headers_ = Headers headers -> Headers: - if body_writer_: throw "headers already written" + if body-writer_: throw "headers already written" return headers_ - write_headers status_code/int --message/string?=null: - if body_writer_: throw "headers already written" - has_body := status_code != STATUS_NO_CONTENT - write_headers_ - status_code + write-headers status-code/int --message/string?=null: + if body-writer_: throw "headers already written" + has-body := status-code != STATUS-NO-CONTENT + write-headers_ + status-code --message=message - --content_length=null - --has_body=has_body + --content-length=null + --has-body=has-body /** Deprecated. Use $(out).write instead. @@ -204,42 +204,42 @@ class ResponseWriter extends Object with io.OutMixin: write data/io.Data: out.write data - try_write_ data/io.Data from/int to/int -> int: - write_headers_ STATUS_OK --message=null --content_length=null --has_body=true - return body_writer_.try_write data from to + try-write_ data/io.Data from/int to/int -> int: + write-headers_ STATUS-OK --message=null --content-length=null --has-body=true + return body-writer_.try-write data from to - write_headers_ status_code/int --message/string? --content_length/int? --has_body/bool: - if body_writer_: return + write-headers_ status-code/int --message/string? --content-length/int? --has-body/bool: + if body-writer_: return // Keep track of the content length, so we can report an error if not enough // data is written. - if content_length: - content_length_ = content_length + if content-length: + content-length_ = content-length else if headers.contains "Content-Length": - content_length_ = int.parse (headers.single "Content-Length") - body_writer_ = connection_.send_headers - "$VERSION $status_code $(message or (status_message status_code))\r\n" + content-length_ = int.parse (headers.single "Content-Length") + body-writer_ = connection_.send-headers + "$VERSION $status-code $(message or (status-message status-code))\r\n" headers - --is_client_request=false - --content_length=content_length - --has_body=has_body + --is-client-request=false + --content-length=content-length + --has-body=has-body /** Redirects the request to the given $location. - Neither $write_headers_ nor any write to $out must have happened. + Neither $write-headers_ nor any write to $out must have happened. */ - redirect status_code/int location/string --message/string?=null --body/string?=null -> none: + redirect status-code/int location/string --message/string?=null --body/string?=null -> none: headers.set "Location" location if body and body.size > 0: - write_headers_ status_code --message=message --content_length=body.size --has_body=true - body_writer_.write body + write-headers_ status-code --message=message --content-length=body.size --has-body=true + body-writer_.write body else: - write_headers_ status_code --message=message --content_length=null --has_body=false + write-headers_ status-code --message=message --content-length=null --has-body=false // Returns true if the connection was closed due to an error. - close_on_exception_ message/string -> bool: + close-on-exception_ message/string -> bool: logger_.info message - if body_writer_: + if body-writer_: // We already sent a good response code, but then something went // wrong. Hard close (RST) the connection to signal to the other end // that we failed. @@ -249,10 +249,10 @@ class ResponseWriter extends Object with io.OutMixin: else: // We don't have a body writer, so perhaps we didn't send a response // yet. Send a 500 to indicate an internal server error. - write_headers_ STATUS_INTERNAL_SERVER_ERROR + write-headers_ STATUS-INTERNAL-SERVER-ERROR --message=message - --content_length=null - --has_body=false + --content-length=null + --has-body=false return false /** @@ -262,24 +262,24 @@ class ResponseWriter extends Object with io.OutMixin: user's router did not call it. */ close -> none: - mark_writer-closed_ - if body_writer_: - too_little := content_length_ ? (body_writer_.processed < content_length_) : false - body_writer_.close - if too_little: + mark-writer-closed_ + if body-writer_: + too-little := content-length_ ? (body-writer_.processed < content-length_) : false + body-writer_.close + if too-little: // This is typically the case if the user's code set a Content-Length // header, but then didn't write enough data. // Will hard close the connection. - close_on_exception_ "Not enough data produced by server" + close-on-exception_ "Not enough data produced by server" return else: // Nothing was written, yet we are already closing. This indicates // We return a 500 error code and log the issue. We don't need to close // the connection. - write_headers_ STATUS_INTERNAL_SERVER_ERROR + write-headers_ STATUS-INTERNAL-SERVER-ERROR --message=null - --content_length=null - --has_body=false + --content-length=null + --has-body=false logger_.info "Returned from router without any data for the client" detach -> tcp.Socket: diff --git a/src/status-codes.toit b/src/status-codes.toit index e1d31ff..1d7e709 100644 --- a/src/status-codes.toit +++ b/src/status-codes.toit @@ -9,19 +9,19 @@ The server switches protocols. For example, the server sends this status code when it switches to websockets. */ -STATUS_SWITCHING_PROTOCOLS ::= 101 +STATUS-SWITCHING-PROTOCOLS ::= 101 /** The server has received the request, and is processing it, but no response is available yet. */ -STATUS_PROCESSING ::= 102 +STATUS-PROCESSING ::= 102 /** Status code for the Link header. It lets the user agent start preloading resources while the server is still processing the request. */ -STATUS_EARLY_HINTS ::= 103 +STATUS-EARLY-HINTS ::= 103 /** The request succeeded. @@ -32,96 +32,96 @@ The actual meaning depends on the method that was used: - $POST or $PUT: The result of the operation is returned in the message body. - $TRACE: The message body contains the request message as received by the server. */ -STATUS_OK ::= 200 +STATUS-OK ::= 200 /** The request succeeded, and the server created a new resource. */ -STATUS_CREATED ::= 201 +STATUS-CREATED ::= 201 /** The request succeeded, and the server accepted the request, but has not yet processed it. */ -STATUS_ACCEPTED ::= 202 +STATUS-ACCEPTED ::= 202 /** The returned metadata in the response is not the definitive set as available from the origin server, but is gathered from a local or a third-party copy. */ -STATUS_NON_AUTHORITATIVE_INFORMATION ::= 203 +STATUS-NON-AUTHORITATIVE-INFORMATION ::= 203 /** The server has successfully processed the request and is not returning any content. */ -STATUS_NO_CONTENT ::= 204 +STATUS-NO-CONTENT ::= 204 /** Status code to inform the user agent to reset the document which sent the request. */ -STATUS_RESET_CONTENT ::= 205 +STATUS-RESET-CONTENT ::= 205 /** The server has fulfilled the partial GET request for the resource. */ -STATUS_PARTIAL_CONTENT ::= 206 +STATUS-PARTIAL-CONTENT ::= 206 /** The message body contains the status of multiple independent operations. */ -STATUS_MULTI_STATUS ::= 207 +STATUS-MULTI-STATUS ::= 207 /** The members of a DAV binding have already been enumerated in a previous reply to this request, and are not being included again. */ -STATUS_ALREADY_REPORTED ::= 208 +STATUS-ALREADY-REPORTED ::= 208 /** The server has fulfilled a request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance. */ -STATUS_IM_USED ::= 226 +STATUS-IM-USED ::= 226 /** The request has more than one possible response. */ -STATUS_MULTIPLE_CHOICES ::= 300 +STATUS-MULTIPLE-CHOICES ::= 300 /** A resource has moved permanently. This might be used when a web site was reorganized. -Similar to $STATUS_PERMANENT_REDIRECT, except that the method is not guaranteed +Similar to $STATUS-PERMANENT-REDIRECT, except that the method is not guaranteed except for $GET. -For clients the simplest is to treat $STATUS_MOVED_PERMANENTLY the same as - $STATUS_PERMANENT_REDIRECT. +For clients the simplest is to treat $STATUS-MOVED-PERMANENTLY the same as + $STATUS-PERMANENT-REDIRECT. # Advanced If the method was $GET, then the redirected target must also be retrieved with $GET. For other methods, the server should not assume that the redirected target is again - retrieved with the same method. The status code $STATUS_PERMANENT_REDIRECT specifies + retrieved with the same method. The status code $STATUS-PERMANENT-REDIRECT specifies that the method must not be changed. */ -STATUS_MOVED_PERMANENTLY ::= 301 +STATUS-MOVED-PERMANENTLY ::= 301 /** The target is temporarily unavailable. -For clients the simplest is to treat $STATUS_FOUND the same as - $STATUS_TEMPORARY_REDIRECT. +For clients the simplest is to treat $STATUS-FOUND the same as + $STATUS-TEMPORARY-REDIRECT. # Advanced If the method was $GET, then the redirected target must also be retrieved with $GET. For other methods, the server should not assume that the redirected target is again - retrieved with the same method. The status code $STATUS_TEMPORARY_REDIRECT specifies + retrieved with the same method. The status code $STATUS-TEMPORARY-REDIRECT specifies that the method must not be changed. */ -STATUS_FOUND ::= 302 +STATUS-FOUND ::= 302 /** The client is redirected to a $GET target. @@ -129,14 +129,14 @@ The client is redirected to a $GET target. This is typically used to redirect after a $PUT or $POST, so that refreshing the result page doesn't resubmit data. */ -STATUS_SEE_OTHER ::= 303 +STATUS-SEE-OTHER ::= 303 /** The target is temporarily unavailable. The method ($GET, $POST, ...) must be the same when accessing the redirection target. */ -STATUS_TEMPORARY_REDIRECT ::= 307 +STATUS-TEMPORARY-REDIRECT ::= 307 /** A resource has moved permanently. @@ -147,37 +147,37 @@ Search engine robots, RSS readers, and similar tools should update the original The method ($GET, $POST, ...) must be the same when accessing the redirection target. */ -STATUS_PERMANENT_REDIRECT ::= 308 +STATUS-PERMANENT-REDIRECT ::= 308 /** A malformed request. */ -STATUS_BAD_REQUEST ::= 400 +STATUS-BAD-REQUEST ::= 400 /** The user is not authorized to access the resource. */ -STATUS_UNAUTHORIZED ::= 401 +STATUS-UNAUTHORIZED ::= 401 /** Reserved for future use. */ -STATUS_PAYMENT_REQUIRED ::= 402 +STATUS-PAYMENT-REQUIRED ::= 402 /** The client does not have access rights to the content. */ -STATUS_FORBIDDEN ::= 403 +STATUS-FORBIDDEN ::= 403 /** The server can not find the requested resource. */ -STATUS_NOT_FOUND ::= 404 +STATUS-NOT-FOUND ::= 404 /** The method is not allowed for the requested resource. */ -STATUS_METHOD_NOT_ALLOWED ::= 405 +STATUS-METHOD-NOT-ALLOWED ::= 405 /** The request is not acceptable. @@ -185,65 +185,65 @@ The request is not acceptable. This happens when the server doesn't find any acceptable content after server-driven content negotiation. */ -STATUS_NOT_ACCEPTABLE ::= 406 +STATUS-NOT-ACCEPTABLE ::= 406 /** The client must first authenticate itself with the proxy. */ -STATUS_PROXY_AUTHENTICATION_REQUIRED ::= 407 +STATUS-PROXY-AUTHENTICATION-REQUIRED ::= 407 /** The server timed out waiting for the request. */ -STATUS_REQUEST_TIMEOUT ::= 408 +STATUS-REQUEST-TIMEOUT ::= 408 /** The request could not be completed because of a conflict in the request. */ -STATUS_CONFLICT ::= 409 +STATUS-CONFLICT ::= 409 /** The requested resource is no longer available at the server and no forwarding address is known. */ -STATUS_GONE ::= 410 +STATUS-GONE ::= 410 /** The server refuses to accept the request without a defined Content-Length. */ -STATUS_LENGTH_REQUIRED ::= 411 +STATUS-LENGTH-REQUIRED ::= 411 /** The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server. */ -STATUS_PRECONDITION_FAILED ::= 412 +STATUS-PRECONDITION-FAILED ::= 412 /** The payload is too big. */ -STATUS_PAYLOAD_TOO_LARGE ::= 413 +STATUS-PAYLOAD-TOO-LARGE ::= 413 /** The URI is too long. */ -STATUS_REQUEST_URI_TOO_LONG ::= 414 +STATUS-REQUEST-URI-TOO-LONG ::= 414 /** The request entity has a media type which the server or resource does not support. */ -STATUS_UNSUPPORTED_MEDIA_TYPE ::= 415 +STATUS-UNSUPPORTED-MEDIA-TYPE ::= 415 /** The client has asked for a portion of the file, but the server cannot supply that portion. */ -STATUS_REQUESTED_RANGE_NOT_SATISFIABLE ::= 416 +STATUS-REQUESTED-RANGE-NOT-SATISFIABLE ::= 416 /** The server cannot meet the requirements of the Expect request-header field. */ -STATUS_EXPECTATION_FAILED ::= 417 +STATUS-EXPECTATION-FAILED ::= 417 /** The server refuses the attempt to brew coffee with a teapot. @@ -254,223 +254,223 @@ This might be returned by a server that does not wish to reveal exactly why the This status code originates from an April fool's joke in RFC 2324, and is not expected to be implemented by actual HTTP servers. */ -STATUS_IM_A_TEAPOT ::= 418 +STATUS-IM-A-TEAPOT ::= 418 /** The request was directed at a server that is not able to produce a response. */ -STATUS_MISDIRECTED_REQUEST ::= 421 +STATUS-MISDIRECTED-REQUEST ::= 421 /** The request was well-formed but was unable to be followed due to semantic errors. */ -STATUS_UNPROCESSABLE_ENTITY ::= 422 +STATUS-UNPROCESSABLE-ENTITY ::= 422 /** The resource that is being accessed is locked. */ -STATUS_LOCKED ::= 423 +STATUS-LOCKED ::= 423 /** The request failed due to failure of a previous request. */ -STATUS_FAILED_DEPENDENCY ::= 424 +STATUS-FAILED-DEPENDENCY ::= 424 /** The server is unwilling to process a request that could be a replay of a previous request. */ -STATUS_TOO_EARLY ::= 425 +STATUS-TOO-EARLY ::= 425 /** The server refuses to perform the request using the current protocol but might be willing to do so after the client upgrades to a different protocol. */ -STATUS_UPGRADE_REQUIRED ::= 426 +STATUS-UPGRADE-REQUIRED ::= 426 /** The origin server requires the request to be conditional. */ -STATUS_PRECONDITION_REQUIRED ::= 428 +STATUS-PRECONDITION-REQUIRED ::= 428 /** The user has sent too many requests in a given amount of time. */ -STATUS_TOO_MANY_REQUESTS ::= 429 +STATUS-TOO-MANY-REQUESTS ::= 429 /** The request's header fields are too large. */ -STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE ::= 431 +STATUS-REQUEST-HEADER-FIELDS-TOO-LARGE ::= 431 /** The server can't fulfill the request because of legal reasons. */ -STATUS_UNAVAILABLE_FOR_LEGAL_REASONS ::= 451 +STATUS-UNAVAILABLE-FOR-LEGAL-REASONS ::= 451 /** The server encountered an unexpected condition that prevented it from fulfilling the request. */ -STATUS_INTERNAL_SERVER_ERROR ::= 500 +STATUS-INTERNAL-SERVER-ERROR ::= 500 /** The method is not supported by the server. */ -STATUS_NOT_IMPLEMENTED ::= 501 +STATUS-NOT-IMPLEMENTED ::= 501 /** The server, while working as a gateway, was unable to get a response needed to handle the request. */ -STATUS_BAD_GATEWAY ::= 502 +STATUS-BAD-GATEWAY ::= 502 /** The server is not able to handle the request. */ -STATUS_SERVICE_UNAVAILABLE ::= 503 +STATUS-SERVICE-UNAVAILABLE ::= 503 /** The server, while acting as a gateway, did not receive a timely response from the upstream server. */ -STATUS_GATEWAY_TIMEOUT ::= 504 +STATUS-GATEWAY-TIMEOUT ::= 504 /** The server does not support the HTTP protocol version used in the request. */ -STATUS_HTTP_VERSION_NOT_SUPPORTED ::= 505 +STATUS-HTTP-VERSION-NOT-SUPPORTED ::= 505 /** The server has an internal configuration error: transparent content negotiation for the request results in a circular reference. */ -STATUS_VARIANT_ALSO_NEGOTIATES ::= 506 +STATUS-VARIANT-ALSO-NEGOTIATES ::= 506 /** The server is unable to store the representation needed to complete the request. */ -STATUS_INSUFFICIENT_STORAGE ::= 507 +STATUS-INSUFFICIENT-STORAGE ::= 507 /** The server detected an infinite loop while processing the request. */ -STATUS_LOOP_DETECTED ::= 508 +STATUS-LOOP-DETECTED ::= 508 /** One of the requested extensions is not supported by the server. */ -STATUS_NOT_EXTENDED ::= 510 +STATUS-NOT-EXTENDED ::= 510 /** Status codes for close packets on the Websockets protocol. */ -STATUS_WEBSOCKET_NORMAL_CLOSURE ::= 1000 -STATUS_WEBSOCKET_GOING_AWAY ::= 1001 -STATUS_WEBSOCKET_PROTOCOL_ERROR ::= 1002 -STATUS_WEBSOCKET_NOT_UNDERSTOOD ::= 1003 -STATUS_WEBSOCKET_RESERVED ::= 1004 -STATUS_WEBSOCKET_NO_STATUS_CODE ::= 1005 -STATUS_WEBSOCKET_CONNECTION_CLOSED ::= 1006 -STATUS_WEBSOCKET_INCONSISTENT_DATA_ ::= 1007 -STATUS_WEBSOCKET_POLICY_VIOLATION ::= 1008 -STATUS_WEBSOCKET_MESSAGE_TOO_BIG ::= 1009 -STATUS_WEBSOCKET_MISSING_EXTENSION ::= 1010 -STATUS_WEBSOCKET_UNEXPECTED_CONDITION ::= 1011 -STATUS_WEBSOCKET_TLS_FAILURE ::= 1015 +STATUS-WEBSOCKET-NORMAL-CLOSURE ::= 1000 +STATUS-WEBSOCKET-GOING-AWAY ::= 1001 +STATUS-WEBSOCKET-PROTOCOL-ERROR ::= 1002 +STATUS-WEBSOCKET-NOT-UNDERSTOOD ::= 1003 +STATUS-WEBSOCKET-RESERVED ::= 1004 +STATUS-WEBSOCKET-NO-STATUS-CODE ::= 1005 +STATUS-WEBSOCKET-CONNECTION-CLOSED ::= 1006 +STATUS-WEBSOCKET-INCONSISTENT-DATA_ ::= 1007 +STATUS-WEBSOCKET-POLICY-VIOLATION ::= 1008 +STATUS-WEBSOCKET-MESSAGE-TOO-BIG ::= 1009 +STATUS-WEBSOCKET-MISSING-EXTENSION ::= 1010 +STATUS-WEBSOCKET-UNEXPECTED-CONDITION ::= 1011 +STATUS-WEBSOCKET-TLS-FAILURE ::= 1015 /** The client needs to authenticate. */ -STATUS_NETWORK_AUTHENTICATION_REQUIRED ::= 511 - -status_messages_/Map ::= { - STATUS_SWITCHING_PROTOCOLS: "Switching Protocols", - STATUS_PROCESSING: "Processing", - STATUS_EARLY_HINTS: "Early Hints", - - STATUS_OK: "OK", - STATUS_CREATED: "Created", - STATUS_ACCEPTED: "Accepted", - STATUS_NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information", - STATUS_NO_CONTENT: "No Content", - STATUS_RESET_CONTENT: "Reset Content", - STATUS_PARTIAL_CONTENT: "Partial Content", - STATUS_ALREADY_REPORTED: "Already Reported", - STATUS_IM_USED: "IM Used", - - STATUS_MULTIPLE_CHOICES: "Multiple Choices", - STATUS_MOVED_PERMANENTLY: "Moved Permanently", - STATUS_FOUND: "Found", - STATUS_SEE_OTHER: "See Other", - STATUS_TEMPORARY_REDIRECT: "Temporary Redirect", - STATUS_PERMANENT_REDIRECT: "Permanent Redirect", - - STATUS_BAD_REQUEST: "Bad Request", - STATUS_UNAUTHORIZED: "Unauthorized", - STATUS_PAYMENT_REQUIRED: "Payment Required", - STATUS_FORBIDDEN: "Forbidden", - STATUS_NOT_FOUND: "Not Found", - STATUS_METHOD_NOT_ALLOWED: "Method Not Allowed", - STATUS_NOT_ACCEPTABLE: "Not Acceptable", - STATUS_PROXY_AUTHENTICATION_REQUIRED: "Proxy Authentication Required", - STATUS_REQUEST_TIMEOUT: "Request Timeout", - STATUS_CONFLICT: "Conflict", - STATUS_GONE: "Gone", - STATUS_LENGTH_REQUIRED: "Length Required", - STATUS_PRECONDITION_FAILED: "Precondition Failed", - STATUS_PAYLOAD_TOO_LARGE: "Payload Too Large", - STATUS_REQUEST_URI_TOO_LONG: "Request-URI Too Long", - STATUS_UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type", - STATUS_REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range Not Satisfiable", - STATUS_EXPECTATION_FAILED: "Expectation Failed", - STATUS_IM_A_TEAPOT: "I'm a teapot", - STATUS_MISDIRECTED_REQUEST: "Misdirected Request", - STATUS_UNPROCESSABLE_ENTITY: "Unprocessable Entity", - STATUS_LOCKED: "Locked", - STATUS_FAILED_DEPENDENCY: "Failed Dependency", - STATUS_TOO_EARLY: "Too Early", - STATUS_UPGRADE_REQUIRED: "Upgrade Required", - STATUS_PRECONDITION_REQUIRED: "Precondition Required", - STATUS_TOO_MANY_REQUESTS: "Too Many Requests", - STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: "Request Header Fields Too Large", - STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: "Unavailable For Legal Reasons", - - STATUS_INTERNAL_SERVER_ERROR: "Internal Server Error", - STATUS_NOT_IMPLEMENTED: "Not Implemented", - STATUS_BAD_GATEWAY: "Bad Gateway", - STATUS_SERVICE_UNAVAILABLE: "Service Unavailable", - STATUS_GATEWAY_TIMEOUT: "Gateway Timeout", - STATUS_HTTP_VERSION_NOT_SUPPORTED: "HTTP Version Not Supported", - STATUS_VARIANT_ALSO_NEGOTIATES: "Variant Also Negotiates", - STATUS_INSUFFICIENT_STORAGE: "Insufficient Storage", - STATUS_LOOP_DETECTED: "Loop Detected", - STATUS_NOT_EXTENDED: "Not Extended", - STATUS_NETWORK_AUTHENTICATION_REQUIRED: "Network Authentication Required", +STATUS-NETWORK-AUTHENTICATION-REQUIRED ::= 511 + +status-messages_/Map ::= { + STATUS-SWITCHING-PROTOCOLS: "Switching Protocols", + STATUS-PROCESSING: "Processing", + STATUS-EARLY-HINTS: "Early Hints", + + STATUS-OK: "OK", + STATUS-CREATED: "Created", + STATUS-ACCEPTED: "Accepted", + STATUS-NON-AUTHORITATIVE-INFORMATION: "Non-Authoritative Information", + STATUS-NO-CONTENT: "No Content", + STATUS-RESET-CONTENT: "Reset Content", + STATUS-PARTIAL-CONTENT: "Partial Content", + STATUS-ALREADY-REPORTED: "Already Reported", + STATUS-IM-USED: "IM Used", + + STATUS-MULTIPLE-CHOICES: "Multiple Choices", + STATUS-MOVED-PERMANENTLY: "Moved Permanently", + STATUS-FOUND: "Found", + STATUS-SEE-OTHER: "See Other", + STATUS-TEMPORARY-REDIRECT: "Temporary Redirect", + STATUS-PERMANENT-REDIRECT: "Permanent Redirect", + + STATUS-BAD-REQUEST: "Bad Request", + STATUS-UNAUTHORIZED: "Unauthorized", + STATUS-PAYMENT-REQUIRED: "Payment Required", + STATUS-FORBIDDEN: "Forbidden", + STATUS-NOT-FOUND: "Not Found", + STATUS-METHOD-NOT-ALLOWED: "Method Not Allowed", + STATUS-NOT-ACCEPTABLE: "Not Acceptable", + STATUS-PROXY-AUTHENTICATION-REQUIRED: "Proxy Authentication Required", + STATUS-REQUEST-TIMEOUT: "Request Timeout", + STATUS-CONFLICT: "Conflict", + STATUS-GONE: "Gone", + STATUS-LENGTH-REQUIRED: "Length Required", + STATUS-PRECONDITION-FAILED: "Precondition Failed", + STATUS-PAYLOAD-TOO-LARGE: "Payload Too Large", + STATUS-REQUEST-URI-TOO-LONG: "Request-URI Too Long", + STATUS-UNSUPPORTED-MEDIA-TYPE: "Unsupported Media Type", + STATUS-REQUESTED-RANGE-NOT-SATISFIABLE: "Requested Range Not Satisfiable", + STATUS-EXPECTATION-FAILED: "Expectation Failed", + STATUS-IM-A-TEAPOT: "I'm a teapot", + STATUS-MISDIRECTED-REQUEST: "Misdirected Request", + STATUS-UNPROCESSABLE-ENTITY: "Unprocessable Entity", + STATUS-LOCKED: "Locked", + STATUS-FAILED-DEPENDENCY: "Failed Dependency", + STATUS-TOO-EARLY: "Too Early", + STATUS-UPGRADE-REQUIRED: "Upgrade Required", + STATUS-PRECONDITION-REQUIRED: "Precondition Required", + STATUS-TOO-MANY-REQUESTS: "Too Many Requests", + STATUS-REQUEST-HEADER-FIELDS-TOO-LARGE: "Request Header Fields Too Large", + STATUS-UNAVAILABLE-FOR-LEGAL-REASONS: "Unavailable For Legal Reasons", + + STATUS-INTERNAL-SERVER-ERROR: "Internal Server Error", + STATUS-NOT-IMPLEMENTED: "Not Implemented", + STATUS-BAD-GATEWAY: "Bad Gateway", + STATUS-SERVICE-UNAVAILABLE: "Service Unavailable", + STATUS-GATEWAY-TIMEOUT: "Gateway Timeout", + STATUS-HTTP-VERSION-NOT-SUPPORTED: "HTTP Version Not Supported", + STATUS-VARIANT-ALSO-NEGOTIATES: "Variant Also Negotiates", + STATUS-INSUFFICIENT-STORAGE: "Insufficient Storage", + STATUS-LOOP-DETECTED: "Loop Detected", + STATUS-NOT-EXTENDED: "Not Extended", + STATUS-NETWORK-AUTHENTICATION-REQUIRED: "Network Authentication Required", } -status_message status_code/int -> string: - return status_messages_.get status_code - --if_absent=: "" +status-message status-code/int -> string: + return status-messages_.get status-code + --if-absent=: "" -is_regular_redirect_ status_code/int -> bool: - return status_code == STATUS_MOVED_PERMANENTLY - or status_code == STATUS_FOUND - or status_code == STATUS_TEMPORARY_REDIRECT - or status_code == STATUS_PERMANENT_REDIRECT +is-regular-redirect_ status-code/int -> bool: + return status-code == STATUS-MOVED-PERMANENTLY + or status-code == STATUS-FOUND + or status-code == STATUS-TEMPORARY-REDIRECT + or status-code == STATUS-PERMANENT-REDIRECT -is_information_status_code status_code/int -> bool: - return 100 <= status_code < 200 +is-information-status-code status-code/int -> bool: + return 100 <= status-code < 200 -is_success_status_code status_code/int -> bool: - return 200 <= status_code < 300 +is-success-status-code status-code/int -> bool: + return 200 <= status-code < 300 -is_redirect_status_code status_code/int -> bool: - return 300 <= status_code < 400 +is-redirect-status-code status-code/int -> bool: + return 300 <= status-code < 400 -is_client_error_status_code status_code/int -> bool: - return 400 <= status_code < 500 +is-client-error-status-code status-code/int -> bool: + return 400 <= status-code < 500 -is_server_error_status_code status_code/int -> bool: - return 500 <= status_code < 600 +is-server-error-status-code status-code/int -> bool: + return 500 <= status-code < 600 diff --git a/src/web-socket.toit b/src/web-socket.toit index bb85173..cf41676 100644 --- a/src/web-socket.toit +++ b/src/web-socket.toit @@ -6,7 +6,7 @@ import bitmap show blit XOR import crypto.sha1 show sha1 import encoding.base64 import io -import io show BIG_ENDIAN +import io show BIG-ENDIAN import monitor show Semaphore import net.tcp @@ -15,42 +15,42 @@ import .request import .response import .client show Client import .server -import .status_codes - -OPCODE_CONTINUATION_ ::= 0 -OPCODE_TEXT_ ::= 1 -OPCODE_BINARY_ ::= 2 -OPCODE_CLOSE_ ::= 8 -OPCODE_PING_ ::= 9 -OPCODE_PONG_ ::= 10 -FIN_FLAG_ ::= 0x80 -EIGHT_BYTE_SIZE_ ::= 127 -TWO_BYTE_SIZE_ ::= 126 -MAX_ONE_BYTE_SIZE_ ::= 125 -MASKING_FLAG_ ::= 0x80 +import .status-codes + +OPCODE-CONTINUATION_ ::= 0 +OPCODE-TEXT_ ::= 1 +OPCODE-BINARY_ ::= 2 +OPCODE-CLOSE_ ::= 8 +OPCODE-PING_ ::= 9 +OPCODE-PONG_ ::= 10 +FIN-FLAG_ ::= 0x80 +EIGHT-BYTE-SIZE_ ::= 127 +TWO-BYTE-SIZE_ ::= 126 +MAX-ONE-BYTE-SIZE_ ::= 125 +MASKING-FLAG_ ::= 0x80 /** A WebSocket connection. A bidirectional socket connection capable of sending binary or text messages according to RFC 6455. -Obtained from the $Client.web_socket or the $Server.web_socket methods. +Obtained from the $Client.web-socket or the $Server.web-socket methods. Currently does not implement ping and pong packets. */ class WebSocket: socket_ /tcp.Socket - current_writer_ /WebSocketWriter? := null - writer_semaphore_ := Semaphore --count=1 - current_reader_ /WebSocketReader? := null - is_client_ /bool + current-writer_ /WebSocketWriter? := null + writer-semaphore_ := Semaphore --count=1 + current-reader_ /WebSocketReader? := null + is-client_ /bool constructor .socket_ --client/bool: - is_client_ = client + is-client_ = client read_ -> ByteArray?: return socket_.in.read - unread_ byte_array/ByteArray -> none: - socket_.in.unget byte_array + unread_ byte-array/ByteArray -> none: + socket_.in.unget byte-array /** Reads a whole message, returning it as a string or a ByteArray. @@ -58,29 +58,29 @@ class WebSocket: Messages transmitted as text are returned as strings. Messages transmitted as binary are returned as byte arrays. For connections with potentially large messages, consider using - $start_receiving instead to stream the data. - With $force_byte_array returns a byte array even if the + $start-receiving instead to stream the data. + With $force-byte-array returns a byte array even if the peer marks the message as text. This can be useful to avoid exceptions if the peer is marking invalid UTF-8 messages as text. */ - receive --force_byte_array=false -> any?: - reader := start_receiving + receive --force-byte-array=false -> any?: + reader := start-receiving if reader == null: return null list := [] while chunk := reader.read: list.add chunk - text := reader.is_text and not force_byte_array + text := reader.is-text and not force-byte-array if list.size == 0: return text ? "" : #[] - if list.size == 1: return text ? list[0].to_string : list[0] - size := list.reduce --initial=0: | sz byte_array | sz + byte_array.size + if list.size == 1: return text ? list[0].to-string : list[0] + size := list.reduce --initial=0: | sz byte-array | sz + byte-array.size result := ByteArray size position := 0 list.do: result.replace position it position += it.size list = [] // Free up some memory before the big to_string. - return text ? result.to_string : result + return text ? result.to-string : result /** Returns a reader for the next message sent to us on the WebSocket. @@ -88,77 +88,77 @@ class WebSocket: Should not be called until the previous reader has been fully read. See also $receive if you know messages are small enough to fit in memory. */ - start_receiving -> WebSocketReader?: - if current_reader_ != null: - close --status_code=STATUS_WEBSOCKET_UNEXPECTED_CONDITION + start-receiving -> WebSocketReader?: + if current-reader_ != null: + close --status-code=STATUS-WEBSOCKET-UNEXPECTED-CONDITION throw "PREVIOUS_READER_NOT_FINISHED" - fragment_reader := null - while not fragment_reader: - fragment_reader = next_fragment_ - if fragment_reader == null or fragment_reader.is_close : + fragment-reader := null + while not fragment-reader: + fragment-reader = next-fragment_ + if fragment-reader == null or fragment-reader.is-close : return null - fragment_reader = handle_any_ping_ fragment_reader - if fragment_reader.is_continuation: - close --status_code=STATUS_WEBSOCKET_PROTOCOL_ERROR + fragment-reader = handle-any-ping_ fragment-reader + if fragment-reader.is-continuation: + close --status-code=STATUS-WEBSOCKET-PROTOCOL-ERROR throw "PROTOCOL_ERROR" - size := fragment_reader.size_ - current_reader_ = WebSocketReader.private_ this fragment_reader fragment_reader.is_text fragment_reader.size - return current_reader_ + size := fragment-reader.size_ + current-reader_ = WebSocketReader.private_ this fragment-reader fragment-reader.is-text fragment-reader.size + return current-reader_ - handle_any_ping_ next_fragment/FragmentReader_ -> FragmentReader_?: - if next_fragment.is_pong: - while next_fragment.read: + handle-any-ping_ next-fragment/FragmentReader_ -> FragmentReader_?: + if next-fragment.is-pong: + while next-fragment.read: null // Drain the pong. return null // Nothing to do in response to a pong. - if next_fragment.is_ping: + if next-fragment.is-ping: payload := #[] - while packet := next_fragment.read: + while packet := next-fragment.read: payload += packet - schedule_ping_ payload OPCODE_PONG_ + schedule-ping_ payload OPCODE-PONG_ return null - return next_fragment + return next-fragment // Reads the header of the next fragment. - next_fragment_ -> FragmentReader_?: + next-fragment_ -> FragmentReader_?: if socket_ == null: return null // Closed. reader := socket_.in if not reader.try-ensure-buffered 1: return null - control_bits := reader.read-byte - masking_length_byte := reader.read-byte - masking := (masking_length_byte & MASKING_FLAG_) != 0 - len := masking_length_byte & 0x7f + control-bits := reader.read-byte + masking-length-byte := reader.read-byte + masking := (masking-length-byte & MASKING-FLAG_) != 0 + len := masking-length-byte & 0x7f - if len == TWO_BYTE_SIZE_: + if len == TWO-BYTE-SIZE_: len = reader.big-endian.read-uint16 // BIG_ENDIAN.uint16 pending_ 2 - else if len == EIGHT_BYTE_SIZE_: + else if len == EIGHT-BYTE-SIZE_: len = reader.big-endian.read-int64 // BIG_ENDIAN.int64 pending_ 2 - masking_bytes := null + masking-bytes := null if masking: - masking_bytes = reader.read-bytes 4 - if masking_bytes == #[0, 0, 0, 0]: - masking_bytes = null - result := FragmentReader_ this len control_bits --masking_bytes=masking_bytes - if not result.is_ok_: - close --status_code=STATUS_WEBSOCKET_PROTOCOL_ERROR + masking-bytes = reader.read-bytes 4 + if masking-bytes == #[0, 0, 0, 0]: + masking-bytes = null + result := FragmentReader_ this len control-bits --masking-bytes=masking-bytes + if not result.is-ok_: + close --status-code=STATUS-WEBSOCKET-PROTOCOL-ERROR throw "PROTOCOL_ERROR" - if result.is_close: + if result.is-close: if result.size >= 2: // Two-byte close reason code. payload := #[] while packet := result.read: payload += packet - code := BIG_ENDIAN.uint16 payload 0 - if code == STATUS_WEBSOCKET_NORMAL_CLOSURE or code == STATUS_WEBSOCKET_GOING_AWAY: + code := BIG-ENDIAN.uint16 payload 0 + if code == STATUS-WEBSOCKET-NORMAL-CLOSURE or code == STATUS-WEBSOCKET-GOING-AWAY: // One of the expected no-error codes. - current_reader_ = null + current-reader_ = null return null throw "Peer closed with code $code" // No code provided. We treat this as a normal close. - current_reader_ = null + current-reader_ = null return null return result @@ -172,7 +172,7 @@ class WebSocket: completely sent. */ send data/io.Data -> none: - writer := start_sending --size=data.byte-size --opcode=((data is string) ? OPCODE_TEXT_ : OPCODE_BINARY_) + writer := start-sending --size=data.byte-size --opcode=((data is string) ? OPCODE-TEXT_ : OPCODE-BINARY_) writer.write data writer.close @@ -187,60 +187,60 @@ class WebSocket: The message is sent as a text message if the first data written to the writer is a string, otherwise as a binary message. Returns a writer, which must be completed (all data sent, and closed) before - another message can be sent. Calls to $send and $start_sending will + another message can be sent. Calls to $send and $start-sending will block until the previous writer is completed. */ - start_sending --size/int?=null --opcode/int?=null -> io.CloseableWriter: - writer_semaphore_.down - assert: current_writer_ == null - current_writer_ = WebSocketWriter.private_ this size --masking=is_client_ --opcode=opcode - return current_writer_ + start-sending --size/int?=null --opcode/int?=null -> io.CloseableWriter: + writer-semaphore_.down + assert: current-writer_ == null + current-writer_ = WebSocketWriter.private_ this size --masking=is-client_ --opcode=opcode + return current-writer_ /** Send a ping with the given $payload, which is a string or a ByteArray. Any pongs we get back are ignored. If we are in the middle of sending a long message that was started with - $start_sending then the ping may be interleaved with the fragments of the + $start-sending then the ping may be interleaved with the fragments of the long message. */ ping payload -> none: - schedule_ping_ payload OPCODE_PING_ + schedule-ping_ payload OPCODE-PING_ - schedule_ping_ payload opcode/int -> none: - if current_writer_: - current_writer_.pending_pings_.add [opcode, payload] + schedule-ping_ payload opcode/int -> none: + if current-writer_: + current-writer_.pending-pings_.add [opcode, payload] else: - writer_semaphore_.down + writer-semaphore_.down try: // Send immediately. - writer := WebSocketWriter.private_ this payload.size --masking=is_client_ --opcode=opcode + writer := WebSocketWriter.private_ this payload.size --masking=is-client_ --opcode=opcode writer.write payload writer.close finally: - critical_do --no-respect_deadline: - writer_semaphore_.up + critical-do --no-respect-deadline: + writer-semaphore_.up - writer_close_ writer/WebSocketWriter -> none: - current_writer_ = null - writer_semaphore_.up + writer-close_ writer/WebSocketWriter -> none: + current-writer_ = null + writer-semaphore_.up - write_ data/io.Data from=0 to=data.byte_size -> none: + write_ data/io.Data from=0 to=data.byte-size -> none: socket_.out.write data from to - reader_close_ -> none: - current_reader_ = null + reader-close_ -> none: + current-reader_ = null /** Closes the websocket. Call this if we do not wish to send or receive any more messages. - After calling this, you do not need to call $close_write. + After calling this, you do not need to call $close-write. May close abruptly in an unclean way. If we are in a suitable place in the protocol, sends a close packet first. */ - close --status_code/int=STATUS_WEBSOCKET_NORMAL_CLOSURE: - close_write --status_code=status_code + close --status-code/int=STATUS-WEBSOCKET-NORMAL-CLOSURE: + close-write --status-code=status-code socket_.close - current_reader_ = null + current-reader_ = null /** Closes the websocket for writing. @@ -250,26 +250,26 @@ class WebSocket: Otherwise, it will close abruptly, without sending the packet. Most peers will respond by closing the other direction. */ - close_write --status_code/int=STATUS_WEBSOCKET_NORMAL_CLOSURE: - if current_writer_ == null: - writer_semaphore_.down + close-write --status-code/int=STATUS-WEBSOCKET-NORMAL-CLOSURE: + if current-writer_ == null: + writer-semaphore_.down try: // If we are not in the middle of a message, we can send a close packet. catch: // Catch because the write end may already be closed. - writer := WebSocketWriter.private_ this 2 --masking=is_client_ --opcode=OPCODE_CLOSE_ + writer := WebSocketWriter.private_ this 2 --masking=is-client_ --opcode=OPCODE-CLOSE_ payload := ByteArray 2 - BIG_ENDIAN.put_uint16 payload 0 status_code + BIG-ENDIAN.put-uint16 payload 0 status-code writer.write payload writer.close finally: - critical_do --no-respect_deadline: - writer_semaphore_.up + critical-do --no-respect-deadline: + writer-semaphore_.up socket_.out.close - if current_writer_: - current_writer_ = null - writer_semaphore_.up + if current-writer_: + current-writer_ = null + writer-semaphore_.up - static add_client_upgrade_headers_ headers/Headers -> string: + static add-client-upgrade-headers_ headers/Headers -> string: // The WebSocket nonce is not very important and does not need to be // cryptographically random. nonce := base64.encode (ByteArray 16: random 0x100) @@ -279,15 +279,15 @@ class WebSocket: headers.add "Connection" "Upgrade" return nonce - static check_client_upgrade_response_ response/Response nonce/string -> none: - if response.status_code != STATUS_SWITCHING_PROTOCOLS: - throw "WebSocket upgrade failed with $response.status_code $response.status_message" - upgrade_header := response.headers.single "Upgrade" - connection_header := response.headers.single "Connection" - if not upgrade_header - or not connection_header - or (Headers.ascii_normalize_ upgrade_header) != "Websocket" - or (Headers.ascii_normalize_ connection_header) != "Upgrade" + static check-client-upgrade-response_ response/Response nonce/string -> none: + if response.status-code != STATUS-SWITCHING-PROTOCOLS: + throw "WebSocket upgrade failed with $response.status-code $response.status-message" + upgrade-header := response.headers.single "Upgrade" + connection-header := response.headers.single "Connection" + if not upgrade-header + or not connection-header + or (Headers.ascii-normalize_ upgrade-header) != "Websocket" + or (Headers.ascii-normalize_ connection-header) != "Upgrade" or (response.headers.single "Sec-WebSocket-Accept") != (WebSocket.response_ nonce): throw "MISSING_HEADER_IN_RESPONSE" if response.headers.single "Sec-WebSocket-Extensions" @@ -300,30 +300,30 @@ class WebSocket: and sends a response confirming the upgrade. Otherwise responds with an error code and returns null. */ - static check_server_upgrade_request_ request/RequestIncoming response_writer/ResponseWriter -> string?: - connection_header := request.headers.single "Connection" - upgrade_header := request.headers.single "Upgrade" - version_header := request.headers.single "Sec-WebSocket-Version" + static check-server-upgrade-request_ request/RequestIncoming response-writer/ResponseWriter -> string?: + connection-header := request.headers.single "Connection" + upgrade-header := request.headers.single "Upgrade" + version-header := request.headers.single "Sec-WebSocket-Version" nonce := request.headers.single "Sec-WebSocket-Key" message := null if nonce == null: message = "No nonce" else if nonce.size != 24: message = "Bad nonce size" - else if not connection_header or not upgrade_header: message = "No upgrade headers" - else if (Headers.ascii_normalize_ connection_header) != "Upgrade": message = "No Connection: Upgrade" - else if (Headers.ascii_normalize_ upgrade_header) != "Websocket": message = "No Upgrade: websocket" - else if version_header != "13": message = "Unrecognized Websocket version" + else if not connection-header or not upgrade-header: message = "No upgrade headers" + else if (Headers.ascii-normalize_ connection-header) != "Upgrade": message = "No Connection: Upgrade" + else if (Headers.ascii-normalize_ upgrade-header) != "Websocket": message = "No Upgrade: websocket" + else if version-header != "13": message = "Unrecognized Websocket version" else: - response_writer.headers.add "Sec-WebSocket-Accept" (response_ nonce) - response_writer.headers.add "Connection" "Upgrade" - response_writer.headers.add "Upgrade" "websocket" + response-writer.headers.add "Sec-WebSocket-Accept" (response_ nonce) + response-writer.headers.add "Connection" "Upgrade" + response-writer.headers.add "Upgrade" "websocket" return nonce - response_writer.write_headers STATUS_BAD_REQUEST --message=message + response-writer.write-headers STATUS-BAD-REQUEST --message=message return null static response_ nonce/string -> string: - expected_response := base64.encode + expected-response := base64.encode sha1 nonce + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - return expected_response + return expected-response /** A writer for writing a single message on a WebSocket connection. @@ -331,103 +331,103 @@ A writer for writing a single message on a WebSocket connection. class WebSocketWriter extends io.CloseableWriter: owner_ /WebSocket? := ? size_ /int? - remaining_in_fragment_ /int := 0 - fragment_sent_ /bool := false + remaining-in-fragment_ /int := 0 + fragment-sent_ /bool := false masking_ /bool // Pings and pongs can be interleaved with the fragments in a message. - pending_pings_ /List := [] + pending-pings_ /List := [] constructor.private_ .owner_ .size_ --masking/bool --opcode/int?=null: masking_ = masking if size_ and opcode: - remaining_in_fragment_ = write_fragment_header_ size_ opcode size_ + remaining-in-fragment_ = write-fragment-header_ size_ opcode size_ - try_write_ data/io.Data from/int to/int -> int: + try-write_ data/io.Data from/int to/int -> int: if owner_ == null: throw "ALREADY_CLOSED" - total_size := to - from + total-size := to - from while from != to: // If no more can be written in the current fragment we need to write a // new fragment header. - if remaining_in_fragment_ == 0: - write_any_ping_ // Interleave pongs with message fragments. + if remaining-in-fragment_ == 0: + write-any-ping_ // Interleave pongs with message fragments. // Determine opcode for the new fragment. - opcode := data is string ? OPCODE_TEXT_ : OPCODE_BINARY_ - if fragment_sent_: - opcode = OPCODE_CONTINUATION_ + opcode := data is string ? OPCODE-TEXT_ : OPCODE-BINARY_ + if fragment-sent_: + opcode = OPCODE-CONTINUATION_ else: - fragment_sent_ = true + fragment-sent_ = true - remaining_in_fragment_ = write_fragment_header_ (to - from) opcode size_ + remaining-in-fragment_ = write-fragment-header_ (to - from) opcode size_ - while from < to and remaining_in_fragment_ != 0: - size := min (to - from) remaining_in_fragment_ + while from < to and remaining-in-fragment_ != 0: + size := min (to - from) remaining-in-fragment_ // We don't use slices because data might be a string with UTF-8 // sequences in it. owner_.write_ data from (from + size) from += size - remaining_in_fragment_ -= size + remaining-in-fragment_ -= size - if remaining_in_fragment_ == 0: write_any_ping_ + if remaining-in-fragment_ == 0: write-any-ping_ - return total_size + return total-size - write_any_ping_ -> none: - while owner_ and pending_pings_.size != 0: - assert: remaining_in_fragment_ == 0 - item := pending_pings_[0] - pending_pings_ = pending_pings_.copy 1 + write-any-ping_ -> none: + while owner_ and pending-pings_.size != 0: + assert: remaining-in-fragment_ == 0 + item := pending-pings_[0] + pending-pings_ = pending-pings_.copy 1 opcode := item[0] payload := item[1] - write_fragment_header_ payload.size opcode payload.size + write-fragment-header_ payload.size opcode payload.size owner_.write_ payload - write_fragment_header_ max_size/int opcode/int size/int?: + write-fragment-header_ max-size/int opcode/int size/int?: header /ByteArray := ? // If the protocol requires it, we supply a 4 byte mask, but it's always // zero so we don't need to apply it on send. - masking_flag := masking_ ? MASKING_FLAG_ : 0 - remaining_in_fragment := ? + masking-flag := masking_ ? MASKING-FLAG_ : 0 + remaining-in-fragment := ? if size: // We know the size. Write a single fragment with the fin flag and the // exact size. - if opcode == OPCODE_CONTINUATION_: throw "TOO_MUCH_WRITTEN" - remaining_in_fragment = size + if opcode == OPCODE-CONTINUATION_: throw "TOO_MUCH_WRITTEN" + remaining-in-fragment = size if size > 0xffff: header = ByteArray (masking_ ? 14 : 10) - header[1] = EIGHT_BYTE_SIZE_ | masking_flag - BIG_ENDIAN.put_int64 header 2 size - else if size > MAX_ONE_BYTE_SIZE_: + header[1] = EIGHT-BYTE-SIZE_ | masking-flag + BIG-ENDIAN.put-int64 header 2 size + else if size > MAX-ONE-BYTE-SIZE_: header = ByteArray (masking_ ? 8 : 4) - header[1] = TWO_BYTE_SIZE_ | masking_flag - BIG_ENDIAN.put_uint16 header 2 size + header[1] = TWO-BYTE-SIZE_ | masking-flag + BIG-ENDIAN.put-uint16 header 2 size else: header = ByteArray (masking_ ? 6 : 2) - header[1] = size | masking_flag - header[0] = opcode | FIN_FLAG_ + header[1] = size | masking-flag + header[0] = opcode | FIN-FLAG_ else: // We don't know the size. Write multiple fragments of up to // 125 bytes. - remaining_in_fragment = min MAX_ONE_BYTE_SIZE_ max_size + remaining-in-fragment = min MAX-ONE-BYTE-SIZE_ max-size header = ByteArray (masking_ ? 6 : 2) header[0] = opcode - header[1] = remaining_in_fragment | masking_flag + header[1] = remaining-in-fragment | masking-flag owner_.write_ header - return remaining_in_fragment + return remaining-in-fragment close_: - if remaining_in_fragment_ != 0: throw "TOO_LITTLE_WRITTEN" + if remaining-in-fragment_ != 0: throw "TOO_LITTLE_WRITTEN" if owner_: if size_ == null: // If size is null, we didn't know the size of the complete message ahead // of time, which means we didn't set the fin flag on the last packet. Send // a zero length packet with a fin flag. header := ByteArray 2 - header[0] = FIN_FLAG_ | (fragment_sent_ ? OPCODE_CONTINUATION_ : OPCODE_BINARY_) + header[0] = FIN-FLAG_ | (fragment-sent_ ? OPCODE-CONTINUATION_ : OPCODE-BINARY_) owner_.write_ header - write_any_ping_ - owner_.writer_close_ this // Notify the websocket that we are done. + write-any-ping_ + owner_.writer-close_ this // Notify the websocket that we are done. owner_ = null /** @@ -435,8 +435,8 @@ A reader for an individual message sent to us. */ class WebSocketReader extends io.Reader: owner_ /WebSocket? := ? - is_text /bool - fragment_reader_ /FragmentReader_ := ? + is-text /bool + fragment-reader_ /FragmentReader_ := ? /** The size of the incoming message if known. @@ -444,7 +444,7 @@ class WebSocketReader extends io.Reader: */ size /int? - constructor.private_ .owner_ .fragment_reader_ .is_text .size: + constructor.private_ .owner_ .fragment-reader_ .is-text .size: /** Returns a byte array, or null if the message has been fully read. @@ -452,81 +452,81 @@ class WebSocketReader extends io.Reader: ByteArrays. */ read_ -> ByteArray?: - result := fragment_reader_.read + result := fragment-reader_.read if result == null: - if fragment_reader_.is_fin: + if fragment-reader_.is-fin: if owner_: - owner_.reader_close_ + owner_.reader-close_ owner_ = null return null if owner_ == null: return null // Closed. - next_fragment := null - while not next_fragment: - next_fragment = owner_.next_fragment_ - if not next_fragment: return null // Closed. - next_fragment = owner_.handle_any_ping_ next_fragment - fragment_reader_ = next_fragment - if not fragment_reader_.is_continuation: + next-fragment := null + while not next-fragment: + next-fragment = owner_.next-fragment_ + if not next-fragment: return null // Closed. + next-fragment = owner_.handle-any-ping_ next-fragment + fragment-reader_ = next-fragment + if not fragment-reader_.is-continuation: throw "PROTOCOL_ERROR" - result = fragment_reader_.read - if fragment_reader_.is_fin and fragment_reader_.is_exhausted: + result = fragment-reader_.read + if fragment-reader_.is-fin and fragment-reader_.is-exhausted: if owner_: - owner_.reader_close_ + owner_.reader-close_ owner_ = null return result class FragmentReader_: owner_ /WebSocket - control_bits_ /int + control-bits_ /int size_ /int received_ := 0 - masking_bytes /ByteArray? - - constructor .owner_ .size_ .control_bits_ --.masking_bytes/ByteArray?=null: - - is_continuation -> bool: return control_bits_ & 0x0f == OPCODE_CONTINUATION_ - is_text -> bool: return control_bits_ & 0x0f == OPCODE_TEXT_ - is_binary -> bool: return control_bits_ & 0x0f == OPCODE_BINARY_ - is_close -> bool: return control_bits_ & 0x0f == OPCODE_CLOSE_ - is_ping -> bool: return control_bits_ & 0x0f == OPCODE_PING_ - is_pong -> bool: return control_bits_ & 0x0f == OPCODE_PONG_ - is_fin -> bool: return control_bits_ & FIN_FLAG_ != 0 - is_exhausted -> bool: return received_ == size_ - - is_ok_ -> bool: - if control_bits_ & 0x70 != 0: return false - opcode := control_bits_ & 0xf + masking-bytes /ByteArray? + + constructor .owner_ .size_ .control-bits_ --.masking-bytes/ByteArray?=null: + + is-continuation -> bool: return control-bits_ & 0x0f == OPCODE-CONTINUATION_ + is-text -> bool: return control-bits_ & 0x0f == OPCODE-TEXT_ + is-binary -> bool: return control-bits_ & 0x0f == OPCODE-BINARY_ + is-close -> bool: return control-bits_ & 0x0f == OPCODE-CLOSE_ + is-ping -> bool: return control-bits_ & 0x0f == OPCODE-PING_ + is-pong -> bool: return control-bits_ & 0x0f == OPCODE-PONG_ + is-fin -> bool: return control-bits_ & FIN-FLAG_ != 0 + is-exhausted -> bool: return received_ == size_ + + is-ok_ -> bool: + if control-bits_ & 0x70 != 0: return false + opcode := control-bits_ & 0xf return opcode < 3 or 8 <= opcode <= 10 read -> ByteArray?: if received_ == size_: return null - next_byte_array := owner_.read_ - if next_byte_array == null: throw "CONNECTION_CLOSED" + next-byte-array := owner_.read_ + if next-byte-array == null: throw "CONNECTION_CLOSED" max := size_ - received_ - if next_byte_array.size > max: - owner_.unread_ next_byte_array[max..] - next_byte_array = next_byte_array[..max] - if masking_bytes: - unmask_bytes_ next_byte_array masking_bytes received_ - received_ += next_byte_array.size - return next_byte_array + if next-byte-array.size > max: + owner_.unread_ next-byte-array[max..] + next-byte-array = next-byte-array[..max] + if masking-bytes: + unmask-bytes_ next-byte-array masking-bytes received_ + received_ += next-byte-array.size + return next-byte-array size -> int?: - if control_bits_ & FIN_FLAG_ == 0: return null + if control-bits_ & FIN-FLAG_ == 0: return null return size_ - static unmask_bytes_ byte_array/ByteArray masking_bytes/ByteArray received/int -> none: - for i := 0; i < byte_array.size; i++: - if received & 3 == 0 and i + 4 < byte_array.size: + static unmask-bytes_ byte-array/ByteArray masking-bytes/ByteArray received/int -> none: + for i := 0; i < byte-array.size; i++: + if received & 3 == 0 and i + 4 < byte-array.size: // When we are at the start of the masking bytes we can accelerate with blit. blit - masking_bytes // Source. - byte_array[i..] // Destination. + masking-bytes // Source. + byte-array[i..] // Destination. 4 // Line width of 4 bytes. - --source_line_stride=0 // Restart at the beginning of the masking bytes on every line. + --source-line-stride=0 // Restart at the beginning of the masking bytes on every line. --operation=XOR // dest[i] ^= source[j]. // Skip the bytes we just blitted. - blitted := round_down (byte_array.size - i) 4 + blitted := round-down (byte-array.size - i) 4 i += blitted - if i < byte_array.size: - byte_array[i] ^= masking_bytes[received++ & 3] + if i < byte-array.size: + byte-array[i] ^= masking-bytes[received++ & 3] diff --git a/tests/cat.toit b/tests/cat.toit index 103894a..201d46b 100644 --- a/tests/cat.toit +++ b/tests/cat.toit @@ -2,7 +2,7 @@ // Use of this source code is governed by a Zero-Clause BSD license that can // be found in the tests/TESTS_LICENSE file. -INDEX_HTML ::= """ +INDEX-HTML ::= """ This is the title diff --git a/tests/google-404-test.toit b/tests/google-404-test.toit index 52a11c0..df50539 100644 --- a/tests/google-404-test.toit +++ b/tests/google-404-test.toit @@ -2,7 +2,7 @@ // Use of this source code is governed by a Zero-Clause BSD license that can // be found in the tests/TESTS_LICENSE file. -import certificate_roots show * +import certificate-roots show * import expect show * import http import net @@ -15,8 +15,8 @@ main: network := net.open client := http.Client.tls network - client.root_certificates_.add GTS_ROOT_R1 + client.root-certificates_.add GTS-ROOT-R1 response := client.get --host=HOST --path=PATH - expect_equals CODE response.status_code + expect-equals CODE response.status-code diff --git a/tests/google-test.toit b/tests/google-test.toit index 2d6a23e..3df7132 100644 --- a/tests/google-test.toit +++ b/tests/google-test.toit @@ -5,14 +5,14 @@ import http import net import net.x509 -import certificate_roots +import certificate-roots main: network := net.open - security_store := http.SecurityStoreInMemory + security-store := http.SecurityStoreInMemory client := http.Client.tls network - --security_store=security_store - --root_certificates=[certificate_roots.GTS_ROOT_R1] + --security-store=security-store + --root-certificates=[certificate-roots.GTS-ROOT-R1] response := client.get "script.google.com" "/" while data := response.body.read: response = client.get "www.google.com" "/" @@ -22,6 +22,6 @@ main: // Deliberately break the session state so that the server rejects our // attempt to use an abbreviated handshake. We harmlessly retry without the // session data. - security_store.session_data_["www.google.com:443"][15] ^= 42 + security-store.session-data_["www.google.com:443"][15] ^= 42 response = client.get "www.google.com" "/" while data := response.body.read: diff --git a/tests/headers-test.toit b/tests/headers-test.toit index d6b1d50..bdee224 100644 --- a/tests/headers-test.toit +++ b/tests/headers-test.toit @@ -7,8 +7,8 @@ import http import io main: - test_from_map - test_keys + test-from-map + test-keys /** Converts the given $headers to a string. @@ -18,28 +18,28 @@ The $http.Headers class has a stringify method that does pretty much */ stringify headers/http.Headers -> string: buffer := io.Buffer - headers.write_to buffer - return buffer.bytes.to_string_non_throwing + headers.write-to buffer + return buffer.bytes.to-string-non-throwing -test_from_map: - headers := http.Headers.from_map {:} - expect_equals "" (stringify headers) +test-from-map: + headers := http.Headers.from-map {:} + expect-equals "" (stringify headers) - headers = http.Headers.from_map {"foo": "bar"} - expect_equals "Foo: bar\r\n" (stringify headers) + headers = http.Headers.from-map {"foo": "bar"} + expect-equals "Foo: bar\r\n" (stringify headers) - headers = http.Headers.from_map {"foo": ["bar", "baz"]} - expect_equals "Foo: bar\r\nFoo: baz\r\n" (stringify headers) + headers = http.Headers.from-map {"foo": ["bar", "baz"]} + expect-equals "Foo: bar\r\nFoo: baz\r\n" (stringify headers) - headers = http.Headers.from_map {"foo": ["bar", "baz"], "qux": "quux"} - expect_equals "Foo: bar\r\nFoo: baz\r\nQux: quux\r\n" (stringify headers) + headers = http.Headers.from-map {"foo": ["bar", "baz"], "qux": "quux"} + expect-equals "Foo: bar\r\nFoo: baz\r\nQux: quux\r\n" (stringify headers) - headers = http.Headers.from_map {"foo": ["bar", "baz"], "Foo": "corge"} - expect_equals "Foo: bar\r\nFoo: baz\r\nFoo: corge\r\n" (stringify headers) + headers = http.Headers.from-map {"foo": ["bar", "baz"], "Foo": "corge"} + expect-equals "Foo: bar\r\nFoo: baz\r\nFoo: corge\r\n" (stringify headers) -test_keys: - headers := http.Headers.from_map {"foo": ["bar", "baz"], "qux": "quux"} - expect_list_equals ["Foo", "Qux"] headers.keys +test-keys: + headers := http.Headers.from-map {"foo": ["bar", "baz"], "qux": "quux"} + expect-list-equals ["Foo", "Qux"] headers.keys headers = http.Headers - expect_list_equals [] headers.keys + expect-list-equals [] headers.keys diff --git a/tests/http-finalizer-test.toit b/tests/http-finalizer-test.toit index c30bc1e..d28f70a 100644 --- a/tests/http-finalizer-test.toit +++ b/tests/http-finalizer-test.toit @@ -15,21 +15,21 @@ import .cat main: network := net.open - port := start_server network - run_client network port - expect_not try_finally_didnt_work + port := start-server network + run-client network port + expect-not try-finally-didnt-work -try_finally_didnt_work := false +try-finally-didnt-work := false -SERVER_CALL_IN_FINALIZER ::= :: - try_finally_didnt_work = true +SERVER-CALL-IN-FINALIZER ::= :: + try-finally-didnt-work = true throw "Server finalizer was called" -CLIENT_CALL_IN_FINALIZER ::= :: - try_finally_didnt_work = true +CLIENT-CALL-IN-FINALIZER ::= :: + try-finally-didnt-work = true throw "Client finalizer was called" -run_client network port/int -> none: +run-client network port/int -> none: client := null 10.repeat: @@ -37,7 +37,7 @@ run_client network port/int -> none: try: client = http.Client network response := client.get --host="localhost" --port=port --path="/cat.png" - client.connection_.call_in_finalizer_ = CLIENT_CALL_IN_FINALIZER + client.connection_.call-in-finalizer_ = CLIENT-CALL-IN-FINALIZER finally: client.close @@ -46,18 +46,18 @@ run_client network port/int -> none: try: client = http.Client network response := client.get --host="localhost" --port=port --path="/cat.png" - client.connection_.call_in_finalizer_ = CLIENT_CALL_IN_FINALIZER + client.connection_.call-in-finalizer_ = CLIENT-CALL-IN-FINALIZER throw MESSAGE finally: client.close - expect_equals MESSAGE exception + expect-equals MESSAGE exception 10.repeat: // Hit the finally clause after making a GET request and reading a bit. try: client = http.Client network response := client.get --host="localhost" --port=port --path="/cat.png" - client.connection_.call_in_finalizer_ = CLIENT_CALL_IN_FINALIZER + client.connection_.call-in-finalizer_ = CLIENT-CALL-IN-FINALIZER data := response.body.read expect data != null finally: @@ -68,44 +68,44 @@ run_client network port/int -> none: try: client = http.Client network response := client.get --host="localhost" --port=port --path="/cause_500" - client.connection_.call_in_finalizer_ = CLIENT_CALL_IN_FINALIZER + client.connection_.call-in-finalizer_ = CLIENT-CALL-IN-FINALIZER finally: client.close -start_server network -> int: - server_socket := network.tcp_listen 0 - port := server_socket.local_address.port +start-server network -> int: + server-socket := network.tcp-listen 0 + port := server-socket.local-address.port server := http.Server task --background:: - listen server server_socket port + listen server server-socket port print "" print "Listening on http://localhost:$port/" print "" return port -listen server server_socket my_port: - server.call_in_finalizer_ = SERVER_CALL_IN_FINALIZER - server.listen server_socket:: | request/http.RequestIncoming response_writer/http.ResponseWriter | +listen server server-socket my-port: + server.call-in-finalizer_ = SERVER-CALL-IN-FINALIZER + server.listen server-socket:: | request/http.RequestIncoming response-writer/http.ResponseWriter | out := response-writer.out if request.path == "/": - response_writer.headers.set "Content-Type" "text/html" - out.write INDEX_HTML + response-writer.headers.set "Content-Type" "text/html" + out.write INDEX-HTML else if request.path == "/foo.json": - response_writer.headers.set "Content-Type" "application/json" + response-writer.headers.set "Content-Type" "application/json" out.write json.encode {"foo": 123, "bar": 1.0/3, "fizz": [1, 42, 103]} else if request.path == "/cat.png": - response_writer.headers.set "Content-Type" "image/png" + response-writer.headers.set "Content-Type" "image/png" out.write CAT else if request.path == "/redirect_from": - response_writer.redirect http.STATUS_FOUND "http://localhost:$my_port/redirect_back" + response-writer.redirect http.STATUS-FOUND "http://localhost:$my-port/redirect_back" else if request.path == "/redirect_back": - response_writer.redirect http.STATUS_FOUND "http://localhost:$my_port/foo.json" + response-writer.redirect http.STATUS-FOUND "http://localhost:$my-port/foo.json" else if request.path == "/redirect_loop": - response_writer.redirect http.STATUS_FOUND "http://localhost:$my_port/redirect_loop" + response-writer.redirect http.STATUS-FOUND "http://localhost:$my-port/redirect_loop" else if request.path == "/cause_500": // Forget to write anything - the server should send 500 - Internal error. else if request.path == "/throw": throw "** Expect a stack trace here caused by testing\n** that we send 500 when server throws" else: - response-writer.write_headers http.STATUS_NOT_FOUND --message="Not Found" + response-writer.write-headers http.STATUS-NOT-FOUND --message="Not Found" diff --git a/tests/http-overload-test.toit b/tests/http-overload-test.toit index 2c19481..2ea86ba 100644 --- a/tests/http-overload-test.toit +++ b/tests/http-overload-test.toit @@ -13,56 +13,56 @@ import .cat main: network := net.open - port := start_server network - run_client network port + port := start-server network + run-client network port -run_client network port/int -> none: - 20.repeat: | client_number | - print client_number +run-client network port/int -> none: + 20.repeat: | client-number | + print client-number client := http.Client network response := client.get --host="localhost" --port=port --path="/" connection := client.connection_ page := "" while data := response.body.read: - page += data.to_string - expect_equals INDEX_HTML.size page.size + page += data.to-string + expect-equals INDEX-HTML.size page.size task:: 10.repeat: sleep --ms=50 - print " $client_number Getting cat" - cat_response := client.get --host="localhost" --port=port --path="/cat.png" - expect_equals connection client.connection_ // Check we reused the connection. - expect_equals "image/png" - cat_response.headers.single "Content-Type" + print " $client-number Getting cat" + cat-response := client.get --host="localhost" --port=port --path="/cat.png" + expect-equals connection client.connection_ // Check we reused the connection. + expect-equals "image/png" + cat-response.headers.single "Content-Type" size := 0 - while data := cat_response.body.read: + while data := cat-response.body.read: size += data.size client.close -start_server network -> int: - server_socket1 := network.tcp_listen 0 - port1 := server_socket1.local_address.port - server1 := http.Server --max_tasks=5 +start-server network -> int: + server-socket1 := network.tcp-listen 0 + port1 := server-socket1.local-address.port + server1 := http.Server --max-tasks=5 print "" print "Listening on http://localhost:$port1/" print "" - task --background:: listen server1 server_socket1 port1 + task --background:: listen server1 server-socket1 port1 return port1 -listen server server_socket my_port: - server.listen server_socket:: | request/http.RequestIncoming response_writer/http.ResponseWriter | +listen server server-socket my-port: + server.listen server-socket:: | request/http.RequestIncoming response-writer/http.ResponseWriter | out := response-writer.out if request.path == "/": - response_writer.headers.set "Content-Type" "text/html" - out.write INDEX_HTML + response-writer.headers.set "Content-Type" "text/html" + out.write INDEX-HTML else if request.path == "/foo.json": - response_writer.headers.set "Content-Type" "application/json" + response-writer.headers.set "Content-Type" "application/json" out.write json.encode {"foo": 123, "bar": 1.0/3, "fizz": [1, 42, 103]} else if request.path == "/cat.png": - response_writer.headers.set "Content-Type" "image/png" + response-writer.headers.set "Content-Type" "image/png" out.write CAT else: - response_writer.write_headers http.STATUS_NOT_FOUND --message="Not Found" + response-writer.write-headers http.STATUS-NOT-FOUND --message="Not Found" diff --git a/tests/http-standalone-test.toit b/tests/http-standalone-test.toit index 507cacf..e938d1d 100644 --- a/tests/http-standalone-test.toit +++ b/tests/http-standalone-test.toit @@ -6,7 +6,7 @@ import encoding.json import encoding.url import expect show * import http -import http.connection show is_close_exception_ +import http.connection show is-close-exception_ import io import net @@ -16,10 +16,10 @@ import .cat main: network := net.open - port := start_server network - run_client network port + port := start-server network + run-client network port -POST_DATA ::= { +POST-DATA ::= { "foo": "bar", "date": "2023-04-25", "baz": "42?103", @@ -28,19 +28,19 @@ POST_DATA ::= { } class NonSizedTestReader extends io.Reader: - call_count_ := 0 + call-count_ := 0 chunks_ := List 5: "$it" * it read_ -> ByteArray?: - if call_count_ == chunks_.size: + if call-count_ == chunks_.size: return null - call_count_++ - return chunks_[call_count_ - 1].to_byte_array + call-count_++ + return chunks_[call-count_ - 1].to-byte-array - full_data -> ByteArray: - return (chunks_.join "").to_byte_array + full-data -> ByteArray: + return (chunks_.join "").to-byte-array -run_client network port/int -> none: +run-client network port/int -> none: client := http.Client network connection := null @@ -50,215 +50,215 @@ run_client network port/int -> none: response := client.get --host="localhost" --port=port --path="/" if connection: - expect_equals connection client.connection_ // Check we reused the connection. + expect-equals connection client.connection_ // Check we reused the connection. else: connection = client.connection_ page := "" while data := response.body.read: - page += data.to_string - expect_equals INDEX_HTML.size page.size + page += data.to-string + expect-equals INDEX-HTML.size page.size response = client.get --host="localhost" --port=port --path="/cat.png" - expect_equals connection client.connection_ // Check we reused the connection. - expect_equals "image/png" + expect-equals connection client.connection_ // Check we reused the connection. + expect-equals "image/png" response.headers.single "Content-Type" size := 0 while data := response.body.read: size += data.size - expect_equals CAT.size size + expect-equals CAT.size size response = client.get --host="localhost" --port=port --path="/unobtainium.jpeg" - expect_equals connection client.connection_ // Check we reused the connection. - expect_equals 404 response.status_code + expect-equals connection client.connection_ // Check we reused the connection. + expect-equals 404 response.status-code response = client.get --uri="http://localhost:$port/204_no_content" - expect_equals 204 response.status_code - expect_equals "Nothing more to say" (response.headers.single "X-Toit-Message") + expect-equals 204 response.status-code + expect-equals "Nothing more to say" (response.headers.single "X-Toit-Message") response = client.get --host="localhost" --port=port --path="/foo.json" - expect_equals connection client.connection_ // Check we reused the connection. + expect-equals connection client.connection_ // Check we reused the connection. - expect_json response: - expect_equals 123 it["foo"] + expect-json response: + expect-equals 123 it["foo"] // Try to buffer the whole response. response := client.get --host="localhost" --port=port --path="/foo.json" - expect_equals 200 response.status_code + expect-equals 200 response.status-code response.body.buffer-all bytes := response.body.read-all decoded := json.decode bytes - expect_equals 123 decoded["foo"] + expect-equals 123 decoded["foo"] response = client.get --uri="http://localhost:$port/content-length.json" - expect_equals 200 response.status_code - expect_equals "application/json" + expect-equals 200 response.status-code + expect-equals "application/json" response.headers.single "Content-Type" content-length := response.headers.single "Content-Length" - expect_not_null content-length - expect_json response: - expect_equals 123 it["foo"] + expect-not-null content-length + expect-json response: + expect-equals 123 it["foo"] // Try to buffer the whole response. response = client.get --uri="http://localhost:$port/content-length.json" - expect_equals 200 response.status_code + expect-equals 200 response.status-code response.body.buffer-all bytes = response.body.read-all decoded = json.decode bytes - expect_equals 123 decoded["foo"] + expect-equals 123 decoded["foo"] response = client.get --uri="http://localhost:$port/redirect_back" expect connection != client.connection_ // Because of the redirect we had to make a new connection. - expect_equals "application/json" + expect-equals "application/json" response.headers.single "Content-Type" - expect_json response: - expect_equals 123 it["foo"] + expect-json response: + expect-equals 123 it["foo"] - expect_throw "Too many redirects": client.get --uri="http://localhost:$port/redirect_loop" + expect-throw "Too many redirects": client.get --uri="http://localhost:$port/redirect_loop" response = client.get --host="localhost" --port=port --path="/foo.json" - expect_equals 200 response.status_code + expect-equals 200 response.status-code response.drain connection = client.connection_ response = client.get --uri="http://localhost:$port/500_because_nothing_written" - expect_equals 500 response.status_code + expect-equals 500 response.status-code - expect_equals connection client.connection_ // Check we reused the connection. + expect-equals connection client.connection_ // Check we reused the connection. response = client.get --host="localhost" --port=port --path="/foo.json" - expect_equals 200 response.status_code - expect_equals connection client.connection_ // Check we reused the connection. + expect-equals 200 response.status-code + expect-equals connection client.connection_ // Check we reused the connection. response.drain response2 := client.get --uri="http://localhost:$port/500_because_throw_before_headers" - expect_equals 500 response2.status_code + expect-equals 500 response2.status-code - expect_equals connection client.connection_ // Check we reused the connection. + expect-equals connection client.connection_ // Check we reused the connection. response = client.get --host="localhost" --port=port --path="/foo.json" - expect_equals 200 response.status_code - expect_equals connection client.connection_ // Check we reused the connection. + expect-equals 200 response.status-code + expect-equals connection client.connection_ // Check we reused the connection. response.drain - exception3 := catch --trace=(: not is_close_exception_ it): + exception3 := catch --trace=(: not is-close-exception_ it): response3 := client.get --uri="http://localhost:$port/hard_close_because_wrote_too_little" - if 200 <= response3.status_code <= 299: + if 200 <= response3.status-code <= 299: while response3.body.read: null // TODO: This should be a smaller number of different exceptions and the // library should export a non-private method that recognizes them. - expect (is_close_exception_ exception3) + expect (is-close-exception_ exception3) response = client.get --host="localhost" --port=port --path="/foo.json" - expect_equals 200 response.status_code + expect-equals 200 response.status-code // We will not be reusing the connection here because the server had to close it // after the user's router did not write enough data. - expect_not_equals connection client.connection_ // Check we reused the connection. + expect-not-equals connection client.connection_ // Check we reused the connection. response.drain connection = client.connection_ - exception4 := catch --trace=(: not is_close_exception_ it): + exception4 := catch --trace=(: not is-close-exception_ it): response4 := client.get --uri="http://localhost:$port/hard_close_because_throw_after_headers" - if 200 <= response4.status_code <= 299: + if 200 <= response4.status-code <= 299: while response4.body.read: null - expect (is_close_exception_ exception4) + expect (is-close-exception_ exception4) response = client.get --host="localhost" --port=port --path="/foo.json" - expect_equals 200 response.status_code + expect-equals 200 response.status-code // We will not be reusing the connection here because the server had to close it expect - is_close_exception_ exception4 + is-close-exception_ exception4 // after the user's router threw after writing success headers. - expect_not_equals connection client.connection_ // Check we reused the connection. + expect-not-equals connection client.connection_ // Check we reused the connection. response.drain connection = client.connection_ response5 := client.get --uri="http://localhost:$port/redirect_from" expect connection != client.connection_ // Because of two redirects we had to make two new connections. - expect_json response5: - expect_equals 123 it["foo"] + expect-json response5: + expect-equals 123 it["foo"] data := {"foo": "bar", "baz": [42, 103]} - response6 := client.post_json data --uri="http://localhost:$port/post_json" - expect_equals "application/json" + response6 := client.post-json data --uri="http://localhost:$port/post_json" + expect-equals "application/json" response6.headers.single "Content-Type" - expect_json response6: - expect_equals data["foo"] it["foo"] - expect_equals data["baz"] it["baz"] + expect-json response6: + expect-equals data["foo"] it["foo"] + expect-equals data["baz"] it["baz"] - response7 := client.post_json data --uri="http://localhost:$port/post_json_redirected_to_cat" - expect_equals "image/png" + response7 := client.post-json data --uri="http://localhost:$port/post_json_redirected_to_cat" + expect-equals "image/png" response7.headers.single "Content-Type" - round_trip_cat := #[] - while byte_array := response7.body.read: - round_trip_cat += byte_array - expect_equals CAT round_trip_cat + round-trip-cat := #[] + while byte-array := response7.body.read: + round-trip-cat += byte-array + expect-equals CAT round-trip-cat response8 := client.get --uri="http://localhost:$port/subdir/redirect_relative" - expect_json response8: - expect_equals 345 it["bar"] + expect-json response8: + expect-equals 345 it["bar"] response9 := client.get --uri="http://localhost:$port/subdir/redirect_absolute" - expect_json response9: - expect_equals 123 it["foo"] + expect-json response9: + expect-equals 123 it["foo"] - request := client.new_request "HEAD" --host="localhost" --port=port --path="/foohead.json" + request := client.new-request "HEAD" --host="localhost" --port=port --path="/foohead.json" response10 := request.send - expect_equals 405 response10.status_code + expect-equals 405 response10.status-code - response11 := client.post_form --host="localhost" --port=port --path="/post_form" POST_DATA - expect_equals 200 response11.status_code + response11 := client.post-form --host="localhost" --port=port --path="/post_form" POST-DATA + expect-equals 200 response11.status-code - test_reader := NonSizedTestReader - request = client.new_request "POST" --host="localhost" --port=port --path="/post_chunked" - request.body = test_reader + test-reader := NonSizedTestReader + request = client.new-request "POST" --host="localhost" --port=port --path="/post_chunked" + request.body = test-reader response12 := request.send - expect_equals 200 response12.status_code - response_data := #[] + expect-equals 200 response12.status-code + response-data := #[] while chunk := response12.body.read: - response_data += chunk - expect_equals test_reader.full_data response_data + response-data += chunk + expect-equals test-reader.full-data response-data - response13 := client.get --host="localhost" --port=port --path="/get_with_parameters" --query_parameters=POST_DATA - response_data = #[] + response13 := client.get --host="localhost" --port=port --path="/get_with_parameters" --query-parameters=POST-DATA + response-data = #[] while chunk := response13.body.read: - response_data += chunk - expect_equals "Response with parameters" response_data.to_string + response-data += chunk + expect-equals "Response with parameters" response-data.to-string - request = client.new_request "GET" --host="localhost" --port=port --path="/get_with_parameters" --query_parameters=POST_DATA + request = client.new-request "GET" --host="localhost" --port=port --path="/get_with_parameters" --query-parameters=POST-DATA response14 := request.send - expect_equals 200 response14.status_code + expect-equals 200 response14.status-code while chunk := response13.body.read: - response_data += chunk - expect_equals "Response with parameters" response_data.to_string + response-data += chunk + expect-equals "Response with parameters" response-data.to-string client.close -expect_json response/http.Response [verify_block]: - expect_equals "application/json" +expect-json response/http.Response [verify-block]: + expect-equals "application/json" response.headers.single "Content-Type" crock := #[] while data := response.body.read: crock += data result := json.decode crock - verify_block.call result + verify-block.call result -start_server network -> int: - server_socket1 := network.tcp_listen 0 - port1 := server_socket1.local_address.port +start-server network -> int: + server-socket1 := network.tcp-listen 0 + port1 := server-socket1.local-address.port server1 := http.Server - server_socket2 := network.tcp_listen 0 - port2 := server_socket2.local_address.port + server-socket2 := network.tcp-listen 0 + port2 := server-socket2.local-address.port server2 := http.Server task --background:: - listen server1 server_socket1 port1 port2 + listen server1 server-socket1 port1 port2 task --background:: - listen server2 server_socket2 port2 port1 + listen server2 server-socket2 port2 port1 print "" print "Listening on http://localhost:$port1/" print "Listening on http://localhost:$port2/" @@ -266,90 +266,90 @@ start_server network -> int: return port1 -listen server server_socket my_port other_port: - server.listen server_socket:: | request/http.RequestIncoming response_writer/http.ResponseWriter | +listen server server-socket my-port other-port: + server.listen server-socket:: | request/http.RequestIncoming response-writer/http.ResponseWriter | if request.method == "POST" and request.path != "/post_chunked": - expect_not_null (request.headers.single "Content-Length") + expect-not-null (request.headers.single "Content-Length") resource := request.query.resource - writer := response_writer.out + writer := response-writer.out if resource == "/": - response_writer.headers.set "Content-Type" "text/html" - writer.write INDEX_HTML + response-writer.headers.set "Content-Type" "text/html" + writer.write INDEX-HTML else if resource == "/foo.json": - response_writer.headers.set "Content-Type" "application/json" + response-writer.headers.set "Content-Type" "application/json" writer.write json.encode {"foo": 123, "bar": 1.0/3, "fizz": [1, 42, 103]} else if resource == "/content-length.json": data := json.encode {"foo": 123, "bar": 1.0/3, "fizz": [1, 42, 103]} - response_writer.headers.set "Content-Type" "application/json" - response_writer.headers.set "Content-Length" "$data.size" + response-writer.headers.set "Content-Type" "application/json" + response-writer.headers.set "Content-Length" "$data.size" writer.write data else if resource == "/cat.png": - response_writer.headers.set "Content-Type" "image/png" + response-writer.headers.set "Content-Type" "image/png" writer.write CAT else if resource == "/redirect_from": - response_writer.redirect http.STATUS_FOUND "http://localhost:$other_port/redirect_back" + response-writer.redirect http.STATUS-FOUND "http://localhost:$other-port/redirect_back" else if resource == "/redirect_back": - response_writer.redirect http.STATUS_FOUND "http://localhost:$other_port/foo.json" + response-writer.redirect http.STATUS-FOUND "http://localhost:$other-port/foo.json" else if resource == "/subdir/redirect_relative": - response_writer.redirect http.STATUS_FOUND "bar.json" + response-writer.redirect http.STATUS-FOUND "bar.json" else if resource == "/subdir/bar.json": - response_writer.headers.set "Content-Type" "application/json" + response-writer.headers.set "Content-Type" "application/json" writer.write json.encode {"bar": 345 } else if resource == "/subdir/redirect_absolute": - response_writer.redirect http.STATUS_FOUND "/foo.json" + response-writer.redirect http.STATUS-FOUND "/foo.json" else if resource == "/redirect_loop": - response_writer.redirect http.STATUS_FOUND "http://localhost:$other_port/redirect_loop" + response-writer.redirect http.STATUS-FOUND "http://localhost:$other-port/redirect_loop" else if resource == "/204_no_content": - response_writer.headers.set "X-Toit-Message" "Nothing more to say" - response_writer.write_headers http.STATUS_NO_CONTENT + response-writer.headers.set "X-Toit-Message" "Nothing more to say" + response-writer.write-headers http.STATUS-NO-CONTENT else if resource == "/500_because_nothing_written": // Forget to write anything - the server should send 500 - Internal error. else if resource == "/500_because_throw_before_headers": throw "** Expect a stack trace here caused by testing: throws_before_headers **" else if resource == "/hard_close_because_wrote_too_little": - response_writer.headers.set "Content-Length" "2" + response-writer.headers.set "Content-Length" "2" writer.write "x" // Only writes half the message. else if resource == "/hard_close_because_throw_after_headers": - response_writer.headers.set "Content-Length" "2" + response-writer.headers.set "Content-Length" "2" writer.write "x" // Only writes half the message. throw "** Expect a stack trace here caused by testing: throws_after_headers **" else if resource == "/post_json": - response_writer.headers.set "Content-Type" "application/json" + response-writer.headers.set "Content-Type" "application/json" while data := request.body.read: writer.write data else if resource == "/post_form": - expect_equals "application/x-www-form-urlencoded" (request.headers.single "Content-Type") - response_writer.headers.set "Content-Type" "text/plain" + expect-equals "application/x-www-form-urlencoded" (request.headers.single "Content-Type") + response-writer.headers.set "Content-Type" "text/plain" str := "" while data := request.body.read: - str += data.to_string + str += data.to-string map := {:} str.split "&": | pair | parts := pair.split "=" key := url.decode parts[0] value := url.decode parts[1] - map[key.to_string] = value.to_string - expect_equals POST_DATA.size map.size - POST_DATA.do: | key value | - expect_equals POST_DATA[key] map[key] + map[key.to-string] = value.to-string + expect-equals POST-DATA.size map.size + POST-DATA.do: | key value | + expect-equals POST-DATA[key] map[key] writer.write "OK" else if resource == "/post_json_redirected_to_cat": - response_writer.headers.set "Content-Type" "application/json" + response-writer.headers.set "Content-Type" "application/json" while data := request.body.read: - response_writer.redirect http.STATUS_SEE_OTHER "http://localhost:$my_port/cat.png" + response-writer.redirect http.STATUS-SEE-OTHER "http://localhost:$my-port/cat.png" else if resource == "/post_chunked": - response_writer.headers.set "Content-Type" "text/plain" + response-writer.headers.set "Content-Type" "text/plain" while data := request.body.read: writer.write data else if request.query.resource == "/get_with_parameters": - response_writer.headers.set "Content-Type" "text/plain" + response-writer.headers.set "Content-Type" "text/plain" writer.write "Response with parameters" - POST_DATA.do: | key/string value/string | - expect_equals value request.query.parameters[key] + POST-DATA.do: | key/string value/string | + expect-equals value request.query.parameters[key] else: print "request.query.resource = '$request.query.resource'" - response_writer.write_headers http.STATUS_NOT_FOUND --message="Not Found" + response-writer.write-headers http.STATUS-NOT-FOUND --message="Not Found" diff --git a/tests/parse-url-test.toit b/tests/parse-url-test.toit index f27388e..5f6787f 100644 --- a/tests/parse-url-test.toit +++ b/tests/parse-url-test.toit @@ -7,269 +7,269 @@ import expect show * main: parts := http.ParsedUri_.parse_ "https://www.youtube.com/watch?v=2HJxya0CWco#t=0m6s" - expect_equals "https" parts.scheme - expect_equals "www.youtube.com" parts.host - expect_equals 443 parts.port - expect_equals "/watch?v=2HJxya0CWco" parts.path - expect_equals "t=0m6s" parts.fragment + expect-equals "https" parts.scheme + expect-equals "www.youtube.com" parts.host + expect-equals 443 parts.port + expect-equals "/watch?v=2HJxya0CWco" parts.path + expect-equals "t=0m6s" parts.fragment parts = http.ParsedUri_.parse_ "https://www.youtube.com/watch?v=2HJxya0CWco" - expect_equals "https" parts.scheme - expect_equals "www.youtube.com" parts.host - expect_equals 443 parts.port - expect_equals "/watch?v=2HJxya0CWco" parts.path + expect-equals "https" parts.scheme + expect-equals "www.youtube.com" parts.host + expect-equals 443 parts.port + expect-equals "/watch?v=2HJxya0CWco" parts.path parts = http.ParsedUri_.parse_ "https://www.youtube.com:443/watch?v=2:HJxya0CWco" - expect_equals "https" parts.scheme - expect_equals "www.youtube.com" parts.host - expect_equals 443 parts.port - expect_equals "/watch?v=2:HJxya0CWco" parts.path + expect-equals "https" parts.scheme + expect-equals "www.youtube.com" parts.host + expect-equals 443 parts.port + expect-equals "/watch?v=2:HJxya0CWco" parts.path YT ::= "www.youtube.com:443/watch/?v=10&encoding=json" parts = http.ParsedUri_.parse_ "https://$YT" - expect_equals "https" parts.scheme - expect_equals "www.youtube.com" parts.host - expect_equals 443 parts.port - expect_equals "/watch/?v=10&encoding=json" parts.path + expect-equals "https" parts.scheme + expect-equals "www.youtube.com" parts.host + expect-equals 443 parts.port + expect-equals "/watch/?v=10&encoding=json" parts.path - expect_throw "Missing scheme in '$YT'": http.ParsedUri_.parse_ YT - expect_throw "Missing scheme in '/$YT'": http.ParsedUri_.parse_ "/$YT" - expect_throw "Missing scheme in '//$YT'": http.ParsedUri_.parse_ "//$YT" - expect_throw "Missing scheme in 'http/$YT'": http.ParsedUri_.parse_ "http/$YT" + expect-throw "Missing scheme in '$YT'": http.ParsedUri_.parse_ YT + expect-throw "Missing scheme in '/$YT'": http.ParsedUri_.parse_ "/$YT" + expect-throw "Missing scheme in '//$YT'": http.ParsedUri_.parse_ "//$YT" + expect-throw "Missing scheme in 'http/$YT'": http.ParsedUri_.parse_ "http/$YT" - expect_throw "Missing scheme in '192.168.86.26:55321/auth'": + expect-throw "Missing scheme in '192.168.86.26:55321/auth'": http.ParsedUri_.parse_ "192.168.86.26:55321/auth" http.ParsedUri_.parse_ "https://www.youtube.com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www.youtube.com-/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www.youtube.-com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www.youtube-.com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www.-youtube.com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www-.youtube.com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://-www.youtube.com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://.www.youtube.com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www..youtube.com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www..y'utube.com/watch?v=2HJxya0CWco" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www..yøutube.com/watch?v=2HJxya0CWco" - - expect_throw "Unknown scheme: 'fisk'": http.ParsedUri_.parse_ "fisk://fishing.net/" - expect_throw "Missing scheme in '/a/relative/url'": http.ParsedUri_.parse_ "/a/relative/url" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "http:/127.0.0.1/path" - expect_throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "http:127.0.0.1/path" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www.youtube.com-/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www.youtube.-com/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www.youtube-.com/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www.-youtube.com/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www-.youtube.com/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://-www.youtube.com/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://.www.youtube.com/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www..youtube.com/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www..y'utube.com/watch?v=2HJxya0CWco" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "https://www..yøutube.com/watch?v=2HJxya0CWco" + + expect-throw "Unknown scheme: 'fisk'": http.ParsedUri_.parse_ "fisk://fishing.net/" + expect-throw "Missing scheme in '/a/relative/url'": http.ParsedUri_.parse_ "/a/relative/url" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "http:/127.0.0.1/path" + expect-throw "URI_PARSING_ERROR": http.ParsedUri_.parse_ "http:127.0.0.1/path" parts = http.ParsedUri_.parse_ "wss://api.example.com./end-point" - expect_equals "wss" parts.scheme - expect_equals "api.example.com." parts.host - expect_equals 443 parts.port - expect_equals "/end-point" parts.path - expect_equals null parts.fragment - expect parts.use_tls + expect-equals "wss" parts.scheme + expect-equals "api.example.com." parts.host + expect-equals 443 parts.port + expect-equals "/end-point" parts.path + expect-equals null parts.fragment + expect parts.use-tls parts = http.ParsedUri_.parse_ "WSS://api.example.com./end-point" - expect_equals "wss" parts.scheme + expect-equals "wss" parts.scheme parts = http.ParsedUri_.parse_ "htTPs://api.example.com./end-point" - expect_equals "https" parts.scheme - - parts = http.ParsedUri_.parse_ "www.yahoo.com" --default_scheme="https" - expect_equals "https" parts.scheme - expect_equals "www.yahoo.com" parts.host - expect_equals 443 parts.port - expect_equals "/" parts.path - expect_equals null parts.fragment - expect parts.use_tls - - parts = http.ParsedUri_.parse_ "localhost:1080" --default_scheme="https" - expect_equals "https" parts.scheme - expect_equals "localhost" parts.host - expect_equals 1080 parts.port - expect_equals "/" parts.path - expect_equals null parts.fragment - expect parts.use_tls + expect-equals "https" parts.scheme + + parts = http.ParsedUri_.parse_ "www.yahoo.com" --default-scheme="https" + expect-equals "https" parts.scheme + expect-equals "www.yahoo.com" parts.host + expect-equals 443 parts.port + expect-equals "/" parts.path + expect-equals null parts.fragment + expect parts.use-tls + + parts = http.ParsedUri_.parse_ "localhost:1080" --default-scheme="https" + expect-equals "https" parts.scheme + expect-equals "localhost" parts.host + expect-equals 1080 parts.port + expect-equals "/" parts.path + expect-equals null parts.fragment + expect parts.use-tls parts = http.ParsedUri_.parse_ "http:1080" - expect_equals "http" parts.scheme - expect_equals "1080" parts.host - expect_equals "/" parts.path - expect_equals null parts.fragment + expect-equals "http" parts.scheme + expect-equals "1080" parts.host + expect-equals "/" parts.path + expect-equals null parts.fragment parts = http.ParsedUri_.parse_ "HTTP:1080" - expect_equals "http" parts.scheme - expect_equals "1080" parts.host - expect_equals "/" parts.path - expect_equals null parts.fragment - - parts = http.ParsedUri_.parse_ "127.0.0.1:1080" --default_scheme="https" - expect_equals "https" parts.scheme - expect_equals "127.0.0.1" parts.host - expect_equals 1080 parts.port - expect_equals "/" parts.path - expect_equals null parts.fragment - expect parts.use_tls + expect-equals "http" parts.scheme + expect-equals "1080" parts.host + expect-equals "/" parts.path + expect-equals null parts.fragment + + parts = http.ParsedUri_.parse_ "127.0.0.1:1080" --default-scheme="https" + expect-equals "https" parts.scheme + expect-equals "127.0.0.1" parts.host + expect-equals 1080 parts.port + expect-equals "/" parts.path + expect-equals null parts.fragment + expect parts.use-tls parts = http.ParsedUri_.parse_ "http://localhost:1080/" - expect_equals "http" parts.scheme - expect_equals "localhost" parts.host - expect_equals 1080 parts.port - expect_equals "/" parts.path - expect_equals null parts.fragment - expect_not parts.use_tls + expect-equals "http" parts.scheme + expect-equals "localhost" parts.host + expect-equals 1080 parts.port + expect-equals "/" parts.path + expect-equals null parts.fragment + expect-not parts.use-tls parts = http.ParsedUri_.parse_ "http://localhost:1080/#" - expect_equals "http" parts.scheme - expect_equals "localhost" parts.host - expect_equals 1080 parts.port - expect_equals "/" parts.path - expect_equals "" parts.fragment - expect_not parts.use_tls + expect-equals "http" parts.scheme + expect-equals "localhost" parts.host + expect-equals 1080 parts.port + expect-equals "/" parts.path + expect-equals "" parts.fragment + expect-not parts.use-tls parts = http.ParsedUri_.parse_ "http://localhost:1080/#x" - expect_equals "http" parts.scheme - expect_equals "localhost" parts.host - expect_equals 1080 parts.port - expect_equals "/" parts.path - expect_equals "x" parts.fragment - expect_not parts.use_tls + expect-equals "http" parts.scheme + expect-equals "localhost" parts.host + expect-equals 1080 parts.port + expect-equals "/" parts.path + expect-equals "x" parts.fragment + expect-not parts.use-tls parts = http.ParsedUri_.parse_ "ws://xn--us--um5a.com/schneemann" - expect_equals "ws" parts.scheme - expect_equals "xn--us--um5a.com" parts.host - expect_equals 80 parts.port - expect_equals "/schneemann" parts.path - expect_equals null parts.fragment - expect_not parts.use_tls - - parts = http.ParsedUri_.parse_ "//127.0.0.1/path" --default_scheme="https" - expect_equals "https" parts.scheme - expect_equals "127.0.0.1" parts.host - expect_equals 443 parts.port - expect_equals "/path" parts.path - expect_equals null parts.fragment - expect parts.use_tls + expect-equals "ws" parts.scheme + expect-equals "xn--us--um5a.com" parts.host + expect-equals 80 parts.port + expect-equals "/schneemann" parts.path + expect-equals null parts.fragment + expect-not parts.use-tls + + parts = http.ParsedUri_.parse_ "//127.0.0.1/path" --default-scheme="https" + expect-equals "https" parts.scheme + expect-equals "127.0.0.1" parts.host + expect-equals 443 parts.port + expect-equals "/path" parts.path + expect-equals null parts.fragment + expect parts.use-tls parts = http.ParsedUri_.parse_ "http://127.0.0.1/path" - expect_equals "http" parts.scheme - expect_equals "127.0.0.1" parts.host - expect_equals 80 parts.port - expect_equals "/path" parts.path - expect_equals null parts.fragment - expect_not parts.use_tls + expect-equals "http" parts.scheme + expect-equals "127.0.0.1" parts.host + expect-equals 80 parts.port + expect-equals "/path" parts.path + expect-equals null parts.fragment + expect-not parts.use-tls parts = http.ParsedUri_.parse_ "https://original.com/foo/#fraggy" - expect_equals "https" parts.scheme - expect_equals "original.com" parts.host - expect_equals 443 parts.port - expect_equals "/foo/" parts.path - expect_equals "fraggy" parts.fragment - expect parts.use_tls + expect-equals "https" parts.scheme + expect-equals "original.com" parts.host + expect-equals 443 parts.port + expect-equals "/foo/" parts.path + expect-equals "fraggy" parts.fragment + expect parts.use-tls parts2 := http.ParsedUri_.parse_ --previous=parts "http://redirect.com/bar" - expect_equals "http" parts2.scheme // Changed in accordance with redirect. - expect_equals "redirect.com" parts2.host - expect_equals 80 parts2.port - expect_equals "/bar" parts2.path - expect_equals "fraggy" parts2.fragment // Kept from original non-redirected URI. - expect_not parts2.use_tls + expect-equals "http" parts2.scheme // Changed in accordance with redirect. + expect-equals "redirect.com" parts2.host + expect-equals 80 parts2.port + expect-equals "/bar" parts2.path + expect-equals "fraggy" parts2.fragment // Kept from original non-redirected URI. + expect-not parts2.use-tls parts2 = http.ParsedUri_.parse_ --previous=parts "/bar#fragment" - expect_equals "https" parts2.scheme - expect_equals "original.com" parts2.host - expect_equals 443 parts2.port - expect_equals "/bar" parts2.path - expect_equals "fragment" parts2.fragment // Kept from original non-redirected URI. - expect parts2.use_tls + expect-equals "https" parts2.scheme + expect-equals "original.com" parts2.host + expect-equals 443 parts2.port + expect-equals "/bar" parts2.path + expect-equals "fragment" parts2.fragment // Kept from original non-redirected URI. + expect parts2.use-tls parts2 = http.ParsedUri_.parse_ --previous=parts "bar#fraggles" - expect_equals "https" parts2.scheme - expect_equals "original.com" parts2.host - expect_equals 443 parts2.port - expect_equals "/foo/bar" parts2.path // composed of original path and relative path. - expect_equals "fraggles" parts2.fragment // Kept from original non-redirected URI. - expect parts2.use_tls + expect-equals "https" parts2.scheme + expect-equals "original.com" parts2.host + expect-equals 443 parts2.port + expect-equals "/foo/bar" parts2.path // composed of original path and relative path. + expect-equals "fraggles" parts2.fragment // Kept from original non-redirected URI. + expect parts2.use-tls parts = http.ParsedUri_.parse_ "https://original.com" // No path - we should add a slash. - expect_equals "https" parts.scheme - expect_equals "original.com" parts.host - expect_equals 443 parts.port - expect_equals "/" parts.path // Slash was implied. - expect_equals null parts.fragment - expect parts.use_tls + expect-equals "https" parts.scheme + expect-equals "original.com" parts.host + expect-equals 443 parts.port + expect-equals "/" parts.path // Slash was implied. + expect-equals null parts.fragment + expect parts.use-tls parts = http.ParsedUri_.parse_ "https://original.com/foo/?value=/../" // Query part. - expect_equals "https" parts.scheme - expect_equals "original.com" parts.host - expect_equals 443 parts.port - expect_equals "/foo/?value=/../" parts.path - expect_equals null parts.fragment - expect parts.use_tls + expect-equals "https" parts.scheme + expect-equals "original.com" parts.host + expect-equals 443 parts.port + expect-equals "/foo/?value=/../" parts.path + expect-equals null parts.fragment + expect parts.use-tls parts2 = http.ParsedUri_.parse_ --previous=parts "bar?value=dotdot#fraggles" - expect_equals "https" parts2.scheme - expect_equals "original.com" parts2.host - expect_equals 443 parts2.port - expect_equals "/foo/bar?value=dotdot" parts2.path // Joined, the old query is not used. - expect_equals "fraggles" parts2.fragment - expect parts2.use_tls + expect-equals "https" parts2.scheme + expect-equals "original.com" parts2.host + expect-equals 443 parts2.port + expect-equals "/foo/bar?value=dotdot" parts2.path // Joined, the old query is not used. + expect-equals "fraggles" parts2.fragment + expect parts2.use-tls // Can't redirect an HTTP connection to a WebSockets connection. - expect_throw "INVALID_REDIRECT": parts = http.ParsedUri_.parse_ --previous=parts "wss://socket.redirect.com/api" + expect-throw "INVALID_REDIRECT": parts = http.ParsedUri_.parse_ --previous=parts "wss://socket.redirect.com/api" parts = http.ParsedUri_.parse_ "https://[::]/foo#fraggy" - expect_equals "https" parts.scheme - expect_equals "::" parts.host - expect_equals 443 parts.port - expect_equals "/foo" parts.path - expect_equals "fraggy" parts.fragment - expect parts.use_tls + expect-equals "https" parts.scheme + expect-equals "::" parts.host + expect-equals 443 parts.port + expect-equals "/foo" parts.path + expect-equals "fraggy" parts.fragment + expect parts.use-tls parts = http.ParsedUri_.parse_ "https://[1234::7890]/foo#fraggy" - expect_equals "https" parts.scheme - expect_equals "1234::7890" parts.host - expect_equals 443 parts.port - expect_equals "/foo" parts.path - expect_equals "fraggy" parts.fragment - expect parts.use_tls + expect-equals "https" parts.scheme + expect-equals "1234::7890" parts.host + expect-equals 443 parts.port + expect-equals "/foo" parts.path + expect-equals "fraggy" parts.fragment + expect parts.use-tls parts = http.ParsedUri_.parse_ "https://[::]:80/foo#fraggy" - expect_equals "https" parts.scheme - expect_equals "::" parts.host - expect_equals 80 parts.port - expect_equals "/foo" parts.path - expect_equals "fraggy" parts.fragment - expect parts.use_tls - - expect_throw "URI_PARSING_ERROR": parts = http.ParsedUri_.parse_ "https://[::] :80/foo#fraggy" - expect_throw "URI_PARSING_ERROR": parts = http.ParsedUri_.parse_ "https://[::/foo#fraggy" - expect_throw "ILLEGAL_HOSTNAME": parts = http.ParsedUri_.parse_ "https://1234::5678/foo#fraggy" - expect_throw "ILLEGAL_HOSTNAME": parts = http.ParsedUri_.parse_ "https://[www.apple.com]/foo#fraggy" - expect_throw "ILLEGAL_HOSTNAME": parts = http.ParsedUri_.parse_ "https://[www.apple.com]:80/foo#fraggy" - expect_throw "ILLEGAL_HOSTNAME": parts = http.ParsedUri_.parse_ "https:// [::]:80/foo#fraggy" - expect_throw "INTEGER_PARSING_ERROR": parts = http.ParsedUri_.parse_ "https:// [::]/foo#fraggy" - - expect_equals "/foo.txt" - http.ParsedUri_.merge_paths_ "/" "foo.txt" - expect_equals "/foo.txt" - http.ParsedUri_.merge_paths_ "/" "./foo.txt" - expect_equals "/foo.txt" - http.ParsedUri_.merge_paths_ "/bar.jpg" "foo.txt" - expect_equals "/bar/foo.txt" - http.ParsedUri_.merge_paths_ "/bar/" "foo.txt" - expect_equals "/bar/foo.txt" - http.ParsedUri_.merge_paths_ "/bar/sdlkfjsdl.sdlkf" "foo.txt" - expect_equals "/bar/foo.txt" - http.ParsedUri_.merge_paths_ "/bar/" "./foo.txt" - expect_equals "/foo.txt" - http.ParsedUri_.merge_paths_ "/bar/" "../foo.txt" - expect_equals "/foo.txt" - http.ParsedUri_.merge_paths_ "/bar/super-duper.jpg" "../foo.txt" - expect_equals "/foo.txt" - http.ParsedUri_.merge_paths_ "/bar/super-duper.jpg" "./.././foo.txt" - expect_equals "/" - http.ParsedUri_.merge_paths_ "/bar/" ".." - expect_throw "ILLEGAL_PATH": - http.ParsedUri_.merge_paths_ "/" "../foo.txt" - expect_throw "ILLEGAL_PATH": - http.ParsedUri_.merge_paths_ "/" "../../foo.txt" - expect_throw "ILLEGAL_PATH": - http.ParsedUri_.merge_paths_ "/bar/" "../../foo.txt" - expect_throw "ILLEGAL_PATH": - http.ParsedUri_.merge_paths_ "/bar/" "./../../foo.txt" + expect-equals "https" parts.scheme + expect-equals "::" parts.host + expect-equals 80 parts.port + expect-equals "/foo" parts.path + expect-equals "fraggy" parts.fragment + expect parts.use-tls + + expect-throw "URI_PARSING_ERROR": parts = http.ParsedUri_.parse_ "https://[::] :80/foo#fraggy" + expect-throw "URI_PARSING_ERROR": parts = http.ParsedUri_.parse_ "https://[::/foo#fraggy" + expect-throw "ILLEGAL_HOSTNAME": parts = http.ParsedUri_.parse_ "https://1234::5678/foo#fraggy" + expect-throw "ILLEGAL_HOSTNAME": parts = http.ParsedUri_.parse_ "https://[www.apple.com]/foo#fraggy" + expect-throw "ILLEGAL_HOSTNAME": parts = http.ParsedUri_.parse_ "https://[www.apple.com]:80/foo#fraggy" + expect-throw "ILLEGAL_HOSTNAME": parts = http.ParsedUri_.parse_ "https:// [::]:80/foo#fraggy" + expect-throw "INTEGER_PARSING_ERROR": parts = http.ParsedUri_.parse_ "https:// [::]/foo#fraggy" + + expect-equals "/foo.txt" + http.ParsedUri_.merge-paths_ "/" "foo.txt" + expect-equals "/foo.txt" + http.ParsedUri_.merge-paths_ "/" "./foo.txt" + expect-equals "/foo.txt" + http.ParsedUri_.merge-paths_ "/bar.jpg" "foo.txt" + expect-equals "/bar/foo.txt" + http.ParsedUri_.merge-paths_ "/bar/" "foo.txt" + expect-equals "/bar/foo.txt" + http.ParsedUri_.merge-paths_ "/bar/sdlkfjsdl.sdlkf" "foo.txt" + expect-equals "/bar/foo.txt" + http.ParsedUri_.merge-paths_ "/bar/" "./foo.txt" + expect-equals "/foo.txt" + http.ParsedUri_.merge-paths_ "/bar/" "../foo.txt" + expect-equals "/foo.txt" + http.ParsedUri_.merge-paths_ "/bar/super-duper.jpg" "../foo.txt" + expect-equals "/foo.txt" + http.ParsedUri_.merge-paths_ "/bar/super-duper.jpg" "./.././foo.txt" + expect-equals "/" + http.ParsedUri_.merge-paths_ "/bar/" ".." + expect-throw "ILLEGAL_PATH": + http.ParsedUri_.merge-paths_ "/" "../foo.txt" + expect-throw "ILLEGAL_PATH": + http.ParsedUri_.merge-paths_ "/" "../../foo.txt" + expect-throw "ILLEGAL_PATH": + http.ParsedUri_.merge-paths_ "/bar/" "../../foo.txt" + expect-throw "ILLEGAL_PATH": + http.ParsedUri_.merge-paths_ "/bar/" "./../../foo.txt" diff --git a/tests/redirect-test-httpbin.toit b/tests/redirect-test-httpbin.toit index 2dac0c1..14d3a60 100644 --- a/tests/redirect-test-httpbin.toit +++ b/tests/redirect-test-httpbin.toit @@ -7,130 +7,130 @@ import http import net import encoding.json import encoding.url -import certificate_roots +import certificate-roots // For testing the HOST url might be rewritten to a localhost. HOST := "httpbin.org" PORT/int? := null -HOST_PORT := PORT ? "$HOST:$PORT" : HOST -PATH_GET ::= "/absolute-redirect/3" +HOST-PORT := PORT ? "$HOST:$PORT" : HOST +PATH-GET ::= "/absolute-redirect/3" -PATH_POST ::= "/redirect-to?url=$(url.encode "http://$HOST_PORT/post")&status_code=302" -PATH_POST_TLS ::= "/redirect-to?url=$(url.encode "https://$HOST_PORT/post")&status_code=302" -PATH_POST303 ::= "/redirect-to?url=$(url.encode "http://$HOST_PORT/get")&status_code=303" +PATH-POST ::= "/redirect-to?url=$(url.encode "http://$HOST-PORT/post")&status_code=302" +PATH-POST-TLS ::= "/redirect-to?url=$(url.encode "https://$HOST-PORT/post")&status_code=302" +PATH-POST303 ::= "/redirect-to?url=$(url.encode "http://$HOST-PORT/get")&status_code=303" -check_get_response response/http.Response --scheme: +check-get-response response/http.Response --scheme: data := #[] while chunk := response.body.read: data += chunk - expect_equals 200 response.status_code + expect-equals 200 response.status-code decoded := json.decode data - host_port := HOST - if PORT: host_port += ":$PORT" - expect_equals "$scheme://$host_port/get" decoded["url"] + host-port := HOST + if PORT: host-port += ":$PORT" + expect-equals "$scheme://$host-port/get" decoded["url"] -test_get network/net.Interface --do_drain/bool=false: - print "Get$(do_drain ? " (manual drain)" : "")" +test-get network/net.Interface --do-drain/bool=false: + print "Get$(do-drain ? " (manual drain)" : "")" client := http.Client network - response := client.get HOST --port=PORT PATH_GET - check_get_response response --scheme="http" + response := client.get HOST --port=PORT PATH-GET + check-get-response response --scheme="http" - response = client.get HOST --port=PORT PATH_GET --no-follow_redirects - expect_equals 302 response.status_code - if do_drain: + response = client.get HOST --port=PORT PATH-GET --no-follow-redirects + expect-equals 302 response.status-code + if do-drain: response.drain client.close -test_post network/net.Interface --do_drain/bool=false: - print "Post$(do_drain ? " (manual drain)" : "")" - client := http.Client network --root_certificates=[certificate_roots.STARFIELD_CLASS_2_CA] +test-post network/net.Interface --do-drain/bool=false: + print "Post$(do-drain ? " (manual drain)" : "")" + client := http.Client network --root-certificates=[certificate-roots.STARFIELD-CLASS-2-CA] - response := client.post --host=HOST --port=PORT --path=PATH_POST #['h', 'e', 'l', 'l', 'o'] + response := client.post --host=HOST --port=PORT --path=PATH-POST #['h', 'e', 'l', 'l', 'o'] data := #[] while chunk := response.body.read: data += chunk - expect_equals 200 response.status_code + expect-equals 200 response.status-code decoded := json.decode data - expect_equals "hello" decoded["data"] + expect-equals "hello" decoded["data"] if HOST == "httpbin.org": // Test that we can redirect from an HTTP to an HTTPS location. - response = client.post --host=HOST --port=PORT --path=PATH_POST_TLS #['h', 'e', 'l', 'l', 'o'] + response = client.post --host=HOST --port=PORT --path=PATH-POST-TLS #['h', 'e', 'l', 'l', 'o'] data = #[] while chunk := response.body.read: data += chunk - expect_equals 200 response.status_code + expect-equals 200 response.status-code decoded = json.decode data - expect_equals "hello" decoded["data"] + expect-equals "hello" decoded["data"] // Test that we see the redirect if we ask not to follow redirects. - response = client.post --host=HOST --port=PORT --path=PATH_POST #['h', 'e', 'l', 'l', 'o'] --no-follow_redirects - expect_equals 302 response.status_code - if do_drain: + response = client.post --host=HOST --port=PORT --path=PATH-POST #['h', 'e', 'l', 'l', 'o'] --no-follow-redirects + expect-equals 302 response.status-code + if do-drain: response.drain - response = client.post_json --host=HOST --port=PORT --path=PATH_POST "hello" + response = client.post-json --host=HOST --port=PORT --path=PATH-POST "hello" data = #[] while chunk := response.body.read: data += chunk - expect_equals 200 response.status_code + expect-equals 200 response.status-code decoded = json.decode data - expect_equals "hello" (json.decode decoded["data"].to_byte_array) + expect-equals "hello" (json.decode decoded["data"].to-byte-array) - response = client.post_json --host=HOST --port=PORT --path=PATH_POST "hello" --no-follow_redirects - expect_equals 302 response.status_code - if do_drain: + response = client.post-json --host=HOST --port=PORT --path=PATH-POST "hello" --no-follow-redirects + expect-equals 302 response.status-code + if do-drain: response.drain - response = client.post_form --host=HOST --port=PORT --path=PATH_POST { "toit": "hello" } + response = client.post-form --host=HOST --port=PORT --path=PATH-POST { "toit": "hello" } data = #[] while chunk := response.body.read: data += chunk - expect_equals 200 response.status_code + expect-equals 200 response.status-code decoded = json.decode data - expect_equals "hello" decoded["form"]["toit"] + expect-equals "hello" decoded["form"]["toit"] - response = client.post_form --host=HOST --port=PORT --path=PATH_POST { "toit": "hello" } --no-follow_redirects - expect_equals 302 response.status_code - if do_drain: + response = client.post-form --host=HOST --port=PORT --path=PATH-POST { "toit": "hello" } --no-follow-redirects + expect-equals 302 response.status-code + if do-drain: response.drain // A post to a redirect 303 should become a GET. - response = client.post --host=HOST --port=PORT --path=PATH_POST303 #['h', 'e', 'l', 'l', 'o'] + response = client.post --host=HOST --port=PORT --path=PATH-POST303 #['h', 'e', 'l', 'l', 'o'] data = #[] while chunk := response.body.read: data += chunk - expect_equals 200 response.status_code + expect-equals 200 response.status-code decoded = json.decode data - expect decoded["args"].is_empty + expect decoded["args"].is-empty - response = client.post --host=HOST --port=PORT --path=PATH_POST303 #['h', 'e', 'l', 'l', 'o'] --no-follow_redirects - expect_equals 303 response.status_code - if do_drain: + response = client.post --host=HOST --port=PORT --path=PATH-POST303 #['h', 'e', 'l', 'l', 'o'] --no-follow-redirects + expect-equals 303 response.status-code + if do-drain: response.drain client.close main args: - if not args.is_empty: - host_port/string := args[0] - if host_port.contains ":": - parts := host_port.split --at_first ":" + if not args.is-empty: + host-port/string := args[0] + if host-port.contains ":": + parts := host-port.split --at-first ":" HOST = parts[0] PORT = int.parse parts[1] else: - HOST = host_port + HOST = host-port if HOST == "httpbin.org": print "May timeout if httpbin is overloaded." network := net.open - test_get network - test_get network --do_drain - test_post network - test_post network --do_drain + test-get network + test-get network --do-drain + test-post network + test-post network --do-drain print "Closing network" network.close diff --git a/tests/websocket-client-test-paused.toit b/tests/websocket-client-test-paused.toit index 4b34bf6..5d6c0ec 100644 --- a/tests/websocket-client-test-paused.toit +++ b/tests/websocket-client-test-paused.toit @@ -2,7 +2,7 @@ // Use of this source code is governed by a Zero-Clause BSD license that can // be found in the tests/TESTS_LICENSE file. -import certificate_roots +import certificate-roots import expect show * import http import net @@ -15,16 +15,16 @@ MSG2 ::= #[0xff, 0x00, 103] main: network := net.open - client := http.Client network --root_certificates=[certificate_roots.ISRG_ROOT_X1] - web_socket := client.web_socket --uri=URI --headers=(http.Headers.from_map ORIGIN) - greeting := web_socket.receive - expect_equals "echo.websocket.events sponsored by Lob.com" greeting + client := http.Client network --root-certificates=[certificate-roots.ISRG-ROOT-X1] + web-socket := client.web-socket --uri=URI --headers=(http.Headers.from-map ORIGIN) + greeting := web-socket.receive + expect-equals "echo.websocket.events sponsored by Lob.com" greeting print greeting - web_socket.send MSG1 - web_socket.ping "Hello" - web_socket.ping #[0xff, 0x80, 0x23] - echo := web_socket.receive - expect_equals MSG1 echo - web_socket.send MSG2 - echo_bytes := web_socket.receive - expect_equals MSG2 echo_bytes + web-socket.send MSG1 + web-socket.ping "Hello" + web-socket.ping #[0xff, 0x80, 0x23] + echo := web-socket.receive + expect-equals MSG1 echo + web-socket.send MSG2 + echo-bytes := web-socket.receive + expect-equals MSG2 echo-bytes diff --git a/tests/websocket-standalone-semaphore-test.toit b/tests/websocket-standalone-semaphore-test.toit index 43d6bf5..b64456d 100644 --- a/tests/websocket-standalone-semaphore-test.toit +++ b/tests/websocket-standalone-semaphore-test.toit @@ -6,7 +6,7 @@ import expect show * import http import monitor show Semaphore import net -import http.web_socket show FragmentReader_ +import http.web-socket show FragmentReader_ // Sets up a web server that can switch to websocket mode on the "/" path. // The server just sends back everything it gets. @@ -14,32 +14,32 @@ import http.web_socket show FragmentReader_ // checks that they are correctly serialized by the semaphore. main: - unmark_bytes_test - client_server_test + unmark-bytes-test + client-server-test -client_server_test: +client-server-test: network := net.open - port := start_server network - run_client network port + port := start-server network + run-client network port -run_client network port/int -> none: +run-client network port/int -> none: client := http.Client network - web_socket := client.web_socket --host="localhost" --port=port --path="/" + web-socket := client.web-socket --host="localhost" --port=port --path="/" - task --background:: client_reading web_socket + task --background:: client-reading web-socket semaphore := Semaphore 3.repeat: - task:: client_sending semaphore web_socket + task:: client-sending semaphore web-socket 3.repeat: semaphore.down // Wait for each of the client writing tasks to terminate. - web_socket.close_write + web-socket.close-write -TEST_PACKETS := [ +TEST-PACKETS := [ "Hello, World!", "*" * 125, (ByteArray 125: it), @@ -57,65 +57,65 @@ TEST_PACKETS := [ "Now is the time for all good men to come to the aid of the party." * 30, ] -sent_but_not_reflected := 0 +sent-but-not-reflected := 0 -client_sending semaphore/Semaphore web_socket -> none: - TEST_PACKETS.do: | packet | +client-sending semaphore/Semaphore web-socket -> none: + TEST-PACKETS.do: | packet | 2.repeat: // Send with a single call to `send`. - sent_but_not_reflected++ - web_socket.send packet + sent-but-not-reflected++ + web-socket.send packet // Send with a writer. - sent_but_not_reflected++ - writer := web_socket.start_sending + sent-but-not-reflected++ + writer := web-socket.start-sending pos := 0 - ping_sent := false + ping-sent := false while pos < packet.size: pos += writer.write packet pos - if pos > 800 and not ping_sent: - web_socket.ping "hello" - ping_sent = true + if pos > 800 and not ping-sent: + web-socket.ping "hello" + ping-sent = true writer.close semaphore.up -client_reading web_socket -> none: +client-reading web-socket -> none: while true: packet := null if (random 2) < 2: - reader := web_socket.start_receiving + reader := web-socket.start-receiving size := 0 - while next_packet := reader.read: - packet = packet ? (packet + next_packet) : next_packet + while next-packet := reader.read: + packet = packet ? (packet + next-packet) : next-packet else: - packet = web_socket.receive + packet = web-socket.receive expect - (TEST_PACKETS.contains packet) or (TEST_PACKETS.contains packet.to_string) + (TEST-PACKETS.contains packet) or (TEST-PACKETS.contains packet.to-string) -start_server network -> int: - server_socket := network.tcp_listen 0 - port := server_socket.local_address.port +start-server network -> int: + server-socket := network.tcp-listen 0 + port := server-socket.local-address.port server := http.Server task --background:: - server.listen server_socket:: | request/http.RequestIncoming response_writer/http.ResponseWriter | + server.listen server-socket:: | request/http.RequestIncoming response-writer/http.ResponseWriter | if request.path == "/": - web_socket := server.web_socket request response_writer + web-socket := server.web-socket request response-writer // For this test, the server end of the web socket just echoes back // what it gets. - while data := web_socket.receive: - sent_but_not_reflected-- - web_socket.send data + while data := web-socket.receive: + sent-but-not-reflected-- + web-socket.send data sleep --ms=10 // Give the client some time to count up before we check the result. - expect_equals 0 sent_but_not_reflected - web_socket.close + expect-equals 0 sent-but-not-reflected + web-socket.close else: - response_writer.write_headers http.STATUS_NOT_FOUND --message="Not Found" + response-writer.write-headers http.STATUS-NOT-FOUND --message="Not Found" return port -unmark_bytes_test -> none: +unmark-bytes-test -> none: mask := ByteArray 4: it * 17 + 2 for offset := 0; offset < 4; offset++: for size := 98; size < 102; size++: data := ByteArray size: it - FragmentReader_.unmask_bytes_ data mask offset + FragmentReader_.unmask-bytes_ data mask offset data.size.repeat: - expect_equals data[it] (it ^ mask[(it + offset) & 3]) + expect-equals data[it] (it ^ mask[(it + offset) & 3]) diff --git a/tests/websocket-standalone-test.toit b/tests/websocket-standalone-test.toit index d8e10e6..8d68783 100644 --- a/tests/websocket-standalone-test.toit +++ b/tests/websocket-standalone-test.toit @@ -5,33 +5,33 @@ import expect show * import http import net -import http.web_socket show FragmentReader_ +import http.web-socket show FragmentReader_ // Sets up a web server that can switch to websocket mode on the "/" path. // The server just sends back everything it gets. // Sets up a client that sends files and expects to receive them back. main: - unmark_bytes_test - client_server_test + unmark-bytes-test + client-server-test -client_server_test: +client-server-test: network := net.open - port := start_server network - run_client network port + port := start-server network + run-client network port -run_client network port/int -> none: +run-client network port/int -> none: client := http.Client network - web_socket := client.web_socket --host="localhost" --port=port --path="/" + web-socket := client.web-socket --host="localhost" --port=port --path="/" - task:: client_reading web_socket + task:: client-reading web-socket - client_sending web_socket + client-sending web-socket - web_socket.close_write + web-socket.close-write -TEST_PACKETS := [ +TEST-PACKETS := [ "Hello, World!", "*" * 125, (ByteArray 125: it), @@ -48,74 +48,74 @@ TEST_PACKETS := [ "€€£" * 40, ] -sent_but_not_reflected := 0 +sent-but-not-reflected := 0 -client_sending web_socket -> none: - TEST_PACKETS.do: | packet | +client-sending web-socket -> none: + TEST-PACKETS.do: | packet | 2.repeat: // Send with a single call to `send`. - sent_but_not_reflected++ - web_socket.send packet + sent-but-not-reflected++ + web-socket.send packet // Send with a writer. - sent_but_not_reflected++ - writer := web_socket.start_sending + sent-but-not-reflected++ + writer := web-socket.start-sending pos := 0 - ping_sent := false + ping-sent := false print packet.size while pos < packet.size: pos += writer.write packet pos - if pos > 800 and not ping_sent: + if pos > 800 and not ping-sent: print "Send ping" - web_socket.ping "hello" - ping_sent = true + web-socket.ping "hello" + ping-sent = true writer.close -client_reading web_socket -> none: - TEST_PACKETS.do: | packet | +client-reading web-socket -> none: + TEST-PACKETS.do: | packet | // Receive with a reader. 2.repeat: - reader := web_socket.start_receiving + reader := web-socket.start-receiving size := 0 ba := #[] while ba.size < packet.size: ba += reader.read - expect_equals null reader.read - expect reader.is_text == (packet is string) - if reader.is_text: - expect_equals packet ba.to_string + expect-equals null reader.read + expect reader.is-text == (packet is string) + if reader.is-text: + expect-equals packet ba.to-string else: - expect_equals ba packet + expect-equals ba packet // Receive with a single call to `receive`. 2.repeat: - round_trip_packet := web_socket.receive - expect_equals packet round_trip_packet - web_socket.close + round-trip-packet := web-socket.receive + expect-equals packet round-trip-packet + web-socket.close -start_server network -> int: - server_socket := network.tcp_listen 0 - port := server_socket.local_address.port +start-server network -> int: + server-socket := network.tcp-listen 0 + port := server-socket.local-address.port server := http.Server task --background:: - server.listen server_socket:: | request/http.RequestIncoming response_writer/http.ResponseWriter | + server.listen server-socket:: | request/http.RequestIncoming response-writer/http.ResponseWriter | if request.path == "/": - web_socket := server.web_socket request response_writer + web-socket := server.web-socket request response-writer // For this test, the server end of the web socket just echoes back // what it gets. - while data := web_socket.receive: - sent_but_not_reflected-- - web_socket.send data + while data := web-socket.receive: + sent-but-not-reflected-- + web-socket.send data sleep --ms=10 // Give the client some time to count up before we check the result. - expect_equals 0 sent_but_not_reflected - web_socket.close + expect-equals 0 sent-but-not-reflected + web-socket.close else: - response_writer.write_headers http.STATUS_NOT_FOUND --message="Not Found" + response-writer.write-headers http.STATUS-NOT-FOUND --message="Not Found" return port -unmark_bytes_test -> none: +unmark-bytes-test -> none: mask := ByteArray 4: it * 17 + 2 for offset := 0; offset < 4; offset++: for size := 98; size < 102; size++: data := ByteArray size: it - FragmentReader_.unmask_bytes_ data mask offset + FragmentReader_.unmask-bytes_ data mask offset data.size.repeat: - expect_equals data[it] (it ^ mask[(it + offset) & 3]) + expect-equals data[it] (it ^ mask[(it + offset) & 3])