itsi-server 0.1.1 → 0.1.2

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/exe/itsi +88 -28
  3. data/ext/itsi_error/Cargo.toml +2 -0
  4. data/ext/itsi_error/src/from.rs +70 -0
  5. data/ext/itsi_error/src/lib.rs +10 -37
  6. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  7. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  8. data/ext/itsi_rb_helpers/Cargo.toml +2 -0
  9. data/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
  10. data/ext/itsi_rb_helpers/src/lib.rs +90 -10
  11. data/ext/itsi_scheduler/Cargo.toml +24 -0
  12. data/ext/itsi_scheduler/extconf.rb +6 -0
  13. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  14. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  15. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  16. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  17. data/ext/itsi_scheduler/src/lib.rs +38 -0
  18. data/ext/itsi_server/Cargo.toml +14 -2
  19. data/ext/itsi_server/extconf.rb +1 -1
  20. data/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  21. data/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  22. data/ext/itsi_server/src/body_proxy/mod.rs +2 -0
  23. data/ext/itsi_server/src/lib.rs +58 -7
  24. data/ext/itsi_server/src/request/itsi_request.rs +238 -104
  25. data/ext/itsi_server/src/response/itsi_response.rs +347 -0
  26. data/ext/itsi_server/src/response/mod.rs +1 -0
  27. data/ext/itsi_server/src/server/bind.rs +50 -20
  28. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  29. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  30. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +11 -30
  31. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +3 -50
  32. data/ext/itsi_server/src/server/itsi_server.rs +181 -133
  33. data/ext/itsi_server/src/server/lifecycle_event.rs +8 -0
  34. data/ext/itsi_server/src/server/listener.rs +169 -128
  35. data/ext/itsi_server/src/server/mod.rs +7 -1
  36. data/ext/itsi_server/src/server/process_worker.rs +196 -0
  37. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +253 -0
  38. data/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  39. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +238 -0
  40. data/ext/itsi_server/src/server/signal.rs +57 -0
  41. data/ext/itsi_server/src/server/thread_worker.rs +368 -0
  42. data/ext/itsi_server/src/server/tls.rs +42 -28
  43. data/ext/itsi_tracing/Cargo.toml +4 -0
  44. data/ext/itsi_tracing/src/lib.rs +36 -6
  45. data/lib/itsi/request.rb +30 -14
  46. data/lib/itsi/server/rack/handler/itsi.rb +25 -0
  47. data/lib/itsi/server/scheduler_mode.rb +6 -0
  48. data/lib/itsi/server/version.rb +1 -1
  49. data/lib/itsi/server.rb +68 -2
  50. data/lib/itsi/signals.rb +18 -0
  51. data/lib/itsi/stream_io.rb +38 -0
  52. metadata +41 -14
  53. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  54. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
