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
    
        data/ext/itsi_server/Cargo.toml
    CHANGED
    
    | @@ -10,20 +10,80 @@ publish = false | |
| 10 10 | 
             
            crate-type = ["cdylib"]
         | 
| 11 11 |  | 
| 12 12 | 
             
            [dependencies]
         | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
            rcgen = { version = "0.13.2", features = ["x509-parser", "pem"] }
         | 
| 13 | 
            +
            async-compression = { version = "0.4.21", features = [
         | 
| 14 | 
            +
              "tokio",
         | 
| 15 | 
            +
              "zstd",
         | 
| 16 | 
            +
              "brotli",
         | 
| 17 | 
            +
              "deflate",
         | 
| 18 | 
            +
              "gzip",
         | 
| 19 | 
            +
            ] }
         | 
| 20 | 
            +
            async-channel = "2.3.1"
         | 
| 21 | 
            +
            async-trait = "0.1.87"
         | 
| 23 22 | 
             
            base64 = "0.22.1"
         | 
| 23 | 
            +
            bytes = "1.3"
         | 
| 24 | 
            +
            chrono = "0.4.35"
         | 
| 25 | 
            +
            crossbeam = "0.8.4"
         | 
| 26 | 
            +
            dashmap = "6.1.0"
         | 
| 27 | 
            +
            derive_more = { version = "2.0.1", features = ["debug"] }
         | 
| 28 | 
            +
            dirs = "6.0.0"
         | 
| 29 | 
            +
            either = "1.15.0"
         | 
| 30 | 
            +
            fnv = "1.0.7"
         | 
| 31 | 
            +
            fs2 = "0.4.3"
         | 
| 32 | 
            +
            futures = "0.3.31"
         | 
| 33 | 
            +
            globset = "0.4.16"
         | 
| 34 | 
            +
            hmac = "0.12.1"
         | 
| 35 | 
            +
            http = "1.3.1"
         | 
| 24 36 | 
             
            http-body-util = "0.1.2"
         | 
| 37 | 
            +
            httpdate = "1.0.3"
         | 
| 38 | 
            +
            httparse = "1.10.1"
         | 
| 25 39 | 
             
            hyper = { version = "1.5.0", features = ["full", "server", "http1", "http2"] }
         | 
| 26 | 
            -
             | 
| 40 | 
            +
            hyper-staticfile = "0.10.1"
         | 
| 27 41 | 
             
            hyper-util = { version = "0.1.10", features = ["full"] }
         | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 42 | 
            +
            itsi_error = { path = "../itsi_error" }
         | 
| 43 | 
            +
            itsi_rb_helpers = { path = "../itsi_rb_helpers" }
         | 
| 44 | 
            +
            itsi_tracing = { path = "../itsi_tracing" }
         | 
| 45 | 
            +
            jwt-simple = "0.12.12"
         | 
| 46 | 
            +
            magnus = { version = "0.7.1", features = ["bytes", "rb-sys"] }
         | 
| 47 | 
            +
            moka = { version = "0.12.10", features = ["sync"] }
         | 
| 48 | 
            +
            notify = { version = "8.0.0" }
         | 
| 49 | 
            +
            nix = { version = "0.29.0", features = [
         | 
| 50 | 
            +
              "socket",
         | 
| 51 | 
            +
              "uio",
         | 
| 52 | 
            +
              "signal",
         | 
| 53 | 
            +
              "fs",
         | 
| 54 | 
            +
              "process",
         | 
| 55 | 
            +
            ] }
         | 
| 56 | 
            +
            num_cpus = "1.16.0"
         | 
| 57 | 
            +
            parking_lot = "0.12.3"
         | 
| 58 | 
            +
            pin-project = "1.1.9"
         | 
| 59 | 
            +
            rand = "0.9.0"
         | 
| 60 | 
            +
            rb-sys = "0.9.111"
         | 
| 61 | 
            +
            rcgen = { version = "0.13.2", features = ["x509-parser", "pem"] }
         | 
| 62 | 
            +
            regex = "1.11.1"
         | 
| 63 | 
            +
            reqwest = { version = "0.12.15", features = ["stream"] }
         | 
| 64 | 
            +
            ring = "0.17.14"
         | 
| 65 | 
            +
            route-recognizer = "0.3.1"
         | 
| 66 | 
            +
            redis = { version = "0.29.2", features = [
         | 
| 67 | 
            +
              "tokio-comp",
         | 
| 68 | 
            +
              "r2d2",
         | 
| 69 | 
            +
              "tokio-rustls-comp",
         | 
| 70 | 
            +
              "connection-manager",
         | 
| 71 | 
            +
            ] }
         | 
| 72 | 
            +
            rustls = "0.23.23"
         | 
