Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to upgrade the current session to a new TCP handler #75

Merged
merged 14 commits into from
Mar 12, 2024
3 changes: 3 additions & 0 deletions .release-notes/next-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Add `Session.upgrade_protocol` behaviour

This can be used to upgrade the underlying TCP connection to a new incompatible protocol, like websockets.
mfelsche marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ endif
PONYC := $(PONYC) $(SSL)

SOURCE_FILES := $(shell find $(SRC_DIR) -name *.pony)
EXAMPLES := $(notdir $(shell find $(EXAMPLES_DIR)/* -type d))
EXAMPLES_SOURCE_FILES := $(shell find $(EXAMPLES_DIR) -name *.pony)
EXAMPLES := $(notdir $(shell find $(EXAMPLES_DIR)/* -maxdepth 0 -type d -not -name websocket_echo_server))
EXAMPLES_SOURCE_FILES := $(shell find $(EXAMPLES_DIR) -name *.pony -not -path '**/websocket_echo_server/*')
mfelsche marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be rolled back to how it was previously.

EXAMPLES_BINARIES := $(addprefix $(BUILD_DIR)/,$(EXAMPLES))
BENCH_SOURCE_FILES := $(shell find $(BENCH_DIR) -name *.pony)

Expand Down
2 changes: 1 addition & 1 deletion http_server/_server_conn_handler.pony
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use "net"
use "debug"
use "net"

class _ServerConnHandler is TCPConnectionNotify
"""
Expand Down
8 changes: 6 additions & 2 deletions http_server/_server_connection.pony
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use "net"
use "collections"
use "valbytes"
use "debug"
use "net"
use "time"

use "valbytes"

actor _ServerConnection is (Session & HTTP11RequestHandler)
"""
Manages a stream of requests coming into a server from a single client,
Expand Down Expand Up @@ -291,3 +292,6 @@ actor _ServerConnection is (Session & HTTP11RequestHandler)
dispose()
end

be upgrade(notify: TCPConnectionNotify iso) =>
_conn.set_notify(consume notify)

11 changes: 6 additions & 5 deletions http_server/_test.pony
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use "pony_check"
use "pony_test"
use "net"
use "buffered"
use "collections"
use "itertools"
use "valbytes"
use "buffered"
use "net"
use "pony_check"
use "pony_test"
use "time"

use "valbytes"

actor \nodoc\ Main is TestList
new create(env: Env) => PonyTest(env, this)

Expand Down
2 changes: 1 addition & 1 deletion http_server/_test_connection_handling.pony
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use "pony_test"
use "net"
use "pony_test"
use "time"

actor \nodoc\ _ConnectionHandlingTests is TestList
Expand Down
1 change: 1 addition & 0 deletions http_server/_test_headers.pony
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use "debug"
use "pony_check"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated at this point, no new uses were added so I think this should be rolled back

use "pony_test"

use "valbytes"

actor \nodoc\ _HeaderTests is TestList
Expand Down
3 changes: 2 additions & 1 deletion http_server/_test_pipelining.pony
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use "net"
use "time"
use "pony_test"
use "random"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated at this point, no new uses were added so I think this should be rolled back

use "time"

use "valbytes"

actor \nodoc\ _PipeliningTests is TestList
Expand Down
1 change: 1 addition & 0 deletions http_server/_test_request_parser.pony
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use "debug"
use "itertools"
use "pony_test"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be rolled back

use "valbytes"

actor \nodoc\ _RequestParserTests is TestList
Expand Down
4 changes: 2 additions & 2 deletions http_server/handler.pony
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface Handler

When an [Request](http_server-Request.md) is received on an [Session](http_server-Session.md) actor,
the corresponding [Handler.apply](http_server-Handler.md#apply) method is called
with the request and a [RequestID](http_server-RequestID). The [Request](http_server-Request.md)
with the request and a [RequestID](http_server-RequestID.md). The [Request](http_server-Request.md)
contains the information extracted from HTTP Headers and the Request Line, but it does not
contain any body data. It is sent to [Handler.apply](http_server-Handler.md#apply) before the body
is fully received.
Expand All @@ -27,7 +27,7 @@ interface Handler
[RequestID](http_server-RequestID.md) of the request it belongs to. Now is the time to act on the full body data,
if it hasn't been processed yet.

The [RequestID](http_server-Requestid.md) must be kept around for sending the response for this request.
The [RequestID](http_server-RequestID.md) must be kept around for sending the response for this request.
This way the session can ensure, all responses are sent in the same order as they have been received,
which is required for HTTP pipelining. This way processing responses can be passed to other actors and
processing can take arbitrary times. The [Session](http_server-Session.md) will take care of sending
Expand Down
44 changes: 43 additions & 1 deletion http_server/request.pony
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

interface val _Version is (Equatable[Version] & Stringable)
interface val _Version is (Equatable[Version] & Stringable & Comparable[Version])
fun to_bytes(): Array[U8] val

primitive HTTP11 is _Version
Expand All @@ -8,23 +8,65 @@ primitive HTTP11 is _Version
"""
fun string(): String iso^ => recover iso String(8).>append("HTTP/1.1") end
fun to_bytes(): Array[U8] val => [as U8: 'H'; 'T'; 'T'; 'P'; '/'; '1'; '.'; '1']
fun u64(): U64 =>
"""
Representation of the bytes for this HTTP Version on the wire in form of an 8-byte unsigned integer with the ASCII bytes written from highest to least significant byte.

Result: `0x485454502F312E31`
"""
'HTTP/1.1'
fun eq(o: Version): Bool => o is this
fun lt(o: Version): Bool =>
match o
| let _: HTTP11 => false
| let _: HTTP10 => false
| let _: HTTP09 => false
end


primitive HTTP10 is _Version
"""
HTTP/1.0
"""
fun string(): String iso^ => recover iso String(8).>append("HTTP/1.0") end
fun to_bytes(): Array[U8] val => [as U8: 'H'; 'T'; 'T'; 'P'; '/'; '1'; '.'; '0']
fun u64(): U64 =>
"""
Representation of the bytes for this HTTP Version on the wire in form of an 8-byte unsigned integer with the ASCII bytes written from highest to least significant byte.

Result: `0x485454502F312E30`
"""
'HTTP/1.0'
fun eq(o: Version): Bool => o is this
fun lt(o: Version): Bool =>
match o
| let _: HTTP11 => true
| let _: HTTP10 => false
| let _: HTTP09 => false
end

primitive HTTP09 is _Version
"""
HTTP/0.9
"""
fun string(): String iso^ => recover iso String(8).>append("HTTP/0.9") end
fun to_bytes(): Array[U8] val => [as U8: 'H'; 'T'; 'T'; 'P'; '/'; '0'; '.'; '9']
fun u64(): U64 =>
"""
Representation of the bytes for this HTTP Version on the wire in form of an 8-byte unsigned integer with the ASCII bytes written from highest to least significant byte.

Result: `0x485454502F302E39`
"""
'HTTP/0.9'
fun eq(o: Version): Bool => o is this
fun lt(o: Version): Bool =>
match o
| let _: HTTP11 => true
| let _: HTTP10 => true
| let _: HTTP09 => false
end



type Version is ((HTTP09 | HTTP10 | HTTP11) & _Version)
"""
Expand Down
5 changes: 5 additions & 0 deletions http_server/request_ids.pony
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@


type RequestID is USize
"""
Opaque identifier for HTTP Requests.

Used to maintain order in sending out HTTP responses when pipelining is used.
"""

primitive RequestIDs
"""
Expand Down
3 changes: 2 additions & 1 deletion http_server/request_parser.pony
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use "valbytes"
use "debug"

use "valbytes"

primitive TooLarge is _RequestParseError
fun string(): String iso^ => "TooLarge".clone()

Expand Down
3 changes: 2 additions & 1 deletion http_server/response.pony
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use "valbytes"
use "format"

use "valbytes"

interface val Response is ByteSeqIter
"""
Representing a HTTP response minus the body.
Expand Down
2 changes: 1 addition & 1 deletion http_server/server.pony
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use "collections"
use "debug"
use "net"
use "net_ssl"
use "time"
use "debug"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated at this point, no new uses were added so I think this should be rolled back


interface tag _SessionRegistry
be register_session(conn: _ServerConnection)
Expand Down
1 change: 1 addition & 0 deletions http_server/server_config.pony
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use "time"

use "json"

class val ServerConfig
Expand Down
16 changes: 16 additions & 0 deletions http_server/session.pony
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use "net"
mfelsche marked this conversation as resolved.
Show resolved Hide resolved

use "valbytes"

trait tag Session
Expand Down Expand Up @@ -243,5 +245,19 @@ trait tag Session
"""
None

be upgrade_protocol(notify: TCPConnectionNotify iso) =>
"""
Upgrade this TCP connection to another handler, serving another protocol (e.g. [WebSocket](https://www.rfc-editor.org/rfc/rfc6455.html)).

Note that this method does not send an HTTP Response with a status of 101. This needs to be done before calling this behaviour. Also, the passed in `notify` will not have its methods [accepted](https://stdlib.ponylang.io/net-TCPConnectionNotify/#connected) or [connected](https://stdlib.ponylang.io/net-TCPConnectionNotify/#connected) called, as the connection is already established.

After calling this behaviour, this session and the connected [Handler](http_server-Handler.md) instance will not be called again, so it is necessary to do any required clean up right after this call.

See:

- [Protocol Upgrade Mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism)
- [Upgrade Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Upgrade)
"""
None


3 changes: 2 additions & 1 deletion http_server/sync_handler.pony
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use "valbytes"
use "debug"

use "valbytes"

interface SyncHandler
"""
Use this handler, when you want to handle your requests without accessing other actors.
Expand Down
Loading