@@ -0,0 +1,347 @@
1
+ use bytes::{Bytes, BytesMut};
2
+ use derive_more::Debug;
3
+ use futures::stream::{unfold, StreamExt};
4
+ use http::{
5
+ header::TRANSFER_ENCODING, request::Parts, HeaderMap, HeaderName, HeaderValue, Request,
6
+ Response, StatusCode,
7
+ };
8
+ use http_body_util::{combinators::BoxBody, Empty, Full, StreamBody};
9
+ use hyper::{body::Frame, upgrade::Upgraded};
10
+ use hyper_util::rt::TokioIo;
11
+ use itsi_error::Result;
12
+ use itsi_tracing::error;
13
+ use magnus::error::Result as MagnusResult;
14
+ use parking_lot::RwLock;
15
+ use std::{
16
+ convert::Infallible,
17
+ io,
18
+ os::{fd::FromRawFd, unix::net::UnixStream},
19
+ str::FromStr,
20
+ sync::Arc,
21
+ };
22
+ use tokio::{
23
+ io::AsyncReadExt,
24
+ net::UnixStream as TokioUnixStream,
25
+ sync::{
26
+ mpsc::{self},
27
+ watch,
28
+ },
29
+ };
30
+ use tokio_stream::wrappers::ReceiverStream;
31
+ use tokio_util::io::ReaderStream;
32
+ use tracing::warn;
33
+
34
+ use crate::server::serve_strategy::single_mode::RunningPhase;
35
+
36
+ #[magnus::wrap(class = "Itsi::Response", free_immediately, size)]
37
+ #[derive(Debug, Clone)]
38
+ pub struct ItsiResponse {
39
+ pub data: Arc<ResponseData>,
40
+ }
41
+
42
+ #[derive(Debug)]
43
+ pub struct ResponseData {
44
+ pub response: RwLock<Option<Response<BoxBody<Bytes, Infallible>>>>,
45
+ pub response_writer: RwLock<Option<mpsc::Sender<Option<Bytes>>>>,
46
+ pub response_buffer: RwLock<BytesMut>,
47
+ pub hijacked_socket: RwLock<Option<UnixStream>>,
48
+ pub parts: Parts,
49
+ }
50
+
51
+ impl ItsiResponse {
52
+ pub async fn build(
53
+ &self,
54
+ first_frame: Option<Bytes>,
55
+ receiver: mpsc::Receiver<Option<Bytes>>,
56
+ shutdown_rx: watch::Receiver<RunningPhase>,
57
+ ) -> Response<BoxBody<Bytes, Infallible>> {
58
+ if self.is_hijacked() {
59
+ return match self.process_hijacked_response().await {
60
+ Ok(result) => result,
61
+ Err(e) => {
62
+ error!("Error processing hijacked response: {}", e);
63
+ Response::new(BoxBody::new(Empty::new()))
64
+ }
65
+ };
66
+ }
67
+
68
+ let mut response = self.data.response.write().take().unwrap();
69
+ *response.body_mut() = if first_frame.is_none() {
70
+ BoxBody::new(Empty::new())
71
+ } else if receiver.is_closed() && receiver.is_empty() {
72
+ BoxBody::new(Full::new(first_frame.unwrap()))
73
+ } else {
74
+ let initial_frame = tokio_stream::once(Ok(Frame::data(first_frame.unwrap())));
75
+ let frame_stream = unfold(
76
+ (ReceiverStream::new(receiver), shutdown_rx),
77
+ |(mut receiver, mut shutdown_rx)| async move {
78
+ if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
79
+ warn!("Disconnecting streaming client.");
80
+ return None;
81
+ }
82
+ loop {
83
+ tokio::select! {
84
+ maybe_bytes = receiver.next() => {
85
+ if let Some(bytes) = maybe_bytes {
86
+ // We assume `bytes` is Some(Bytes) here.
87
+ return Some((Ok(Frame::data(bytes.unwrap())), (receiver, shutdown_rx)));
88
+ } else {
89
+ // Receiver closed, end the stream.
90
+ return None;
91
+ }
92
+ },
93
+ _ = shutdown_rx.changed() => {
94
+ match *shutdown_rx.borrow() {
95
+ RunningPhase::ShutdownPending => {
96
+ warn!("Disconnecting streaming client.");
97
+ return None;
98
+ },
99
+ _ => continue,
100
+ }
101
+ }
102
+ }
103
+ }
104
+ },
105
+ );
106
+
107
+ let combined_stream = initial_frame.chain(frame_stream);
108
+ BoxBody::new(StreamBody::new(combined_stream))
109
+ };
110
+ response
111
+ }
112
+
113
+ pub fn close(&self) {
114
+ self.data.response_writer.write().take();
115
+ }
116
+
117
+ async fn two_way_bridge(upgraded: Upgraded, local: TokioUnixStream) -> io::Result<()> {
118
+ let client_io = TokioIo::new(upgraded);
119
+
120
+ // Split each side
121
+ let (mut lr, mut lw) = tokio::io::split(local);
122
+ let (mut cr, mut cw) = tokio::io::split(client_io);
123
+
124
+ let to_ruby = tokio::spawn(async move {
125
+ if let Err(e) = tokio::io::copy(&mut cr, &mut lw).await {
126
+ eprintln!("Error copying upgraded->local: {:?}", e);
127
+ }
128
+ });
129
+ let from_ruby = tokio::spawn(async move {
130
+ if let Err(e) = tokio::io::copy(&mut lr, &mut cw).await {
131
+ eprintln!("Error copying upgraded->local: {:?}", e);
132
+ }
133
+ });
134
+
135
+ let _ = to_ruby.await;
136
+ let _ = from_ruby.await;
137
+ Ok(())
138
+ }
139
+
140
+ async fn read_response_headers(&self, reader: &mut TokioUnixStream) -> Result<Vec<u8>> {
141
+ let mut buf = [0u8; 1];
142
+ let mut collected = Vec::new();
143
+ loop {
144
+ let n = reader.read(&mut buf).await?;
145
+ if n == 0 {
146
+ // EOF reached unexpectedly
147
+ break;
148
+ }
149
+ collected.push(buf[0]);
150
+ if collected.ends_with(b"\r\n\r\n") {
151
+ break;
152
+ }
153
+ }
154
+
155
+ Ok(collected)
156
+ }
157
+
158
+ pub async fn read_hijacked_headers(
159
+ &self,
160
+ ) -> Result<(HeaderMap, StatusCode, bool, TokioUnixStream)> {
161
+ let hijacked_socket =
162
+ self.data
163
+ .hijacked_socket
164
+ .write()
165
+ .take()
166
+ .ok_or(itsi_error::ItsiError::InvalidInput(
167
+ "Couldn't hijack stream".to_owned(),
168
+ ))?;
169
+ let mut reader = TokioUnixStream::from_std(hijacked_socket).unwrap();
170
+ let response_headers = self.read_response_headers(&mut reader).await?;
171
+ let mut headers = [httparse::EMPTY_HEADER; 64];
172
+ let mut resp = httparse::Response::new(&mut headers);
173
+ resp.parse(&response_headers)?;
174
+
175
+ let status = StatusCode::from_u16(resp.code.unwrap_or(200)).unwrap_or(StatusCode::OK);
176
+ let mut headers = HeaderMap::new();
177
+ for header in resp.headers.iter() {
178
+ headers.insert(
179
+ HeaderName::from_str(header.name).unwrap(),
180
+ HeaderValue::from_bytes(header.value).unwrap(),
181
+ );
182
+ }
183
+ let requires_upgrade = status == StatusCode::SWITCHING_PROTOCOLS;
184
+ Ok((headers, status, requires_upgrade, reader))
185
+ }
186
+
187
+ pub async fn process_hijacked_response(&self) -> Result<Response<BoxBody<Bytes, Infallible>>> {
188
+ let (headers, status, requires_upgrade, reader) = self.read_hijacked_headers().await?;
189
+ let mut response = if requires_upgrade {
190
+ let parts = self.data.parts.clone();
191
+ tokio::spawn(async move {
192
+ let mut req = Request::from_parts(parts, Empty::<Bytes>::new());
193
+ match hyper::upgrade::on(&mut req).await {
194
+ Ok(upgraded) => {
195
+ Self::two_way_bridge(upgraded, reader)
196
+ .await
197
+ .expect("Error in creating two way bridge");
198
+ }
199
+ Err(e) => eprintln!("upgrade error: {:?}", e),
200
+ }
201
+ });
202
+ Response::new(BoxBody::new(Empty::new()))
203
+ } else {
204
+ let stream = ReaderStream::new(reader);
205
+ let boxed_body = if headers
206
+ .get(TRANSFER_ENCODING)
207
+ .is_some_and(|h| h == "chunked")
208
+ {
209
+ BoxBody::new(StreamBody::new(unfold(
210
+ (stream, Vec::new()),
211
+ |(mut stream, mut buf)| async move {
212
+ loop {
213
+ if let Some(pos) = buf.iter().position(|&b| b == b'\n') {
214
+ let line = buf.drain(..=pos).collect::<Vec<u8>>();
215
+ let line = std::str::from_utf8(&line).ok()?.trim();
216
+ let chunk_size = usize::from_str_radix(line, 16).ok()?;
217
+ if chunk_size == 0 {
218
+ return None;
219
+ }
220
+ while buf.len() < chunk_size {
221
+ match stream.next().await {
222
+ Some(Ok(chunk)) => buf.extend_from_slice(&chunk),
223
+ _ => return None,
224
+ }
225
+ }
226
+ let data = buf.drain(..chunk_size).collect::<Vec<u8>>();
227
+ if buf.starts_with(b"\r\n") {
228
+ buf.drain(..2);
229
+ }
230
+ return Some((Ok(Frame::data(Bytes::from(data))), (stream, buf)));
231
+ }
232
+ match stream.next().await {
233
+ Some(Ok(chunk)) => buf.extend_from_slice(&chunk),
234
+ _ => return None,
235
+ }
236
+ }
237
+ },
238
+ )))
239
+ } else {
240
+ BoxBody::new(StreamBody::new(stream.map(
241
+ |result: std::result::Result<Bytes, io::Error>| {
242
+ result
243
+ .map(Frame::data)
244
+ .map_err(|e| unreachable!("unexpected io error: {:?}", e))
245
+ },
246
+ )))
247
+ };
248
+ Response::new(boxed_body)
249
+ };
250
+
251
+ *response.status_mut() = status;
252
+ *response.headers_mut() = headers;
253
+ Ok(response)
254
+ }
255
+
256
+ pub fn internal_server_error(&self, message: String) {
257
+ error!(message);
258
+ self.data.response_writer.write().take();
259
+ if let Some(ref mut response) = *self.data.response.write() {
260
+ *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
261
+ }
262
+ }
263
+
264
+ pub fn send_frame(&self, frame: Bytes) -> MagnusResult<usize> {
265
+ self.send_frame_into(frame, &self.data.response_writer)
266
+ }
267
+
268
+ pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<usize> {
269
+ let result = self.send_frame_into(frame, &self.data.response_writer);
270
+ self.data.response_writer.write().take();
271
+ result
272
+ }
273
+
274
+ pub fn send_frame_into(
275
+ &self,
276
+ frame: Bytes,
277
+ writer: &RwLock<Option<mpsc::Sender<Option<Bytes>>>>,
278
+ ) -> MagnusResult<usize> {
279
+ if let Some(writer) = writer.write().as_ref() {
280
+ writer
281
+ .blocking_send(Some(frame))
282
+ .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?;
283
+ }
284
+ Ok(0)
285
+ }
286
+
287
+ pub fn is_hijacked(&self) -> bool {
288
+ self.data.hijacked_socket.read().is_some()
289
+ }
290
+
291
+ pub fn close_write(&self) -> MagnusResult<bool> {
292
+ self.data.response_writer.write().take();
293
+ Ok(true)
294
+ }
295
+
296
+ pub fn close_read(&self) -> MagnusResult<bool> {
297
+ todo!();
298
+ }
299
+
300
+ pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes>>) -> Self {
301
+ Self {
302
+ data: Arc::new(ResponseData {
303
+ response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
304
+ response_writer: RwLock::new(Some(response_writer)),
305
+ response_buffer: RwLock::new(BytesMut::new()),
306
+ hijacked_socket: RwLock::new(None),
307
+ parts,
308
+ }),
309
+ }
310
+ }
311
+
312
+ pub fn add_header(&self, name: Bytes, value: Bytes) -> MagnusResult<()> {
313
+ let header_name: HeaderName = HeaderName::from_bytes(&name).map_err(|e| {
314
+ itsi_error::ItsiError::InvalidInput(format!("Invalid header name {:?}: {:?}", name, e))
315
+ })?;
316
+ let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
317
+ if let Some(ref mut resp) = *self.data.response.write() {
318
+ resp.headers_mut().insert(header_name, header_value);
319
+ }
320
+ Ok(())
321
+ }
322
+
323
+ pub fn set_status(&self, status: u16) -> MagnusResult<()> {
324
+ if let Some(ref mut resp) = *self.data.response.write() {
325
+ *resp.status_mut() = StatusCode::from_u16(status).map_err(|e| {
326
+ itsi_error::ItsiError::InvalidInput(format!(
327
+ "Invalid status code {:?}: {:?}",
328
+ status, e
329
+ ))
330
+ })?;
331
+ }
332
+ Ok(())
333
+ }
334
+
335
+ pub fn hijack(&self, fd: i32) -> MagnusResult<()> {
336
+ let stream = unsafe { UnixStream::from_raw_fd(fd) };
337
+
338
+ *self.data.hijacked_socket.write() = Some(stream);
339
+ if let Some(writer) = self.data.response_writer.write().as_ref() {
340
+ writer
341
+ .blocking_send(None)
342
+ .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?;
343
+ }
344
+ self.close();
345
+ Ok(())
346
+ }
347
+ }
@@ -0,0 +1 @@
1
+ pub mod itsi_response;
@@ -1,4 +1,4 @@
1
- use super::{tls::configure_tls, transfer_protocol::TransferProtocol};
1
+ use super::{bind_protocol::BindProtocol, tls::configure_tls};
2
2
  use itsi_error::ItsiError;
