Skip to content

Commit

Permalink
Update (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
floitsch authored Mar 13, 2024
1 parent caea996 commit fb0a186
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 62 deletions.
25 changes: 19 additions & 6 deletions src/chunked.toit
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ import .connection

/**
This is an adapter that converts a chunked stream (RFC 2616) to a stream of
just the payload bytes. It takes an io.Reader as a constructor argument,
and acts like an io.Reader.
just the payload bytes. It takes an $io.Reader as a constructor argument,
and acts like an $io.Reader.
Deprecated. Use the type $io.Reader instead.
Deprecated for public use. Use the type $io.Reader instead.
This class will be made private in the future.
*/
class ChunkedReader extends Object with io.Reader:
class ChunkedReader extends ChunkedReader_:
constructor connection/Connection reader/io.Reader:
super connection reader

/**
This is an adapter that converts a chunked stream (RFC 2616) to a stream of
just the payload bytes. It takes an $io.Reader as a constructor argument,
and acts like an $io.Reader.
*/
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.
Expand Down Expand Up @@ -65,10 +74,14 @@ class ChunkedReader extends Object with io.Reader:
reader_.skip 1

/**
Deprecated. Use the type $io.CloseableWriter instead.
Deprecated for public use. Use the type $io.CloseableWriter instead.
This class will be made private in the future.
*/
class ChunkedWriter extends Object with io.CloseableWriter:
class ChunkedWriter extends ChunkedWriter_:
constructor connection/Connection writer/io.Writer:
super connection writer

class ChunkedWriter_ extends io.CloseableWriter:
static CRLF_ ::= "\r\n"

connection_/Connection? := null
Expand Down
2 changes: 1 addition & 1 deletion src/client.toit
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ class Client:
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.
connection_.drain_ // Remove any remnants of previous requests.
return true
// Hostname etc. didn't match so we need a new connection.
connection_.close
Expand Down
79 changes: 53 additions & 26 deletions src/connection.toit
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class Connection:
location_/ParsedUri_? := null
// These are writers and readers that have been given to API users.
current_writer_/io.CloseableWriter? := null
// TODO(florian): should this be a closeable reader?
current_reader_/io.Reader? := null
write_closed_ := false

Expand All @@ -42,10 +41,8 @@ class Connection:
close -> none:
if socket_:
socket_.close
// TODO(florian): should we close the writer?
if current_writer_:
current_writer_.close
// TODO(florian): should current_reader be closeable and be closed here?
socket_ = null
remove_finalizer this
write_closed_ = true
Expand All @@ -59,12 +56,18 @@ class Connection:
if call_in_finalizer_ and socket_: call_in_finalizer_.call this
close


/**
Deprecated.
*/
drain -> none:
drain_

drain_ -> none:
if write_closed_:
current_reader_ = null
close
if current_reader_:
// TODO(florian): should reader be closeable and be closed here?
current_reader_.drain
current_reader_ = null
if current_writer_:
Expand All @@ -77,8 +80,20 @@ class Connection:
If we are not currently reading from the connection the connection is
completely closed. Otherwise the connection will be closed on completing
the current read.
Deprecated.
*/
close_write -> none:
close_write_

/**
Indicates to the other side that we won't be writing any more on this
connection. On TCP this means sending a FIN packet.
If we are not currently reading from the connection the connection is
completely closed. Otherwise the connection will be closed on completing
the current read.
*/
close_write_ -> none:
if not current_reader_:
close
else if socket_:
Expand Down Expand Up @@ -116,13 +131,13 @@ class Connection:
headers.set "Content-Length" "$content_length"

if content_length:
body_writer = ContentLengthWriter this writer content_length
body_writer = ContentLengthWriter_ this writer content_length
else:
needs_to_write_chunked_header = true
body_writer = ChunkedWriter this writer
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"

Expand Down Expand Up @@ -169,7 +184,7 @@ class Connection:
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
body_reader := current_reader_ or ContentLengthReader_ this reader 0
return RequestIncoming.private_ this body_reader method path version headers

detach -> tcp.Socket:
Expand Down Expand Up @@ -197,7 +212,7 @@ class Connection:
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
body_reader := current_reader_ or ContentLengthReader_ this reader 0
return Response this version status_code status_message headers body_reader

finally:
Expand All @@ -208,14 +223,14 @@ class Connection:
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
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":
return ChunkedReader this reader
return ChunkedReader_ this reader
else if not headers.matches T_E "identity":
throw "No support for $T_E: $(headers.single T_E)"

Expand All @@ -233,7 +248,7 @@ class Connection:
// transfer succeeded. Incidentally this also means the connection
// can't be reused, but that should happen automatically because it
// is closed.
return UnknownContentLengthReader this reader
return UnknownContentLengthReader_ this reader

// Optional whitespace is spaces and tabs.
is_whitespace_ char:
Expand Down Expand Up @@ -276,38 +291,46 @@ class Connection:
current_writer_ = null

/**
Deprecated. Use the type $io.Reader instead.
Deprecated for public use. Use the type $io.Reader instead.
This class will be made private in the future.
*/
class ContentLengthReader extends Object with io.Reader:
class ContentLengthReader extends ContentLengthReader_:
constructor connection/Connection reader/io.Reader size/int:
super connection reader size

class ContentLengthReader_ extends io.Reader:
connection_/Connection
reader_/io.Reader

size/int
content-size/int

constructor .connection_ .reader_ .size:
constructor .connection_ .reader_ .content-size:

/**
Deprecated. Use $size instead.
Deprecated. Use $content-size instead.
*/
content_length -> int:
return size
return content-size

consume_ -> ByteArray?:
if consumed >= size:
if processed >= content-size:
connection_.reading_done_ this
return null
data := reader_.read --max_size=(size - consumed)
data := reader_.read --max_size=(content-size - processed)
if not data:
connection_.close
throw io.Reader.UNEXPECTED_END_OF_READER
return data

/**
Deprecated. Use the type $io.Reader instead.
Deprecated for public use. Use the type $io.Reader instead.
This class will be made private in the future.
*/
class UnknownContentLengthReader extends Object with io.Reader:
class UnknownContentLengthReader extends UnknownContentLengthReader_:
constructor connection/Connection reader/io.Reader:
super connection reader

class UnknownContentLengthReader_ extends io.Reader:
connection_/Connection
reader_/io.Reader

Expand All @@ -321,26 +344,30 @@ class UnknownContentLengthReader extends Object with io.Reader:
return data

/**
Deprecated. Use the type $io.CloseableWriter instead.
Deprecated for public use. Use the type $io.CloseableWriter instead.
*/
interface BodyWriter:
write data -> int
is_done -> bool
close -> none

/**
Deprecated. Use the type $io.CloseableWriter instead.
Deprecated for public use. Use the type $io.CloseableWriter instead.
This class will be made private in the future.
*/
class ContentLengthWriter extends Object with io.CloseableWriter implements BodyWriter:
class ContentLengthWriter extends ContentLengthWriter_:
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 := ?

constructor .connection_ .writer_ .content_length_:

is_done -> bool:
return written >= content_length_
return processed >= content_length_

try_write_ data/io.Data from/int to/int -> int:
return writer_.try_write data from to
Expand Down
4 changes: 2 additions & 2 deletions src/headers.toit
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file.
import bytes
import io

class Headers:
headers_/Map? := null
Expand Down Expand Up @@ -130,7 +130,7 @@ class Headers:
values.do block

stringify -> string:
buffer := bytes.Buffer
buffer := io.Buffer
write_to buffer
return buffer.to_string

Expand Down
4 changes: 2 additions & 2 deletions src/request.toit
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class RequestOutgoing extends Request:

send -> Response:
has_body := body != null
content_length := has_body ? body.size : 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"
Expand Down Expand Up @@ -92,7 +92,7 @@ class RequestIncoming extends Request:
The length of the body, if known.
*/
content_length -> int?:
return body.size
return body.content-size

drain:
body.drain
2 changes: 1 addition & 1 deletion src/response.toit
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Response:
The length of the response body, if known.
*/
content_length -> int?:
return body.size
return body.content-size

stringify: return "$status_code: $status_message"

Expand Down
4 changes: 2 additions & 2 deletions src/server.toit
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class Server:
detached := false
e := catch --trace=(: not is_close_exception_ it and it != DEADLINE_EXCEEDED_ERROR):
detached = run_connection_ connection handler logger
connection.close_write
connection.close_write_
close_logger := e ? logger.with_tag "reason" e : logger
if detached:
close_logger.debug "client socket detached"
Expand Down Expand Up @@ -264,7 +264,7 @@ class ResponseWriter extends Object with io.OutMixin:
close -> none:
close_writer_
if body_writer_:
too_little := content_length_ ? (body_writer_.written < content_length_) : false
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
Expand Down
4 changes: 2 additions & 2 deletions src/web_socket.toit
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ class WebSocket:
/**
A writer for writing a single message on a WebSocket connection.
*/
class WebSocketWriter extends Object with io.CloseableWriter:
class WebSocketWriter extends io.CloseableWriter:
owner_ /WebSocket? := ?
size_ /int?
remaining_in_fragment_ /int := 0
Expand Down Expand Up @@ -433,7 +433,7 @@ class WebSocketWriter extends Object with io.CloseableWriter:
/**
A reader for an individual message sent to us.
*/
class WebSocketReader extends Object with io.Reader:
class WebSocketReader extends io.Reader:
owner_ /WebSocket? := ?
is_text /bool
fragment_reader_ /FragmentReader_ := ?
Expand Down
4 changes: 2 additions & 2 deletions tests/headers_test.toit
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// Use of this source code is governed by a Zero-Clause BSD license that can
// be found in the tests/TESTS_LICENSE file.
import bytes
import expect show *
import http
import io

main:
test_from_map
Expand All @@ -16,7 +16,7 @@ The $http.Headers class has a stringify method that does pretty much
the same, but we don't want to rely on `stringify` since that could change.
*/
stringify headers/http.Headers -> string:
buffer := bytes.Buffer
buffer := io.Buffer
headers.write_to buffer
return buffer.bytes.to_string_non_throwing

Expand Down
9 changes: 5 additions & 4 deletions tests/http_finalizer_test.toit
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,17 @@ start_server network -> int:
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"
response_writer.write INDEX_HTML
out.write INDEX_HTML
else if request.path == "/foo.json":
response_writer.headers.set "Content-Type" "application/json"
response_writer.write
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.write CAT
out.write CAT
else if request.path == "/redirect_from":
response_writer.redirect http.STATUS_FOUND "http://localhost:$my_port/redirect_back"
else if request.path == "/redirect_back":
Expand All @@ -107,4 +108,4 @@ listen server server_socket my_port:
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"
Loading

0 comments on commit fb0a186

Please sign in to comment.