Skip to content

Commit

Permalink
Better client handling of parameters on URLS
Browse files Browse the repository at this point in the history
  • Loading branch information
Erik Corry committed Mar 7, 2024
1 parent dc6a89e commit f6671b2
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 37 deletions.
1 change: 1 addition & 0 deletions examples/package.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ packages:
path: ..
toit-cert-roots:
url: github.com/toitware/toit-cert-roots
name: certificate_roots
version: 1.4.0
hash: bf41ee60ebba65ba01bc9ba1aa6d697e4cc4a8c7
15 changes: 8 additions & 7 deletions examples/server.toit
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ main:
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.Request writer/http.ResponseWriter |
if request.path == "/empty":
else if request.path == "/":
server.listen tcp_socket:: | request/http.RequestIncoming writer/http.ResponseWriter |
resource := request.query.resource
if resource == "/empty":
else if resource == "/":
writer.headers.set "Content-Type" "text/html"
writer.write """
<html>
Expand All @@ -29,18 +30,18 @@ main:
</body>
</html>
"""
else if request.path == "/json":
else if resource == "/json":
writer.headers.set "Content-Type" "application/json"
writer.write
json.encode ITEMS
else if request.path == "/headers":
else if resource == "/headers":
writer.headers.set "Http-Test-Header" "going strong"
writer.write_headers 200
else if request.path == "/500":
else if resource == "/500":
writer.headers.set "Content-Type" "text/plain"
writer.write_headers 500
writer.write "Failure\n"
else if request.path == "/599":
else if resource == "/599":
writer.headers.set "Content-Type" "text/plain"
writer.write_headers 599 --message="Dazed and confused"
else:
Expand Down
35 changes: 23 additions & 12 deletions src/client.toit
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,10 @@ class Client:
--host/string
--port/int?=null
--path/string="/"
--parameters/Map?=null
--headers/Headers?=null
--use_tls/bool?=null:
parsed := parse_ host port path use_tls --web_socket=false
parsed := parse_ host port path use_tls parameters --web_socket=false
request := null
try_to_reuse_ parsed: | connection |
request = connection.new_request method parsed.path headers
Expand Down Expand Up @@ -254,9 +255,10 @@ class Client:
--port/int?=null
--path/string="/"
--headers/Headers?=null
--parameters/Map?=null
--follow_redirects/bool=true
--use_tls/bool?=null:
parsed := parse_ host port path use_tls --web_socket=false
parsed := parse_ host port path use_tls parameters --web_socket=false
return get_ parsed headers --follow_redirects=follow_redirects

