From b3c483448012b1b356d2987a926732debf3dcdf6 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Wed, 19 May 2021 13:28:59 -0400 Subject: [PATCH 001/299] add some very simple benchmarks --- benchmarks/clients/environment.yml | 10 + benchmarks/clients/stress.py | 35 + benchmarks/golang/go.mod | 5 + benchmarks/golang/go.sum | 2 + benchmarks/golang/main.go | 128 ++ benchmarks/rust/.gitignore | 1 + benchmarks/rust/Cargo.lock | 1977 ++++++++++++++++++++++++++++ benchmarks/rust/Cargo.toml | 23 + benchmarks/rust/src/main.rs | 255 ++++ 9 files changed, 2436 insertions(+) create mode 100644 benchmarks/clients/environment.yml create mode 100644 benchmarks/clients/stress.py create mode 100644 benchmarks/golang/go.mod create mode 100644 benchmarks/golang/go.sum create mode 100644 benchmarks/golang/main.go create mode 100644 benchmarks/rust/.gitignore create mode 100644 benchmarks/rust/Cargo.lock create mode 100644 benchmarks/rust/Cargo.toml create mode 100644 benchmarks/rust/src/main.rs diff --git a/benchmarks/clients/environment.yml b/benchmarks/clients/environment.yml new file mode 100644 index 000000000..d8b87c4dc --- /dev/null +++ b/benchmarks/clients/environment.yml @@ -0,0 +1,10 @@ +name: ott +channels: + - conda-forge + - defaults +dependencies: + - python=3.9 + - pip + - pip: + - websockets==9.0.2 + - tqdm diff --git a/benchmarks/clients/stress.py b/benchmarks/clients/stress.py new file mode 100644 index 000000000..4a5240fd6 --- /dev/null +++ b/benchmarks/clients/stress.py @@ -0,0 +1,35 @@ +import asyncio +import websockets +import time +from tqdm import tqdm +import os + +port = os.environ.get("PORT") or "8080" + +url = f"ws://localhost:{port}/echo" + +async def main(): + conns: websockets.ClientConnection = await asyncio.gather(*[websockets.connect(url) for _ in range(2000)]) + print("all clients connected") + while True: + + # start = None + # for conn in tqdm(conns): + # await conn.recv() + # if not start: + # print("start") + # start = time.time() + # end = time.time() + # print(f"time to receive all messages: {end - start}s") + + start = None + for coro in asyncio.as_completed([conn.recv() for conn in conns]): + await coro + if not start: + print("start") + start = time.time() + end = time.time() + print(f"time to receive all messages: {end - start}s") + + +asyncio.get_event_loop().run_until_complete(main()) diff --git a/benchmarks/golang/go.mod b/benchmarks/golang/go.mod new file mode 100644 index 000000000..39a3e1ade --- /dev/null +++ b/benchmarks/golang/go.mod @@ -0,0 +1,5 @@ +module test + +go 1.16 + +require github.com/gorilla/websocket v1.4.2 // indirect diff --git a/benchmarks/golang/go.sum b/benchmarks/golang/go.sum new file mode 100644 index 000000000..85efffd99 --- /dev/null +++ b/benchmarks/golang/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/benchmarks/golang/main.go b/benchmarks/golang/main.go new file mode 100644 index 000000000..acdddd966 --- /dev/null +++ b/benchmarks/golang/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "flag" + "html/template" + "log" + "net/http" + + "github.com/gorilla/websocket" +) + +var addr = flag.String("addr", "localhost:8081", "http service address") + +var upgrader = websocket.Upgrader{} // use default options + +var connections []*websocket.Conn + +func echo(w http.ResponseWriter, r *http.Request) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("upgrade:", err) + return + } + connections = append(connections, c) + defer c.Close() + for { + mt, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + log.Printf("recv: %s", message) + for i := 0; i < len(connections); i++ { + connections[i].WriteMessage(mt, message) + } + // err = c.WriteMessage(mt, message) + // if err != nil { + // log.Println("write:", err) + // break + // } + } +} + +func home(w http.ResponseWriter, r *http.Request) { + homeTemplate.Execute(w, "ws://"+r.Host+"/echo") +} + +func main() { + flag.Parse() + log.SetFlags(0) + http.HandleFunc("/echo", echo) + http.HandleFunc("/", home) + log.Fatal(http.ListenAndServe(*addr, nil)) +} + +var homeTemplate = template.Must(template.New("").Parse(` + + + + + + + + +
+

Click "Open" to create a connection to the server, +"Send" to send a message to the server and "Close" to close the connection. +You can change the message and send multiple times. +

+

+ + +

+ +

