diff --git a/.gitignore b/.gitignore index 6d12233..5d0256d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ tags /.coverage/ /_corral/ /_repos/ -/lock.json +lock.json \ No newline at end of file diff --git a/.release-notes/next-release.md b/.release-notes/next-release.md index e69de29..ed541ff 100644 --- a/.release-notes/next-release.md +++ b/.release-notes/next-release.md @@ -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) diff --git a/http_server/_server_connection.pony b/http_server/_server_connection.pony index 258a5fd..af2f0ee 100644 --- a/http_server/_server_connection.pony +++ b/http_server/_server_connection.pony @@ -291,3 +291,6 @@ actor _ServerConnection is (Session & HTTP11RequestHandler) dispose() end + be upgrade(notify: TCPConnectionNotify iso) => + _conn.set_notify(consume notify) + diff --git a/http_server/handler.pony b/http_server/handler.pony index 605b2e9..4ee366f 100644 --- a/http_server/handler.pony +++ b/http_server/handler.pony @@ -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. @@ -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 diff --git a/http_server/request.pony b/http_server/request.pony index ce8ab59..e180a61 100644 --- a/http_server/request.pony +++ b/http_server/request.pony @@ -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 @@ -8,7 +8,21 @@ 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 """ @@ -16,7 +30,20 @@ primitive HTTP10 is _Version """ 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 """ @@ -24,7 +51,22 @@ primitive HTTP09 is _Version """ 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) """ diff --git a/http_server/session.pony b/http_server/session.pony index db6b9b2..c6aa7fb 100644 --- a/http_server/session.pony +++ b/http_server/session.pony @@ -1,3 +1,4 @@ +use "net" use "valbytes" trait tag Session @@ -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