itsi-server 0.1.1 → 0.1.13
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.
Potentially problematic release.
This version of itsi-server might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +4417 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +94 -45
- data/ext/itsi_error/Cargo.toml +2 -0
- data/ext/itsi_error/src/from.rs +68 -0
- data/ext/itsi_error/src/lib.rs +18 -34
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +73 -13
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +100 -40
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
- data/ext/itsi_server/src/server/bind.rs +75 -31
- data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/cache_store.rs +74 -0
- data/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/ext/itsi_server/src/server/itsi_service.rs +172 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/listener.rs +332 -132
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
- data/ext/itsi_server/src/server/mod.rs +15 -2
- data/ext/itsi_server/src/server/process_worker.rs +229 -0
- data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +93 -0
- data/ext/itsi_server/src/server/static_file_server.rs +984 -0
- data/ext/itsi_server/src/server/thread_worker.rs +444 -0
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/tls.rs +187 -60
- data/ext/itsi_server/src/server/types.rs +43 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +225 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request.rb +87 -0
- data/lib/itsi/http_response.rb +39 -0
- data/lib/itsi/server/Itsi.rb +119 -0
- data/lib/itsi/server/config/dsl.rb +506 -0
- data/lib/itsi/server/config.rb +131 -0
- data/lib/itsi/server/default_app/default_app.rb +38 -0
- data/lib/itsi/server/default_app/index.html +91 -0
- data/lib/itsi/server/grpc_interface.rb +213 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +90 -9
- data/lib/itsi/standard_headers.rb +86 -0
- metadata +122 -31
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
| @@ -0,0 +1,216 @@ | |
| 1 | 
            +
            use std::{
         | 
| 2 | 
            +
                collections::HashMap,
         | 
| 3 | 
            +
                convert::Infallible,
         | 
| 4 | 
            +
                net::SocketAddr,
         | 
| 5 | 
            +
                sync::{Arc, OnceLock},
         | 
| 6 | 
            +
                time::Duration,
         | 
| 7 | 
            +
            };
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            use crate::server::{
         | 
| 10 | 
            +
                bind::{Bind, BindAddress},
         | 
| 11 | 
            +
                itsi_service::RequestContext,
         | 
| 12 | 
            +
                types::{HttpRequest, HttpResponse},
         | 
| 13 | 
            +
            };
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            use super::{string_rewrite::StringRewrite, ErrorResponse, FromValue, MiddlewareLayer};
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            use async_trait::async_trait;
         | 
| 18 | 
            +
            use either::Either;
         | 
| 19 | 
            +
            use futures::TryStreamExt;
         | 
| 20 | 
            +
            use http::Response;
         | 
| 21 | 
            +
            use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
         | 
| 22 | 
            +
            use hyper::body::Frame;
         | 
| 23 | 
            +
            use magnus::error::Result;
         | 
| 24 | 
            +
            use reqwest::{dns::Resolve, Body, Client, Url};
         | 
| 25 | 
            +
            use serde::Deserialize;
         | 
| 26 | 
            +
            use tracing::error;
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            #[derive(Debug, Clone, Deserialize)]
         | 
| 29 | 
            +
            pub struct Proxy {
         | 
| 30 | 
            +
                pub to: StringRewrite,
         | 
| 31 | 
            +
                pub backends: Vec<String>,
         | 
| 32 | 
            +
                pub headers: HashMap<String, Option<ProxiedHeader>>,
         | 
| 33 | 
            +
                pub verify_ssl: bool,
         | 
| 34 | 
            +
                pub timeout: u64,
         | 
| 35 | 
            +
                pub tls_sni: bool,
         | 
| 36 | 
            +
                #[serde(skip_deserializing)]
         | 
| 37 | 
            +
                pub client: OnceLock<Client>,
         | 
| 38 | 
            +
                pub error_response: ErrorResponse,
         | 
| 39 | 
            +
            }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            #[derive(Debug, Clone, Deserialize)]
         | 
| 42 | 
            +
            pub enum ProxiedHeader {
         | 
| 43 | 
            +
                #[serde(rename(deserialize = "value"))]
         | 
| 44 | 
            +
                String(String),
         | 
| 45 | 
            +
                #[serde(rename(deserialize = "rewrite"))]
         | 
| 46 | 
            +
                StringRewrite(StringRewrite),
         | 
| 47 | 
            +
            }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            impl ProxiedHeader {
         | 
| 50 | 
            +
                pub fn to_string(&self, req: &HttpRequest, context: &RequestContext) -> String {
         | 
| 51 | 
            +
                    match self {
         | 
| 52 | 
            +
                        ProxiedHeader::String(value) => value.clone(),
         | 
| 53 | 
            +
                        ProxiedHeader::StringRewrite(rewrite) => rewrite.rewrite_request(req, context),
         | 
| 54 | 
            +
                    }
         | 
| 55 | 
            +
                }
         | 
| 56 | 
            +
            }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            #[derive(Debug, Clone)]
         | 
