Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions frameworks/uio-ws/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM golang:1.27-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY main.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o server .

FROM scratch
COPY --from=build /app/server /server
EXPOSE 8080
CMD ["/server"]
13 changes: 13 additions & 0 deletions frameworks/uio-ws/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module httparena/uio-ws

go 1.25.0

require github.com/urpc/uio v1.5.3

require (
github.com/klauspost/compress v1.20.1 // indirect
github.com/libp2p/go-reuseport v0.4.0 // indirect
github.com/limpo1989/taskgo v1.5.0 // indirect
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect
golang.org/x/sys v0.44.0 // indirect
)
20 changes: 20 additions & 0 deletions frameworks/uio-ws/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
github.com/limpo1989/taskgo v1.5.0 h1:xczgiyKl20SyWJ5OwH6CcYFMPliCTMUnRWXciRhegLc=
github.com/limpo1989/taskgo v1.5.0/go.mod h1:x3mZYu4EyiAGBqy/qENxNKr78yLgHfjPg58RJRXklmY=
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E=
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urpc/uio v1.5.3 h1:4o8KSldSsLh/tva5rtNA8fxM4+wC4otSPjStCxpFOGs=
github.com/urpc/uio v1.5.3/go.mod h1:Jso/P2w3//g9d2ibEbFjDlqc/lLYBmh709BxKVfOhS8=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
51 changes: 51 additions & 0 deletions frameworks/uio-ws/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import (
"log"
"os"
"os/signal"
"runtime"
"syscall"

"github.com/urpc/uio"
"github.com/urpc/uio/uws"
)

// uws answers the RFC 6455 upgrade on uio's connections and parses frames in
// the connection's own I/O task over an edge-triggered loop, so a message is
// echoed by the task that read it, with no goroutine per connection. Anything
// that is not an upgrade is answered with 400 by the server itself.
type echoHandler struct{}

func (echoHandler) OnOpen(*uws.Conn) {}

func (echoHandler) OnMessage(conn *uws.Conn, message uws.Message) {
switch message.Type {
case uws.TextMessage:
_ = conn.SendText(message.Payload)
case uws.BinaryMessage:
_ = conn.SendBinary(message.Payload)
}
}

func (echoHandler) OnClose(*uws.Conn, uws.CloseEvent) {}

func main() {
server := uws.NewServer(echoHandler{})
// One event loop per CPU: uio's default is four pollers, which leaves the
// cores of a benchmark host idle. A read round's replies leave coalesced
// in one sendmsg, and a send the socket refuses is finished by the next
// write turn without pausing the read.
server.Events = &uio.Events{Pollers: runtime.NumCPU()}

stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-stop
_ = server.Close(nil)
}()