3
3
  use std::{
4
4
  collections::HashMap,
@@ -8,7 +8,6 @@ use std::{
8
8
  };
9
9
  use tokio_rustls::rustls::ServerConfig;
10
10
 
11
- // Support binding to either IP or Unix Socket
12
11
  #[derive(Debug, Clone)]
13
12
  pub enum BindAddress {
14
13
  Ip(IpAddr),
@@ -21,23 +20,54 @@ impl Default for BindAddress {
21
20
  }
22
21
  }
23
22
 
24
- #[derive(Debug, Default, Clone)]
23
+ #[derive(Default, Clone)]
25
24
  #[magnus::wrap(class = "Itsi::Bind")]
26
25
  pub struct Bind {
27
26
  pub address: BindAddress,
28
27
  pub port: Option<u16>, // None for Unix Sockets
29
- pub protocol: TransferProtocol,
28
+ pub protocol: BindProtocol,
30
29
  pub tls_config: Option<ServerConfig>,
31
30
  }
32
31
 
32
+ impl std::fmt::Debug for Bind {
33
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34
+ match &self.address {
35
+ BindAddress::Ip(ip) => match self.protocol {
36
+ BindProtocol::Unix | BindProtocol::Unixs => {
37
+ write!(f, "{}://{}", self.protocol, ip)
38
+ }
39
+ BindProtocol::Https if self.port == Some(443) => {
40
+ write!(f, "{}://{}", self.protocol, ip)
41
+ }
42
+ BindProtocol::Http if self.port == Some(80) => {
43
+ write!(f, "{}://{}", self.protocol, ip)
44
+ }
45
+ _ => match self.port {
46
+ Some(port) => write!(f, "{}://{}:{}", self.protocol, ip, port),
47
+ None => write!(f, "{}://{}", self.protocol, ip),
48
+ },
49
+ },
50
+ BindAddress::UnixSocket(path) => {
51
+ write!(f, "{}://{}", self.protocol, path.display())
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ /// We can build a Bind from a string in the format `protocol://host:port?options`
58
+ /// E.g.
59
+ /// *`https://example.com:443?tls_cert=/path/to/cert.pem&tls_key=/path/to/key.pem`
60
+ /// *`unix:///path/to/socket.sock`
61
+ /// *`http://example.com:80`
62
+ /// *`https://[::]:80`
33
63
  impl FromStr for Bind {
34
64
  type Err = ItsiError;
35
65
 
36
66
  fn from_str(s: &str) -> Result<Self, Self::Err> {
37
67
  let (protocol, remainder) = if let Some((proto, rest)) = s.split_once("://") {
38
- (proto.parse::<TransferProtocol>()?, rest)
68
+ (proto.parse::<BindProtocol>()?, rest)
39
69
  } else {
40
- (TransferProtocol::Https, s)
70
+ (BindProtocol::Https, s)
41
71
  };
42
72
 
43
73
  let (url, options) = if let Some((base, options)) = remainder.split_once('?') {
@@ -80,22 +110,23 @@ impl FromStr for Bind {
80
110
  } else {
81
111
  resolve_hostname(host)
82
112
  .map(BindAddress::Ip)
83
- .unwrap_or(BindAddress::Ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED)))
113
+ .ok_or(ItsiError::ArgumentError(format!(
114
+ "Failed to resolve hostname {}",
115
+ host
116
+ )))?
84
117
  };
85
118
  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())),
119
+ BindProtocol::Http => (port.or(Some(80)), address),
120
+ BindProtocol::Https => (port.or(Some(443)), address),
121
+ BindProtocol::Unix => (None, BindAddress::UnixSocket(host.into())),
122
+ BindProtocol::Unixs => (None, BindAddress::UnixSocket(host.into())),
89
123
  };
90
124
 
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
125
+ let tls_config = match protocol {
126
+ BindProtocol::Http => None,
127
+ BindProtocol::Https => Some(configure_tls(host, &options)?),
128
+ BindProtocol::Unix => None,
129
+ BindProtocol::Unixs => Some(configure_tls(host, &options)?),
99
130
  };
100
131
 
101
132
  Ok(Self {
@@ -120,14 +151,13 @@ fn resolve_hostname(hostname: &str) -> Option<IpAddr> {
120
151
  (hostname, 0)
121
152
  .to_socket_addrs()
122
153
  .ok()?
123
- .filter_map(|addr| {
154
+ .find_map(|addr| {
124
155
  if addr.is_ipv6() {
125
156
  Some(addr.ip()) // Prefer IPv6
126
157
  } else {
127
158
  None
128
159
  }
129
160
  })
130
- .next()
131
161
  .or_else(|| {
132
162
  (hostname, 0)
133
163
  .to_socket_addrs()
@@ -0,0 +1,37 @@
1
+ use itsi_error::ItsiError;
2
+ use std::str::FromStr;
3
+
4
+ #[derive(Debug, Default, Clone)]
5
+ pub enum BindProtocol {
6
+ #[default]
7
+ Https,
8
+ Http,
9
+ Unix,
10
+ Unixs,
11
+ }
12
+
13
+ impl FromStr for BindProtocol {
14
+ type Err = ItsiError;
15
+
16
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
17
+ match s {
18
+ "http" => Ok(BindProtocol::Http),
19
+ "https" => Ok(BindProtocol::Https),
20
+ "unix" => Ok(BindProtocol::Unix),
21
+ "tls" => Ok(BindProtocol::Unixs),
22
+ _ => Err(ItsiError::UnsupportedProtocol(s.to_string())),
23
+ }
24
+ }
25
+ }
26
+
27
+ impl std::fmt::Display for BindProtocol {
28
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29
+ let s = match self {
30
+ BindProtocol::Https => "https",
31
+ BindProtocol::Http => "http",
32
+ BindProtocol::Unix => "unix",
33
+ BindProtocol::Unixs => "tls",
34
+ };
35
+ write!(f, "{}", s)
36
+ }
37
+ }
@@ -0,0 +1,104 @@
1
+ use super::listener::SockAddr;
2
+ use pin_project::pin_project;
3
+ use tokio::net::{TcpStream, UnixStream};
4
+ use tokio_rustls::server::TlsStream;
5
+
6
+ use std::os::unix::io::{AsRawFd, RawFd};
7
+ use std::pin::Pin;
8
+ use std::task::{Context, Poll};
9
+ use tokio::io::{AsyncRead, AsyncWrite};
10
+
11
+ #[pin_project(project = IoStreamEnumProj)]
12
+ pub enum IoStream {
13
+ Tcp {
14
+ #[pin]
15
+ stream: TcpStream,
16
+ addr: SockAddr,
17
+ },
18
+ TcpTls {
19
+ #[pin]
20
+ stream: TlsStream<TcpStream>,
21
+ addr: SockAddr,
22
+ },
23
+ Unix {
24
+ #[pin]
25
+ stream: UnixStream,
26
+ addr: SockAddr,
27
+ },
28
+ UnixTls {
29
+ #[pin]
30
+ stream: TlsStream<UnixStream>,
31
+ addr: SockAddr,
32
+ },
33
+ }
34
+
35
+ impl IoStream {
36
+ pub fn addr(&self) -> SockAddr {
37
+ match self {
38
+ IoStream::Tcp { addr, .. } => addr.clone(),
39
+ IoStream::TcpTls { addr, .. } => addr.clone(),
40
+ IoStream::Unix { addr, .. } => addr.clone(),
41
+ IoStream::UnixTls { addr, .. } => addr.clone(),
42
+ }
43
+ }
44
+ }
45
+
46
+ impl AsyncRead for IoStream {
47
+ fn poll_read(
48
+ self: Pin<&mut Self>,
49
+ cx: &mut Context<'_>,
50
+ buf: &mut tokio::io::ReadBuf<'_>,
51
+ ) -> Poll<std::io::Result<()>> {
52
+ match self.project() {
53
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_read(cx, buf),
54
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_read(cx, buf),
55
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_read(cx, buf),
56
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_read(cx, buf),
57
+ }
58
+ }
59
+ }
60
+
61
+ impl AsyncWrite for IoStream {
62
+ fn poll_write(
63
+ self: Pin<&mut Self>,
64
+ cx: &mut Context<'_>,
65
+ buf: &[u8],
66
+ ) -> Poll<std::io::Result<usize>> {
67
+ match self.project() {
68
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_write(cx, buf),
69
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_write(cx, buf),
70
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_write(cx, buf),
71
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_write(cx, buf),
72
+ }
73
+ }
74
+
75
+ fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
76
+ match self.project() {
77
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_flush(cx),
78
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_flush(cx),
79
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_flush(cx),
80
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_flush(cx),
81
+ }
82
+ }
83
+
84
+ fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
85
+ match self.project() {
86
+ IoStreamEnumProj::Tcp { stream, .. } => stream.poll_shutdown(cx),
87
+ IoStreamEnumProj::TcpTls { stream, .. } => stream.poll_shutdown(cx),
88
+ IoStreamEnumProj::Unix { stream, .. } => stream.poll_shutdown(cx),
89
+ IoStreamEnumProj::UnixTls { stream, .. } => stream.poll_shutdown(cx),
90
+ }
91
+ }
92
+ }
93
+
94
+ impl AsRawFd for IoStream {
95
+ fn as_raw_fd(&self) -> RawFd {
96
+ // For immutable access, we can simply pattern-match on self.
97
+ match self {
98
+ IoStream::Tcp { stream, .. } => stream.as_raw_fd(),
99
+ IoStream::TcpTls { stream, .. } => stream.get_ref().0.as_raw_fd(),
100
+ IoStream::Unix { stream, .. } => stream.as_raw_fd(),
101
+ IoStream::UnixTls { stream, .. } => stream.get_ref().0.as_raw_fd(),
102
+ }
103
+ }
104
+ }
@@ -1,32 +1,13 @@
1
1
  -----BEGIN CERTIFICATE-----
2
- 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
- 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
- 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
- NTkyNVoXDTM1MDIyNjAyNTkyNVowUDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBEl0
6
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- vBTkAs9hTFV1IFIgJNP5CxEbkWr5FcFYUKAopFhDKfQkXGCKEn4s3wuNqUuzEEdE
18
- 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
- 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
- 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
- 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
- HmIZrCQm7rn5uQm2Q9G++JQ0RgQfLngnz3FBL+aERW2wxw1vOcPwg8JWCeGUSCd4
23
- Lj4jtpvlniS5uIiCD2GYxRIub401egX60XAiHdR0k6rS2dKxab9vMKW28RvRddak
24
- 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
- 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
- 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
- nM3AJRT5dHlcHkn6z1snFqRHko4QvG0bSHlZFepogLG9yOGB0B1Hp4JPTSimEb6f
28
- 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
- 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
- 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
31
- jI16bCLO/G1NaQ+N9YFL4TGfvbCe
2
+ MIIB9TCCAZugAwIBAgIUMpQtAScU2Ow9c1Xy/0b/kS/BuwcwCgYIKoZIzj0EAwIw
3
+ UDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBEl0c2kxDTALBgNVBAcMBEl0c2kxEDAO
4
+ BgNVBAoMB0l0c2kgQ0ExETAPBgNVBAMMCGl0c2kuZnlpMB4XDTI1MDMwMzIwMjg1
5
+ N1oXDTM1MDMwMTIwMjg1N1owUDELMAkGA1UEBhMCVVMxDTALBgNVBAgMBEl0c2kx
6
+ DTALBgNVBAcMBEl0c2kxEDAOBgNVBAoMB0l0c2kgQ0ExETAPBgNVBAMMCGl0c2ku
7
+ ZnlpMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqGdC9Vi1r7ARvqSkPXkAgiV5
8
+ gn2MMTeEafagrWT7G1onSh/G+Qstxl61kfFNLOTiy6NSgAtKG+gfveCTo0Pcz6NT
9
+ MFEwHQYDVR0OBBYEFN7zzDodmiK2VAzLDydDvb6Er+U+MB8GA1UdIwQYMBaAFN7z
10
+ zDodmiK2VAzLDydDvb6Er+U+MA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID
11
+ SAAwRQIhAP8q3PiwqTwCbRvYvvetxH39mAce1mfQMosb33ns228VAiBXdb+p9s0o
12
+ 5ug5g9/MTvrIPI7GgolXCWZunkouy0LSrw==
32
13
  -----END CERTIFICATE-----