| 59 | 
            +
            pub struct Resolver {
         | 
| 60 | 
            +
                backends: Arc<Vec<SocketAddr>>,
         | 
| 61 | 
            +
            }
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            /// An iterator that owns an Arc to the backend list and iterates over it.
         | 
| 64 | 
            +
            pub struct ResolverIter {
         | 
| 65 | 
            +
                backends: Arc<Vec<SocketAddr>>,
         | 
| 66 | 
            +
                index: usize,
         | 
| 67 | 
            +
            }
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            impl Iterator for ResolverIter {
         | 
| 70 | 
            +
                type Item = SocketAddr;
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                fn next(&mut self) -> Option<Self::Item> {
         | 
| 73 | 
            +
                    if self.index < self.backends.len() {
         | 
| 74 | 
            +
                        let addr = self.backends[self.index];
         | 
| 75 | 
            +
                        self.index += 1;
         | 
| 76 | 
            +
                        Some(addr)
         | 
| 77 | 
            +
                    } else {
         | 
| 78 | 
            +
                        None
         | 
| 79 | 
            +
                    }
         | 
| 80 | 
            +
                }
         | 
| 81 | 
            +
            }
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            impl Resolve for Resolver {
         | 
| 84 | 
            +
                fn resolve(&self, _name: reqwest::dns::Name) -> reqwest::dns::Resolving {
         | 
| 85 | 
            +
                    let backends = self.backends.clone();
         | 
| 86 | 
            +
                    let fut = async move {
         | 
| 87 | 
            +
                        let iter = ResolverIter { backends, index: 0 };
         | 
| 88 | 
            +
                        Ok(Box::new(iter) as Box<dyn Iterator<Item = SocketAddr> + Send>)
         | 
| 89 | 
            +
                    };
         | 
| 90 | 
            +
                    Box::pin(fut)
         | 
| 91 | 
            +
                }
         | 
| 92 | 
            +
            }
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            #[async_trait]
         | 