| 73 | 
            +
            rustls-pemfile = "2.2.0"
         | 
| 74 | 
            +
            serde = "1.0.219"
         | 
| 75 | 
            +
            serde_json = "1.0.140"
         | 
| 76 | 
            +
            serde_magnus = "0.9.0"
         | 
| 77 | 
            +
            sha2 = "0.10.8"
         | 
| 78 | 
            +
            socket2 = "0.5.8"
         | 
| 79 | 
            +
            sysinfo = "0.33.1"
         | 
| 80 | 
            +
            tempfile = "3.18.0"
         | 
| 81 | 
            +
            tokio = { version = "1.44.1", features = ["full"] }
         | 
| 82 | 
            +
            tokio-rustls = "0.26.2"
         | 
| 83 | 
            +
            tokio-rustls-acme = "0.6.0"
         | 
| 84 | 
            +
            tokio-stream = "0.1.17"
         | 
| 85 | 
            +
            tokio-util = "0.7.13"
         | 
| 86 | 
            +
            tracing = "0.1.41"
         | 
| 87 | 
            +
            url = "2.5.4"
         | 
| 88 | 
            +
            uuid = "1.16.0"
         | 
| 89 | 
            +
            md5 = "0.7.0"
         | 
    
        data/ext/itsi_server/extconf.rb
    CHANGED
    
    
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            use std::{
         | 
| 2 | 
            +
                env::{var, VarError},
         | 
| 3 | 
            +
                path::PathBuf,
         | 
| 4 | 
            +
                sync::LazyLock,
         | 
| 5 | 
            +
            };
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            type StringVar = LazyLock<String>;
         | 
| 8 | 
            +
            type MaybeStringVar = LazyLock<Result<String, VarError>>;
         | 
| 9 | 
            +
            type PathVar = LazyLock<PathBuf>;
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            /// ACME Configuration for auto-generating production certificates
         | 
| 12 | 
            +
            /// *ITSI_ACME_CACHE_DIR* - Directory to store cached certificates
         | 
| 13 | 
            +
            /// so that these are not regenerated every time the server starts
         | 
| 14 | 
            +
            pub static ITSI_ACME_CACHE_DIR: StringVar = LazyLock::new(|| {
         | 
| 15 | 
            +
                var("ITSI_ACME_CACHE_DIR").unwrap_or_else(|_| "./.rustls_acme_cache".to_string())
         | 
| 16 | 
            +
            });
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            /// *ITSI_ACME_CONTACT_EMAIL* - Contact Email address to provide to ACME server during certificate renewal
         | 
| 19 | 
            +
            pub static ITSI_ACME_CONTACT_EMAIL: MaybeStringVar =
         | 
| 20 | 
            +
                LazyLock::new(|| var("ITSI_ACME_CONTACT_EMAIL"));
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            /// *ITSI_ACME_CA_PEM_PATH* - Optional CA Pem path, used for testing with non-trusted CAs for certifcate generation.
         | 
| 23 | 
            +
            pub static ITSI_ACME_CA_PEM_PATH: MaybeStringVar = LazyLock::new(|| var("ITSI_ACME_CA_PEM_PATH"));
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            /// *ITSI_ACME_DIRECTORY_URL* - Directory URL to use for ACME certificate generation.
         | 
| 26 | 
            +
            pub static ITSI_ACME_DIRECTORY_URL: StringVar = LazyLock::new(|| {
         | 
| 27 | 
            +
                var("ITSI_ACME_DIRECTORY_URL")
         | 
| 28 | 
            +
                    .unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string())
         | 
| 29 | 
            +
            });
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            /// *ITSI_ACME_LOCK_FILE_NAME* - Name of the lock file used to prevent concurrent certificate generation.
         | 
| 32 | 
            +
            pub static ITSI_ACME_LOCK_FILE_NAME: StringVar =
         | 
| 33 | 
            +
                LazyLock::new(|| var("ITSI_ACME_LOCK_FILE_NAME").unwrap_or(".acme.lock".to_string()));
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            pub static ITSI_LOCAL_CA_DIR: PathVar = LazyLock::new(|| {
         | 
| 36 | 
            +
                var("ITSI_LOCAL_CA_DIR")
         | 
| 37 | 
            +
                    .map(PathBuf::from)
         | 
| 38 | 
            +
                    .unwrap_or_else(|_| {
         | 
| 39 | 
            +
                        dirs::home_dir()
         | 
| 40 | 
            +
                            .expect("Failed to find HOME directory when initializing ITSI_LOCAL_CA_DIR")
         | 
| 41 | 
            +
                            .join(".itsi")
         | 
| 42 | 
            +
                    })
         | 
| 43 | 
            +
            });
         | 
    
        data/ext/itsi_server/src/lib.rs
    CHANGED
    
    | @@ -1,52 +1,112 @@ | |
