itsi-server 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +22 -0
- data/exe/itsi +84 -0
- data/ext/itsi_error/Cargo.lock +368 -0
- data/ext/itsi_error/Cargo.toml +9 -0
- data/ext/itsi_error/src/lib.rs +49 -0
- data/ext/itsi_rb_helpers/Cargo.lock +355 -0
- data/ext/itsi_rb_helpers/Cargo.toml +8 -0
- data/ext/itsi_rb_helpers/src/lib.rs +98 -0
- data/ext/itsi_server/Cargo.toml +29 -0
- data/ext/itsi_server/extconf.rb +6 -0
- data/ext/itsi_server/src/lib.rs +52 -0
- data/ext/itsi_server/src/request/itsi_request.rs +143 -0
- data/ext/itsi_server/src/request/mod.rs +1 -0
- data/ext/itsi_server/src/server/bind.rs +138 -0
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +32 -0
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +52 -0
- data/ext/itsi_server/src/server/itsi_server.rs +182 -0
- data/ext/itsi_server/src/server/listener.rs +218 -0
- data/ext/itsi_server/src/server/mod.rs +5 -0
- data/ext/itsi_server/src/server/tls.rs +138 -0
- data/ext/itsi_server/src/server/transfer_protocol.rs +23 -0
- data/ext/itsi_server/src/stream_writer/mod.rs +21 -0
- data/ext/itsi_tracing/Cargo.lock +274 -0
- data/ext/itsi_tracing/Cargo.toml +12 -0
- data/ext/itsi_tracing/src/lib.rs +11 -0
- data/lib/itsi/request.rb +39 -0
- data/lib/itsi/server/version.rb +7 -0
- data/lib/itsi/server.rb +21 -0
- data/sig/itsi_server.rbs +4 -0
- metadata +121 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
use super::{tls::configure_tls, transfer_protocol::TransferProtocol};
|
2
|
+
use itsi_error::ItsiError;
|
3
|
+
use std::{
|
4
|
+
collections::HashMap,
|
5
|
+
net::{IpAddr, Ipv4Addr, Ipv6Addr, ToSocketAddrs},
|
6
|
+
path::PathBuf,
|
7
|
+
str::FromStr,
|
8
|
+
};
|
9
|
+
use tokio_rustls::rustls::ServerConfig;
|
10
|
+
|
11
|
+
// Support binding to either IP or Unix Socket
|
12
|
+
#[derive(Debug, Clone)]
|
13
|
+
pub enum BindAddress {
|
14
|
+
Ip(IpAddr),
|
15
|
+
UnixSocket(PathBuf),
|
16
|
+
}
|
17
|
+
|
18
|
+
impl Default for BindAddress {
|
19
|
+
fn default() -> Self {
|
20
|
+
BindAddress::Ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
#[derive(Debug, Default, Clone)]
|
25
|
+
#[magnus::wrap(class = "Itsi::Bind")]
|
26
|
+
pub struct Bind {
|
27
|
+
pub address: BindAddress,
|
28
|
+
pub port: Option<u16>, // None for Unix Sockets
|
29
|
+
pub protocol: TransferProtocol,
|
30
|
+
pub tls_config: Option<ServerConfig>,
|
31
|
+
}
|
32
|
+
|
33
|
+
impl FromStr for Bind {
|
34
|
+
type Err = ItsiError;
|
35
|
+
|
36
|
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
37
|
+
let (protocol, remainder) = if let Some((proto, rest)) = s.split_once("://") {
|
38
|
+
(proto.parse::<TransferProtocol>()?, rest)
|
39
|
+
} else {
|
40
|
+
(TransferProtocol::Https, s)
|
41
|
+
};
|
42
|
+
|
43
|
+
let (url, options) = if let Some((base, options)) = remainder.split_once('?') {
|
44
|
+
(base, parse_bind_options(options))
|
45
|
+
} else {
|
46
|
+
(remainder, HashMap::new())
|
47
|
+
};
|
48
|
+
|
49
|
+
let (host, port) = if url.starts_with('[') {
|
50
|
+
// IPv6 with brackets `[::]:port`
|
51
|
+
if let Some(end) = url.find(']') {
|
52
|
+
let host = &url[1..end]; // Extract `::`
|
53
|
+
let port = url[end + 1..]
|
54
|
+
.strip_prefix(':')
|
55
|
+
.and_then(|p| p.parse().ok());
|
56
|
+
(host, port)
|
57
|
+
} else {
|
58
|
+
return Err(ItsiError::InvalidInput(
|
59
|
+
"Invalid IPv6 address format".to_owned(),
|
60
|
+
));
|
61
|
+
}
|
62
|
+
} else if let Some((h, p)) = url.rsplit_once(':') {
|
63
|
+
// Check if `h` is an IPv6 address before assuming it's a port
|
64
|
+
if h.contains('.') || h.parse::<Ipv4Addr>().is_ok() {
|
65
|
+
(h, p.parse::<u16>().ok()) // IPv4 case
|
66
|
+
} else if h.parse::<Ipv6Addr>().is_ok() {
|
67
|
+
// If it's IPv6, require brackets for port
|
68
|
+
return Err(ItsiError::InvalidInput(
|
69
|
+
"IPv6 addresses must use [ ] when specifying a port".to_owned(),
|
70
|
+
));
|
71
|
+
} else {
|
72
|
+
(h, None) // Treat as a hostname
|
73
|
+
}
|
74
|
+
} else {
|
75
|
+
(url, None)
|
76
|
+
};
|
77
|
+
|
78
|
+
let address = if let Ok(ip) = host.parse::<IpAddr>() {
|
79
|
+
BindAddress::Ip(ip)
|
80
|
+
} else {
|
81
|
+
resolve_hostname(host)
|
82
|
+
.map(BindAddress::Ip)
|
83
|
+
.unwrap_or(BindAddress::Ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED)))
|
84
|
+
};
|
85
|
+
let (port, address) = match protocol {
|
86
|
+
TransferProtocol::Http => (port.or(Some(80)), address),
|
87
|
+
TransferProtocol::Https => (port.or(Some(443)), address),
|
88
|
+
TransferProtocol::Unix => (None, BindAddress::UnixSocket(host.into())),
|
89
|
+
};
|
90
|
+
|
91
|
+
let tls_config = if let TransferProtocol::Http = protocol {
|
92
|
+
None
|
93
|
+
} else if let TransferProtocol::Https = protocol {
|
94
|
+
Some(configure_tls(host, &options)?)
|
95
|
+
} else if options.contains_key("cert") {
|
96
|
+
Some(configure_tls(host, &options)?)
|
97
|
+
} else {
|
98
|
+
None
|
99
|
+
};
|
100
|
+
|
101
|
+
Ok(Self {
|
102
|
+
address,
|
103
|
+
port,
|
104
|
+
protocol,
|
105
|
+
tls_config,
|
106
|
+
})
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
fn parse_bind_options(query: &str) -> HashMap<String, String> {
|
111
|
+
query
|
112
|
+
.split('&')
|
113
|
+
.filter_map(|pair| pair.split_once('='))
|
114
|
+
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
115
|
+
.collect()
|
116
|
+
}
|
117
|
+
|
118
|
+
/// Attempts to resolve a hostname into an IP address.
|
119
|
+
fn resolve_hostname(hostname: &str) -> Option<IpAddr> {
|
120
|
+
(hostname, 0)
|
121
|
+
.to_socket_addrs()
|
122
|
+
.ok()?
|
123
|
+
.filter_map(|addr| {
|
124
|
+
if addr.is_ipv6() {
|
125
|
+
Some(addr.ip()) // Prefer IPv6
|
126
|
+
} else {
|
127
|
+
None
|
128
|
+
}
|
129
|
+
})
|
130
|
+
.next()
|
131
|
+
.or_else(|| {
|
132
|
+
(hostname, 0)
|
133
|
+
.to_socket_addrs()
|
134
|
+
.ok()?
|
135
|
+
.map(|addr| addr.ip())
|
136
|
+
.next()
|
137
|
+
}) // Fallback to IPv4
|
138
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIFgTCCA2mgAwIBAgIUe316+G3qdkcWtRxYKlNBEAEenzAwDQYJKoZIhvcNAQEL
|
3
|
+
BQAwUDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBEl0c2kxDTALBgNVBAcMBEl0c2kx
|
4
|
+
EDAOBgNVBAoMB0l0c2kgQ0ExETAPBgNVBAMMCGl0c2kuZnlpMB4XDTI1MDIyODAy
|
5
|
+
NTkyNVoXDTM1MDIyNjAyNTkyNVowUDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBEl0
|
6
|
+
c2kxDTALBgNVBAcMBEl0c2kxEDAOBgNVBAoMB0l0c2kgQ0ExETAPBgNVBAMMCGl0
|
7
|
+
c2kuZnlpMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzKAK2RIvnATa
|
8
|
+
pOxNYubaGjOx8z4thxwWM/KxMqI8gABmgRFTa99n1vrTenlfdX4PcoRyCXEJ3krh
|
9
|
+
nPsRmP1nnUS1BEIX4Ha9xVPIy0UoP0JN1a4kVInejZe2ZTcpRv8k2KlkafIw0ISt
|
10
|
+
/hSoopNV2SESCMDxx2e3F9d4wzHmkfs2+6k+12TTYkkifXaXgSOXm91a5ABqTfa8
|
11
|
+
wS6td/zmwv6W5RNBU3kS3TsqoKjc0xdu25aifJ40i/+82b5OOWhWt+psSPlTZb+Y
|
12
|
+
5elMhlh8Hjs52S3u48wz5joPsBi9r4yLYGhgB+AiW+b+y0uss7EiTQR24U+CroEB
|
13
|
+
4c1LA4qTgQPUvUIXyyFGC3lPsjKHGiEitbIT4sDQOz3nMMvT0FIP7DPtiSAuyx0I
|
14
|
+
J6/3+/QQ/8dzGqZskolKTSWGToOysNWcIxbbBprDAXYseOTJvREVpwC/Qra5OUqU
|
15
|
+
6P8K72yp2hSCu/5EQwV3kwKMw0JmjJFyaL8SC2GJnueWWCIIZjYdc0ZAA9Dvl9eo
|
16
|
+
SfQA4emLjUcScFpj8kv3Iu5tGJxnO1gqJ4JV+NKJ/09AxXvFYpNhaBXJ0xUOTA8/
|
17
|
+
vBTkAs9hTFV1IFIgJNP5CxEbkWr5FcFYUKAopFhDKfQkXGCKEn4s3wuNqUuzEEdE
|
18
|
+
Sx2YVV8XKbb9eKpHN5cQ6ljeFvanKpMCAwEAAaNTMFEwHQYDVR0OBBYEFIaKuU5Z
|
19
|
+
CCv9JAsdqvHyR5QnE1OdMB8GA1UdIwQYMBaAFIaKuU5ZCCv9JAsdqvHyR5QnE1Od
|
20
|
+
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAGslKyiOzrJzfVEA
|
21
|
+
HwJu/4CpDs7MpZuzAJ/AS0shWR3qlpHJUn6c/NIaQBfmVIo+vfxTvwV+ih6tZ+4A
|
22
|
+
HmIZrCQm7rn5uQm2Q9G++JQ0RgQfLngnz3FBL+aERW2wxw1vOcPwg8JWCeGUSCd4
|
23
|
+
Lj4jtpvlniS5uIiCD2GYxRIub401egX60XAiHdR0k6rS2dKxab9vMKW28RvRddak
|
24
|
+
YCk7UKSXOZlj39XVH/JCB8+4IokDLTikpoAUqiNfytO+kx9+1JHKti8NjSQJwjqO
|
25
|
+
JtBd1OD2ziQqd0gVKjr3J4IwIBv3Yl3GGUi3c5HVyDejriPciPrK+4G6a4IDGiyG
|
26
|
+
rQRTzTR98ILsPX5uvOJadF1TAyZpS8oN7xFM8BnKfdpB34x3p9KgBqk34dRIXxbF
|
27
|
+
nM3AJRT5dHlcHkn6z1snFqRHko4QvG0bSHlZFepogLG9yOGB0B1Hp4JPTSimEb6f
|
28
|
+
b+CL5o8mXzAMRDIzdjTkBM/nQg5NMoXaXvmKypw/zIYI/ffb6Kwu/Y6gWmXubQrA
|
29
|
+
fJ95Ssb14RKtmW6IDmHD5mNpybjhoSzwtBZyuHAyVZT/5P8/QHNkMBwpfQsevY5f
|
30
|
+
FmOcAZIq9bHTBvn5SNNvi2NxZfTRD7sTMogXgqKKcxwe2rs52IYecamNekQMjrPL
|
31
|
+
jI16bCLO/G1NaQ+N9YFL4TGfvbCe
|
32
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,52 @@
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
2
|
+
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
|
51
|
+
cN5973/E4MpAKMt1shs0YWr3axraOhkg
|
52
|
+
-----END PRIVATE KEY-----
|
@@ -0,0 +1,182 @@
|
|
1
|
+
use super::{
|
2
|
+
bind::Bind,
|
3
|
+
listener::{Listener, SockAddr},
|
4
|
+
};
|
5
|
+
use crate::{request::itsi_request::ItsiRequest, ITSI_SERVER};
|
6
|
+
use bytes::Bytes;
|
7
|
+
use derive_more::Debug;
|
8
|
+
use http_body_util::{combinators::BoxBody, Empty};
|
9
|
+
use hyper::{
|
10
|
+
body::Incoming, header::HeaderName, service::service_fn, HeaderMap, Request, Response,
|
11
|
+
StatusCode,
|
12
|
+
};
|
13
|
+
use hyper_util::{rt::TokioExecutor, server::conn::auto::Builder};
|
14
|
+
use itsi_tracing::{error, info};
|
15
|
+
use magnus::{
|
16
|
+
error::Result,
|
17
|
+
scan_args::{get_kwargs, scan_args, Args, KwArgs},
|
18
|
+
value::{Opaque, ReprValue},
|
19
|
+
RHash, Ruby, Value,
|
20
|
+
};
|
21
|
+
use parking_lot::Mutex;
|
22
|
+
use std::{collections::HashMap, convert::Infallible, sync::Arc};
|
23
|
+
use tokio::runtime::Builder as RuntimeBuilder;
|
24
|
+
use tokio::task::JoinSet;
|
25
|
+
|
26
|
+
#[magnus::wrap(class = "Itsi::Server", free_immediately, size)]
|
27
|
+
#[derive(Debug)]
|
28
|
+
pub struct Server {
|
29
|
+
#[debug(skip)]
|
30
|
+
app: Opaque<Value>,
|
31
|
+
#[allow(unused)]
|
32
|
+
workers: u16,
|
33
|
+
#[allow(unused)]
|
34
|
+
threads: u16,
|
35
|
+
#[allow(unused)]
|
36
|
+
shutdown_timeout: f64,
|
37
|
+
script_name: String,
|
38
|
+
pub(crate) binds: Mutex<Vec<Bind>>,
|
39
|
+
}
|
40
|
+
|
41
|
+
impl Server {
|
42
|
+
pub fn new(args: &[Value]) -> Result<Self> {
|
43
|
+
type OptionalArgs = (
|
44
|
+
Option<u16>,
|
45
|
+
Option<u16>,
|
46
|
+
Option<f64>,
|
47
|
+
Option<String>,
|
48
|
+
Option<Vec<String>>,
|
49
|
+
);
|
50
|
+
|
51
|
+
let scan_args: Args<(), (), (), (), RHash, ()> = scan_args(args)?;
|
52
|
+
let args: KwArgs<(Value,), OptionalArgs, ()> = get_kwargs(
|
53
|
+
scan_args.keywords,
|
54
|
+
&["app"],
|
55
|
+
&[
|
56
|
+
"workers",
|
57
|
+
"threads",
|
58
|
+
"shutdown_timeout",
|
59
|
+
"script_name",
|
60
|
+
"binds",
|
61
|
+
],
|
62
|
+
)?;
|
63
|
+
let server = Server {
|
64
|
+
app: Opaque::from(args.required.0),
|
65
|
+
workers: args.optional.0.unwrap_or(1),
|
66
|
+
threads: args.optional.1.unwrap_or(1),
|
67
|
+
shutdown_timeout: args.optional.2.unwrap_or(5.0),
|
68
|
+
script_name: args.optional.3.unwrap_or("".to_string()),
|
69
|
+
binds: Mutex::new(
|
70
|
+
args.optional
|
71
|
+
.4
|
72
|
+
.unwrap_or_else(|| vec!["localhost:3000".to_string()])
|
73
|
+
.into_iter()
|
74
|
+
.map(|s| s.parse().unwrap_or_else(|_| Bind::default()))
|
75
|
+
.collect(),
|
76
|
+
),
|
77
|
+
};
|
78
|
+
Ok(server)
|
79
|
+
}
|
80
|
+
|
81
|
+
pub(crate) async fn process_request(
|
82
|
+
hyper_request: Request<Incoming>,
|
83
|
+
app: Opaque<Value>,
|
84
|
+
script_name: String,
|
85
|
+
listener: Arc<Listener>,
|
86
|
+
addr: SockAddr,
|
87
|
+
) -> itsi_error::Result<Response<BoxBody<Bytes, Infallible>>> {
|
88
|
+
let request = ItsiRequest::build_from(hyper_request, addr, script_name, listener).await;
|
89
|
+
let ruby = Ruby::get().unwrap();
|
90
|
+
let server = ruby.get_inner(&ITSI_SERVER);
|
91
|
+
let response: Result<(u16, HashMap<String, String>, Value)> =
|
92
|
+
server.funcall("call", (app, request));
|
93
|
+
if let Ok((status, headers_raw, body)) = response {
|
94
|
+
let mut body_buf = vec![];
|
95
|
+
for body_chunk in body.enumeratorize("each", ()) {
|
96
|
+
body_buf.push(body_chunk.unwrap().to_string())
|
97
|
+
}
|
98
|
+
body.check_funcall::<_, _, Value>("close", ());
|
99
|
+
let boxed_body = BoxBody::new(body_buf.join(""));
|
100
|
+
let mut response = Response::new(boxed_body);
|
101
|
+
let mut headers = HeaderMap::new();
|
102
|
+
headers_raw.into_iter().for_each(|(key, value)| {
|
103
|
+
let header_name: HeaderName = key.parse().unwrap();
|
104
|
+
headers.insert(header_name, value.parse().unwrap());
|
105
|
+
});
|
106
|
+
*response.headers_mut() = headers;
|
107
|
+
*response.status_mut() = StatusCode::from_u16(status).unwrap();
|
108
|
+
Ok(response)
|
109
|
+
} else {
|
110
|
+
let mut response = Response::new(BoxBody::new(Empty::new()));
|
111
|
+
*response.status_mut() = StatusCode::BAD_REQUEST;
|
112
|
+
Ok(response)
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
pub fn start(&self) {
|
117
|
+
let mut builder: RuntimeBuilder = RuntimeBuilder::new_current_thread();
|
118
|
+
let runtime = builder
|
119
|
+
.thread_name("itsi-server-accept-loop")
|
120
|
+
.thread_stack_size(3 * 1024 * 1024)
|
121
|
+
.enable_io()
|
122
|
+
.enable_time()
|
123
|
+
.build()
|
124
|
+
.expect("Failed to build Tokio runtime");
|
125
|
+
|
126
|
+
runtime.block_on(async {
|
127
|
+
let server = Arc::new(Builder::new(TokioExecutor::new()));
|
128
|
+
let listeners: Vec<Listener> = self
|
129
|
+
.binds
|
130
|
+
.lock()
|
131
|
+
.iter()
|
132
|
+
.cloned()
|
133
|
+
.map(Listener::from)
|
134
|
+
.collect::<Vec<_>>();
|
135
|
+
|
136
|
+
let mut set = JoinSet::new();
|
137
|
+
|
138
|
+
for listener in listeners {
|
139
|
+
let app = self.app;
|
140
|
+
let server_clone = server.clone();
|
141
|
+
let listener_clone = Arc::new(listener);
|
142
|
+
let script_name = self.script_name.clone();
|
143
|
+
|
144
|
+
set.spawn(async move {
|
145
|
+
loop {
|
146
|
+
let server = server_clone.clone();
|
147
|
+
let listener = listener_clone.clone();
|
148
|
+
let script_name = script_name.clone();
|
149
|
+
let (stream, addr) = match listener.accept().await {
|
150
|
+
Ok(stream) => stream,
|
151
|
+
Err(e) => {
|
152
|
+
error!("Failed to accept connection: {:?}", e);
|
153
|
+
continue;
|
154
|
+
}
|
155
|
+
};
|
156
|
+
|
157
|
+
tokio::spawn(async move {
|
158
|
+
if let Err(e) = server
|
159
|
+
.serve_connection_with_upgrades(
|
160
|
+
stream,
|
161
|
+
service_fn(move |hyper_request: Request<Incoming>| {
|
162
|
+
Server::process_request(
|
163
|
+
hyper_request,
|
164
|
+
app,
|
165
|
+
script_name.clone(),
|
166
|
+
listener.clone(),
|
167
|
+
addr.clone(),
|
168
|
+
)
|
169
|
+
}),
|
170
|
+
)
|
171
|
+
.await
|
172
|
+
{
|
173
|
+
info!("Closed connection due to: {:?}", e);
|
174
|
+
}
|
175
|
+
});
|
176
|
+
}
|
177
|
+
});
|
178
|
+
}
|
179
|
+
while let Some(_res) = set.join_next().await {}
|
180
|
+
})
|
181
|
+
}
|
182
|
+
}
|
@@ -0,0 +1,218 @@
|
|
1
|
+
use super::bind::{Bind, BindAddress};
|
2
|
+
use super::transfer_protocol::TransferProtocol;
|
3
|
+
use hyper_util::rt::TokioIo;
|
4
|
+
use itsi_error::Result;
|
5
|
+
use itsi_tracing::info;
|
6
|
+
use socket2::{Domain, Protocol, Socket, Type};
|
7
|
+
use std::net::{IpAddr, SocketAddr, TcpListener as StdTcpListener};
|
8
|
+
use std::pin::Pin;
|
9
|
+
use std::sync::Arc;
|
10
|
+
use std::{os::unix::net::UnixListener as StdUnixListener, path::PathBuf};
|
11
|
+
use tokio::net::{unix, TcpListener, TcpStream, UnixListener, UnixStream};
|
12
|
+
use tokio_rustls::TlsAcceptor;
|
13
|
+
|
14
|
+
pub(crate) trait IoStream:
|
15
|
+
tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Unpin
|
16
|
+
{
|
17
|
+
}
|
18
|
+
impl<T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Unpin> IoStream for T {}
|
19
|
+
|
20
|
+
pub(crate) enum Listener {
|
21
|
+
Tcp(TcpListener),
|
22
|
+
TcpTls((TcpListener, TlsAcceptor)),
|
23
|
+
Unix(UnixListener),
|
24
|
+
UnixTls((UnixListener, TlsAcceptor)),
|
25
|
+
}
|
26
|
+
|
27
|
+
enum Stream {
|
28
|
+
TcpStream((TcpStream, SocketAddr)),
|
29
|
+
UnixStream((UnixStream, unix::SocketAddr)),
|
30
|
+
}
|
31
|
+
|
32
|
+
#[derive(Clone, Debug)]
|
33
|
+
pub enum SockAddr {
|
34
|
+
Tcp(Arc<SocketAddr>),
|
35
|
+
Unix(Arc<unix::SocketAddr>),
|
36
|
+
}
|
37
|
+
impl std::fmt::Display for SockAddr {
|
38
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
39
|
+
match self {
|
40
|
+
SockAddr::Tcp(socket_addr) => write!(f, "{}", socket_addr.ip().to_canonical()),
|
41
|
+
SockAddr::Unix(socket_addr) => match socket_addr.as_pathname() {
|
42
|
+
Some(path) => write!(f, "{:?}", path),
|
43
|
+
None => write!(f, ""),
|
44
|
+
},
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
impl Listener {
|
50
|
+
pub(crate) async fn accept(&self) -> Result<(TokioIo<Pin<Box<dyn IoStream>>>, SockAddr)> {
|
51
|
+
match self {
|
52
|
+
Listener::Tcp(listener) => Listener::accept_tcp(listener).await,
|
53
|
+
Listener::TcpTls((listener, acceptor)) => {
|
54
|
+
Listener::accept_tls(listener, acceptor).await
|
55
|
+
}
|
56
|
+
Listener::Unix(stream) => Listener::accept_unix(stream).await,
|
57
|
+
Listener::UnixTls((listener, acceptor)) => {
|
58
|
+
Listener::accept_unix_tls(listener, acceptor).await
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
async fn to_tokio_io(
|
64
|
+
input_stream: Stream,
|
65
|
+
tls_acceptor: Option<&TlsAcceptor>,
|
66
|
+
) -> Result<(TokioIo<Pin<Box<dyn IoStream>>>, SockAddr)> {
|
67
|
+
match tls_acceptor {
|
68
|
+
Some(acceptor) => match input_stream {
|
69
|
+
Stream::TcpStream((tcp_stream, socket_address)) => {
|
70
|
+
match acceptor.accept(tcp_stream).await {
|
71
|
+
Ok(tls_stream) => Ok((
|
72
|
+
TokioIo::new(Box::pin(tls_stream) as Pin<Box<dyn IoStream>>),
|
73
|
+
SockAddr::Tcp(Arc::new(socket_address)),
|
74
|
+
)),
|
75
|
+
Err(err) => Err(err.into()),
|
76
|
+
}
|
77
|
+
}
|
78
|
+
Stream::UnixStream((unix_stream, socket_address)) => {
|
79
|
+
match acceptor.accept(unix_stream).await {
|
80
|
+
Ok(tls_stream) => Ok((
|
81
|
+
TokioIo::new(Box::pin(tls_stream) as Pin<Box<dyn IoStream>>),
|
82
|
+
SockAddr::Unix(Arc::new(socket_address)),
|
83
|
+
)),
|
84
|
+
Err(err) => Err(err.into()),
|
85
|
+
}
|
86
|
+
}
|
87
|
+
},
|
88
|
+
None => match input_stream {
|
89
|
+
Stream::TcpStream((tcp_stream, socket_address)) => Ok((
|
90
|
+
TokioIo::new(Box::pin(tcp_stream) as Pin<Box<dyn IoStream>>),
|
91
|
+
SockAddr::Tcp(Arc::new(socket_address)),
|
92
|
+
)),
|
93
|
+
Stream::UnixStream((unix_stream, socket_address)) => Ok((
|
94
|
+
TokioIo::new(Box::pin(unix_stream) as Pin<Box<dyn IoStream>>),
|
95
|
+
SockAddr::Unix(Arc::new(socket_address)),
|
96
|
+
)),
|
97
|
+
},
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
async fn accept_tcp(
|
102
|
+
listener: &TcpListener,
|
103
|
+
) -> Result<(TokioIo<Pin<Box<dyn IoStream>>>, SockAddr)> {
|
104
|
+
let tcp_stream = listener.accept().await?;
|
105
|
+
Self::to_tokio_io(Stream::TcpStream(tcp_stream), None).await
|
106
|
+
}
|
107
|
+
|
108
|
+
async fn accept_tls(
|
109
|
+
listener: &TcpListener,
|
110
|
+
acceptor: &TlsAcceptor,
|
111
|
+
) -> Result<(TokioIo<Pin<Box<dyn IoStream>>>, SockAddr)> {
|
112
|
+
let tcp_stream = listener.accept().await?;
|
113
|
+
Self::to_tokio_io(Stream::TcpStream(tcp_stream), Some(acceptor)).await
|
114
|
+
}
|
115
|
+
|
116
|
+
async fn accept_unix(
|
117
|
+
listener: &UnixListener,
|
118
|
+
) -> Result<(TokioIo<Pin<Box<dyn IoStream>>>, SockAddr)> {
|
119
|
+
let unix_stream = listener.accept().await?;
|
120
|
+
Self::to_tokio_io(Stream::UnixStream(unix_stream), None).await
|
121
|
+
}
|
122
|
+
|
123
|
+
async fn accept_unix_tls(
|
124
|
+
listener: &UnixListener,
|
125
|
+
acceptor: &TlsAcceptor,
|
126
|
+
) -> Result<(TokioIo<Pin<Box<dyn IoStream>>>, SockAddr)> {
|
127
|
+
let unix_stream = listener.accept().await?;
|
128
|
+
Self::to_tokio_io(Stream::UnixStream(unix_stream), Some(acceptor)).await
|
129
|
+
}
|
130
|
+
|
131
|
+
pub(crate) fn scheme(&self) -> String {
|
132
|
+
match self {
|
133
|
+
Listener::Tcp(_) => "http".to_string(),
|
134
|
+
Listener::TcpTls(_) => "https".to_string(),
|
135
|
+
Listener::Unix(_) => "http".to_string(),
|
136
|
+
Listener::UnixTls(_) => "https".to_string(),
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
pub(crate) fn port(&self) -> u16 {
|
141
|
+
match self {
|
142
|
+
Listener::Tcp(listener) => listener.local_addr().unwrap().port(),
|
143
|
+
Listener::TcpTls((listener, _)) => listener.local_addr().unwrap().port(),
|
144
|
+
Listener::Unix(_) => 0,
|
145
|
+
Listener::UnixTls(_) => 0,
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
pub(crate) fn host(&self) -> String {
|
150
|
+
match self {
|
151
|
+
Listener::Tcp(listener) => listener.local_addr().unwrap().ip().to_string(),
|
152
|
+
Listener::TcpTls((listener, _)) => listener.local_addr().unwrap().ip().to_string(),
|
153
|
+
Listener::Unix(_) => "unix".to_string(),
|
154
|
+
Listener::UnixTls(_) => "unix".to_string(),
|
155
|
+
}
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
impl From<Bind> for Listener {
|
160
|
+
fn from(bind: Bind) -> Self {
|
161
|
+
match bind.address {
|
162
|
+
BindAddress::Ip(addr) => match bind.protocol {
|
163
|
+
TransferProtocol::Http => Listener::Tcp(
|
164
|
+
TcpListener::from_std(connect_tcp_socket(addr, bind.port.unwrap())).unwrap(),
|
165
|
+
),
|
166
|
+
TransferProtocol::Https => {
|
167
|
+
let tcp_listener =
|
168
|
+
TcpListener::from_std(connect_tcp_socket(addr, bind.port.unwrap()))
|
169
|
+
.unwrap();
|
170
|
+
let tls_acceptor = TlsAcceptor::from(Arc::new(bind.tls_config.unwrap()));
|
171
|
+
Listener::TcpTls((tcp_listener, tls_acceptor))
|
172
|
+
}
|
173
|
+
_ => unreachable!(),
|
174
|
+
},
|
175
|
+
BindAddress::UnixSocket(path) => match bind.tls_config {
|
176
|
+
Some(tls_config) => {
|
177
|
+
let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));
|
178
|
+
Listener::UnixTls((
|
179
|
+
UnixListener::from_std(connect_unix_socket(&path)).unwrap(),
|
180
|
+
tls_acceptor,
|
181
|
+
))
|
182
|
+
}
|
183
|
+
None => Listener::Unix(UnixListener::from_std(connect_unix_socket(&path)).unwrap()),
|
184
|
+
},
|
185
|
+
}
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
fn connect_tcp_socket(addr: IpAddr, port: u16) -> StdTcpListener {
|
190
|
+
let domain = match addr {
|
191
|
+
IpAddr::V4(_) => Domain::IPV4,
|
192
|
+
IpAddr::V6(_) => Domain::IPV6,
|
193
|
+
};
|
194
|
+
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)).unwrap();
|
195
|
+
let socket_address: SocketAddr = SocketAddr::new(addr, port);
|
196
|
+
socket.set_reuse_address(true).ok();
|
197
|
+
socket.set_reuse_port(true).ok();
|
198
|
+
socket.set_nonblocking(true).ok();
|
199
|
+
socket.set_nodelay(true).ok();
|
200
|
+
socket.set_recv_buffer_size(1_048_576).ok();
|
201
|
+
info!("Binding to {}", socket_address);
|
202
|
+
socket.bind(&socket_address.into()).unwrap();
|
203
|
+
socket.listen(1024).unwrap();
|
204
|
+
socket.into()
|
205
|
+
}
|
206
|
+
|
207
|
+
fn connect_unix_socket(path: &PathBuf) -> StdUnixListener {
|
208
|
+
let _ = std::fs::remove_file(path);
|
209
|
+
let socket = Socket::new(Domain::UNIX, Type::STREAM, None).unwrap();
|
210
|
+
socket.set_nonblocking(true).ok();
|
211
|
+
let socket_address = socket2::SockAddr::unix(path).unwrap();
|
212
|
+
|
213
|
+
info!("Binding to {:?}", path);
|
214
|
+
socket.bind(&socket_address).unwrap();
|
215
|
+
socket.listen(1024).unwrap();
|
216
|
+
|
217
|
+
socket.into()
|
218
|
+
}
|