if err := server.Serve(":8080"); err != nil {
log.Fatal(err)
}
}
16 changes: 16 additions & 0 deletions frameworks/uio-ws/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"display_name": "uio-ws",
"language": "Go",
"type": "experimental",
"mode": "standard",
"engine": "uio",
"description": "uws WebSocket echo on uio: the RFC 6455 handshake and frame parser run in the connection's own I/O task on uio's edge-triggered event loops, one per CPU, so each message is parsed from the buffer its task read into and echoed from that task with no goroutine per connection; a read round's replies leave coalesced in one sendmsg, and a send the socket refuses is finished by the next write turn without pausing the read. Default configuration otherwise.",
"repo": "https://github.com/urpc/uio",
"enabled": true,
"tests": [
"echo-ws",
"echo-ws-pipeline",
"echo-ws-limited"
],
"maintainers": ["limpo1989"]
}
26 changes: 24 additions & 2 deletions site/data/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -1299,8 +1299,8 @@
"response": true
}
},
"neton": {
"dir": "neton",
"neton-hyper": {
"dir": "neton-hyper",
"description": "Neton, a Kotlin Multiplatform framework compiled to a native executable. Routing, middleware, request parsing and the response pipeline are the framework's; the hyper4k engine (Tokio + Hyper 1.x, linked in as a Rust static library) only does protocol and transport. Serves HTTP/1.1 on 8080, HTTP/2 cleartext on 8082, HTTP/1.1+TLS on 8081 and HTTP/2+TLS on 8443 (ALPN) from one route table; static files and TLS terminate in the framework.",
"repo": "https://github.com/netonframework/neton",
"type": "emerging",
Expand All @@ -1313,6 +1313,20 @@
"response": true
}
},
"neton": {
"dir": "neton",
"description": "Neton with the Kotlin/Native neton-http-netonstream adapter, using com.netonstream io/http/tls and OpenSSL. The same framework routes, middleware, request parsing and response pipeline as neton-hyper; only the engine differs. HTTP/1.1 on 8080, h2c on 8082, h1 TLS on 8081 and h2 TLS on 8443.",
"repo": "https://github.com/netonframework/neton",
"type": "emerging",
"engine": "netonstream",
"mode": "standard",
"completeness": {
"routing": true,
"middleware": true,
"request": true,
"response": true
}
},
"nginx": {
"dir": "nginx",
"description": "Nginx with a custom C handler module, compiled with -O3 -march=native. The module answers /baseline11, /baseline2 and /json/{count}, serializing the JSON body per request from the dataset parsed at worker startup; /static/* is served by nginx core's sendfile path. TLS via quictls: HTTP/1.1 on :8081, h2 and h3 on :8443.",
Expand Down Expand Up @@ -2088,6 +2102,14 @@
"type": "engine",
"engine": "io_uring"
},
"uio-ws": {
"dir": "uio-ws",
"description": "uws WebSocket echo on uio: the RFC 6455 handshake and frame parser run in the connection's own I/O task on uio's edge-triggered event loops, one per CPU, so each message is parsed from the buffer its task read into and echoed from that task with no goroutine per connection; a read round's replies leave coalesced in one sendmsg, and a send the socket refuses is finished by the next write turn without pausing the read. Default configuration otherwise.",
"repo": "https://github.com/urpc/uio",
"type": "experimental",
"engine": "uio",
"mode": "standard"
},
"ultimate-express": {
"dir": "ultimate-express",
"description": "ultimate-express (Express API on uWebSockets.js) with the cluster module for multi-core scaling.",
Expand Down
157 changes: 157 additions & 0 deletions site/data/results/uio-ws.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
{
"framework": "uio-ws",
"results": {
"echo-ws-16384": {
"framework": "uio-ws",
"language": "Go",
"rps": 1519552,
"avg_latency": "10.44ms",
"p99_latency": "13.00ms",
"cpu": "3035.5%",
"memory": "165MiB",
"connections": 16384,
"threads": 64,
"duration": "5s",
"pipeline": 1,
"bandwidth": "10.54MB/s",
"reconnects": 0,
"status_2xx": 7597763,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0
},
"echo-ws-4096": {
"framework": "uio-ws",
"language": "Go",
"rps": 1957550,
"avg_latency": "2.09ms",
"p99_latency": "3.94ms",
"cpu": "3307.9%",
"memory": "77MiB",
"connections": 4096,
"threads": 64,
"duration": "5s",
"pipeline": 1,
"bandwidth": "13.11MB/s",
"reconnects": 0,
"status_2xx": 9787750,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0
},
"echo-ws-512": {
"framework": "uio-ws",
"language": "Go",
"rps": 1866244,
"avg_latency": "273us",
"p99_latency": "498us",
"cpu": "3276.6%",
"memory": "51MiB",
"connections": 512,
"threads": 64,
"duration": "5s",
"pipeline": 1,
"bandwidth": "12.46MB/s",
"reconnects": 0,
"status_2xx": 9331221,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0
},
"echo-ws-limited-4096": {
"framework": "uio-ws",
"language": "Go",
"rps": 521759,
"avg_latency": "1.71ms",
"p99_latency": "6.33ms",
"cpu": "1441.5%",
"memory": "94MiB",
"connections": 4096,
"threads": 64,
"duration": "5s",
"pipeline": 1,
"bandwidth": "9.90MB/s",
"reconnects": 260628,
"status_2xx": 2608799,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0
},
"echo-ws-limited-512": {
"framework": "uio-ws",
"language": "Go",
"rps": 410035,
"avg_latency": "920us",
"p99_latency": "3.37ms",
"cpu": "1465.1%",
"memory": "64MiB",
"connections": 512,
"threads": 64,
"duration": "5s",
"pipeline": 1,
"bandwidth": "7.78MB/s",
"reconnects": 205034,
"status_2xx": 2050176,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0
},
"echo-ws-pipeline-16384": {
"framework": "uio-ws",
"language": "Go",
"rps": 23882179,
"avg_latency": "10.64ms",
"p99_latency": "13.50ms",
"cpu": "3121.0%",
"memory": "171MiB",
"connections": 16384,
"threads": 64,
"duration": "5s",
"pipeline": 16,
"bandwidth": "159.78MB/s",
"reconnects": 0,
"status_2xx": 119410896,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0
},
"echo-ws-pipeline-4096": {
"framework": "uio-ws",
"language": "Go",
"rps": 30318937,
"avg_latency": "2.15ms",
"p99_latency": "3.98ms",
"cpu": "3765.6%",
"memory": "80MiB",
"connections": 4096,
"threads": 64,
"duration": "5s",
"pipeline": 16,
"bandwidth": "202.39MB/s",
"reconnects": 0,
"status_2xx": 151594688,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0
},
"echo-ws-pipeline-512": {
"framework": "uio-ws",
"language": "Go",
"rps": 29343251,
"avg_latency": "278us",
"p99_latency": "502us",
"cpu": "3681.4%",
"memory": "51MiB",
"connections": 512,
"threads": 64,
"duration": "5s",
"pipeline": 16,
"bandwidth": "195.84MB/s",
"reconnects": 0,
"status_2xx": 146716256,
"status_3xx": 0,
"status_4xx": 0,
"status_5xx": 0
}
}
}
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.