+
+
+
+ + +`)) diff --git a/benchmarks/rust/.gitignore b/benchmarks/rust/.gitignore new file mode 100644 index 000000000..9f970225a --- /dev/null +++ b/benchmarks/rust/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/benchmarks/rust/Cargo.lock b/benchmarks/rust/Cargo.lock new file mode 100644 index 000000000..4ec830b27 --- /dev/null +++ b/benchmarks/rust/Cargo.lock @@ -0,0 +1,1977 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "actix" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be241f88f3b1e7e9a3fbe3b5a8a0f6915b5a1d7ee0d9a248d3376d01068cc60" +dependencies = [ + "actix-rt", + "actix_derive", + "bitflags", + "bytes 0.5.6", + "crossbeam-channel", + "derive_more", + "futures-channel", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project 0.4.28", + "smallvec", + "tokio", + "tokio-util", + "trust-dns-proto", + "trust-dns-resolver", +] + +[[package]] +name = "actix-codec" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" +dependencies = [ + "bitflags", + "bytes 0.5.6", + "futures-core", + "futures-sink", + "log", + "pin-project 0.4.28", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-connect" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "derive_more", + "either", + "futures-util", + "http", + "log", + "trust-dns-proto", + "trust-dns-resolver", +] + +[[package]] +name = "actix-files" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8035f08f194893b199f4928b40425bd727c0257cf0fcf36f4ac214968d649ec7" +dependencies = [ + "actix-http", + "actix-service", + "actix-web", + "bitflags", + "bytes 0.5.6", + "derive_more", + "futures-core", + "futures-util", + "log", + "mime", + "mime_guess", + "percent-encoding", + "v_htmlescape", +] + +[[package]] +name = "actix-http" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" +dependencies = [ + "actix-codec", + "actix-connect", + "actix-rt", + "actix-service", + "actix-threadpool", + "actix-utils", + "base64", + "bitflags", + "brotli2", + "bytes 0.5.6", + "cookie", + "copyless", + "derive_more", + "either", + "encoding_rs", + "flate2", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "h2", + "http", + "httparse", + "indexmap", + "itoa", + "language-tags", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project 1.0.7", + "rand", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "sha-1", + "slab", + "time", +] + +[[package]] +name = "actix-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ca8ce00b267af8ccebbd647de0d61e0674b6e61185cc7a592ff88772bed655" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad299af73649e1fc893e333ccf86f377751eb95ff875d095131574c6f43452c" +dependencies = [ + "bytestring", + "http", + "log", + "regex", + "serde", +] + +[[package]] +name = "actix-rt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" +dependencies = [ + "actix-macros", + "actix-threadpool", + "copyless", + "futures-channel", + "futures-util", + "smallvec", + "tokio", +] + +[[package]] +name = "actix-server" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-channel", + "futures-util", + "log", + "mio", + "mio-uds", + "num_cpus", + "slab", + "socket2", +] + +[[package]] +name = "actix-service" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0052435d581b5be835d11f4eb3bce417c8af18d87ddf8ace99f8e67e595882bb" +dependencies = [ + "futures-util", + "pin-project 0.4.28", +] + +[[package]] +name = "actix-testing" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c" +dependencies = [ + "actix-macros", + "actix-rt", + "actix-server", + "actix-service", + "log", + "socket2", +] + +[[package]] +name = "actix-threadpool" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d209f04d002854b9afd3743032a27b066158817965bf5d036824d19ac2cc0e30" +dependencies = [ + "derive_more", + "futures-channel", + "lazy_static", + "log", + "num_cpus", + "parking_lot", + "threadpool", +] + +[[package]] +name = "actix-tls" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" +dependencies = [ + "actix-codec", + "actix-service", + "actix-utils", + "futures-util", +] + +[[package]] +name = "actix-utils" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "bitflags", + "bytes 0.5.6", + "either", + "futures-channel", + "futures-sink", + "futures-util", + "log", + "pin-project 0.4.28", + "slab", +] + +[[package]] +name = "actix-web" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-testing", + "actix-threadpool", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "awc", + "bytes 0.5.6", + "derive_more", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "fxhash", + "log", + "mime", + "pin-project 1.0.7", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "socket2", + "time", + "tinyvec", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6edf3c2693e2a8c422800c87ee89a6a4eac7dd01109bc172a1093ce1f4f001" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes 0.5.6", + "futures-channel", + "futures-core", + "pin-project 0.4.28", +] + +[[package]] +name = "actix-web-codegen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad26f77093333e0e7c6ffe54ebe3582d908a104e448723eec6d43d08b07143fb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "actix_derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95aceadaf327f18f0df5962fedc1bde2f870566a0b9f65c89508a3b1f79334c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "async-trait" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "awc" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "base64", + "bytes 0.5.6", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "log", + "mime", + "percent-encoding", + "rand", + "serde", + "serde_json", + "serde_urlencoded", +] + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "brotli2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +dependencies = [ + "brotli-sys", + "libc", +] + +[[package]] +name = "buf-min" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ae7069aad07c7cdefe6a22a671f00650728bd2331a4cc62e1e5d0becdf9ca4" +dependencies = [ + "bytes 0.5.6", +] + +[[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "bytestring" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" +dependencies = [ + "bytes 1.0.1", +] + +[[package]] +name = "cc" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" +dependencies = [ + "percent-encoding", + "time", + "version_check 0.9.3", +] + +[[package]] +name = "copyless" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" + +[[package]] +name = "cpufeatures" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "derive_more" +version = "0.99.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum-as-inner" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "flate2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" + +[[package]] +name = "futures-io" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" + +[[package]] +name = "futures-macro" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +dependencies = [ + "autocfg", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" + +[[package]] +name = "futures-task" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" + +[[package]] +name = "futures-util" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +dependencies = [ + "autocfg", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite 0.2.6", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check 0.9.3", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", + "tracing-futures", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + +[[package]] +name = "http" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +dependencies = [ + "bytes 1.0.1", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2", + "widestring", + "winapi 0.3.9", + "winreg", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check 0.1.5", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918192b5c59119d51e0cd221f4d49dde9112824ba717369e903c97d076083d0f" +dependencies = [ + "pin-project-internal 0.4.28", +] + +[[package]] +name = "pin-project" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" +dependencies = [ + "pin-project-internal 1.0.7", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be26700300be6d9d23264c73211d8190e755b6b5ca7a1b28230025511b52a5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "rust" +version = "0.1.0" +dependencies = [ + "actix", + "actix-files", + "actix-web", + "actix-web-actors", + "awc", + "env_logger", + "rand", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "signal-hook-registry" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check 0.9.3", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "syn" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check 0.9.3", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-uds", + "pin-project-lite 0.1.12", + "signal-hook-registry", + "slab", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.6", + "futures-core", + "futures-io", + "futures-sink", + "log", + "pin-project-lite 0.1.12", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.6", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project 1.0.7", + "tracing", +] + +[[package]] +name = "trust-dns-proto" +version = "0.19.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "enum-as-inner", + "futures", + "idna", + "lazy_static", + "log", + "rand", + "smallvec", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f593b371175db53a26d0b38ed2978fafb9e9e8d3868b1acd753ea18df0ceb" +dependencies = [ + "cfg-if 0.1.10", + "futures", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto", +] + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check 0.9.3", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "v_escape" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039a44473286eb84e4e74f90165feff67c802dbeced7ee4c5b00d719b0d0475e" +dependencies = [ + "buf-min", + "v_escape_derive", +] + +[[package]] +name = "v_escape_derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c860ad1273f4eee7006cee05db20c9e60e5d24cba024a32e1094aa8e574f3668" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "v_htmlescape" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7c2a33ed7cf0dc1b42bcf39e01b6512f9df08f09e1cd8a49d9dc49a6a9482" +dependencies = [ + "cfg-if 1.0.0", + "v_escape", +] + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/benchmarks/rust/Cargo.toml b/benchmarks/rust/Cargo.toml new file mode 100644 index 000000000..9db6783c6 --- /dev/null +++ b/benchmarks/rust/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "rust" +version = "0.1.0" +authors = ["Carson McManus "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[profile.release] +codegen-units = 1 +lto = true + +[profile.release.package."*"] +codegen-units = 1 + +[dependencies] +actix = "0.10" +actix-web = "3" +actix-web-actors = "3.0.0" +actix-files = "0.3" +awc = "2" +env_logger = "0.8" +rand = "0.7" diff --git a/benchmarks/rust/src/main.rs b/benchmarks/rust/src/main.rs new file mode 100644 index 000000000..8f3167a63 --- /dev/null +++ b/benchmarks/rust/src/main.rs @@ -0,0 +1,255 @@ +//! Simple echo websocket server. +//! Open `http://localhost:8080/ws/index.html` in browser +//! or [python console client](https://github.com/actix/examples/blob/master/websocket/websocket-client.py) +//! could be used for testing. + +use std::{collections::HashMap, time::{Duration, Instant}}; + +use actix::prelude::*; +use actix_files as fs; +use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web_actors::ws; +use rand::{rngs::ThreadRng, Rng}; + +// How often heartbeat pings are sent +const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); +/// How long before lack of client response causes a timeout +const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); + + +/// do websocket handshake and start `MyWebSocket` actor +async fn ws_index(r: HttpRequest, stream: web::Payload, room: web::Data>) -> Result { + let res = ws::start( + Client { + id: 0, + hb: Instant::now(), + addr: room.get_ref().clone() + }, + &r, + stream, + ); + res +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct Message(pub String); + +/// New chat session is created +#[derive(Message)] +#[rtype(usize)] +pub struct Connect { + pub addr: Recipient, +} + +/// Session is disconnected +#[derive(Message)] +#[rtype(result = "()")] +pub struct Disconnect { + pub id: usize, +} + +/// Send message to specific room +#[derive(Message)] +#[rtype(result = "()")] +pub struct ClientMessage { + /// Id of the client session + pub id: usize, + /// Peer message + pub msg: String, +} + +#[derive(Debug, Clone)] +pub struct Room { + clients: HashMap>, + rng: ThreadRng, +} + +impl Room { + pub fn new() -> Room { + Room { + clients: HashMap::new(), + rng: rand::thread_rng(), + } + } + + fn send_message(&self, message: &str) { + for client in &self.clients { + let _ = client.1.do_send(Message(message.to_owned())); + } + } +} + +impl Actor for Room { + type Context = Context; +} + +impl Handler for Room { + type Result = usize; + + fn handle(&mut self, msg: Connect, _: &mut Context) -> Self::Result { + println!("client connected"); + let id = self.rng.gen::(); + self.clients.insert(id, msg.addr); + id + } +} + +impl Handler for Room { + type Result = (); + + fn handle(&mut self, msg: Disconnect, _: &mut Context) -> Self::Result { + println!("client disconnected"); + self.clients.remove(&msg.id); + } +} + +impl Handler for Room { + type Result = (); + + fn handle(&mut self, msg: ClientMessage, _: &mut Context) { + self.send_message(msg.msg.as_str()); + } +} + +struct Client { + /// unique session id + id: usize, + /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT), + /// otherwise we drop connection. + hb: Instant, + /// Chat server + addr: Addr, +} + +impl Actor for Client { + type Context = ws::WebsocketContext; + + /// Method is called on actor start. + /// We register ws session with ChatServer + fn started(&mut self, ctx: &mut Self::Context) { + // we'll start heartbeat process on session start. + self.hb(ctx); + + // register self in chat server. `AsyncContext::wait` register + // future within context, but context waits until this future resolves + // before processing any other events. + // HttpContext::state() is instance of WsChatSessionState, state is shared + // across all routes within application + let addr = ctx.address(); + self.addr + .send(Connect { + addr: addr.recipient(), + }) + .into_actor(self) + .then(|res, act, ctx| { + match res { + Ok(res) => act.id = res, + // something is wrong with chat server + _ => ctx.stop(), + } + fut::ready(()) + }) + .wait(ctx); + } + + fn stopping(&mut self, _: &mut Self::Context) -> Running { + // notify chat server + self.addr.do_send(Disconnect { id: self.id }); + Running::Stop + } +} + +impl Client { + fn hb(&self, ctx: &mut ::Context) { + ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { + // check client heartbeats + if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { + // heartbeat timed out + println!("Websocket Client heartbeat failed, disconnecting!"); + + // stop actor + ctx.stop(); + + // don't try to send a ping + return; + } + + ctx.ping(b""); + }); + } +} + +impl Handler for Client { + type Result = (); + + fn handle(&mut self, msg: Message, ctx: &mut Self::Context) { + ctx.text(msg.0); + } +} + +impl StreamHandler> for Client { + fn handle( + &mut self, + msg: Result, + ctx: &mut Self::Context, + ) { + let msg = match msg { + Err(_) => { + ctx.stop(); + return; + } + Ok(msg) => msg, + }; + + // println!("WEBSOCKET MESSAGE: {:?}", msg); + match msg { + ws::Message::Ping(msg) => { + self.hb = Instant::now(); + ctx.pong(&msg); + } + ws::Message::Pong(_) => { + self.hb = Instant::now(); + } + ws::Message::Text(text) => { + let m = text.trim(); + self.addr.do_send(ClientMessage { + id: self.id, + msg: m.to_owned(), + }) + } + ws::Message::Binary(_) => println!("Unexpected binary"), + ws::Message::Close(reason) => { + ctx.close(reason); + ctx.stop(); + } + ws::Message::Continuation(_) => { + ctx.stop(); + } + ws::Message::Nop => (), + } + } +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); + env_logger::init(); + + let server = Room::new().start(); + + HttpServer::new(move || { + App::new() + .data(server.clone()) + // enable logger + .wrap(middleware::Logger::default()) + // websocket route + .service(web::resource("/echo").route(web::get().to(ws_index))) + // static files + .service(fs::Files::new("/", "static/").index_file("index.html")) + }) + // start http server on 127.0.0.1:8080 + .bind("127.0.0.1:8080")? + .run() + .await +} From 41a94bef0e649efa423eda274fd70d1b7503aba6 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 20 May 2021 08:29:34 -0400 Subject: [PATCH 002/299] disable old room manager, add client manager --- api.js | 35 +++++++++++ app.js | 10 ++- package-lock.json | 136 +++++++++++++++++++++++++++++++++++----- package.json | 8 +++ roommanager.js | 28 ++++++++- server/clientmanager.ts | 73 +++++++++++++++++++++ server/room.ts | 28 +++++++++ server/websockets.js | 28 +++++++++ tsconfig.json | 9 +++ 9 files changed, 336 insertions(+), 19 deletions(-) create mode 100644 server/clientmanager.ts create mode 100644 server/room.ts create mode 100644 server/websockets.js create mode 100644 tsconfig.json diff --git a/api.js b/api.js index a38a6e873..c7d55549c 100644 --- a/api.js +++ b/api.js @@ -26,6 +26,8 @@ const VALID_ROOM_VISIBILITY = [ const VALID_ROOM_QUEUE_MODE = [ "manual", "vote", + "loop", + "dj", ]; function handleGetRoomFailure(res, err) { @@ -531,6 +533,39 @@ router.post("/room/:name/undo", (req, res) => { }).catch(err => handleGetRoomFailure(res, err)); }); +router.post("/room/:name/play", async (req, res) => { + let room; + try { + let points = 1; + try { + let info = await rateLimiter.consume(req.ip, points); + setRateLimitHeaders(res, info); + } + catch (e) { + if (e instanceof Error) { + throw e; + } + else { + handleRateLimit(res, e); + return; + } + } + room = await roommanager.getOrLoadRoom(req.params.name); + } + catch (err) { + handleGetRoomFailure(res, err); + return; + } + // if (req.body.index) { + + // } + // else { + let client = room.getClient(req.session); + room.play(client); + // } + res.json({success: true}); +}); + router.get("/data/previewAdd", async (req, res) => { let points = 5; try { diff --git a/app.js b/app.js index ae994f2a6..a50045129 100644 --- a/app.js +++ b/app.js @@ -1,3 +1,4 @@ +require('ts-node').register(); const express = require('express'); const http = require('http'); const fs = require('fs'); @@ -174,9 +175,14 @@ app.use((req, res, next) => { next(); }); -const roommanager = require("./roommanager"); +// const roommanager = require("./roommanager"); const api = require("./api"); -roommanager.start(server, sessions); +// roommanager.start(server, sessions); + +const websockets = require("./server/websockets.js"); +websockets.Setup(server, sessions); +const clientmanager = require("./server/clientmanager.ts"); +clientmanager.Setup(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); // to support JSON-encoded bodies diff --git a/package-lock.json b/package-lock.json index 669e6155d..6686bf0a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3270,6 +3270,12 @@ "defer-to-connect": "^1.0.1" } }, + "@tsconfig/node12": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.7.tgz", + "integrity": "sha512-dgasobK/Y0wVMswcipr3k0HpevxFJLijN03A8mYfEPvWvOs14v0ZlYTR4kIgMx8g4+fTyTFv8/jLCIfRqLDJ4A==", + "dev": true + }, "@types/accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", @@ -3388,14 +3394,26 @@ "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" }, "@types/express": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", - "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", + "integrity": "sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==", "requires": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.18", "@types/qs": "*", "@types/serve-static": "*" + }, + "dependencies": { + "@types/express-serve-static-core": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz", + "integrity": "sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + } } }, "@types/express-serve-static-core": { @@ -3408,6 +3426,15 @@ "@types/range-parser": "*" } }, + "@types/express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-57DnyxiqClXOIjoCgeKCUYfKxBPOlOY/k+l1TPK+7bSwyiPTrS5FIk1Ycql7twk4wO7P5lfOVy6akDGiaMSLfw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/fs-capacitor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz", @@ -3527,6 +3554,12 @@ "@types/koa": "*" } }, + "@types/lodash": { + "version": "4.14.169", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.169.tgz", + "integrity": "sha512-DvmZHoHTFJ8zhVYwCLWbQ7uAbYQEk52Ev2/ZiQ7Y7gQGeV9pjBqjnQpECMHfKS1rCYAhMI7LHVxwyZLZinJgdw==", + "dev": true + }, "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", @@ -3595,6 +3628,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/passport": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.6.tgz", + "integrity": "sha512-9oKfrJXuAxvyxdrtMCxKkHgmd6DMO8NDOLvMJ1LvIWd6/xP+i81PAkpTaEca7VhJX9S009RciwZL/j6dsLsHrA==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -3762,9 +3804,9 @@ } }, "@types/ws": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz", - "integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.4.tgz", + "integrity": "sha512-d/7W23JAXPodQNbOZNXvl2K+bqAQrCMwlh/nuQsPSQk6Fq0opHoPrUw43aHsvSbIiQPr8Of2hkFbnz1XBFVyZQ==", "requires": { "@types/node": "*" } @@ -7884,6 +7926,11 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==" } } }, @@ -8652,6 +8699,19 @@ "parseurl": "^1.3.2", "subscriptions-transport-ws": "^0.9.16", "type-is": "^1.6.16" + }, + "dependencies": { + "@types/express": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz", + "integrity": "sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + } } }, "apollo-server-plugin-base": { @@ -12529,6 +12589,11 @@ "sha.js": "^2.4.8" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, "cross-fetch": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz", @@ -27316,15 +27381,32 @@ } }, "ts-node": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz", - "integrity": "sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", "requires": { "arg": "^4.1.0", + "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.6", + "source-map-support": "^0.5.17", "yn": "3.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } } }, "ts-pnp": { @@ -27443,9 +27525,9 @@ } }, "typescript": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", - "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==" + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==" }, "uglify-js": { "version": "3.4.10", @@ -28415,6 +28497,20 @@ "semver": "^5.0.3" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "term-size": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", @@ -28439,6 +28535,18 @@ } } }, + "ts-node": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, "unique-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", diff --git a/package.json b/package.json index 0ab0d5567..d6c58f7b6 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "secure-password": "^3.1.0", "sequelize": "5.21.4", "sequelize-cli": "^5.5.1", + "ts-node": "^9.1.1", + "typescript": "^4.2.4", "unique-names-generator": "^3.1.1", "uuid": "^3.4.0", "validator": "13.0.0", @@ -65,6 +67,12 @@ }, "devDependencies": { "@babel/core": "7.9.0", + "@tsconfig/node12": "^1.0.7", + "@types/express": "^4.17.11", + "@types/express-session": "^1.17.3", + "@types/lodash": "^4.14.169", + "@types/passport": "^1.0.6", + "@types/ws": "^7.4.4", "@useoptic/cli": "^9.0.20", "@vue/cli-plugin-babel": "^4.4.4", "@vue/cli-plugin-eslint": "^4.4.4", diff --git a/roommanager.js b/roommanager.js index 2fbec0614..04105e0f0 100644 --- a/roommanager.js +++ b/roommanager.js @@ -175,6 +175,28 @@ class Room { return this.playbackPosition + (this.isPlaying * now.diff(this.playbackStartTime, "seconds")); } + /** + * Play the video. + * @param {Client} client + */ + play(client) { + if (client) { + this.commitRoomEvent(new RoomEvent(this.name, ROOM_EVENT_TYPE.PLAY, client.username, {})); + } + else { + this.commitRoomEvent(new RoomEvent(this.name, ROOM_EVENT_TYPE.PLAY, "API client", {})); + } + } + + /** + * Get the client associated with a session. + * @param {Object} session + * @returns Client + */ + getClient(session) { + return _.find(this.clients, { session: session }); + } + /** * Modifies the room state based on the room event given, sends the event to clients, and syncs clients. * @param {RoomEvent} event @@ -672,7 +694,7 @@ class Room { /** * Called when this room receives a message from one of it's users. - * @param {Object} client The client that sent the message + * @param {Client} client The client that sent the message * @param {Object} message The message that the client sent as a object. Should always have an `action` attribute. */ onMessageReceived(client, msg) { @@ -687,11 +709,11 @@ class Room { let role = this.getRole(client); if (actionToPerm[msg.action]) { - permissions.check(this.permissions, role, actionToPerm[msg.action]); + this.permissions.check(role, actionToPerm[msg.action]); } if (msg.action === "play") { - this.commitRoomEvent(new RoomEvent(this.name, ROOM_EVENT_TYPE.PLAY, client.username, {})); + this.play(client); } else if (msg.action === "pause") { this.commitRoomEvent(new RoomEvent(this.name, ROOM_EVENT_TYPE.PAUSE, client.username, {})); diff --git a/server/clientmanager.ts b/server/clientmanager.ts new file mode 100644 index 000000000..119e3ebfa --- /dev/null +++ b/server/clientmanager.ts @@ -0,0 +1,73 @@ +import { Session } from "express-session"; +import WebSocket from "ws"; +import { uniqueNamesGenerator } from "unique-names-generator"; +import _ from "lodash"; +import { wss } from "./websockets.js"; +import { getLogger } from "../logger.js"; +import { Request } from 'express'; + +const log = getLogger("clientmanager"); +let connections: Client[] = []; + +enum OttWebsocketError { + INVALID_CONNECTION_URL = 4001, + ROOM_NOT_FOUND = 4002, + ROOM_UNLOADED = 4003, +} + + +export class Client { + Socket: WebSocket + Session: Session + User: any + UnregisteredUsername: string | null + + constructor (session: Session, socket: WebSocket) { + this.Session = session; + this.Socket = socket; + this.UnregisteredUsername = uniqueNamesGenerator() + + this.Socket.on("close", (code, reason) => { + let idx = _.findIndex(connections, { Session: this.Session }); + connections.splice(idx, 1) + }) + } + + public get Username() : string { + if (this.User) { + return this.User.username; + } + else { + return this.UnregisteredUsername!; + } + } +} + +/** + * Called when a websocket connects. + * @param session + * @param socket + */ +function OnConnect(session: Session, socket: WebSocket, req: Request) { + let roomName = req.url!.replace("/api/room/", ""); + log.debug(`connection received: ${roomName}`) + let client = new Client(session, socket); + connections.push(client); +} + +export function Setup() { + log.debug("setting up client manager..."); + const server = wss as WebSocket.Server; + server.on("connection", (ws, req: Request & { session: Session }) => { + if (!req.url!.startsWith("/api/room/")) { + log.error("Rejecting connection because the connection url was invalid"); + ws.close(OttWebsocketError.INVALID_CONNECTION_URL, "Invalid connection url"); + return; + } + OnConnect(req.session, ws, req); + }) +} + +export default { + Setup, +} diff --git a/server/room.ts b/server/room.ts new file mode 100644 index 000000000..0f90bce77 --- /dev/null +++ b/server/room.ts @@ -0,0 +1,28 @@ +import { Grants } from "./permissions.js"; + +enum Visibility { + Public, + Unlisted, + Private, +} + +enum QueueMode { + Manual, + Vote, + Loop, + Dj, +} + +interface RoomContext { + +} + +class Room { + Name: string = ""; + Title: string = ""; + Description: string = ""; + IsTemporary: boolean = false; + Visibility: Visibility = Visibility.Public; + QueueMode: QueueMode = QueueMode.Manual; + Grants: Grants = new Grants(); +} diff --git a/server/websockets.js b/server/websockets.js new file mode 100644 index 000000000..eb012b651 --- /dev/null +++ b/server/websockets.js @@ -0,0 +1,28 @@ +import WebSocket from 'ws'; +import { getLogger } from "../logger.js"; + +const log = getLogger("websockets"); +export const wss = new WebSocket.Server({ noServer: true }); + +/** + * Set up the websocket server. + * + * I wish I could put it in the clientmanager instead, but the type annotations were being a huge bitch. + * @param {*} httpServer + * @param {*} sessions + */ +export function Setup(httpServer, sessions) { + log.debug("setting up websocket upgrader..."); + httpServer.on('upgrade', (req, socket, head) => { + sessions(req, {}, () => { + wss.handleUpgrade(req, socket, head, ws => { + wss.emit('connection', ws, req); + }); + }); + }); +} + +export default { + Setup, + wss, +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..3778ae06e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@tsconfig/node12/tsconfig.json", + "compilerOptions": { + "preserveConstEnums": true, + "allowJs": true, + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "**/*.spec.ts"] + } \ No newline at end of file From ac51b245128a64c1d4e1e7286b2ba74b6c187691 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 20 May 2021 08:40:29 -0400 Subject: [PATCH 003/299] convert exceptions to typescript es module --- roommanager.js | 2 +- server/{exceptions.js => exceptions.ts} | 62 +++++++++---------------- server/permissions.js | 2 +- 3 files changed, 25 insertions(+), 41 deletions(-) rename server/{exceptions.js => exceptions.ts} (65%) diff --git a/roommanager.js b/roommanager.js index 04105e0f0..c7f6da166 100644 --- a/roommanager.js +++ b/roommanager.js @@ -10,7 +10,7 @@ const { getLogger } = require("./logger.js"); const { redisClient } = require('./redisclient.js'); const permissions = require("./server/permissions.js"); const { ROLES, ROLE_DISPLAY_NAMES } = permissions; -const { ImpossiblePromotionException, PermissionDeniedException } = require('./server/exceptions.js'); +import { ImpossiblePromotionException, PermissionDeniedException } from './server/exceptions'; const { ROLE_NAMES } = require('./server/permissions.js'); const log = getLogger("roommanager"); diff --git a/server/exceptions.js b/server/exceptions.ts similarity index 65% rename from server/exceptions.js rename to server/exceptions.ts index 7efbb6510..dba64ea92 100644 --- a/server/exceptions.js +++ b/server/exceptions.ts @@ -1,7 +1,9 @@ -class UnsupportedServiceException extends Error { - constructor(url) { +import { Url } from "url"; + +export class UnsupportedServiceException extends Error { + constructor(url: Url) { let msg = ""; - if (/\/*\.([a-z0-9])$/i.exec(url.path.split("?")[0])) { + if (/\/*\.([a-z0-9])$/i.exec(url.path!.split("?")[0])) { msg = `If this is a direct link to a video file, please open a "service support request" issue on github, so we can see if this file format works. Otherwise, the service at "${url.host}" is not yet supported.`; } else { @@ -12,15 +14,15 @@ class UnsupportedServiceException extends Error { } } -class InvalidAddPreviewInputException extends Error { - constructor(minLength) { +export class InvalidAddPreviewInputException extends Error { + constructor(minLength: number) { super(`Your search query must at least ${minLength} characters, or supply a Youtube video, playlist, or channel link.`); this.name = "InvalidAddPreviewInputException"; } } -class OutOfQuotaException extends Error { - constructor(service) { +export class OutOfQuotaException extends Error { + constructor(service: string) { if (service === "youtube") { super(`We don't have enough Youtube API quota to complete the request. We currently have a limit of 50,000 quota per day.`); } @@ -34,22 +36,22 @@ class OutOfQuotaException extends Error { } } -class InvalidVideoIdException extends Error { - constructor(service, id) { +export class InvalidVideoIdException extends Error { + constructor(service: string, id: string) { super(`"${id} is an invalid ${service} video ID."`); this.name = "InvalidVideoIdException"; } } -class FeatureDisabledException extends Error { - constructor(reason) { +export class FeatureDisabledException extends Error { + constructor(reason: string) { super(`Sorry, this feature is disabled: ${reason}`); this.name = "FeatureDisabledException"; } } -class UnsupportedMimeTypeException extends Error { - constructor(mime) { +export class UnsupportedMimeTypeException extends Error { + constructor(mime: string) { if (mime.startsWith("video/")) { super(`Files that are ${mime} are not supported. Mp4 videos work the best.`); } @@ -60,58 +62,40 @@ class UnsupportedMimeTypeException extends Error { } } -class LocalFileException extends Error { +export class LocalFileException extends Error { constructor() { super(`The video URL provided references a local file. It is not possible to play videos on your computer, nor files located on the server. Videos must be hosted somewhere all users in the room can access.`); this.name = "LocalFileException"; } } -class MissingMetadataException extends Error { +export class MissingMetadataException extends Error { constructor() { super(`The video provided is missing metadata required to let playback work correctly (probably length). For best results, reencode the video as an mp4.`); this.name = "MissingMetadataException"; } } -class IncompleteServiceAdapterException extends Error { +export class IncompleteServiceAdapterException extends Error { } -class PermissionDeniedException extends Error { - constructor(permission) { +export class PermissionDeniedException extends Error { + constructor(permission: string) { super(`Permission denied: ${permission}`); this.name = "PermissionDeniedException"; } } -class ImpossiblePromotionException extends Error { +export class ImpossiblePromotionException extends Error { constructor() { super(`Can't promote/demote unregistered user`); this.name = "ImpossiblePromotionException"; } } -class InvalidRoleException extends Error { - /** - * @param {int} role - */ - constructor(role) { +export class InvalidRoleException extends Error { + constructor(role: any) { super(`Role ${role} (type: ${typeof role}) is not valid.`); this.name = "InvalidRoleException"; } } - -module.exports = { - UnsupportedServiceException, - InvalidAddPreviewInputException, - OutOfQuotaException, - InvalidVideoIdException, - FeatureDisabledException, - UnsupportedMimeTypeException, - LocalFileException, - MissingMetadataException, - IncompleteServiceAdapterException, - PermissionDeniedException, - ImpossiblePromotionException, - InvalidRoleException, -}; diff --git a/server/permissions.js b/server/permissions.js index f670ced83..6f3042a38 100644 --- a/server/permissions.js +++ b/server/permissions.js @@ -1,6 +1,6 @@ const _ = require("lodash"); const { getLogger } = require("../logger.js"); -const { PermissionDeniedException, InvalidRoleException } = require("./exceptions.js"); +import { PermissionDeniedException, InvalidRoleException } from "./exceptions"; const log = getLogger("permissions"); const ROLES = { From a0ae0ee6df7fb00c7096e7b3f42a3151b0fd96ed Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 20 May 2021 12:54:17 -0400 Subject: [PATCH 004/299] accept connections --- package-lock.json | 50 ++++++++++++++++++++++++++++++++++++----- package.json | 7 +++--- server/clientmanager.ts | 48 +++++++++++++++++++++++++++++---------- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6686bf0a6..35e6d1d25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7843,6 +7843,17 @@ "mkdirp": "^0.5.1", "opener": "^1.5.1", "ws": "^6.0.0" + }, + "dependencies": { + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } } }, "yallist": { @@ -8662,6 +8673,16 @@ "sha.js": "^2.4.11", "subscriptions-transport-ws": "^0.9.11", "ws": "^6.0.0" + }, + "dependencies": { + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "requires": { + "async-limiter": "~1.0.0" + } + } } }, "apollo-server-env": { @@ -36063,6 +36084,17 @@ "mkdirp": "^0.5.1", "opener": "^1.5.1", "ws": "^6.0.0" + }, + "dependencies": { + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } } }, "webpack-chain": { @@ -36252,6 +36284,15 @@ "requires": { "has-flag": "^3.0.0" } + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } } } }, @@ -36720,12 +36761,9 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "requires": { - "async-limiter": "~1.0.0" - } + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==" }, "x-is-string": { "version": "0.1.0", diff --git a/package.json b/package.json index d6c58f7b6..0723e7b73 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,13 @@ "private": true, "scripts": { "start": "node app.js", - "debug": "nodemon --inspect app.js", + "debug": "nodemon --inspect -e ts,js app.js", "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint .", "lint-ci": "vue-cli-service --no-fix lint .", "test": "vue-cli-service test:unit --runInBand --detectOpenHandles --forceExit", - "api-server": "nodemon --inspect app.js", + "api-server": "nodemon --inspect -e ts,js app.js", "dev": "NODE_ENV=development concurrently \"npm:api-server\" \"PORT=8080 npm run serve\"", "dev-windows": "SET NODE_ENV=development&&concurrently \"npm:api-server\" \"npm:serve\"", "optic-dev": "npx api start", @@ -63,7 +63,8 @@ "vuedraggable": "^2.23.2", "vuetify": "^2.2.19", "vuex": "^3.1.2", - "winston": "^3.2.1" + "winston": "^3.2.1", + "ws": "^7.4.5" }, "devDependencies": { "@babel/core": "7.9.0", diff --git a/server/clientmanager.ts b/server/clientmanager.ts index 119e3ebfa..6a5213d9d 100644 --- a/server/clientmanager.ts +++ b/server/clientmanager.ts @@ -4,7 +4,7 @@ import { uniqueNamesGenerator } from "unique-names-generator"; import _ from "lodash"; import { wss } from "./websockets.js"; import { getLogger } from "../logger.js"; -import { Request } from 'express'; +import { json, Request } from 'express'; const log = getLogger("clientmanager"); let connections: Client[] = []; @@ -15,6 +15,9 @@ enum OttWebsocketError { ROOM_UNLOADED = 4003, } +interface ClientMessage { + action: string +} export class Client { Socket: WebSocket @@ -41,18 +44,24 @@ export class Client { return this.UnregisteredUsername!; } } -} -/** - * Called when a websocket connects. - * @param session - * @param socket - */ -function OnConnect(session: Session, socket: WebSocket, req: Request) { - let roomName = req.url!.replace("/api/room/", ""); - log.debug(`connection received: ${roomName}`) - let client = new Client(session, socket); - connections.push(client); + public OnMessage(text: string) { + let msg = JSON.parse(text) as ClientMessage; + switch (msg.action) { + default: + log.warn(`Unknown message: ${msg.action}`); + break; + } + } + + public OnPing(data: Buffer) { + log.debug(`sending pong`); + this.Socket.pong(); + } + + public JoinRoom(room: string) { + log.info(`${this.Username} joining ${room}`); + } } export function Setup() { @@ -68,6 +77,21 @@ export function Setup() { }) } +/** + * Called when a websocket connects. + * @param session + * @param socket + */ +function OnConnect(session: Session, socket: WebSocket, req: Request) { + let roomName = req.url!.replace("/api/room/", ""); + log.debug(`connection received: ${roomName}`) + let client = new Client(session, socket); + connections.push(client); + socket.on("ping", client.OnPing); + socket.on("message", client.OnMessage); + client.JoinRoom(roomName); +} + export default { Setup, } From 6570fcbbc2956e50e382d4999f1ddb91e97b56ce Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 20 May 2021 15:18:09 -0400 Subject: [PATCH 005/299] implement initial room pubsub via redis --- api.js | 12 ++-- app.js | 4 +- common/video.js | 2 +- package-lock.json | 9 +++ package.json | 1 + redisclient.js | 25 +++++--- server/clientmanager.ts | 55 ++++++++++++++++-- server/messages.ts | 19 ++++++ server/permissions.js | 2 +- server/room.ts | 126 ++++++++++++++++++++++++++++++++++++---- server/roommanager.ts | 36 ++++++++++++ 11 files changed, 257 insertions(+), 34 deletions(-) create mode 100644 server/messages.ts create mode 100644 server/roommanager.ts diff --git a/api.js b/api.js index c7d55549c..6db65e19e 100644 --- a/api.js +++ b/api.js @@ -5,7 +5,8 @@ const InfoExtract = require("./server/infoextractor"); const { getLogger } = require('./logger.js'); const permissions = require("./server/permissions.js"); const storage = require("./storage.js"); -const roommanager = require("./roommanager.js"); +import roommanager from "./server/roommanager"; +import { RoomOptions } from "./server/room"; const { rateLimiter, handleRateLimit, setRateLimitHeaders } = require("./server/rate-limit.js"); const log = getLogger("api"); @@ -246,10 +247,10 @@ router.post("/room/create", async (req, res) => { } } if (req.user) { - await roommanager.createRoom({ ...req.body, owner: req.user }); + await roommanager.CreateRoom({ ...req.body, owner: req.user }); } else { - await roommanager.createRoom(req.body); + await roommanager.CreateRoom(req.body); } log.info(`${req.body.temporary ? "Temporary" : "Permanent"} room created: name=${req.body.name} ip=${req.ip} user-agent=${req.headers["user-agent"]}`); res.json({ @@ -303,7 +304,10 @@ router.post("/room/generate", async (req, res) => { } let roomName = uuid(); log.debug(`Generating room: ${roomName}`); - await roommanager.createRoom(roomName, true); + await roommanager.CreateRoom({ + name: roomName, + isTemporary: true, + }); log.info(`room generated: ip=${req.ip} user-agent=${req.headers["user-agent"]}`); res.json({ success: true, diff --git a/app.js b/app.js index a50045129..3a5462f53 100644 --- a/app.js +++ b/app.js @@ -175,14 +175,14 @@ app.use((req, res, next) => { next(); }); -// const roommanager = require("./roommanager"); const api = require("./api"); -// roommanager.start(server, sessions); const websockets = require("./server/websockets.js"); websockets.Setup(server, sessions); const clientmanager = require("./server/clientmanager.ts"); clientmanager.Setup(); +const roommanager = require("./server/roommanager"); +roommanager.start(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); // to support JSON-encoded bodies diff --git a/common/video.js b/common/video.js index d004f50c8..6929184eb 100644 --- a/common/video.js +++ b/common/video.js @@ -3,7 +3,7 @@ const _ = require("lodash"); /** * Represents a video on any video providing service. */ -class Video { +export class Video { constructor(args=undefined) { this.service = null; this.id = null; diff --git a/package-lock.json b/package-lock.json index 35e6d1d25..3674888cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3687,6 +3687,15 @@ "@types/react": "*" } }, + "@types/redis": { + "version": "2.8.28", + "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.28.tgz", + "integrity": "sha512-8l2gr2OQ969ypa7hFOeKqtFoY70XkHxISV0pAwmQ2nm6CSPb1brmTmqJCGGrekCo+pAZyWlNXr+Kvo6L/1wijA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/scheduler": { "version": "0.16.1", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", diff --git a/package.json b/package.json index 0723e7b73..f362ce13d 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@types/express-session": "^1.17.3", "@types/lodash": "^4.14.169", "@types/passport": "^1.0.6", + "@types/redis": "^2.8.28", "@types/ws": "^7.4.4", "@useoptic/cli": "^9.0.20", "@vue/cli-plugin-babel": "^4.4.4", diff --git a/redisclient.js b/redisclient.js index e3c43d1e5..103c7ad08 100644 --- a/redisclient.js +++ b/redisclient.js @@ -1,18 +1,25 @@ const redis = require('redis'); -const redisClient = (process.env.REDIS_TLS_URL || process.env.REDIS_URL) ? - redis.createClient(process.env.REDIS_TLS_URL || process.env.REDIS_URL, { +const redisOptions = (process.env.REDIS_TLS_URL || process.env.REDIS_URL) ? + { + url: process.env.REDIS_TLS_URL || process.env.REDIS_URL, tls: { rejectUnauthorized: false, }, - }) : - redis.createClient({ - port: process.env.REDIS_PORT || undefined, - host: process.env.REDIS_HOST || undefined, - password: process.env.REDIS_PASSWORD || undefined, - db: process.env.REDIS_DB || undefined, - }); +} : { + port: process.env.REDIS_PORT || undefined, + host: process.env.REDIS_HOST || undefined, + password: process.env.REDIS_PASSWORD || undefined, + db: process.env.REDIS_DB || undefined, +}; + +const redisClient = redis.createClient(redisOptions); + +function createSubscriber() { + return redis.createClient(redisOptions); +} module.exports = { redisClient, + createSubscriber, }; diff --git a/server/clientmanager.ts b/server/clientmanager.ts index 6a5213d9d..ee749dc1e 100644 --- a/server/clientmanager.ts +++ b/server/clientmanager.ts @@ -5,9 +5,19 @@ import _ from "lodash"; import { wss } from "./websockets.js"; import { getLogger } from "../logger.js"; import { json, Request } from 'express'; +import { redisClient, createSubscriber } from "../redisclient"; +import { promisify } from "util"; +import { ServerMessage, ServerMessageSync } from "./messages"; +import { RoomState } from "./room.js"; +// WARN: do NOT import roommanager const log = getLogger("clientmanager"); +const redisSubscriber = createSubscriber(); +const get = promisify(redisClient.get).bind(redisClient); +const subscribe: (channel: string) => Promise = promisify(redisSubscriber.subscribe).bind(redisSubscriber); let connections: Client[] = []; +let roomStates: Map = new Map(); +let roomJoins: Map = new Map(); enum OttWebsocketError { INVALID_CONNECTION_URL = 4001, @@ -15,10 +25,6 @@ enum OttWebsocketError { ROOM_UNLOADED = 4003, } -interface ClientMessage { - action: string -} - export class Client { Socket: WebSocket Session: Session @@ -46,7 +52,7 @@ export class Client { } public OnMessage(text: string) { - let msg = JSON.parse(text) as ClientMessage; + let msg = JSON.parse(text) as ServerMessage; switch (msg.action) { default: log.warn(`Unknown message: ${msg.action}`); @@ -59,8 +65,29 @@ export class Client { this.Socket.pong(); } - public JoinRoom(room: string) { + public async JoinRoom(room: string) { log.info(`${this.Username} joining ${room}`); + subscribe(`room:${room}`); + let clients = roomJoins.get(room); + if (clients === undefined) { + log.warn("room joins not present, creating") + clients = []; + } + clients.push(this); + roomJoins.set(room, clients); + + // full sync + let state = roomStates.get(room); + if (state === undefined) { + log.warn("room state not present, grabbing") + let stateText = await get(`room:${room}`); + if (stateText === null) { + throw Error("piss and shit") + } + state = JSON.parse(stateText)!; + } + let syncMsg: ServerMessageSync = Object.assign({action: "sync"}, state) as ServerMessageSync; + this.Socket.send(JSON.stringify(syncMsg)); } } @@ -92,6 +119,22 @@ function OnConnect(session: Session, socket: WebSocket, req: Request) { client.JoinRoom(roomName); } +redisClient.on("message", function(channel, text) { + log.debug(`pubsub message: ${channel}: ${text}`); + if (!channel.startsWith("room:")) { + return; + } + let msg = JSON.parse(text) as ServerMessage; + if (msg.action === "sync") { + let state = roomStates.get(msg.name!); + if (state === undefined) { + state = {} as RoomState; + } + Object.assign(state, _.omit(msg, "action")) + roomStates.set(msg.name!, state); + } +}); + export default { Setup, } diff --git a/server/messages.ts b/server/messages.ts new file mode 100644 index 000000000..1d63763d3 --- /dev/null +++ b/server/messages.ts @@ -0,0 +1,19 @@ +import { QueueMode, Visibility } from "./room"; + +export type ServerMessage = ServerMessageSync + +interface ServerMessageBase { + action: string +} + +export interface ServerMessageSync extends ServerMessageBase { + action: "sync" + name?: string + title?: string, + description?: string, + isTemporary?: boolean, + visibility?: Visibility, + queueMode?: QueueMode, + isPlaying?: boolean, + playbackPosition?: number, +} diff --git a/server/permissions.js b/server/permissions.js index 6f3042a38..e249a7ab3 100644 --- a/server/permissions.js +++ b/server/permissions.js @@ -227,7 +227,7 @@ function check(grants, role, permission) { * Represents permissions for all roles. Handles permission inheritance, and serialization/deserialization. * If grants are not provided, the defaults will be used. */ -class Grants { +export class Grants { /** * @param {Object|undefined} grants Opional object that maps roles to grant masks. */ diff --git a/server/room.ts b/server/room.ts index 0f90bce77..d24338034 100644 --- a/server/room.ts +++ b/server/room.ts @@ -1,28 +1,132 @@ import { Grants } from "./permissions.js"; +import { redisClient } from "../redisclient"; +import { promisify } from "util"; +import { getLogger } from "../logger.js"; +import winston from "winston"; +import { ServerMessageSync } from "./messages"; +import _ from "lodash"; +import { Video } from "../common/video"; -enum Visibility { +const publish = promisify(redisClient.publish).bind(redisClient); +const set = promisify(redisClient.set).bind(redisClient); + +export enum Visibility { Public, Unlisted, Private, } -enum QueueMode { +export enum QueueMode { Manual, Vote, Loop, Dj, } -interface RoomContext { +export interface RoomOptions { + name: string + title: string + description: string + visibility: Visibility + queueMode: QueueMode + isTemporary: boolean +} +export interface RoomState extends RoomOptions { + currentSource: Video | null + queue: Video[] + isPlaying: boolean + playbackPosition: number + grants: Grants } -class Room { - Name: string = ""; - Title: string = ""; - Description: string = ""; - IsTemporary: boolean = false; - Visibility: Visibility = Visibility.Public; - QueueMode: QueueMode = QueueMode.Manual; - Grants: Grants = new Grants(); +export class Room implements RoomState { + _name: string = ""; + _title: string = ""; + _description: string = ""; + _visibility: Visibility = Visibility.Public; + _queueMode: QueueMode = QueueMode.Manual; + isTemporary: boolean = false; + + currentSource: Video | null = null + queue: Video[] = [] + isPlaying: boolean = false + playbackPosition: number = 0 + grants: Grants = new Grants(); + + _dirty: Set = new Set(); + log: winston.Logger + + constructor (options: RoomOptions) { + Object.assign(this, options); + this.log = getLogger(`room/${this.name}`); + } + + public get name() { + return this._name; + } + + public set name(value: string) { + this._name = value; + this._dirty.add("name"); + } + + public get title() { + return this._title; + } + + public set title(value: string) { + this._title = value; + this._dirty.add("title"); + } + + public get description() { + return this._description; + } + + public set description(value: string) { + this._description = value; + this._dirty.add("description"); + } + + public get visibility() { + return this._visibility; + } + + public set visibility(value: Visibility) { + this._visibility = value; + this._dirty.add("visibility"); + } + + public get queueMode() { + return this._queueMode; + } + + public set queueMode(value: QueueMode) { + this._queueMode = value; + this._dirty.add("queueMode"); + } + + public async update() { + this.log.info("updating"); + let state: RoomState = _.pick(this, "name", "title", "description", "isTemporary", "visibility", "queueMode", "currentSource", "queue", "isPlaying", "playbackPosition", "grants"); + await set(`room:${this.name}`, JSON.stringify(state)); + } + + public async sync() { + if (this._dirty.size === 0) { + return; + } + + let msg: ServerMessageSync = { + action: "sync", + } + + let state: RoomState = _.pick(this, "name", "title", "description", "isTemporary", "visibility", "queueMode", "currentSource", "queue", "isPlaying", "playbackPosition", "grants"); + + msg = Object.assign(msg, _.pick(state, Array.from(this._dirty))) + + await set(`room:${this.name}`, JSON.stringify(state)); + await publish(`room:${this.name}`, JSON.stringify(msg)); + } } diff --git a/server/roommanager.ts b/server/roommanager.ts new file mode 100644 index 000000000..106ba9270 --- /dev/null +++ b/server/roommanager.ts @@ -0,0 +1,36 @@ +import { Room, Visibility, QueueMode, RoomOptions } from "./room"; +import _ from "lodash"; +const NanoTimer = require("nanotimer"); +import { getLogger } from "../logger.js"; +// WARN: do NOT import clientmanager + +const log = getLogger("roommanager"); +let rooms: Room[] = []; + +export function start() { + const nanotimer = new NanoTimer(); + nanotimer.setInterval(async () => { + for (const room of rooms) { + await room.update(); + await room.sync(); + } + }, '', '1000m'); +} + +export default { + async CreateRoom(options: RoomOptions) { + if (options.name === "") { + throw new Error("bad"); + } + let room = new Room(options); + await room.update(); + rooms.push(room); + log.info(`Room created: ${room.name}`); + }, + + async GetRoom(roomName: string) { + return _.find(rooms, { name: roomName }); + }, + + start, +} \ No newline at end of file From 493cc1cdcaba50129c77cc6e8290fe7cb041e573 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 20 May 2021 15:26:14 -0400 Subject: [PATCH 006/299] implement RoomNotFoundException on websocket connect --- server/clientmanager.ts | 40 +++++++++++++++++++++++++++------------- server/exceptions.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/server/clientmanager.ts b/server/clientmanager.ts index ee749dc1e..4cbc5442a 100644 --- a/server/clientmanager.ts +++ b/server/clientmanager.ts @@ -9,6 +9,7 @@ import { redisClient, createSubscriber } from "../redisclient"; import { promisify } from "util"; import { ServerMessage, ServerMessageSync } from "./messages"; import { RoomState } from "./room.js"; +import { RoomNotFoundException } from "./exceptions"; // WARN: do NOT import roommanager const log = getLogger("clientmanager"); @@ -20,6 +21,7 @@ let roomStates: Map = new Map(); let roomJoins: Map = new Map(); enum OttWebsocketError { + UNKNOWN = 4000, INVALID_CONNECTION_URL = 4001, ROOM_NOT_FOUND = 4002, ROOM_UNLOADED = 4003, @@ -67,14 +69,6 @@ export class Client { public async JoinRoom(room: string) { log.info(`${this.Username} joining ${room}`); - subscribe(`room:${room}`); - let clients = roomJoins.get(room); - if (clients === undefined) { - log.warn("room joins not present, creating") - clients = []; - } - clients.push(this); - roomJoins.set(room, clients); // full sync let state = roomStates.get(room); @@ -82,25 +76,35 @@ export class Client { log.warn("room state not present, grabbing") let stateText = await get(`room:${room}`); if (stateText === null) { - throw Error("piss and shit") + throw new RoomNotFoundException(room); } state = JSON.parse(stateText)!; } let syncMsg: ServerMessageSync = Object.assign({action: "sync"}, state) as ServerMessageSync; this.Socket.send(JSON.stringify(syncMsg)); + + // actually join the room + subscribe(`room:${room}`); + let clients = roomJoins.get(room); + if (clients === undefined) { + log.warn("room joins not present, creating") + clients = []; + } + clients.push(this); + roomJoins.set(room, clients); } } export function Setup() { log.debug("setting up client manager..."); const server = wss as WebSocket.Server; - server.on("connection", (ws, req: Request & { session: Session }) => { + server.on("connection", async (ws, req: Request & { session: Session }) => { if (!req.url!.startsWith("/api/room/")) { log.error("Rejecting connection because the connection url was invalid"); ws.close(OttWebsocketError.INVALID_CONNECTION_URL, "Invalid connection url"); return; } - OnConnect(req.session, ws, req); + await OnConnect(req.session, ws, req); }) } @@ -109,14 +113,24 @@ export function Setup() { * @param session * @param socket */ -function OnConnect(session: Session, socket: WebSocket, req: Request) { +async function OnConnect(session: Session, socket: WebSocket, req: Request) { let roomName = req.url!.replace("/api/room/", ""); log.debug(`connection received: ${roomName}`) let client = new Client(session, socket); connections.push(client); socket.on("ping", client.OnPing); socket.on("message", client.OnMessage); - client.JoinRoom(roomName); + try { + await client.JoinRoom(roomName); + } + catch (e) { + log.error(`Failed to join room: ${e.message}`); + if (e instanceof RoomNotFoundException) { + socket.close(OttWebsocketError.ROOM_NOT_FOUND); + } else { + socket.close(OttWebsocketError.UNKNOWN); + } + } } redisClient.on("message", function(channel, text) { diff --git a/server/exceptions.ts b/server/exceptions.ts index dba64ea92..dcca61a28 100644 --- a/server/exceptions.ts +++ b/server/exceptions.ts @@ -99,3 +99,31 @@ export class InvalidRoleException extends Error { this.name = "InvalidRoleException"; } } + +export class RoomNotFoundException extends Error { + constructor(roomName: string) { + super(`The room "${roomName}" could not be found.`); + this.name = "RoomNotFoundException"; + } +} + +export class RoomAlreadyLoadedException extends Error { + constructor(roomName: string) { + super(`The room "${roomName}" is already loaded.`); + this.name = "RoomAlreadyLoadedException"; + } +} + +export class RoomNameTakenException extends Error { + constructor(roomName: string) { + super(`The room "${roomName}" is taken.`); + this.name = "RoomNameTakenException"; + } +} + +export class VideoAlreadyQueuedException extends Error { + constructor(title: string) { + super(`The video "${title}" is already in the queue`); + this.name = "VideoAlreadyQueuedException"; + } +} From b69f4f80f8e7e513c3b076a1cecc7b84505ba271 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Thu, 20 May 2021 16:05:52 -0400 Subject: [PATCH 007/299] make add to queue work kinda --- api.js | 5 ++--- common/video.js | 4 +--- server/clientmanager.ts | 17 ++++++++++++++--- server/infoextractor.js | 8 ++++---- server/rate-limit.js | 4 ++-- server/room.ts | 30 +++++++++++++++++++++++++++++- 6 files changed, 52 insertions(+), 16 deletions(-) diff --git a/api.js b/api.js index 6db65e19e..f0cd0cb42 100644 --- a/api.js +++ b/api.js @@ -1,12 +1,11 @@ const express = require('express'); const uuid = require("uuid/v4"); const _ = require("lodash"); -const InfoExtract = require("./server/infoextractor"); +import InfoExtract from "./server/infoextractor"; const { getLogger } = require('./logger.js'); const permissions = require("./server/permissions.js"); const storage = require("./storage.js"); import roommanager from "./server/roommanager"; -import { RoomOptions } from "./server/room"; const { rateLimiter, handleRateLimit, setRateLimitHeaders } = require("./server/rate-limit.js"); const log = getLogger("api"); @@ -415,7 +414,7 @@ router.post("/room/:name/queue", async (req, res) => { return; } } - room = await roommanager.getOrLoadRoom(req.params.name); + room = await roommanager.GetRoom(req.params.name); } catch (err) { handleGetRoomFailure(res, err); diff --git a/common/video.js b/common/video.js index 6929184eb..b219d98f8 100644 --- a/common/video.js +++ b/common/video.js @@ -3,7 +3,7 @@ const _ = require("lodash"); /** * Represents a video on any video providing service. */ -export class Video { +export default class Video { constructor(args=undefined) { this.service = null; this.id = null; @@ -48,5 +48,3 @@ export class Video { return Object.assign(_.cloneDeep(a), _.pickBy(b, x => x)); } } - -module.exports = Video; diff --git a/server/clientmanager.ts b/server/clientmanager.ts index 4cbc5442a..3326d53a3 100644 --- a/server/clientmanager.ts +++ b/server/clientmanager.ts @@ -133,19 +133,30 @@ async function OnConnect(session: Session, socket: WebSocket, req: Request) { } } -redisClient.on("message", function(channel, text) { +redisSubscriber.on("message", function(channel, text) { log.debug(`pubsub message: ${channel}: ${text}`); if (!channel.startsWith("room:")) { return; } let msg = JSON.parse(text) as ServerMessage; if (msg.action === "sync") { - let state = roomStates.get(msg.name!); + let roomName = channel.replace("room:", "") + + let state = roomStates.get(roomName!); if (state === undefined) { state = {} as RoomState; } Object.assign(state, _.omit(msg, "action")) - roomStates.set(msg.name!, state); + roomStates.set(roomName!, state); + + for (let client of roomJoins.get(roomName)!) { + try { + client.Socket.send(text); + } + catch (e) { + log.error(`failed to send to client: ${e.message}`); + } + } } }); diff --git a/server/infoextractor.js b/server/infoextractor.js index 005c21e9b..cd65fe435 100644 --- a/server/infoextractor.js +++ b/server/infoextractor.js @@ -8,7 +8,7 @@ const DirectVideoAdapter = require("./services/direct"); const RedditAdapter = require("./services/reddit"); const NeverthinkAdapter = require("./services/neverthink"); const storage = require("../storage"); -const Video = require("../common/video"); +import Video from "../common/video"; const { UnsupportedMimeTypeException, OutOfQuotaException, UnsupportedServiceException, InvalidAddPreviewInputException, FeatureDisabledException } = require("./exceptions"); const { getLogger } = require("../logger"); const { redisClient } = require("../redisclient"); @@ -29,7 +29,7 @@ const adapters = [ const ADD_PREVIEW_SEARCH_MIN_LENGTH = parseInt(process.env.ADD_PREVIEW_SEARCH_MIN_LENGTH) || 3; const ENABLE_SEARCH = process.env.ENABLE_SEARCH === undefined || process.env.ENABLE_SEARCH === true || process.env.ENABLE_SEARCH === "true"; -module.exports = { +export default { isURL(str) { return URL.parse(str).host !== null; }, @@ -124,7 +124,7 @@ module.exports = { * to the cache. * @param {string} service * @param {string} videoId - * @returns {Video} + * @returns {Promise