Skip to content

Commit

Permalink
Add possibility to upgrade the current session to a new TCP handler
Browse files Browse the repository at this point in the history
and add a Websocket example demonstrating this new feature.
  • Loading branch information
mfelsche committed Feb 15, 2024
1 parent 16ae3f9 commit 67094fe
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 4 deletions.
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ 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/*')
EXAMPLES_BINARIES := $(addprefix $(BUILD_DIR)/,$(EXAMPLES))
WEBSOCKET_EXAMPLE_SOURCE_FILES := $(EXAMPLES_DIR)/websocket_echo_server/main.pony
WEBSOCKET_EXAMPLES_BINARY := $(BUILD_DIR)/websocket_echo_server
BENCH_SOURCE_FILES := $(shell find $(BENCH_DIR) -name *.pony)

test: unit-tests build-examples
Expand All @@ -54,12 +56,17 @@ $(tests_binary): $(SOURCE_FILES) | $(BUILD_DIR)
$(GET_DEPENDENCIES_WITH)
$(PONYC) -o $(BUILD_DIR) $(SRC_DIR)

build-examples: $(EXAMPLES_BINARIES)
build-examples: $(EXAMPLES_BINARIES) $(WEBSOCKET_EXAMPLES_BINARY)

$(EXAMPLES_BINARIES): $(BUILD_DIR)/%: $(SOURCE_FILES) $(EXAMPLES_SOURCE_FILES) | $(BUILD_DIR)
$(GET_DEPENDENCIES_WITH)
$(PONYC) -o $(BUILD_DIR) $(EXAMPLES_DIR)/$*

$(WEBSOCKET_EXAMPLES_BINARY): $(BUILD_DIR)/%: $(SOURCE_FILES) $(WEBSOCKET_EXAMPLE_SOURCE_FILES) | $(BUILD_DIR)
cd $(EXAMPLES_DIR)/$* && \
$(GET_DEPENDENCIES_WITH) && \
$(PONYC) -o ../../$(BUILD_DIR) $(EXAMPLES_DIR)/$*

clean:
$(CLEAN_DEPENDENCIES_WITH)
rm -rf $(BUILD_DIR)
Expand Down
2 changes: 2 additions & 0 deletions examples/websocket_echo_server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/_corral/
/_repos/
27 changes: 27 additions & 0 deletions examples/websocket_echo_server/corral.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"packages": [
"websocket_echo_server"
],
"deps": [
{
"locator": "../.."
},
{
"locator": "github.com/mfelsche/pony-websocket.git",
"version": "0.7.0",
"note": "forked from https://github.com/oraoto/pony-websocket"
},
{
"locator": "github.com/ponylang/crypto.git",
"version": "1.2.2"
}
],
"info": {
"description": "http server example websocket echo server",
"homepage": "https://github.com/ponylang/http_server",
"license": "bsd-2-clause",
"documentation_url": "https://ponylang.github.io/http_server/",
"version": "0.4.6",
"name": "websocket_echo_server"
}
}
32 changes: 32 additions & 0 deletions examples/websocket_echo_server/lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"locks": [
{
"locator": "../..",
"revision": "main"
},
{
"locator": "github.com/ponylang/json.git",
"revision": "0.1.0"
},
{
"locator": ".",
"revision": "main"
},
{
"locator": "github.com/ponylang/valbytes.git",
"revision": "0.6.2"
},
{
"locator": "github.com/mfelsche/pony-websocket.git",
"revision": "0.7.0"
},
{
"locator": "github.com/ponylang/crypto.git",
"revision": "1.2.2"
},
{
"locator": "github.com/ponylang/net_ssl.git",
"revision": "1.3.2"
}
]
}
124 changes: 124 additions & 0 deletions examples/websocket_echo_server/main.pony
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use "crypto"
use "encode/base64"
use "net"

use "http_server"
use ws = "websocket"

actor Main
new create(env: Env) =>
let host = "localhost"
let port = "9999"
let limit: USize = 9000
let server = Server(
TCPListenAuth(env.root),
SimpleServerNotify, // notify for server lifecycle events
BackendMaker // factory for session-based application backend
where config = ServerConfig( // configuration of Server
where host' = host,
port' = port,
max_concurrent_connections' = limit)
)

class SimpleServerNotify
fun ref listening(server: Server ref) =>
None

fun ref not_listening(server: Server ref) =>
None

fun ref closed(server: Server ref) =>
None

class BackendMaker is HandlerFactory
fun apply(session: Session): Handler^ =>
RequestHandler.create(session)

class RequestHandler is Handler
let _session: Session
var _response_builder: ResponseBuilder

new ref create(session: Session) =>
_session = session
_response_builder = Responses.builder()

fun ref apply(request: Request val, request_id: RequestID) =>
if (request.method() isnt GET) or (request.version() < HTTP11) then
let body = "Invalid Method or HTTP version"
this._send_err(request_id, StatusBadRequest, body)
else
try
let upgrade_header = request.header("Upgrade") as String
if upgrade_header != "websocket" then
error
end
let conn_header = request.header("Connection") as String
if conn_header != "Upgrade" then
error
end
// calculate Sec-Websocket-Accept from Sec-Websocket-Key
let ws_key = request.header("Sec-WebSocket-Key") as String
let sha1_digest = Digest.sha1()
sha1_digest.append(ws_key)?
sha1_digest.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")?
let hash: Array[U8] val = sha1_digest.final()
let sec_ws_accept: String val = Base64.encode(hash)

let ws_version = request.header("Sec-WebSocket-Version") as String
if ws_version.u64()? != 13 then
error
end
_session.send_raw(
_response_builder.set_status(StatusSwitchingProtocols)
.add_header("Upgrade", "websocket")
.add_header("Connection", "Upgrade")
.add_header("Sec-WebSocket-Accept", sec_ws_accept)
.finish_headers()
.build(),
request_id
)
_session.send_finished(request_id)
_session.upgrade(
ws.WebsocketTCPConnectionNotify.open(
MyLittleWebSocketConnectionNotify.create()
)
)
else
this._send_err(request_id, StatusBadRequest, "Invalid Websocket Handshake Request")
end
end

fun ref chunk(data: ByteSeq val, request_id: RequestID) => None

fun ref finished(request_id: RequestID) =>
_response_builder = _response_builder.reset()

fun ref _send_err(request_id: RequestID, status: Status, body: String) =>
_session.send_raw(
_response_builder.set_status(StatusBadRequest)
.add_header("Server", "Pony/http_server")
.add_header("Content-Length", body.size().string())
.finish_headers()
.add_chunk(body.array())
.build(),
request_id
)
_session.send_finished(request_id)

class iso MyLittleWebSocketConnectionNotify is ws.WebSocketConnectionNotify

new iso create() => None

fun ref opened(conn: ws.WebSocketConnection ref) =>
None

fun ref closed(conn: ws.WebSocketConnection ref) =>
None

fun ref text_received(conn: ws.WebSocketConnection ref, text: String) =>
conn.send_text(text)

fun ref binary_received(
conn: ws.WebSocketConnection ref,
data: Array[U8 val] val) =>
conn.send_binary(data)
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)

26 changes: 25 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,47 @@ 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 => '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 => '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 => '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
6 changes: 6 additions & 0 deletions http_server/session.pony
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use "valbytes"
use "net"

trait tag Session
"""
Expand Down Expand Up @@ -243,5 +244,10 @@ trait tag Session
"""
None

be upgrade(notify: TCPConnectionNotify iso) =>
"""
Upgrade this TCP connection to another handler
"""
None


0 comments on commit 67094fe

Please sign in to comment.