| 95 | 
            +
            impl MiddlewareLayer for Proxy {
         | 
| 96 | 
            +
                async fn initialize(&self) -> Result<()> {
         | 
| 97 | 
            +
                    let backends = self
         | 
| 98 | 
            +
                        .backends
         | 
| 99 | 
            +
                        .iter()
         | 
| 100 | 
            +
                        .filter_map(|be| {
         | 
| 101 | 
            +
                            let bind: Bind = be.parse().ok()?;
         | 
| 102 | 
            +
                            match (bind.address, bind.port) {
         | 
| 103 | 
            +
                                (BindAddress::Ip(ip_addr), port) => {
         | 
| 104 | 
            +
                                    Some(SocketAddr::new(ip_addr, port.unwrap()))
         | 
| 105 | 
            +
                                }
         | 
| 106 | 
            +
                                (BindAddress::UnixSocket(_), _) => None,
         | 
| 107 | 
            +
                            }
         | 
| 108 | 
            +
                        })
         | 
| 109 | 
            +
                        .collect::<Vec<_>>();
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    self.client
         | 
| 112 | 
            +
                        .set(
         | 
| 113 | 
            +
                            Client::builder()
         | 
| 114 | 
            +
                                .timeout(Duration::from_secs(self.timeout))
         | 
| 115 | 
            +
                                .danger_accept_invalid_certs(!self.verify_ssl)
         | 
| 116 | 
            +
                                .danger_accept_invalid_hostnames(!self.verify_ssl)
         | 
| 117 | 
            +
                                .dns_resolver(Arc::new(Resolver {
         | 
| 118 | 
            +
                                    backends: Arc::new(backends),
         | 
| 119 | 
            +
                                }))
         | 
| 120 | 
            +
                                .tls_sni(self.tls_sni)
         | 
| 121 | 
            +
                                .build()
         | 
| 122 | 
            +
                                .map_err(|e| {
         | 
| 123 | 
            +
                                    magnus::Error::new(
         | 
| 124 | 
            +
                                        magnus::exception::runtime_error(),
         | 
| 125 | 
            +
                                        format!("Failed to build Reqwest client: {}", e),
         | 
| 126 | 
            +
                                    )
         | 
| 127 | 
            +
                                })?,
         | 
| 128 | 
            +
                        )
         | 
| 129 | 
            +
                        .map_err(|_e| {
         | 
| 130 | 
            +
                            magnus::Error::new(
         | 
| 131 | 
            +
                                magnus::exception::exception(),
         | 
| 132 | 
            +
                                "Failed to save resolver backends",
         | 
| 133 | 
            +
                            )
         | 
| 134 | 
            +
                        })?;
         | 
| 135 | 
            +
                    Ok(())
         | 
| 136 | 
            +
                }
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                async fn before(
         | 
| 139 | 
            +
                    &self,
         | 
| 140 | 
            +
                    req: HttpRequest,
         | 
| 141 | 
            +
                    context: &mut RequestContext,
         | 
| 142 | 
            +
                ) -> Result<Either<HttpRequest, HttpResponse>> {
         | 
| 143 | 
            +
                    let url = self.to.rewrite_request(&req, context);
         | 
| 144 | 
            +
                    let error_response = self.error_response.to_http_response(&req).await;
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                    let destination = match Url::parse(&url) {
         | 
| 147 | 
            +
                        Ok(dest) => dest,
         | 
| 148 | 
            +
                        Err(_) => return Ok(Either::Right(error_response)),
         | 
| 149 | 
            +
                    };
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    let host_str = destination.host_str().unwrap_or_else(|| {
         | 
| 152 | 
            +
                        req.headers()
         | 
| 153 | 
            +
                            .get("Host")
         | 
| 154 | 
            +
                            .and_then(|h| h.to_str().ok())
         | 
| 155 | 
            +
                            .unwrap_or("")
         | 
| 156 | 
            +
                    });
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                    let mut reqwest_builder = self
         | 
| 159 | 
            +
                        .client
         | 
| 160 | 
            +
                        .get()
         | 
| 161 | 
            +
                        .unwrap()
         | 
| 162 | 
            +
                        .request(req.method().clone(), url);
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                    // Forward incoming headers unless they're in remove_headers or overridden.
         | 
| 165 | 
            +
                    for (name, value) in req.headers().iter() {
         | 
| 166 | 
            +
                        let name_str = name.as_str();
         | 
| 167 | 
            +
                        if self.headers.contains_key(name_str) {
         | 
| 168 | 
            +
                            continue;
         | 
| 169 | 
            +
                        }
         | 
| 170 | 
            +
                        reqwest_builder = reqwest_builder.header(name, value);
         | 
| 171 | 
            +
                    }
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                    // Add the host header if it's not overridden and host_str is non-empty.
         | 
| 174 | 
            +
                    if !self.headers.contains_key("host") && !host_str.is_empty() {
         | 
| 175 | 
            +
                        reqwest_builder = reqwest_builder.header("Host", host_str);
         | 
| 176 | 
            +
                    }
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                    // Add overriding headers.
         | 
| 179 | 
            +
                    for (name, header_value) in self.headers.iter() {
         | 
| 180 | 
            +
                        if let Some(header_value) = header_value {
         | 
| 181 | 
            +
                            reqwest_builder =
         | 
| 182 | 
            +
                                reqwest_builder.header(name, header_value.to_string(&req, context));
         | 
| 183 | 
            +
                        }
         | 
| 184 | 
            +
                    }
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                    let reqwest_builder = reqwest_builder.body(Body::wrap_stream(req.into_data_stream()));
         | 
| 187 | 
            +
                    let reqwest_response = reqwest_builder.send().await;
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    let response = match reqwest_response {
         | 
| 190 | 
            +
                        Ok(response) => {
         | 
| 191 | 
            +
                            let status = response.status();
         | 
| 192 | 
            +
                            let mut builder = Response::builder().status(status);
         | 
| 193 | 
            +
                            for (hn, hv) in response.headers() {
         | 
| 194 | 
            +
                                builder = builder.header(hn, hv);
         | 
| 195 | 
            +
                            }
         | 
| 196 | 
            +
                            let response = builder.body(BoxBody::new(StreamBody::new(
         | 
| 197 | 
            +
                                response
         | 
| 198 | 
            +
                                    .bytes_stream()
         | 
| 199 | 
            +
                                    .map_ok(Frame::data)
         | 
| 200 | 
            +
                                    .map_err(|_| -> Infallible { unreachable!("We handle IO errors above") }),
         | 
| 201 | 
            +
                            )));
         | 
| 202 | 
            +
                            if let Ok(response) = response {
         | 
| 203 | 
            +
                                response
         | 
| 204 | 
            +
                            } else {
         | 
| 205 | 
            +
                                error_response
         | 
| 206 | 
            +
                            }
         | 
| 207 | 
            +
                        }
         | 
| 208 | 
            +
                        Err(e) => {
         | 
| 209 | 
            +
                            error!("Error sending request: {}", e);
         | 
| 210 | 
            +
                            error_response
         | 
| 211 | 
            +
                        }
         | 
| 212 | 
            +
                    };
         | 
| 213 | 
            +
                    Ok(Either::Right(response))
         | 
| 214 | 
            +
                }
         | 
| 215 | 
            +
            }
         | 
