Skip to content

Commit

Permalink
Add possibility to upgrade the current session to a new TCP handler (#75
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mfelsche authored Mar 12, 2024
1 parent a945455 commit 0983542
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ tags
/.coverage/
/_corral/
/_repos/
/lock.json
lock.json
13 changes: 13 additions & 0 deletions .release-notes/next-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Add `Session.upgrade_protocol` behaviour

This can be used to upgrade the underlying TCP connection to a new incompatible protocol, like websockets.

Calling this new behaviour allows this TCP connection to be upgraded 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 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)
3 changes: 3 additions & 0 deletions http_server/_server_connection.pony
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,6 @@ actor _ServerConnection is (Session & HTTP11RequestHandler)
dispose()
end

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

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
15 changes: 15 additions & 0 deletions http_server/session.pony
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use "net"
use "valbytes"

trait tag Session
Expand Down Expand Up @@ -243,5 +244,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


0 comments on commit 0983542

Please sign in to comment.