| 1 | 
            -
            use magnus::{error::Result, function, method,  | 
| 2 | 
            -
            use  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 1 | 
            +
            use magnus::{error::Result, function, method, Module, Object, Ruby};
         | 
| 2 | 
            +
            use ruby_types::{
         | 
| 3 | 
            +
                itsi_body_proxy::ItsiBodyProxy, itsi_grpc_request::ItsiGrpcRequest,
         | 
| 4 | 
            +
                itsi_grpc_stream::ItsiGrpcStream, itsi_http_request::ItsiHttpRequest,
         | 
| 5 | 
            +
                itsi_http_response::ItsiHttpResponse, itsi_server::ItsiServer, ITSI_BODY_PROXY,
         | 
| 6 | 
            +
                ITSI_GRPC_REQUEST, ITSI_GRPC_RESPONSE, ITSI_GRPC_STREAM, ITSI_MODULE, ITSI_REQUEST,
         | 
| 7 | 
            +
                ITSI_RESPONSE, ITSI_SERVER,
         | 
| 8 | 
            +
            };
         | 
| 9 | 
            +
            use server::signal::reset_signal_handlers;
         | 
| 10 | 
            +
            use tracing::*;
         | 
| 11 | 
            +
            pub mod env;
         | 
| 12 | 
            +
            pub mod ruby_types;
         | 
| 7 13 | 
             
            pub mod server;
         | 
| 8 | 
            -
            pub mod stream_writer;
         | 
| 9 | 
            -
             | 
| 10 | 
            -
            pub static ITSI_MODULE: Lazy<RModule> = Lazy::new(|ruby| ruby.define_module("Itsi").unwrap());
         | 
| 11 | 
            -
            pub static ITSI_SERVER: Lazy<RClass> = Lazy::new(|ruby| {
         | 
| 12 | 
            -
                ruby.get_inner(&ITSI_MODULE)
         | 
| 13 | 
            -
                    .define_class("Server", ruby.class_object())
         | 
| 14 | 
            -
                    .unwrap()
         | 
| 15 | 
            -
            });
         | 
| 16 | 
            -
            pub static ITSI_REQUEST: Lazy<RClass> = Lazy::new(|ruby| {
         | 
| 17 | 
            -
                ruby.get_inner(&ITSI_MODULE)
         | 
| 18 | 
            -
                    .define_class("Request", ruby.class_object())
         | 
| 19 | 
            -
                    .unwrap()
         | 
| 20 | 
            -
            });
         | 
| 21 | 
            -
            pub static ITSI_STREAM_WRITER: Lazy<RClass> = Lazy::new(|ruby| {
         | 
| 22 | 
            -
                ruby.get_inner(&ITSI_MODULE)
         | 
| 23 | 
            -
                    .define_class("StreamWriter", ruby.class_object())
         | 
| 24 | 
            -
                    .unwrap()
         | 
| 25 | 
            -
            });
         | 
| 26 14 |  | 
| 27 15 | 
             
            #[magnus::init]
         | 
| 28 16 | 
             
            fn init(ruby: &Ruby) -> Result<()> {
         | 
| 29 17 | 
             
                itsi_tracing::init();
         | 
| 18 | 
            +
                rustls::crypto::aws_lc_rs::default_provider()
         | 
| 19 | 
            +
                    .install_default()
         | 
| 20 | 
            +
                    .ok();
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                let itsi = ruby.get_inner(&ITSI_MODULE);
         | 
| 23 | 
            +
                itsi.define_singleton_method("log_debug", function!(log_debug, 1))?;
         | 
| 24 | 
            +
                itsi.define_singleton_method("log_info", function!(log_info, 1))?;
         | 
| 25 | 
            +
                itsi.define_singleton_method("log_warn", function!(log_warn, 1))?;
         | 
| 26 | 
            +
                itsi.define_singleton_method("log_error", function!(log_error, 1))?;
         | 
| 30 27 |  | 
| 31 28 | 
             
                let server = ruby.get_inner(&ITSI_SERVER);
         | 
| 32 | 
            -
                server.define_singleton_method("new", function!( | 
| 33 | 
            -
                server. | 
| 29 | 
            +
                server.define_singleton_method("new", function!(ItsiServer::new, 3))?;
         | 
| 30 | 
            +
                server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
         | 
| 31 | 
            +
                server.define_method("start", method!(ItsiServer::start, 0))?;
         | 
| 32 | 
            +
                server.define_method("stop", method!(ItsiServer::stop, 0))?;
         | 
| 34 33 |  | 
| 35 34 | 
             
                let request = ruby.get_inner(&ITSI_REQUEST);
         | 
| 36 | 
            -
                request.define_method("path", method!( | 
| 37 | 
            -
                request.define_method("script_name", method!( | 
| 38 | 
            -
                request.define_method("query_string", method!( | 
| 39 | 
            -
                request.define_method("method", method!( | 
| 40 | 
            -
                request.define_method("version", method!( | 
| 41 | 
            -
                request.define_method("rack_protocol", method!( | 
| 42 | 
            -
                request.define_method("host", method!( | 
| 43 | 
            -
                request.define_method("headers", method!( | 
| 44 | 
            -
                request.define_method(" | 
| 45 | 
            -
                request.define_method(" | 
| 46 | 
            -
                request.define_method(" | 
| 47 | 
            -
             | 
| 48 | 
            -
                 | 
| 49 | 
            -
                 | 
| 35 | 
            +
                request.define_method("path", method!(ItsiHttpRequest::path, 0))?;
         | 
| 36 | 
            +
                request.define_method("script_name", method!(ItsiHttpRequest::script_name, 0))?;
         | 
| 37 | 
            +
                request.define_method("query_string", method!(ItsiHttpRequest::query_string, 0))?;
         | 
| 38 | 
            +
                request.define_method("method", method!(ItsiHttpRequest::method, 0))?;
         | 
| 39 | 
            +
                request.define_method("version", method!(ItsiHttpRequest::version, 0))?;
         | 
| 40 | 
            +
                request.define_method("rack_protocol", method!(ItsiHttpRequest::rack_protocol, 0))?;
         | 
| 41 | 
            +
                request.define_method("host", method!(ItsiHttpRequest::host, 0))?;
         | 
| 42 | 
            +
                request.define_method("headers", method!(ItsiHttpRequest::headers, 0))?;
         | 
| 43 | 
            +
                request.define_method("header", method!(ItsiHttpRequest::header, 1))?;
         | 
| 44 | 
            +
                request.define_method("[]", method!(ItsiHttpRequest::header, 1))?;
         | 
| 45 | 
            +
                request.define_method("scheme", method!(ItsiHttpRequest::scheme, 0))?;
         | 
| 46 | 
            +
                request.define_method("remote_addr", method!(ItsiHttpRequest::remote_addr, 0))?;
         | 
| 47 | 
            +
                request.define_method("port", method!(ItsiHttpRequest::port, 0))?;
         | 
| 48 | 
            +
                request.define_method("body", method!(ItsiHttpRequest::body, 0))?;
         | 
| 49 | 
            +
                request.define_method("response", method!(ItsiHttpRequest::response, 0))?;
         | 
| 50 | 
            +
                request.define_method("json?", method!(ItsiHttpRequest::is_json, 0))?;
         | 
| 51 | 
            +
                request.define_method("html?", method!(ItsiHttpRequest::is_html, 0))?;
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY);
         | 
| 54 | 
            +
                body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?;
         | 
| 55 | 
            +
                body_proxy.define_method("each", method!(ItsiBodyProxy::each, 0))?;
         | 
| 56 | 
            +
                body_proxy.define_method("read", method!(ItsiBodyProxy::read, -1))?;
         | 
| 57 | 
            +
                body_proxy.define_method("close", method!(ItsiBodyProxy::close, 0))?;
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                let response = ruby.get_inner(&ITSI_RESPONSE);
         | 
| 60 | 
            +
                response.define_method("[]=", method!(ItsiHttpResponse::add_header, 2))?;
         | 
| 61 | 
            +
                response.define_method("add_header", method!(ItsiHttpResponse::add_header, 2))?;
         | 
| 62 | 
            +
                response.define_method("add_headers", method!(ItsiHttpResponse::add_headers, 1))?;
         | 
| 63 | 
            +
                response.define_method("status=", method!(ItsiHttpResponse::set_status, 1))?;
         | 
| 64 | 
            +
                response.define_method("send_frame", method!(ItsiHttpResponse::send_frame, 1))?;
         | 
| 65 | 
            +
                response.define_method("<<", method!(ItsiHttpResponse::send_frame, 1))?;
         | 
| 66 | 
            +
                response.define_method("write", method!(ItsiHttpResponse::send_frame, 1))?;
         | 
| 67 | 
            +
                response.define_method("read", method!(ItsiHttpResponse::recv_frame, 0))?;
         | 
| 68 | 
            +
                response.define_method(
         | 
| 69 | 
            +
                    "send_and_close",
         | 
| 70 | 
            +
                    method!(ItsiHttpResponse::send_and_close, 1),
         | 
| 71 | 
            +
                )?;
         | 
| 72 | 
            +
                response.define_method("close_write", method!(ItsiHttpResponse::close_write, 0))?;
         | 
| 73 | 
            +
                response.define_method("close_read", method!(ItsiHttpResponse::close_read, 0))?;
         | 
| 74 | 
            +
                response.define_method("close", method!(ItsiHttpResponse::close, 0))?;
         | 
| 75 | 
            +
                response.define_method("hijack", method!(ItsiHttpResponse::hijack, 1))?;
         | 
| 76 | 
            +
                response.define_method("json?", method!(ItsiHttpResponse::is_json, 0))?;
         | 
| 77 | 
            +
                response.define_method("html?", method!(ItsiHttpResponse::is_html, 0))?;
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                let grpc_request = ruby.get_inner(&ITSI_GRPC_REQUEST);
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                grpc_request.define_method("service_name", method!(ItsiGrpcRequest::service_name, 0))?;
         | 
| 82 | 
            +
                grpc_request.define_method("method_name", method!(ItsiGrpcRequest::method_name, 0))?;
         | 
| 83 | 
            +
                grpc_request.define_method("stream", method!(ItsiGrpcRequest::stream, 0))?;
         | 
| 84 | 
            +
                grpc_request.define_method("json?", method!(ItsiGrpcRequest::is_json, 0))?;
         | 
| 85 | 
            +
                grpc_request.define_method(
         | 
| 86 | 
            +
                    "content_type",
         | 
| 87 | 
            +
                    method!(ItsiGrpcRequest::content_type_str, 0),
         | 
| 88 | 
            +
                )?;
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                let grpc_stream = ruby.get_inner(&ITSI_GRPC_STREAM);
         | 
| 91 | 
            +
                grpc_stream.define_method("read", method!(ItsiGrpcStream::read, 1))?;
         | 
| 92 | 
            +
                grpc_stream.define_method("write", method!(ItsiGrpcStream::write, 1))?;
         | 
| 93 | 
            +
                grpc_stream.define_method("flush", method!(ItsiGrpcStream::flush, 0))?;
         | 
| 94 | 
            +
                grpc_stream.define_method("send_trailers", method!(ItsiGrpcStream::send_trailers, 1))?;
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                let _grpc_response = ruby.get_inner(&ITSI_GRPC_RESPONSE);
         | 
| 50 97 |  | 
| 51 98 | 
             
                Ok(())
         | 
| 52 99 | 
             
            }
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            pub fn log_debug(msg: String) {
         | 
| 102 | 
            +
                debug!(msg);
         | 
| 103 | 
            +
            }
         | 
| 104 | 
            +
            pub fn log_info(msg: String) {
         | 
| 105 | 
            +
                info!(msg);
         | 
| 106 | 
            +
            }
         | 
| 107 | 
            +
            pub fn log_warn(msg: String) {
         | 
| 108 | 
            +
                warn!(msg);
         | 
| 109 | 
            +
            }
         | 
| 110 | 
            +
            pub fn log_error(msg: String) {
         | 
| 111 | 
            +
                error!(msg);
         | 
| 112 | 
            +
            }
         | 
| @@ -0,0 +1,109 @@ | |
| 1 | 
            +
            use bytes::Bytes;
         | 
| 2 | 
            +
            use magnus::{IntoValue, Ruby, Value};
         | 
| 3 | 
            +
            use std::io::{Result as IoResult, Write};
         | 
| 4 | 
            +
            use std::path::PathBuf;
         | 
| 5 | 
            +
            use tempfile::NamedTempFile;
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            const THRESHOLD: usize = 1024 * 1024; // 1 MB
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            /// A container that will hold data in memory if it’s small, or in a temporary file on disk if it exceeds THRESHOLD.
         | 
| 10 | 
            +
            /// Used for providing Rack input data.
         | 
| 11 | 
            +
            pub enum BigBytes {
         | 
| 12 | 
            +
                InMemory(Vec<u8>),
         | 
| 13 | 
            +
                OnDisk(NamedTempFile),
         | 
| 14 | 
            +
            }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            /// The result type for reading the contents of a `BigBytes` value.
         | 
| 17 | 
            +
            pub enum BigBytesReadResult {
         | 
| 18 | 
            +
                /// When the data is stored in memory, returns the cached bytes.
         | 
| 19 | 
            +
                InMemory(Vec<u8>),
         | 
| 20 | 
            +
                /// When the data is stored on disk, returns the path to the temporary file.
         | 
| 21 | 
            +
                OnDisk(PathBuf),
         | 
| 22 | 
            +
            }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            impl BigBytes {
         | 
| 25 | 
            +
                /// Creates a new, empty BigBytes instance (initially in memory).
         | 
| 26 | 
            +
                pub fn new() -> Self {
         | 
| 27 | 
            +
                    BigBytes::InMemory(Vec::new())
         | 
| 28 | 
            +
                }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                /// Returns either the raw bytes, or the file path of the BigBytes
         | 
| 31 | 
            +
                ///
         | 
| 32 | 
            +
                /// - If stored in memory, returns a clone of the bytes.
         | 
| 33 | 
            +
                /// - If stored on disk, returns the file path of the temporary file.
         | 
| 34 | 
            +
                pub fn read(&self) -> IoResult<BigBytesReadResult> {
         | 
| 35 | 
            +
                    match self {
         | 
| 36 | 
            +
                        BigBytes::InMemory(vec) => Ok(BigBytesReadResult::InMemory(vec.clone())),
         | 
| 37 | 
            +
                        BigBytes::OnDisk(temp_file) => {
         | 
| 38 | 
            +
                            // Flush to be safe, then return the file path.
         | 
| 39 | 
            +
                            temp_file.as_file().sync_all()?;
         | 
| 40 | 
            +
                            Ok(BigBytesReadResult::OnDisk(temp_file.path().to_path_buf()))
         | 
| 41 | 
            +
                        }
         | 
| 42 | 
            +
                    }
         | 
| 43 | 
            +
                }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                /// Turn this into a value that can be used in Ruby.
         | 
| 46 | 
            +
                pub fn as_value(&self) -> Option<Value> {
         | 
| 47 | 
            +
                    match self {
         | 
| 48 | 
            +
                        BigBytes::InMemory(bytes) => {
         | 
| 49 | 
            +
                            let bytes = Bytes::from(bytes.to_owned());
         | 
| 50 | 
            +
                            if bytes.is_empty() {
         | 
| 51 | 
            +
                                None
         | 
| 52 | 
            +
                            } else {
         | 
| 53 | 
            +
                                Some(bytes.into_value())
         | 
| 54 | 
            +
                            }
         | 
| 55 | 
            +
                        }
         | 
| 56 | 
            +
                        BigBytes::OnDisk(path) => {
         | 
| 57 | 
            +
                            let ruby = Ruby::get().unwrap();
         | 
| 58 | 
            +
                            let rarray = ruby.ary_new();
         | 
| 59 | 
            +
                            rarray.push(path.path().to_str().unwrap().into_value()).ok();
         | 
| 60 | 
            +
                            Some(rarray.into_value())
         | 
| 61 | 
            +
                        }
         | 
| 62 | 
            +
                    }
         | 
| 63 | 
            +
                }
         | 
| 64 | 
            +
            }
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            impl Drop for BigBytes {
         | 
| 67 | 
            +
                fn drop(&mut self) {
         | 
| 68 | 
            +
                    match self {
         | 
| 69 | 
            +
                        BigBytes::InMemory(_) => {}
         | 
| 70 | 
            +
                        BigBytes::OnDisk(path) => {
         | 
| 71 | 
            +
                            let _ = std::fs::remove_file(path);
         | 
| 72 | 
            +
                        }
         | 
| 73 | 
            +
                    }
         | 
| 74 | 
            +
                }
         | 
| 75 | 
            +
            }
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            impl Default for BigBytes {
         | 
| 78 | 
            +
                fn default() -> Self {
         | 
| 79 | 
            +
                    Self::new()
         | 
| 80 | 
            +
                }
         | 
| 81 | 
            +
            }
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            impl Write for BigBytes {
         | 
| 84 | 
            +
                fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
         | 
| 85 | 
            +
                    match self {
         | 
| 86 | 
            +
                        BigBytes::InMemory(vec) => {
         | 
| 87 | 
            +
                            // Check if writing the new bytes would exceed the threshold.
         | 
| 88 | 
            +
                            if vec.len() + buf.len() > THRESHOLD {
         | 
| 89 | 
            +
                                let mut tmp = NamedTempFile::new()?;
         | 
| 90 | 
            +
                                tmp.write_all(vec)?;
         | 
| 91 | 
            +
                                tmp.write_all(buf)?;
         | 
| 92 | 
            +
                                *self = BigBytes::OnDisk(tmp);
         | 
| 93 | 
            +
                                Ok(buf.len())
         | 
| 94 | 
            +
                            } else {
         | 
| 95 | 
            +
                                vec.extend_from_slice(buf);
         | 
| 96 | 
            +
                                Ok(buf.len())
         | 
| 97 | 
            +
                            }
         | 
| 98 | 
            +
                        }
         | 
| 99 | 
            +
                        BigBytes::OnDisk(tmp_file) => tmp_file.write(buf),
         | 
| 100 | 
            +
                    }
         | 
| 101 | 
            +
                }
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                fn flush(&mut self) -> IoResult<()> {
         | 
| 104 | 
            +
                    match self {
         | 
| 105 | 
            +
                        BigBytes::InMemory(_) => Ok(()),
         | 
| 106 | 
            +
                        BigBytes::OnDisk(tmp_file) => tmp_file.flush(),
         | 
| 107 | 
            +
                    }
         | 
| 108 | 
            +
                }
         | 
| 109 | 
            +
            }
         | 
| @@ -0,0 +1,141 @@ | |
| 1 | 
            +
            pub mod big_bytes;
         | 
| 2 | 
            +
            use big_bytes::BigBytes;
         | 
| 3 | 
            +
            use bytes::Bytes;
         | 
| 4 | 
            +
            use futures::executor::block_on;
         | 
| 5 | 
            +
            use http_body_util::{BodyDataStream, BodyExt};
         | 
| 6 | 
            +
            use hyper::body::Incoming;
         | 
| 7 | 
            +
            use magnus::{error::Result as MagnusResult, scan_args, IntoValue, RString, Ruby, Value};
         | 
| 8 | 
            +
            use parking_lot::Mutex;
         | 
| 9 | 
            +
            use std::sync::{
         | 
| 10 | 
            +
                atomic::{self, AtomicBool},
         | 
| 11 | 
            +
                Arc,
         | 
| 12 | 
            +
            };
         | 
| 13 | 
            +
            use tokio_stream::StreamExt;
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            #[magnus::wrap(class = "Itsi::BodyProxy", free_immediately, size)]
         | 
| 16 | 
            +
            #[derive(Debug, Clone)]
         | 
| 17 | 
            +
            pub struct ItsiBodyProxy {
         | 
| 18 | 
            +
                pub incoming: Arc<Mutex<BodyDataStream<Incoming>>>,
         | 
| 19 | 
            +
                pub closed: Arc<AtomicBool>,
         | 
| 20 | 
            +
                pub buf: Arc<Mutex<Vec<u8>>>,
         | 
| 21 | 
            +
            }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            pub enum ItsiBody {
         | 
| 24 | 
            +
                Buffered(BigBytes),
         | 
| 25 | 
            +
                Stream(ItsiBodyProxy),
         | 
| 26 | 
            +
            }
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            impl ItsiBody {
         | 
| 29 | 
            +
                pub fn into_value(&self) -> Option<Value> {
         | 
| 30 | 
            +
                    match self {
         | 
| 31 | 
            +
                        ItsiBody::Buffered(bytes) => bytes.as_value(),
         | 
| 32 | 
            +
                        ItsiBody::Stream(proxy) => Some(proxy.clone().into_value()),
         | 
| 33 | 
            +
                    }
         | 
| 34 | 
            +
                }
         | 
| 35 | 
            +
            }
         | 
| 36 | 
            +
            impl ItsiBodyProxy {
         | 
| 37 | 
            +
                pub fn new(incoming: Incoming) -> Self {
         | 
| 38 | 
            +
                    ItsiBodyProxy {
         | 
| 39 | 
            +
                        incoming: Arc::new(Mutex::new(incoming.into_data_stream())),
         | 
| 40 | 
            +
                        closed: Arc::new(AtomicBool::new(false)),
         | 
| 41 | 
            +
                        buf: Arc::new(Mutex::new(vec![])),
         | 
| 42 | 
            +
                    }
         | 
| 43 | 
            +
                }
         | 
| 44 | 
            +
                /// Read up to the next line-break OR EOF
         | 
| 45 | 
            +
                pub fn gets(&self) -> MagnusResult<Option<Bytes>> {
         | 
| 46 | 
            +
                    self.verify_open()?;
         | 
| 47 | 
            +
                    let mut stream = self.incoming.lock();
         | 
| 48 | 
            +
                    let mut buf = self.buf.lock();
         | 
| 49 | 
            +
                    while !buf.contains(&b'\n') {
         | 
| 50 | 
            +
                        if let Some(chunk) = block_on(stream.next()) {
         | 
| 51 | 
            +
                            let chunk = chunk.map_err(|err| {
         | 
| 52 | 
            +
                                magnus::Error::new(
         | 
| 53 | 
            +
                                    magnus::exception::exception(),
         | 
| 54 | 
            +
                                    format!("Error reading body {:?}", err),
         | 
| 55 | 
            +
                                )
         | 
| 56 | 
            +
                            })?;
         | 
| 57 | 
            +
                            buf.extend_from_slice(&chunk);
         | 
| 58 | 
            +
                        } else {
         | 
| 59 | 
            +
                            break;
         | 
| 60 | 
            +
                        }
         | 
| 61 | 
            +
                    }
         | 
| 62 | 
            +
                    if let Some(pos) = buf.iter().position(|&x| x == b'\n') {
         | 
| 63 | 
            +
                        let line = buf.drain(..=pos).collect::<Vec<u8>>();
         | 
| 64 | 
            +
                        Ok(Some(line.into()))
         | 
| 65 | 
            +
                    } else if !buf.is_empty() {
         | 
| 66 | 
            +
                        let line = buf.drain(..).collect::<Vec<u8>>();
         | 
| 67 | 
            +
                        Ok(Some(line.into()))
         | 
| 68 | 
            +
                    } else {
         | 
| 69 | 
            +
                        Ok(None)
         | 
| 70 | 
            +
                    }
         | 
| 71 | 
            +
                }
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                pub fn read(&self, args: &[Value]) -> MagnusResult<Option<RString>> {
         | 
| 74 | 
            +
                    self.verify_open()?;
         | 
| 75 | 
            +
                    let scanned =
         | 
| 76 | 
            +
                        scan_args::scan_args::<(), (Option<usize>, Option<RString>), (), (), (), ()>(args)?;
         | 
| 77 | 
            +
                    let (length, mut buffer) = scanned.optional;
         | 
| 78 | 
            +
                    let mut stream = self.incoming.lock();
         | 
| 79 | 
            +
                    let mut buf = self.buf.lock();
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    while length.is_none_or(|target_length| buf.len() < target_length) {
         | 
| 82 | 
            +
                        if let Some(chunk) = block_on(stream.next()) {
         | 
| 83 | 
            +
                            let chunk = chunk.map_err(|err| {
         | 
| 84 | 
            +
                                magnus::Error::new(
         | 
| 85 | 
            +
                                    magnus::exception::exception(),
         | 
| 86 | 
            +
                                    format!("Error reading body {:?}", err),
         | 
| 87 | 
            +
                                )
         | 
| 88 | 
            +
                            })?;
         | 
| 89 | 
            +
                            buf.extend_from_slice(&chunk);
         | 
| 90 | 
            +
                        } else if length.is_some() {
         | 
| 91 | 
            +
                            return Ok(None);
         | 
| 92 | 
            +
                        } else {
         | 
| 93 | 
            +
                            break;
         | 
| 94 | 
            +
                        }
         | 
| 95 | 
            +
                    }
         | 
| 96 | 
            +
                    let output_string = buffer.take().unwrap_or(RString::buf_new(buf.len()));
         | 
| 97 | 
            +
                    output_string.cat(buf.clone());
         | 
| 98 | 
            +
                    buf.clear();
         | 
| 99 | 
            +
                    Ok(Some(output_string))
         | 
| 100 | 
            +
                }
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                pub fn to_bytes(&self) -> MagnusResult<Vec<u8>> {
         | 
| 103 | 
            +
                    self.verify_open()?;
         | 
| 104 | 
            +
                    let mut stream = self.incoming.lock();
         | 
| 105 | 
            +
                    let mut buf = self.buf.lock();
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    while let Some(chunk) = block_on(stream.next()) {
         | 
| 108 | 
            +
                        let chunk = chunk.map_err(|err| {
         | 
| 109 | 
            +
                            magnus::Error::new(
         | 
| 110 | 
            +
                                magnus::exception::exception(),
         | 
| 111 | 
            +
                                format!("Error reading body {:?}", err),
         | 
| 112 | 
            +
                            )
         | 
| 113 | 
            +
                        })?;
         | 
| 114 | 
            +
                        buf.extend_from_slice(&chunk);
         | 
| 115 | 
            +
                    }
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    Ok(buf.clone())
         | 
| 118 | 
            +
                }
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                /// Equivalent to calling gets and yielding it, until we reach EOF
         | 
| 121 | 
            +
                pub fn each(ruby: &Ruby, rbself: &Self) -> MagnusResult<()> {
         | 
| 122 | 
            +
                    let proc = ruby.block_proc()?;
         | 
| 123 | 
            +
                    while let Some(str) = rbself.gets()? {
         | 
| 124 | 
            +
                        proc.call::<_, Value>((str,))?;
         | 
| 125 | 
            +
                    }
         | 
| 126 | 
            +
                    Ok(())
         | 
| 127 | 
            +
                }
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                fn verify_open(&self) -> MagnusResult<()> {
         | 
| 130 | 
            +
                    if self.closed.load(atomic::Ordering::SeqCst) {
         | 
| 131 | 
            +
                        return Err(magnus::Error::new(
         | 
| 132 | 
            +
                            magnus::exception::exception(),
         | 
| 133 | 
            +
                            "Body stream is closed",
         | 
| 134 | 
            +
                        ));
         | 
| 135 | 
            +
                    }
         | 
| 136 | 
            +
                    Ok(())
         | 
| 137 | 
            +
                }
         | 
| 138 | 
            +
                pub fn close(&self) {
         | 
| 139 | 
            +
                    self.closed.store(true, atomic::Ordering::SeqCst);
         | 
| 140 | 
            +
                }
         | 
| 141 | 
            +
            }
         |