| 216 | 
            +
            impl FromValue for Proxy {}
         | 
| @@ -0,0 +1,124 @@ | |
| 1 | 
            +
            use super::{token_source::TokenSource, ErrorResponse, FromValue, MiddlewareLayer};
         | 
| 2 | 
            +
            use crate::server::{
         | 
| 3 | 
            +
                itsi_service::RequestContext,
         | 
| 4 | 
            +
                rate_limiter::{
         | 
| 5 | 
            +
                    create_rate_limit_key, get_rate_limiter, RateLimitError, RateLimiter, RateLimiterConfig,
         | 
| 6 | 
            +
                },
         | 
| 7 | 
            +
                types::{HttpRequest, HttpResponse, RequestExt},
         | 
| 8 | 
            +
            };
         | 
| 9 | 
            +
            use async_trait::async_trait;
         | 
| 10 | 
            +
            use either::Either;
         | 
| 11 | 
            +
            use magnus::error::Result;
         | 
| 12 | 
            +
            use serde::Deserialize;
         | 
| 13 | 
            +
            use std::sync::{Arc, OnceLock};
         | 
| 14 | 
            +
            use std::time::Duration;
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            #[derive(Debug, Clone, Deserialize)]
         | 
| 17 | 
            +
            pub struct RateLimit {
         | 
| 18 | 
            +
                pub requests: u64,
         | 
| 19 | 
            +
                pub seconds: u64,
         | 
| 20 | 
            +
                pub key: RateLimitKey,
         | 
| 21 | 
            +
                #[serde(skip_deserializing)]
         | 
| 22 | 
            +
                pub rate_limiter: OnceLock<Arc<dyn RateLimiter>>,
         | 
| 23 | 
            +
                pub store_config: RateLimiterConfig,
         | 
| 24 | 
            +
                pub error_response: ErrorResponse,
         | 
| 25 | 
            +
            }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            #[derive(Debug, Clone, Deserialize)]
         | 
| 28 | 
            +
            pub enum RateLimitKey {
         | 
| 29 | 
            +
                #[serde(rename(deserialize = "address"))]
         | 
| 30 | 
            +
                SocketAddress,
         | 
| 31 | 
            +
                #[serde(rename(deserialize = "parameter"))]
         | 
| 32 | 
            +
                Parameter(TokenSource),
         | 
| 33 | 
            +
            }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            #[async_trait]
         | 
