✨ WebSocket Support
This commit is contained in:
parent
e40fe6049f
commit
7eee10c4ff
179
Cargo.lock
generated
179
Cargo.lock
generated
@ -2,6 +2,31 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix"
|
||||||
|
version = "0.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb72882332b6d6282f428b77ba0358cb2687e61a6f6df6a6d3871e8a177c2d4f"
|
||||||
|
dependencies = [
|
||||||
|
"actix-macros",
|
||||||
|
"actix-rt",
|
||||||
|
"actix_derive",
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"bytes",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"smallvec",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-codec"
|
name = "actix-codec"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@ -228,6 +253,24 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-web-actors"
|
||||||
|
version = "4.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "420b001bb709d8510c3e2659dae046e54509ff9528018d09c78381e765a1f9fa"
|
||||||
|
dependencies = [
|
||||||
|
"actix",
|
||||||
|
"actix-codec",
|
||||||
|
"actix-http",
|
||||||
|
"actix-web",
|
||||||
|
"bytes",
|
||||||
|
"bytestring",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-web-codegen"
|
name = "actix-web-codegen"
|
||||||
version = "4.2.2"
|
version = "4.2.2"
|
||||||
@ -255,6 +298,17 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix_derive"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
@ -425,12 +479,6 @@ dependencies = [
|
|||||||
"alloc-stdlib",
|
"alloc-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -546,6 +594,21 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@ -562,12 +625,6 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "data-encoding"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@ -664,12 +721,65 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
@ -688,9 +798,13 @@ version = "0.3.30"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
@ -1290,13 +1404,16 @@ dependencies = [
|
|||||||
name = "roadsign"
|
name = "roadsign"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"actix",
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-proxy",
|
"actix-proxy",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"actix-web-actors",
|
||||||
"actix-web-httpauth",
|
"actix-web-httpauth",
|
||||||
"awc",
|
"awc",
|
||||||
"config",
|
"config",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"mime",
|
"mime",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
@ -1308,7 +1425,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@ -1698,18 +1814,6 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-tungstenite"
|
|
||||||
version = "0.21.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
|
|
||||||
dependencies = [
|
|
||||||
"futures-util",
|
|
||||||
"log",
|
|
||||||
"tokio",
|
|
||||||
"tungstenite",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
@ -1816,25 +1920,6 @@ dependencies = [
|
|||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tungstenite"
|
|
||||||
version = "0.21.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"bytes",
|
|
||||||
"data-encoding",
|
|
||||||
"http 1.0.0",
|
|
||||||
"httparse",
|
|
||||||
"log",
|
|
||||||
"rand",
|
|
||||||
"sha1",
|
|
||||||
"thiserror",
|
|
||||||
"url",
|
|
||||||
"utf-8",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
@ -1900,12 +1985,6 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf-8"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "v_htmlescape"
|
name = "v_htmlescape"
|
||||||
version = "0.15.8"
|
version = "0.15.8"
|
||||||
|
@ -26,7 +26,6 @@ tokio = { version = "1.35.1", features = [
|
|||||||
"time",
|
"time",
|
||||||
"full",
|
"full",
|
||||||
] }
|
] }
|
||||||
tokio-tungstenite = "0.21.0"
|
|
||||||
toml = "0.8.8"
|
toml = "0.8.8"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
@ -34,3 +33,6 @@ wildmatch = "2.3.0"
|
|||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
rustls = "0.22.2"
|
rustls = "0.22.2"
|
||||||
rustls-pemfile = "2.0.0"
|
rustls-pemfile = "2.0.0"
|
||||||
|
futures = "0.3.30"
|
||||||
|
actix-web-actors = "4.3.0"
|
||||||
|
actix = "0.13.3"
|
||||||
|
10
README.md
10
README.md
@ -74,9 +74,9 @@ rds cli with this command.
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
rds connect <id> <url> <password>
|
rds connect <id> <url> <password>
|
||||||
# ID will allow you find this server.rs in after commands.
|
# ID will allow you find this server.py.rs in after commands.
|
||||||
# URL is to your roadsign server.rs sideload api.
|
# URL is to your roadsign server.py.rs sideload api.
|
||||||
# Password is your roadsign server.rs credential.
|
# Password is your roadsign server.py.rs credential.
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
# !WARNING! All these things will storage in your $HOME/.roadsignrc.yaml
|
# !WARNING! All these things will storage in your $HOME/.roadsignrc.yaml
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
@ -85,8 +85,8 @@ rds connect <id> <url> <password>
|
|||||||
Then, sync your local config to remote.
|
Then, sync your local config to remote.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
rds sync <server.rs id> <site id> <config file>
|
rds sync <server.py.rs id> <site id> <config file>
|
||||||
# Server ID is your server.rs added by last command.
|
# Server ID is your server.py.rs added by last command.
|
||||||
# Site ID is your new site id or old site id if you need update it.
|
# Site ID is your new site id or old site id if you need update it.
|
||||||
# Config File is your local config file path.
|
# Config File is your local config file path.
|
||||||
```
|
```
|
||||||
|
@ -5,8 +5,11 @@ id = "root"
|
|||||||
hosts = ["localhost"]
|
hosts = ["localhost"]
|
||||||
paths = ["/"]
|
paths = ["/"]
|
||||||
[[locations.destinations]]
|
[[locations.destinations]]
|
||||||
id = "hypertext"
|
id = "websocket"
|
||||||
uri = "https://postman-echo.com/get"
|
uri = "http://localhost:8765"
|
||||||
|
# [[locations.destinations]]
|
||||||
|
# id = "hypertext"
|
||||||
|
# uri = "https://example.com"
|
||||||
# [[locations.destinations]]
|
# [[locations.destinations]]
|
||||||
# id = "static"
|
# id = "static"
|
||||||
# uri = "files://regions?index=index.html"
|
# uri = "files://regions?index=index.html"
|
||||||
@ -15,4 +18,4 @@ uri = "https://postman-echo.com/get"
|
|||||||
# [[applications]]
|
# [[applications]]
|
||||||
# id = "script"
|
# id = "script"
|
||||||
# exe = "./script.sh"
|
# exe = "./script.sh"
|
||||||
# workdir = "regions"
|
# workdir = "regions"
|
||||||
|
@ -21,6 +21,9 @@ pub mod server;
|
|||||||
|
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
pub enum ProxyError {
|
pub enum ProxyError {
|
||||||
|
#[display(fmt = "Upgrade required for this connection")]
|
||||||
|
UpgradeRequired,
|
||||||
|
|
||||||
#[display(fmt = "Remote gateway issue")]
|
#[display(fmt = "Remote gateway issue")]
|
||||||
BadGateway,
|
BadGateway,
|
||||||
|
|
||||||
@ -43,6 +46,7 @@ pub enum ProxyError {
|
|||||||
impl error::ResponseError for ProxyError {
|
impl error::ResponseError for ProxyError {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match *self {
|
match *self {
|
||||||
|
ProxyError::UpgradeRequired => StatusCode::UPGRADE_REQUIRED,
|
||||||
ProxyError::BadGateway => StatusCode::BAD_GATEWAY,
|
ProxyError::BadGateway => StatusCode::BAD_GATEWAY,
|
||||||
ProxyError::NoGateway => StatusCode::NOT_FOUND,
|
ProxyError::NoGateway => StatusCode::NOT_FOUND,
|
||||||
ProxyError::NotFound => StatusCode::NOT_FOUND,
|
ProxyError::NotFound => StatusCode::NOT_FOUND,
|
||||||
|
@ -1,55 +1,206 @@
|
|||||||
|
use crate::proxies::ProxyError;
|
||||||
|
use crate::proxies::ProxyError::{BadGateway, UpgradeRequired};
|
||||||
|
use actix_files::NamedFile;
|
||||||
|
use actix_web::http::{header, Method};
|
||||||
|
use actix_web::web::BytesMut;
|
||||||
|
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
||||||
|
use awc::error::HeaderValue;
|
||||||
|
use awc::http::Uri;
|
||||||
|
use awc::Client;
|
||||||
|
use futures::{channel::mpsc::unbounded, Sink, sink::SinkExt, stream::StreamExt};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use actix_files::{NamedFile};
|
use actix::io::{SinkWrite, WriteHandler};
|
||||||
use actix_proxy::IntoHttpResponse;
|
use actix::{Actor, ActorContext, AsyncContext, StreamHandler};
|
||||||
use actix_web::{HttpRequest, HttpResponse, web};
|
use actix_web_actors::ws;
|
||||||
use actix_web::http::{header, Method};
|
use actix_web_actors::ws::{CloseReason, handshake, ProtocolError, WebsocketContext};
|
||||||
use actix_web::http::header::HeaderValue;
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use awc::Client;
|
|
||||||
use tracing::log::warn;
|
use tracing::log::warn;
|
||||||
use crate::proxies::ProxyError;
|
|
||||||
|
|
||||||
pub async fn respond_hypertext(
|
pub async fn respond_hypertext(
|
||||||
uri: String,
|
uri: String,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
|
payload: web::Payload,
|
||||||
client: web::Data<Client>,
|
client: web::Data<Client>,
|
||||||
) -> Result<HttpResponse, ProxyError> {
|
) -> Result<HttpResponse, ProxyError> {
|
||||||
let conn = req.connection_info();
|
let mut append_part = req.uri().to_string();
|
||||||
let ip = conn.realip_remote_addr().unwrap_or("0.0.0.0");
|
if let Some(stripped_uri) = append_part.strip_prefix('/') {
|
||||||
let proto = conn.scheme();
|
append_part = stripped_uri.to_string();
|
||||||
let host = conn.host();
|
}
|
||||||
|
|
||||||
let mut headers = req.headers().clone();
|
let uri = Uri::from_str(uri.as_str()).expect("Invalid upstream");
|
||||||
headers.insert(header::X_FORWARDED_FOR, ip.parse().unwrap());
|
|
||||||
headers.insert(header::X_FORWARDED_PROTO, proto.parse().unwrap());
|
|
||||||
headers.insert(header::X_FORWARDED_HOST, host.parse().unwrap());
|
|
||||||
headers.insert(
|
|
||||||
header::FORWARDED,
|
|
||||||
format!("by={};for={};host={};proto={}", ip, ip, host, proto)
|
|
||||||
.parse()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let append_part = req.uri().to_string().chars().skip(1).collect::<String>();
|
|
||||||
let target_url = format!("{}{}", uri, append_part);
|
let target_url = format!("{}{}", uri, append_part);
|
||||||
|
|
||||||
let res = client.request(req.method().clone(), target_url).send().await;
|
let forwarded_req = client
|
||||||
|
.request_from(target_url.as_str(), req.head())
|
||||||
|
.insert_header((header::HOST, uri.host().expect("Invalid upstream")));
|
||||||
|
|
||||||
return match res {
|
let forwarded_req = match req.connection_info().realip_remote_addr() {
|
||||||
Ok(result) => {
|
Some(addr) => forwarded_req
|
||||||
let mut res = result.into_http_response();
|
.insert_header((header::X_FORWARDED_FOR, addr))
|
||||||
res.headers_mut().insert(header::SERVER, HeaderValue::from_static("RoadSign"));
|
.insert_header((header::X_FORWARDED_PROTO, req.connection_info().scheme()))
|
||||||
res.headers_mut().remove(header::CONTENT_ENCODING);
|
.insert_header((header::X_FORWARDED_HOST, req.connection_info().host()))
|
||||||
Ok(res)
|
.insert_header((
|
||||||
}
|
header::FORWARDED,
|
||||||
|
format!(
|
||||||
Err(error) => {
|
"by={};for={};host={};proto={}",
|
||||||
warn!("Proxy got a upstream issue... {:?}", error);
|
addr,
|
||||||
Err(ProxyError::BadGateway)
|
addr,
|
||||||
}
|
req.connection_info().host(),
|
||||||
|
req.connection_info().scheme()
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
None => forwarded_req,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if req
|
||||||
|
.headers()
|
||||||
|
.get(header::UPGRADE)
|
||||||
|
.unwrap_or(&HeaderValue::from_static(""))
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_lowercase()
|
||||||
|
== "websocket"
|
||||||
|
{
|
||||||
|
let uri = uri.to_string().replacen("http", "ws", 1);
|
||||||
|
return respond_websocket(uri, req, payload).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = forwarded_req
|
||||||
|
.timeout(Duration::from_secs(1800))
|
||||||
|
.send_stream(payload)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!("Remote gateway issue... {}", err);
|
||||||
|
BadGateway
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut client_resp = HttpResponse::build(res.status());
|
||||||
|
for (header_name, header_value) in res
|
||||||
|
.headers()
|
||||||
|
.iter()
|
||||||
|
.filter(|(h, _)| *h != header::CONNECTION && *h != header::CONTENT_ENCODING)
|
||||||
|
{
|
||||||
|
client_resp.insert_header((header_name.clone(), header_value.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(client_resp.streaming(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WebsocketProxy<S>
|
||||||
|
where
|
||||||
|
S: Unpin + Sink<ws::Message>,
|
||||||
|
{
|
||||||
|
send: SinkWrite<ws::Message, S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> WriteHandler<ProtocolError> for WebsocketProxy<S>
|
||||||
|
where
|
||||||
|
S: Unpin + 'static + Sink<ws::Message>,
|
||||||
|
{
|
||||||
|
fn error(&mut self, err: ProtocolError, ctx: &mut Self::Context) -> actix::Running {
|
||||||
|
self.error(err, ctx);
|
||||||
|
actix::Running::Stop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Actor for WebsocketProxy<S>
|
||||||
|
where
|
||||||
|
S: Unpin + 'static + Sink<ws::Message>,
|
||||||
|
{
|
||||||
|
type Context = WebsocketContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> StreamHandler<Result<ws::Frame, ProtocolError>> for WebsocketProxy<S>
|
||||||
|
where
|
||||||
|
S: Unpin + Sink<ws::Message> + 'static,
|
||||||
|
{
|
||||||
|
fn handle(&mut self, item: Result<ws::Frame, ProtocolError>, ctx: &mut Self::Context) {
|
||||||
|
let frame = match item {
|
||||||
|
Ok(frame) => frame,
|
||||||
|
Err(err) => return self.error(err, ctx),
|
||||||
|
};
|
||||||
|
let msg = match frame {
|
||||||
|
ws::Frame::Text(t) => match t.try_into() {
|
||||||
|
Ok(t) => ws::Message::Text(t),
|
||||||
|
Err(e) => {
|
||||||
|
self.error(e, ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ws::Frame::Binary(b) => ws::Message::Binary(b),
|
||||||
|
ws::Frame::Continuation(c) => ws::Message::Continuation(c),
|
||||||
|
ws::Frame::Ping(p) => ws::Message::Ping(p),
|
||||||
|
ws::Frame::Pong(p) => ws::Message::Pong(p),
|
||||||
|
ws::Frame::Close(r) => ws::Message::Close(r),
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.write_raw(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> StreamHandler<Result<ws::Message, ProtocolError>> for WebsocketProxy<S>
|
||||||
|
where
|
||||||
|
S: Unpin + Sink<ws::Message> + 'static,
|
||||||
|
{
|
||||||
|
fn handle(&mut self, item: Result<ws::Message, ProtocolError>, ctx: &mut Self::Context) {
|
||||||
|
let msg = match item {
|
||||||
|
Ok(msg) => msg,
|
||||||
|
Err(err) => return self.error(err, ctx),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = self.send.write(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> WebsocketProxy<S>
|
||||||
|
where
|
||||||
|
S: Unpin + Sink<ws::Message> + 'static,
|
||||||
|
{
|
||||||
|
fn error<E>(&mut self, err: E, ctx: &mut <Self as Actor>::Context)
|
||||||
|
where
|
||||||
|
E: std::error::Error,
|
||||||
|
{
|
||||||
|
let reason = Some(CloseReason {
|
||||||
|
code: ws::CloseCode::Error,
|
||||||
|
description: Some(err.to_string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.close(reason.clone());
|
||||||
|
let _ = self.send.write(ws::Message::Close(reason));
|
||||||
|
self.send.close();
|
||||||
|
|
||||||
|
ctx.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn respond_websocket(
|
||||||
|
uri: String,
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: web::Payload,
|
||||||
|
) -> Result<HttpResponse, ProxyError> {
|
||||||
|
let mut res = handshake(&req).map_err(|_| UpgradeRequired)?;
|
||||||
|
|
||||||
|
let (_, conn) = awc::Client::new()
|
||||||
|
.ws(uri)
|
||||||
|
.connect()
|
||||||
|
.await
|
||||||
|
.map_err(|_| BadGateway)?;
|
||||||
|
|
||||||
|
let (send, recv) = conn.split();
|
||||||
|
|
||||||
|
let out = WebsocketContext::with_factory(payload, |ctx| {
|
||||||
|
ctx.add_stream(recv);
|
||||||
|
WebsocketProxy {
|
||||||
|
send: SinkWrite::new(send, ctx),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res.streaming(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StaticResponderConfig {
|
pub struct StaticResponderConfig {
|
||||||
@ -123,7 +274,7 @@ pub async fn respond_static(
|
|||||||
return Err(ProxyError::NotFound);
|
return Err(ProxyError::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
return if file_path.is_file() {
|
if file_path.is_file() {
|
||||||
Ok(NamedFile::open(file_path).unwrap().into_response(&req))
|
Ok(NamedFile::open(file_path).unwrap().into_response(&req))
|
||||||
} else {
|
} else {
|
||||||
if let Some(index_file) = &cfg.index {
|
if let Some(index_file) = &cfg.index {
|
||||||
@ -133,6 +284,6 @@ pub async fn respond_static(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(ProxyError::NotFound);
|
Err(ProxyError::NotFound)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use crate::proxies::ProxyError;
|
use crate::proxies::ProxyError;
|
||||||
|
|
||||||
pub async fn handle(req: HttpRequest, client: web::Data<Client>) -> HttpResponse {
|
pub async fn handle(req: HttpRequest, payload: web::Payload, client: web::Data<Client>) -> HttpResponse {
|
||||||
let readable_app = ROAD.lock().await;
|
let readable_app = ROAD.lock().await;
|
||||||
let (region, location) = match readable_app.filter(req.uri(), req.method(), req.headers()) {
|
let (region, location) = match readable_app.filter(req.uri(), req.method(), req.headers()) {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
@ -29,6 +29,7 @@ pub async fn handle(req: HttpRequest, client: web::Data<Client>) -> HttpResponse
|
|||||||
async fn forward(
|
async fn forward(
|
||||||
end: &Destination,
|
end: &Destination,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
|
payload: web::Payload,
|
||||||
client: web::Data<Client>,
|
client: web::Data<Client>,
|
||||||
) -> Result<HttpResponse, ProxyError> {
|
) -> Result<HttpResponse, ProxyError> {
|
||||||
// Handle normal web request
|
// Handle normal web request
|
||||||
@ -38,7 +39,7 @@ pub async fn handle(req: HttpRequest, client: web::Data<Client>) -> HttpResponse
|
|||||||
return Err(ProxyError::NotImplemented);
|
return Err(ProxyError::NotImplemented);
|
||||||
};
|
};
|
||||||
|
|
||||||
responder::respond_hypertext(uri, req, client).await
|
responder::respond_hypertext(uri, req, payload, client).await
|
||||||
}
|
}
|
||||||
DestinationType::StaticFiles => {
|
DestinationType::StaticFiles => {
|
||||||
let Ok(cfg) = end.get_static_config() else {
|
let Ok(cfg) = end.get_static_config() else {
|
||||||
@ -64,7 +65,7 @@ pub async fn handle(req: HttpRequest, client: web::Data<Client>) -> HttpResponse
|
|||||||
Some(val) => val.to_str().unwrap().to_string(),
|
Some(val) => val.to_str().unwrap().to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match forward(&end, req, client).await {
|
match forward(&end, req, payload, client).await {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let writable_app = &mut ROAD.lock().await;
|
let writable_app = &mut ROAD.lock().await;
|
||||||
|
@ -10,7 +10,7 @@ const ClientAddressNotAvailable = {
|
|||||||
const StaticClientAddressNotAvailable = {
|
const StaticClientAddressNotAvailable = {
|
||||||
name: "StaticClientAddressNotAvailable",
|
name: "StaticClientAddressNotAvailable",
|
||||||
title: "`Astro.clientAddress` is not available in static mode.",
|
title: "`Astro.clientAddress` is not available in static mode.",
|
||||||
message: "`Astro.clientAddress` is only available when using `output: 'server.rs'` or `output: 'hybrid'`. Update your Astro config if you need SSR features.",
|
message: "`Astro.clientAddress` is only available when using `output: 'server.py.rs'` or `output: 'hybrid'`. Update your Astro config if you need SSR features.",
|
||||||
hint: "See https://docs.astro.build/en/guides/server-side-rendering/ for more information on how to enable SSR."
|
hint: "See https://docs.astro.build/en/guides/server-side-rendering/ for more information on how to enable SSR."
|
||||||
};
|
};
|
||||||
const NoMatchingStaticPathFound = {
|
const NoMatchingStaticPathFound = {
|
||||||
@ -212,7 +212,7 @@ const CantRenderPage = {
|
|||||||
name: "CantRenderPage",
|
name: "CantRenderPage",
|
||||||
title: "Astro can't render the route.",
|
title: "Astro can't render the route.",
|
||||||
message: "Astro cannot find any content to render for this route. There is no file or redirect associated with this route.",
|
message: "Astro cannot find any content to render for this route. There is no file or redirect associated with this route.",
|
||||||
hint: "If you expect to find a route here, this may be an Astro bug. Please file an issue/restart the dev server.rs"
|
hint: "If you expect to find a route here, this may be an Astro bug. Please file an issue/restart the dev server.py.rs"
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizeLF(code) {
|
function normalizeLF(code) {
|
||||||
@ -524,7 +524,7 @@ function extractDirectives(inputProps, clientDirectives) {
|
|||||||
propsWithoutTransitionAttributes: {}
|
propsWithoutTransitionAttributes: {}
|
||||||
};
|
};
|
||||||
for (const [key, value] of Object.entries(inputProps)) {
|
for (const [key, value] of Object.entries(inputProps)) {
|
||||||
if (key.startsWith("server.rs:")) {
|
if (key.startsWith("server.py.rs:")) {
|
||||||
if (key === "server:root") {
|
if (key === "server:root") {
|
||||||
extracted.isPage = true;
|
extracted.isPage = true;
|
||||||
}
|
}
|
||||||
|
12
test/data/warden/dist/server/entry.mjs
vendored
12
test/data/warden/dist/server/entry.mjs
vendored
@ -10,7 +10,7 @@ import buffer from 'node:buffer';
|
|||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import https$1 from 'https';
|
import https$1 from 'https';
|
||||||
import enableDestroy from 'server.rs-destroy';
|
import enableDestroy from 'server.py.rs-destroy';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import url from 'node:url';
|
import url from 'node:url';
|
||||||
import send from 'send';
|
import send from 'send';
|
||||||
@ -1972,7 +1972,7 @@ class NodeApp extends App {
|
|||||||
* import { NodeApp } from 'astro/app/node';
|
* import { NodeApp } from 'astro/app/node';
|
||||||
* import { createServer } from 'node:http';
|
* import { createServer } from 'node:http';
|
||||||
*
|
*
|
||||||
* const server.rs = createServer(async (req, res) => {
|
* const server.py.rs = createServer(async (req, res) => {
|
||||||
* const request = NodeApp.createRequest(req);
|
* const request = NodeApp.createRequest(req);
|
||||||
* const response = await app.render(request);
|
* const response = await app.render(request);
|
||||||
* await NodeApp.writeResponse(response, res);
|
* await NodeApp.writeResponse(response, res);
|
||||||
@ -2003,7 +2003,7 @@ class NodeApp extends App {
|
|||||||
* import { NodeApp } from 'astro/app/node';
|
* import { NodeApp } from 'astro/app/node';
|
||||||
* import { createServer } from 'node:http';
|
* import { createServer } from 'node:http';
|
||||||
*
|
*
|
||||||
* const server.rs = createServer(async (req, res) => {
|
* const server.py.rs = createServer(async (req, res) => {
|
||||||
* const request = NodeApp.createRequest(req);
|
* const request = NodeApp.createRequest(req);
|
||||||
* const response = await app.render(request);
|
* const response = await app.render(request);
|
||||||
* await NodeApp.writeResponse(response, res);
|
* await NodeApp.writeResponse(response, res);
|
||||||
@ -2032,7 +2032,7 @@ class NodeApp extends App {
|
|||||||
result = await reader.read();
|
result = await reader.read();
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
destination.write("Internal server.rs error");
|
destination.write("Internal server.py.rs error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
destination.end();
|
destination.end();
|
||||||
@ -2116,7 +2116,7 @@ function createStaticHandler(app, options) {
|
|||||||
if (forwardError) {
|
if (forwardError) {
|
||||||
console.error(err.toString());
|
console.error(err.toString());
|
||||||
res.writeHead(500);
|
res.writeHead(500);
|
||||||
res.end("Internal server.rs error");
|
res.end("Internal server.py.rs error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ssr();
|
ssr();
|
||||||
@ -2340,7 +2340,7 @@ const _manifest = Object.assign(manifest, {
|
|||||||
pageMap,
|
pageMap,
|
||||||
renderers,
|
renderers,
|
||||||
});
|
});
|
||||||
const _args = {"mode":"standalone","client":"file:///Users/littlesheep/Documents/Projects/Capital/dist/client/","server":"file:///Users/littlesheep/Documents/Projects/Capital/dist/server.rs/","host":false,"port":4321,"assets":"_astro"};
|
const _args = {"mode":"standalone","client":"file:///Users/littlesheep/Documents/Projects/Capital/dist/client/","server":"file:///Users/littlesheep/Documents/Projects/Capital/dist/server.py.rs/","host":false,"port":4321,"assets":"_astro"};
|
||||||
|
|
||||||
const _exports = createExports(_manifest, _args);
|
const _exports = createExports(_manifest, _args);
|
||||||
const handler = _exports['handler'];
|
const handler = _exports['handler'];
|
||||||
|
4
test/data/warden/dist/server/renderers.mjs
vendored
4
test/data/warden/dist/server/renderers.mjs
vendored
@ -1,5 +1,5 @@
|
|||||||
import React, { createElement } from 'react';
|
import React, { createElement } from 'react';
|
||||||
import ReactDOM from 'react-dom/server.rs';
|
import ReactDOM from 'react-dom/server.py.rs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Astro passes `children` as a string of HTML, so we need
|
* Astro passes `children` as a string of HTML, so we need
|
||||||
@ -258,6 +258,6 @@ const _renderer0 = {
|
|||||||
supportsAstroStaticSlot: true,
|
supportsAstroStaticSlot: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderers = [Object.assign({"name":"@astrojs/react","clientEntrypoint":"@astrojs/react/client.js","serverEntrypoint":"@astrojs/react/server.rs.js"}, { ssr: _renderer0 }),];
|
const renderers = [Object.assign({"name":"@astrojs/react","clientEntrypoint":"@astrojs/react/client.js","serverEntrypoint":"@astrojs/react/server.py.rs.js"}, { ssr: _renderer0 }),];
|
||||||
|
|
||||||
export { renderers };
|
export { renderers };
|
||||||
|
29
test/websocket/server.py
Normal file
29
test/websocket/server.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import asyncio
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
async def handle_websocket(websocket, path):
|
||||||
|
# This function will be called whenever a new WebSocket connection is established
|
||||||
|
|
||||||
|
# Send a welcome message to the client
|
||||||
|
await websocket.send("Welcome to the WebSocket server!")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Enter the main loop to handle incoming messages
|
||||||
|
async for message in websocket:
|
||||||
|
# Print the received message
|
||||||
|
print(f"Received message: {message}")
|
||||||
|
|
||||||
|
# Send a response back to the client
|
||||||
|
response = f"Server received: {message}"
|
||||||
|
await websocket.send(response)
|
||||||
|
except websockets.exceptions.ConnectionClosedError:
|
||||||
|
print("Connection closed by the client.")
|
||||||
|
|
||||||
|
# Create the WebSocket server
|
||||||
|
start_server = websockets.serve(handle_websocket, "localhost", 8765)
|
||||||
|
|
||||||
|
print("WebSocket server started at ws://localhost:8765")
|
||||||
|
|
||||||
|
# Run the server indefinitely
|
||||||
|
asyncio.get_event_loop().run_until_complete(start_server)
|
||||||
|
asyncio.get_event_loop().run_forever()
|
Loading…
Reference in New Issue
Block a user