/**
Expand Down Expand Up @@ -337,9 +339,10 @@ class Client:
--port/int?=null
--path/string="/"
--headers/Headers?=null
--parameters/Map?=null
--follow_redirects/bool=true
--use_tls/bool?=null:
parsed := parse_ host port path use_tls --web_socket
parsed := parse_ host port path use_tls parameters --web_socket
return web_socket_ parsed headers follow_redirects

web_socket_ parsed/ParsedUri_ headers/Headers? follow_redirects/bool -> WebSocket:
Expand Down Expand Up @@ -422,10 +425,11 @@ class Client:
--port/int?=null
--path/string="/"
--headers/Headers?=null
--parameters/Map?=null
--content_type/string?=null
--follow_redirects/bool=true
--use_tls/bool?=null:
parsed := parse_ host port path use_tls --web_socket=false
parsed := parse_ host port path use_tls parameters --web_socket=false
return post_ data parsed --headers=headers --content_type=content_type --follow_redirects=follow_redirects

parse_ uri/string --web_socket/bool -> ParsedUri_:
Expand All @@ -439,10 +443,13 @@ class Client:

/// 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 use_tls/bool? --web_socket/bool -> ParsedUri_:
parse_ host/string port/int? path/string use_tls/bool? parameters/Map? --web_socket/bool -> ParsedUri_:
default_scheme := (use_tls == null ? use_tls_by_default_ : use_tls)
? (web_socket ? "wss" : "https")
: (web_socket ? "ws" : "http")
if parameters and parameters.size != 0:
path += "?"
path += (url_encode_ parameters).to_string
return ParsedUri_.private_
--scheme=default_scheme
--host=host
Expand Down Expand Up @@ -529,11 +536,12 @@ class Client:
--port/int?=null
--path/string="/"
--headers/Headers?=null
--parameters/Map?=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 use_tls --web_socket=false
parsed := parse_ host port path use_tls parameters --web_socket=false
return post_ encoded parsed --headers=headers --content_type="application/json" --follow_redirects=follow_redirects

/**
Expand Down Expand Up @@ -580,15 +588,13 @@ class Client:
--port/int?=null
--path/string="/"
--headers/Headers?=null
--parameters/Map?=null
--follow_redirects/bool=true
--use_tls/bool?=null:
parsed := parse_ host port path use_tls --web_socket=false
parsed := parse_ host port path use_tls parameters --web_socket=false
return post_form_ map parsed --headers=headers --follow_redirects=follow_redirects


post_form_ map/Map parsed/ParsedUri_ -> Response
--headers/Headers?
--follow_redirects/bool=true:
url_encode_ map/Map -> ByteArray:
buffer := bytes.Buffer
first := true
map.do: | key value |
Expand All @@ -605,7 +611,12 @@ class Client:
buffer.write "="
buffer.write
url.encode value
encoded := buffer.bytes
return buffer.bytes

post_form_ map/Map parsed/ParsedUri_ -> Response
--headers/Headers?
--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

Expand Down
7 changes: 7 additions & 0 deletions src/request.toit
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file.
import encoding.url
import reader
import writer

Expand Down Expand Up @@ -68,9 +69,14 @@ class RequestOutgoing extends Request:
class RequestIncoming extends Request:
connection_/Connection := ?

/// The HTTP method, usually "GET" or "POST".
method/string
/// The full path of the request, eg. "/page?id=123".
path/string
/// The parsed version of the path. For routing purposes use query.resource.
query/url.QueryString
headers/Headers
/// The HTTP version.
version/string

/**
Expand All @@ -80,6 +86,7 @@ class RequestIncoming extends Request:
body/reader.Reader

constructor.private_ .connection_ .body .method .path .version .headers:
query = url.QueryString.parse path

content_length -> int?:
if body is ContentLengthReader:
Expand Down
57 changes: 39 additions & 18 deletions tests/http_standalone_test.toit
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,19 @@ run_client network port/int -> none:
response_data += chunk
expect_equals test_reader.full_data response_data

response13 := client.get --host="localhost" --port=port --path="/get_with_parameters" --parameters=POST_DATA
response_data = #[]
while chunk := response13.body.read:
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" --parameters=POST_DATA
response14 := request.send
expect_equals 200 response14.status_code
while chunk := response13.body.read:
response_data += chunk
expect_equals "Response with parameters" response_data.to_string

client.close

expect_json response/http.Response [verify_block]:
Expand Down Expand Up @@ -233,49 +246,51 @@ listen server server_socket my_port other_port:
if request.method == "POST" and request.path != "/post_chunked":
expect_not_null (request.headers.single "Content-Length")

if request.path == "/":
resource := request.query.resource

if resource == "/":
response_writer.headers.set "Content-Type" "text/html"
response_writer.write INDEX_HTML
else if request.path == "/foo.json":
else if resource == "/foo.json":
response_writer.headers.set "Content-Type" "application/json"
response_writer.write
json.encode {"foo": 123, "bar": 1.0/3, "fizz": [1, 42, 103]}
else if request.path == "/cat.png":
else if resource == "/cat.png":
response_writer.headers.set "Content-Type" "image/png"
response_writer.write CAT
else if request.path == "/redirect_from":
else if resource == "/redirect_from":
response_writer.redirect http.STATUS_FOUND "http://localhost:$other_port/redirect_back"
else if request.path == "/redirect_back":
else if resource == "/redirect_back":
response_writer.redirect http.STATUS_FOUND "http://localhost:$other_port/foo.json"
else if request.path == "/subdir/redirect_relative":
else if resource == "/subdir/redirect_relative":
response_writer.redirect http.STATUS_FOUND "bar.json"
else if request.path == "/subdir/bar.json":
else if resource == "/subdir/bar.json":
response_writer.headers.set "Content-Type" "application/json"
response_writer.write
json.encode {"bar": 345 }
else if request.path == "/subdir/redirect_absolute":
else if resource == "/subdir/redirect_absolute":
response_writer.redirect http.STATUS_FOUND "/foo.json"
else if request.path == "/redirect_loop":
else if resource == "/redirect_loop":
response_writer.redirect http.STATUS_FOUND "http://localhost:$other_port/redirect_loop"
else if request.path == "/204_no_content":
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
else if request.path == "/500_because_nothing_written":
else if resource == "/500_because_nothing_written":
// Forget to write anything - the server should send 500 - Internal error.
else if request.path == "/500_because_throw_before_headers":
else if resource == "/500_because_throw_before_headers":
throw "** Expect a stack trace here caused by testing: throws_before_headers **"
else if request.path == "/hard_close_because_wrote_too_little":
else if resource == "/hard_close_because_wrote_too_little":
response_writer.headers.set "Content-Length" "2"
response_writer.write "x" // Only writes half the message.
else if request.path == "/hard_close_because_throw_after_headers":
else if resource == "/hard_close_because_throw_after_headers":
response_writer.headers.set "Content-Length" "2"
response_writer.write "x" // Only writes half the message.
throw "** Expect a stack trace here caused by testing: throws_after_headers **"
else if request.path == "/post_json":
else if resource == "/post_json":
response_writer.headers.set "Content-Type" "application/json"
while data := request.body.read:
response_writer.write data
else if request.path == "/post_form":
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"
str := ""
Expand All @@ -291,13 +306,19 @@ listen server server_socket my_port other_port:
POST_DATA.do: | key value |
expect_equals POST_DATA[key] map[key]
response_writer.write "OK"
else if request.path == "/post_json_redirected_to_cat":
else if resource == "/post_json_redirected_to_cat":
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"
else if request.path == "/post_chunked":
else if resource == "/post_chunked":
response_writer.headers.set "Content-Type" "text/plain"
while data := request.body.read:
response_writer.write data
else if request.query.resource == "/get_with_parameters":
response_writer.headers.set "Content-Type" "text/plain"
response_writer.write "Response with parameters"
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"
1 change: 1 addition & 0 deletions tests/package.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ packages:
path: ..
toit-cert-roots:
url: github.com/toitware/toit-cert-roots
name: certificate_roots
version: 1.3.2
hash: 288547039d8a3797330064e91d8c79ad16313545

0 comments on commit f6671b2

Please sign in to comment.