| 36 | 
            +
            impl MiddlewareLayer for RateLimit {
         | 
| 37 | 
            +
                async fn initialize(&self) -> Result<()> {
         | 
| 38 | 
            +
                    // Instantiate our rate limiter based on the rate limit config here.
         | 
| 39 | 
            +
                    // This will automatically fall back to in-memory if Redis fails
         | 
| 40 | 
            +
                    if let Ok(limiter) = get_rate_limiter(&self.store_config).await {
         | 
| 41 | 
            +
                        let _ = self.rate_limiter.set(limiter);
         | 
| 42 | 
            +
                    }
         | 
| 43 | 
            +
                    Ok(())
         | 
| 44 | 
            +
                }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                async fn before(
         | 
| 47 | 
            +
                    &self,
         | 
| 48 | 
            +
                    req: HttpRequest,
         | 
| 49 | 
            +
                    context: &mut RequestContext,
         | 
| 50 | 
            +
                ) -> Result<Either<HttpRequest, HttpResponse>> {
         | 
| 51 | 
            +
                    // Get the key to rate limit on
         | 
| 52 | 
            +
                    let key_value = match &self.key {
         | 
| 53 | 
            +
                        RateLimitKey::SocketAddress => {
         | 
| 54 | 
            +
                            // Use the socket address from the context
         | 
| 55 | 
            +
                            &context.addr
         | 
| 56 | 
            +
                        }
         | 
| 57 | 
            +
                        RateLimitKey::Parameter(token_source) => {
         | 
| 58 | 
            +
                            match token_source {
         | 
| 59 | 
            +
                                TokenSource::Header { name, prefix } => {
         | 
| 60 | 
            +
                                    if let Some(header) = req.header(name) {
         | 
| 61 | 
            +
                                        if let Some(prefix) = prefix {
         | 
| 62 | 
            +
                                            header.strip_prefix(prefix).unwrap_or("").trim_ascii()
         | 
| 63 | 
            +
                                        } else {
         | 
| 64 | 
            +
                                            header.trim_ascii()
         | 
| 65 | 
            +
                                        }
         | 
| 66 | 
            +
                                    } else {
         | 
| 67 | 
            +
                                        // If no token is found, skip rate limiting
         | 
| 68 | 
            +
                                        tracing::warn!("No token found in header for rate limiting");
         | 
| 69 | 
            +
                                        return Ok(Either::Left(req));
         | 
| 70 | 
            +
                                    }
         | 
| 71 | 
            +
                                }
         | 
| 72 | 
            +
                                TokenSource::Query(query_name) => {
         | 
| 73 | 
            +
                                    if let Some(value) = req.query_param(query_name) {
         | 
| 74 | 
            +
                                        value
         | 
| 75 | 
            +
                                    } else {
         | 
| 76 | 
            +
                                        // If no token is found, skip rate limiting
         | 
| 77 | 
            +
                                        tracing::warn!("No token found in query for rate limiting");
         | 
| 78 | 
            +
                                        return Ok(Either::Left(req));
         | 
| 79 | 
            +
                                    }
         | 
| 80 | 
            +
                                }
         | 
| 81 | 
            +
                            }
         | 
| 82 | 
            +
                        }
         | 
| 83 | 
            +
                    };
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    // Create a rate limit key
         | 
| 86 | 
            +
                    let rate_limit_key = create_rate_limit_key(key_value, req.uri().path());
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    // Get the rate limiter
         | 
| 89 | 
            +
                    if let Some(limiter) = self.rate_limiter.get() {
         | 
| 90 | 
            +
                        // Check if rate limit is exceeded
         | 
| 91 | 
            +
                        let timeout = Duration::from_secs(self.seconds);
         | 
| 92 | 
            +
                        let limit = self.requests;
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                        match limiter.check_limit(&rate_limit_key, limit, timeout).await {
         | 
| 95 | 
            +
                            Ok(_) => {
         | 
| 96 | 
            +
                                // Rate limit not exceeded, allow request
         | 
| 97 | 
            +
                                Ok(Either::Left(req))
         | 
| 98 | 
            +
                            }
         | 
| 99 | 
            +
                            Err(RateLimitError::RateLimitExceeded { limit, count }) => {
         | 
| 100 | 
            +
                                // Rate limit exceeded, return error response
         | 
| 101 | 
            +
                                tracing::info!(
         | 
| 102 | 
            +
                                    "Rate limit exceeded for key '{}': {}/{} requests",
         | 
| 103 | 
            +
                                    rate_limit_key,
         | 
| 104 | 
            +
                                    count,
         | 
| 105 | 
            +
                                    limit
         | 
| 106 | 
            +
                                );
         | 
| 107 | 
            +
                                Ok(Either::Right(
         | 
| 108 | 
            +
                                    self.error_response.to_http_response(&req).await,
         | 
| 109 | 
            +
                                ))
         | 
| 110 | 
            +
                            }
         | 
| 111 | 
            +
                            Err(e) => {
         | 
| 112 | 
            +
                                // Other error, log and allow request (fail open)
         | 
| 113 | 
            +
                                tracing::error!("Rate limiter error: {:?}", e);
         | 
| 114 | 
            +
                                Ok(Either::Left(req))
         | 
| 115 | 
            +
                            }
         | 
| 116 | 
            +
                        }
         | 
| 117 | 
            +
                    } else {
         | 
| 118 | 
            +
                        // If rate limiter is not initialized, allow request
         | 
| 119 | 
            +
                        tracing::warn!("Rate limiter not initialized");
         | 
| 120 | 
            +
                        Ok(Either::Left(req))
         | 
| 121 | 
            +
                    }
         | 
| 122 | 
            +
                }
         | 
| 123 | 
            +
            }
         | 
| 124 | 
            +
            impl FromValue for RateLimit {}
         | 
| @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            use crate::server::{
         | 
| 2 | 
            +
                itsi_service::RequestContext,
         | 
| 3 | 
            +
                types::{HttpRequest, HttpResponse},
         | 
| 4 | 
            +
            };
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            use async_trait::async_trait;
         | 
| 9 | 
            +
            use either::Either;
         | 
| 10 | 
            +
            use http::{Response, StatusCode};
         | 
| 11 | 
            +
            use http_body_util::{combinators::BoxBody, Empty};
         | 
| 12 | 
            +
            use magnus::error::Result;
         | 
| 13 | 
            +
            use serde::Deserialize;
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            /// A simple API key filter.
         | 
| 16 | 
            +
            /// The API key can be given inside the header or a query string
         | 
| 17 | 
            +
            /// Keys are validated against a list of allowed key values (Changing these requires a restart)
         | 
| 18 | 
            +
            ///
         | 
| 19 | 
            +
            #[derive(Debug, Clone, Deserialize)]
         | 
| 20 | 
            +
            pub struct Redirect {
         | 
| 21 | 
            +
                pub to: StringRewrite,
         | 
| 22 | 
            +
                #[serde(default)]
         | 
| 23 | 
            +
                #[serde(rename(deserialize = "type"))]
         | 
| 24 | 
            +
                pub redirect_type: RedirectType,
         | 
| 25 | 
            +
            }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            #[derive(Debug, Clone, Deserialize, Default)]
         | 
| 28 | 
            +
            pub enum RedirectType {
         | 
| 29 | 
            +
                #[serde(rename(deserialize = "permanent"))]
         | 
| 30 | 
            +
                #[default]
         | 
| 31 | 
            +
                Permanent,
         | 
| 32 | 
            +
                #[serde(rename(deserialize = "temporary"))]
         | 
| 33 | 
            +
                Temporary,
         | 
| 34 | 
            +
                #[serde(rename(deserialize = "found"))]
         | 
| 35 | 
            +
                Found,
         | 
| 36 | 
            +
                #[serde(rename(deserialize = "moved_permanently"))]
         | 
| 37 | 
            +
                MovedPermanently,
         | 
| 38 | 
            +
            }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            #[async_trait]
         | 
| 41 | 
            +
            impl MiddlewareLayer for Redirect {
         | 
| 42 | 
            +
                async fn before(
         | 
| 43 | 
            +
                    &self,
         | 
| 44 | 
            +
                    req: HttpRequest,
         | 
| 45 | 
            +
                    context: &mut RequestContext,
         | 
| 46 | 
            +
                ) -> Result<Either<HttpRequest, HttpResponse>> {
         | 
| 47 | 
            +
                    Ok(Either::Right(self.redirect_response(&req, context)?))
         | 
| 48 | 
            +
                }
         | 
| 49 | 
            +
            }
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            impl Redirect {
         | 
| 52 | 
            +
                pub fn redirect_response(
         | 
| 53 | 
            +
                    &self,
         | 
| 54 | 
            +
                    req: &HttpRequest,
         | 
| 55 | 
            +
                    context: &mut RequestContext,
         | 
| 56 | 
            +
                ) -> Result<HttpResponse> {
         | 
| 57 | 
            +
                    let mut response = Response::new(BoxBody::new(Empty::new()));
         | 
| 58 | 
            +
                    *response.status_mut() = match self.redirect_type {
         | 
| 59 | 
            +
                        RedirectType::Permanent => StatusCode::PERMANENT_REDIRECT,
         | 
| 60 | 
            +
                        RedirectType::Temporary => StatusCode::TEMPORARY_REDIRECT,
         | 
| 61 | 
            +
                        RedirectType::MovedPermanently => StatusCode::MOVED_PERMANENTLY,
         | 
| 62 | 
            +
                        RedirectType::Found => StatusCode::FOUND,
         | 
| 63 | 
            +
                    };
         | 
| 64 | 
            +
                    response.headers_mut().append(
         | 
| 65 | 
            +
                        "Location",
         | 
| 66 | 
            +
                        self.to.rewrite_request(req, context).parse().map_err(|e| {
         | 
| 67 | 
            +
                            magnus::Error::new(
         | 
| 68 | 
            +
                                magnus::exception::exception(),
         | 
| 69 | 
            +
                                format!("Invalid Rewrite String: {:?}: {}", self.to, e),
         | 
| 70 | 
            +
                            )
         | 
| 71 | 
            +
                        })?,
         | 
| 72 | 
            +
                    );
         | 
| 73 | 
            +
                    Ok(response)
         | 
| 74 | 
            +
                }
         | 
| 75 | 
            +
            }
         | 
| 76 | 
            +
            impl FromValue for Redirect {}
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            use std::collections::HashMap;
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            use super::{FromValue, MiddlewareLayer};
         | 
| 4 | 
            +
            use crate::server::{
         | 
| 5 | 
            +
                itsi_service::RequestContext,
         | 
| 6 | 
            +
                types::{HttpRequest, HttpResponse},
         | 
| 7 | 
            +
            };
         | 
| 8 | 
            +
            use async_trait::async_trait;
         | 
| 9 | 
            +
            use either::Either;
         | 
| 10 | 
            +
            use http::HeaderName;
         | 
| 11 | 
            +
            use magnus::error::Result;
         | 
| 12 | 
            +
            use serde::Deserialize;
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            #[derive(Debug, Clone, Deserialize)]
         | 
| 15 | 
            +
            pub struct RequestHeaders {
         | 
| 16 | 
            +
                pub additions: HashMap<String, Vec<String>>,
         | 
| 17 | 
            +
                pub removals: Vec<String>,
         | 
| 18 | 
            +
            }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            #[async_trait]
         | 
| 21 | 
            +
            impl MiddlewareLayer for RequestHeaders {
         | 
| 22 | 
            +
                async fn before(
         | 
| 23 | 
            +
                    &self,
         | 
| 24 | 
            +
                    mut req: HttpRequest,
         | 
| 25 | 
            +
                    _: &mut RequestContext,
         | 
| 26 | 
            +
                ) -> Result<Either<HttpRequest, HttpResponse>> {
         | 
| 27 | 
            +
                    let headers = req.headers_mut();
         | 
| 28 | 
            +
                    for removal in &self.removals {
         | 
| 29 | 
            +
                        headers.remove(removal);
         | 
| 30 | 
            +
                    }
         | 
| 31 | 
            +
                    for (header_name, header_values) in &self.additions {
         | 
| 32 | 
            +
                        for header_value in header_values {
         | 
| 33 | 
            +
                            if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
         | 
| 34 | 
            +
                                if let Ok(parsed_header_value) = header_value.parse() {
         | 
| 35 | 
            +
                                    headers.append(parsed_header_name, parsed_header_value);
         | 
| 36 | 
            +
                                }
         | 
| 37 | 
            +
                            }
         | 
| 38 | 
            +
                        }
         | 
| 39 | 
            +
                    }
         | 
| 40 | 
            +
                    Ok(Either::Left(req))
         | 
| 41 | 
            +
                }
         | 
| 42 | 
            +
            }
         | 
| 43 | 
            +
            impl FromValue for RequestHeaders {}
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            use std::collections::HashMap;
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            use super::{FromValue, MiddlewareLayer};
         | 
| 4 | 
            +
            use crate::server::{itsi_service::RequestContext, types::HttpResponse};
         | 
| 5 | 
            +
            use async_trait::async_trait;
         | 
| 6 | 
            +
            use http::HeaderName;
         | 
| 7 | 
            +
            use serde::Deserialize;
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            #[derive(Debug, Clone, Deserialize)]
         | 
| 10 | 
            +
            pub struct ResponseHeaders {
         | 
| 11 | 
            +
                pub additions: HashMap<String, Vec<String>>,
         | 
| 12 | 
            +
                pub removals: Vec<String>,
         | 
| 13 | 
            +
            }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            #[async_trait]
         | 
| 16 | 
            +
            impl MiddlewareLayer for ResponseHeaders {
         | 
| 17 | 
            +
                async fn after(&self, mut resp: HttpResponse, _: &mut RequestContext) -> HttpResponse {
         | 
| 18 | 
            +
                    let headers = resp.headers_mut();
         | 
| 19 | 
            +
                    for removal in &self.removals {
         | 
| 20 | 
            +
                        headers.remove(removal);
         | 
| 21 | 
            +
                    }
         | 
| 22 | 
            +
                    for (header_name, header_values) in &self.additions {
         | 
| 23 | 
            +
                        for header_value in header_values {
         | 
| 24 | 
            +
                            if let Ok(parsed_header_name) = header_name.parse::<HeaderName>() {
         | 
| 25 | 
            +
                                if let Ok(parsed_header_value) = header_value.parse() {
         | 
| 26 | 
            +
                                    headers.append(parsed_header_name, parsed_header_value);
         | 
| 27 | 
            +
                                }
         | 
| 28 | 
            +
                            }
         | 
| 29 | 
            +
                        }
         | 
| 30 | 
            +
                    }
         | 
| 31 | 
            +
                    resp
         | 
| 32 | 
            +
                }
         | 
| 33 | 
            +
            }
         | 
| 34 | 
            +
            impl FromValue for ResponseHeaders {}
         | 
| @@ -0,0 +1,93 @@ | |
| 1 | 
            +
            use super::MiddlewareLayer;
         | 
| 2 | 
            +
            use crate::ruby_types::itsi_grpc_request::ItsiGrpcRequest;
         | 
| 3 | 
            +
            use crate::server::static_file_server::ROOT_STATIC_FILE_SERVER;
         | 
| 4 | 
            +
            use crate::{
         | 
| 5 | 
            +
                ruby_types::itsi_http_request::ItsiHttpRequest,
         | 
| 6 | 
            +
                server::{
         | 
| 7 | 
            +
                    itsi_service::RequestContext,
         | 
| 8 | 
            +
                    types::{HttpRequest, HttpResponse},
         | 
| 9 | 
            +
                },
         | 
| 10 | 
            +
            };
         | 
| 11 | 
            +
            use async_trait::async_trait;
         | 
| 12 | 
            +
            use derive_more::Debug;
         | 
| 13 | 
            +
            use either::Either;
         | 
| 14 | 
            +
            use itsi_rb_helpers::{HeapVal, HeapValue};
         | 
| 15 | 
            +
            use magnus::{block::Proc, error::Result, value::ReprValue, Symbol};
         | 
| 16 | 
            +
            use std::str::FromStr;
         | 
| 17 | 
            +
            use std::sync::Arc;
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            #[derive(Debug)]
         | 
| 20 | 
            +
            pub struct RubyApp {
         | 
| 21 | 
            +
                app: Arc<HeapValue<Proc>>,
         | 
| 22 | 
            +
                request_type: RequestType,
         | 
| 23 | 
            +
                sendfile: bool,
         | 
| 24 | 
            +
            }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            #[derive(Debug)]
         | 
| 27 | 
            +
            pub enum RequestType {
         | 
| 28 | 
            +
                Http,
         | 
| 29 | 
            +
                Grpc,
         | 
| 30 | 
            +
            }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            impl FromStr for RequestType {
         | 
| 33 | 
            +
                type Err = &'static str;
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
         | 
| 36 | 
            +
                    match s {
         | 
| 37 | 
            +
                        "http" => Ok(RequestType::Http),
         | 
| 38 | 
            +
                        "grpc" => Ok(RequestType::Grpc),
         | 
| 39 | 
            +
                        _ => Err("Invalid request type"),
         | 
| 40 | 
            +
                    }
         | 
| 41 | 
            +
                }
         | 
| 42 | 
            +
            }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            impl RubyApp {
         | 
| 45 | 
            +
                pub fn from_value(params: HeapVal) -> magnus::error::Result<Self> {
         | 
| 46 | 
            +
                    let app = params.funcall::<_, _, Proc>(Symbol::new("[]"), ("app_proc",))?;
         | 
| 47 | 
            +
                    let sendfile = params
         | 
| 48 | 
            +
                        .funcall::<_, _, bool>(Symbol::new("[]"), ("sendfile",))
         | 
| 49 | 
            +
                        .unwrap_or(true);
         | 
| 50 | 
            +
                    let request_type: RequestType = params
         | 
| 51 | 
            +
                        .funcall::<_, _, String>(Symbol::new("[]"), ("request_type",))
         | 
| 52 | 
            +
                        .unwrap_or("http".to_string())
         | 
| 53 | 
            +
                        .parse()
         | 
| 54 | 
            +
                        .unwrap_or(RequestType::Http);
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    Ok(RubyApp {
         | 
| 57 | 
            +
                        app: Arc::new(app.into()),
         | 
| 58 | 
            +
                        sendfile,
         | 
| 59 | 
            +
                        request_type,
         | 
| 60 | 
            +
                    })
         | 
| 61 | 
            +
                }
         | 
| 62 | 
            +
            }
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            #[async_trait]
         | 
| 65 | 
            +
            impl MiddlewareLayer for RubyApp {
         | 
| 66 | 
            +
                async fn before(
         | 
| 67 | 
            +
                    &self,
         | 
| 68 | 
            +
                    req: HttpRequest,
         | 
| 69 | 
            +
                    context: &mut RequestContext,
         | 
| 70 | 
            +
                ) -> Result<Either<HttpRequest, HttpResponse>> {
         | 
| 71 | 
            +
                    match self.request_type {
         | 
| 72 | 
            +
                        RequestType::Http => ItsiHttpRequest::process_request(self.app.clone(), req, context)
         | 
| 73 | 
            +
                            .await
         | 
| 74 | 
            +
                            .map_err(|e| e.into())
         | 
| 75 | 
            +
                            .map(Either::Right),
         | 
| 76 | 
            +
                        RequestType::Grpc => ItsiGrpcRequest::process_request(self.app.clone(), req, context)
         | 
| 77 | 
            +
                            .await
         | 
| 78 | 
            +
                            .map_err(|e| e.into())
         | 
| 79 | 
            +
                            .map(Either::Right),
         | 
| 80 | 
            +
                    }
         | 
| 81 | 
            +
                }
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                async fn after(&self, resp: HttpResponse, _context: &mut RequestContext) -> HttpResponse {
         | 
| 84 | 
            +
                    if self.sendfile {
         | 
| 85 | 
            +
                        if let Some(sendfile_header) = resp.headers().get("X-Sendfile") {
         | 
| 86 | 
            +
                            return ROOT_STATIC_FILE_SERVER
         | 
| 87 | 
            +
                                .serve_single(sendfile_header.to_str().unwrap())
         | 
| 88 | 
            +
                                .await;
         | 
| 89 | 
            +
                        }
         | 
| 90 | 
            +
                    }
         | 
| 91 | 
            +
                    resp
         | 
| 92 | 
            +
                }
         | 
| 93 | 
            +
            }
         |