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.

Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +7 -0
  4. data/Cargo.lock +4417 -0
  5. data/Cargo.toml +7 -0
  6. data/README.md +4 -0
  7. data/Rakefile +8 -1
  8. data/_index.md +6 -0
  9. data/exe/itsi +94 -45
  10. data/ext/itsi_error/Cargo.toml +2 -0
  11. data/ext/itsi_error/src/from.rs +68 -0
  12. data/ext/itsi_error/src/lib.rs +18 -34
  13. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  14. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  15. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  16. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  17. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  18. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  19. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  20. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  21. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  22. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  23. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  24. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  25. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  26. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  27. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  28. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  29. data/ext/itsi_rb_helpers/Cargo.toml +3 -0
  30. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  31. data/ext/itsi_rb_helpers/src/lib.rs +140 -10
  32. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  33. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  34. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  35. 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
  36. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  37. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  38. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  39. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  40. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  41. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  42. data/ext/itsi_scheduler/Cargo.toml +24 -0
  43. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  44. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  45. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  46. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  47. data/ext/itsi_scheduler/src/lib.rs +38 -0
  48. data/ext/itsi_server/Cargo.lock +2956 -0
  49. data/ext/itsi_server/Cargo.toml +73 -13
  50. data/ext/itsi_server/extconf.rb +1 -1
  51. data/ext/itsi_server/src/env.rs +43 -0
  52. data/ext/itsi_server/src/lib.rs +100 -40
  53. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
  54. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
  55. data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
  56. data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
  57. data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
  58. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
  59. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
  60. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  61. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
  62. data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
  63. data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
  64. data/ext/itsi_server/src/server/bind.rs +75 -31
  65. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  66. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  67. data/ext/itsi_server/src/server/cache_store.rs +74 -0
  68. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  69. data/ext/itsi_server/src/server/itsi_service.rs +172 -0
  70. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  71. data/ext/itsi_server/src/server/listener.rs +332 -132
  72. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
  73. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
  74. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
  75. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
  76. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
  77. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
  78. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
  79. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
  80. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
  81. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
  82. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
  83. data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
  84. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
  85. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
  86. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  87. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
  88. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
  89. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
  90. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  91. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
  92. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  97. data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
  98. data/ext/itsi_server/src/server/mod.rs +15 -2
  99. data/ext/itsi_server/src/server/process_worker.rs +229 -0
  100. data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
  101. data/ext/itsi_server/src/server/request_job.rs +11 -0
  102. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
  103. data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
  104. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
  105. data/ext/itsi_server/src/server/signal.rs +93 -0
  106. data/ext/itsi_server/src/server/static_file_server.rs +984 -0
  107. data/ext/itsi_server/src/server/thread_worker.rs +444 -0
  108. data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
  109. data/ext/itsi_server/src/server/tls.rs +187 -60
  110. data/ext/itsi_server/src/server/types.rs +43 -0
  111. data/ext/itsi_tracing/Cargo.toml +5 -0
  112. data/ext/itsi_tracing/src/lib.rs +225 -7
  113. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  114. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  115. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  116. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  117. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  118. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  119. data/lib/itsi/http_request.rb +87 -0
  120. data/lib/itsi/http_response.rb +39 -0
  121. data/lib/itsi/server/Itsi.rb +119 -0
  122. data/lib/itsi/server/config/dsl.rb +506 -0
  123. data/lib/itsi/server/config.rb +131 -0
  124. data/lib/itsi/server/default_app/default_app.rb +38 -0
  125. data/lib/itsi/server/default_app/index.html +91 -0
  126. data/lib/itsi/server/grpc_interface.rb +213 -0
  127. data/lib/itsi/server/rack/handler/itsi.rb +27 -0
  128. data/lib/itsi/server/rack_interface.rb +94 -0
  129. data/lib/itsi/server/scheduler_interface.rb +21 -0
  130. data/lib/itsi/server/scheduler_mode.rb +10 -0
  131. data/lib/itsi/server/signal_trap.rb +29 -0
  132. data/lib/itsi/server/version.rb +1 -1
  133. data/lib/itsi/server.rb +90 -9
  134. data/lib/itsi/standard_headers.rb +86 -0
  135. metadata +122 -31
  136. data/ext/itsi_server/src/request/itsi_request.rs +0 -143
  137. data/ext/itsi_server/src/request/mod.rs +0 -1
  138. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  139. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  140. data/ext/itsi_server/src/server/itsi_server.rs +0 -182
  141. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  142. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
  143. data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,139 @@
1
+ use super::{FromValue, MiddlewareLayer};
2
+ use crate::server::{itsi_service::RequestContext, types::HttpResponse};
3
+ use async_trait::async_trait;
4
+ use http::{HeaderName, HeaderValue};
5
+ use magnus::error::Result;
6
+ use serde::Deserialize;
7
+ use std::{collections::HashMap, sync::OnceLock};
8
+
9
+ #[derive(Debug, Clone, Deserialize)]
10
+ pub struct CacheControl {
11
+ #[serde(default)]
12
+ pub max_age: Option<u64>,
13
+ #[serde(default)]
14
+ pub s_max_age: Option<u64>,
15
+ #[serde(default)]
16
+ pub stale_while_revalidate: Option<u64>,
17
+ #[serde(default)]
18
+ pub stale_if_error: Option<u64>,
19
+ #[serde(default)]
20
+ pub public: bool,
21
+ #[serde(default)]
22
+ pub private: bool,
23
+ #[serde(default)]
24
+ pub no_cache: bool,
25
+ #[serde(default)]
26
+ pub no_store: bool,
27
+ #[serde(default)]
28
+ pub must_revalidate: bool,
29
+ #[serde(default)]
30
+ pub proxy_revalidate: bool,
31
+ #[serde(default)]
32
+ pub immutable: bool,
33
+ #[serde(default)]
34
+ pub vary: Vec<String>,
35
+ #[serde(default)]
36
+ pub additional_headers: HashMap<String, String>,
37
+ #[serde(skip_deserializing)]
38
+ pub cache_control_str: OnceLock<String>,
39
+ }
40
+
41
+ #[async_trait]
42
+ impl MiddlewareLayer for CacheControl {
43
+ async fn initialize(&self) -> Result<()> {
44
+ let mut directives = Vec::new();
45
+
46
+ if self.public && !self.private {
47
+ directives.push("public".to_owned());
48
+ } else if self.private && !self.public {
49
+ directives.push("private".to_owned());
50
+ }
51
+ if self.no_cache {
52
+ directives.push("no-cache".to_owned());
53
+ }
54
+ if self.no_store {
55
+ directives.push("no-store".to_owned());
56
+ }
57
+ if self.must_revalidate {
58
+ directives.push("must-revalidate".to_owned());
59
+ }
60
+ if self.proxy_revalidate {
61
+ directives.push("proxy-revalidate".to_owned());
62
+ }
63
+ if self.immutable {
64
+ directives.push("immutable".to_owned());
65
+ }
66
+
67
+ // Add age parameters
68
+ if let Some(max_age) = self.max_age {
69
+ directives.push(format!("max-age={}", max_age));
70
+ }
71
+
72
+ if let Some(s_max_age) = self.s_max_age {
73
+ directives.push(format!("s-maxage={}", s_max_age));
74
+ }
75
+
76
+ if let Some(stale_while_revalidate) = self.stale_while_revalidate {
77
+ directives.push(format!("stale-while-revalidate={}", stale_while_revalidate));
78
+ }
79
+
80
+ if let Some(stale_if_error) = self.stale_if_error {
81
+ directives.push(format!("stale-if-error={}", stale_if_error));
82
+ }
83
+
84
+ // Set the Cache-Control header if we have directives
85
+ if !directives.is_empty() {
86
+ let cache_control_value = directives.join(", ");
87
+ self.cache_control_str.set(cache_control_value).unwrap();
88
+ }
89
+
90
+ Ok(())
91
+ }
92
+
93
+ async fn after(&self, mut resp: HttpResponse, _: &mut RequestContext) -> HttpResponse {
94
+ // Skip for statuses where caching doesn't make sense
95
+ let status = resp.status().as_u16();
96
+ if matches!(status, 401 | 403 | 500..=599) {
97
+ return resp;
98
+ }
99
+
100
+ // Set the Cache-Control header if we have directives
101
+ if let Some(cache_control_value) = self.cache_control_str.get() {
102
+ if let Ok(value) = HeaderValue::from_str(cache_control_value) {
103
+ resp.headers_mut().insert("Cache-Control", value);
104
+ }
105
+ }
106
+
107
+ // Set Expires header based on max-age if present
108
+ if let Some(max_age) = self.max_age {
109
+ // Set the Expires header based on max-age
110
+ // Use a helper to format the HTTP date correctly
111
+ let expires = chrono::Utc::now() + chrono::Duration::seconds(max_age as i64);
112
+ let expires_str = expires.format("%a, %d %b %Y %H:%M:%S GMT").to_string();
113
+ if let Ok(value) = HeaderValue::from_str(&expires_str) {
114
+ resp.headers_mut().insert("Expires", value);
115
+ }
116
+ }
117
+
118
+ // Set Vary header
119
+ if !self.vary.is_empty() {
120
+ let vary_value = self.vary.join(", ");
121
+ if let Ok(value) = HeaderValue::from_str(&vary_value) {
122
+ resp.headers_mut().insert("Vary", value);
123
+ }
124
+ }
125
+
126
+ // Set additional custom headers
127
+ for (name, value) in &self.additional_headers {
128
+ if let Ok(header_value) = HeaderValue::from_str(value) {
129
+ if let Ok(header_name) = name.parse::<HeaderName>() {
130
+ resp.headers_mut().insert(header_name, header_value);
131
+ }
132
+ }
133
+ }
134
+
135
+ resp
136
+ }
137
+ }
138
+
139
+ impl FromValue for CacheControl {}
@@ -0,0 +1,300 @@
1
+ use super::{
2
+ header_interpretation::{find_first_supported, header_contains},
3
+ FromValue, MiddlewareLayer,
4
+ };
5
+ use crate::server::{
6
+ itsi_service::RequestContext,
7
+ types::{HttpRequest, HttpResponse},
8
+ };
9
+ use async_compression::{
10
+ tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder},
11
+ Level,
12
+ };
13
+ use async_trait::async_trait;
14
+ use bytes::{Bytes, BytesMut};
15
+ use either::Either;
16
+ use futures::TryStreamExt;
17
+ use http::{
18
+ header::{GetAll, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE},
19
+ HeaderValue, Response,
20
+ };
21
+ use http_body_util::{combinators::BoxBody, BodyExt, Full, StreamBody};
22
+ use hyper::body::{Body, Frame};
23
+ use magnus::error::Result;
24
+ use serde::{Deserialize, Serialize};
25
+ use std::convert::Infallible;
26
+ use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
27
+ use tokio_stream::StreamExt;
28
+ use tokio_util::io::{ReaderStream, StreamReader};
29
+ #[derive(Debug, Clone, Serialize, Deserialize)]
30
+ pub struct Compression {
31
+ min_size: usize,
32
+ algorithms: Vec<CompressionAlgorithm>,
33
+ compress_streams: bool,
34
+ mime_types: Vec<MimeType>,
35
+ level: CompressionLevel,
36
+ }
37
+
38
+ #[derive(Debug, Clone, Serialize, Deserialize)]
39
+ enum CompressionLevel {
40
+ #[serde(rename(deserialize = "fastest"))]
41
+ Fastest,
42
+ #[serde(rename(deserialize = "best"))]
43
+ Best,
44
+ #[serde(rename(deserialize = "default"))]
45
+ Default,
46
+ #[serde(rename(deserialize = "precise"))]
47
+ Precise(i32),
48
+ }
49
+
50
+ impl CompressionLevel {
51
+ fn to_async_compression_level(&self) -> Level {
52
+ match self {
53
+ CompressionLevel::Fastest => Level::Fastest,
54
+ CompressionLevel::Best => Level::Best,
55
+ CompressionLevel::Default => Level::Default,
56
+ CompressionLevel::Precise(level) => Level::Precise(*level),
57
+ }
58
+ }
59
+ }
60
+
61
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
62
+ pub enum CompressionAlgorithm {
63
+ #[serde(rename(deserialize = "gzip"))]
64
+ Gzip,
65
+ #[serde(rename(deserialize = "brotli"))]
66
+ Brotli,
67
+ #[serde(rename(deserialize = "deflate"))]
68
+ Deflate,
69
+ #[serde(rename(deserialize = "zstd"))]
70
+ Zstd,
71
+ #[serde(rename(deserialize = "none"))]
72
+ None,
73
+ }
74
+
75
+ impl CompressionAlgorithm {
76
+ pub fn as_str(&self) -> &'static str {
77
+ match self {
78
+ CompressionAlgorithm::Gzip => "gzip",
79
+ CompressionAlgorithm::Brotli => "br",
80
+ CompressionAlgorithm::Deflate => "deflate",
81
+ CompressionAlgorithm::Zstd => "zstd",
82
+ CompressionAlgorithm::None => "none",
83
+ }
84
+ }
85
+
86
+ pub fn header_value(&self) -> HeaderValue {
87
+ HeaderValue::from_str(self.as_str()).unwrap()
88
+ }
89
+ }
90
+
91
+ #[derive(Debug, Clone, Serialize, Deserialize)]
92
+ enum MimeType {
93
+ #[serde(rename(deserialize = "text"))]
94
+ Text,
95
+ #[serde(rename(deserialize = "image"))]
96
+ Image,
97
+ #[serde(rename(deserialize = "application"))]
98
+ Application,
99
+ #[serde(rename(deserialize = "audio"))]
100
+ Audio,
101
+ #[serde(rename(deserialize = "video"))]
102
+ Video,
103
+ #[serde(rename(deserialize = "other"))]
104
+ Other(String),
105
+ #[serde(rename(deserialize = "all"))]
106
+ All,
107
+ }
108
+
109
+ impl MimeType {
110
+ pub fn matches(&self, content_encodings: &GetAll<HeaderValue>) -> bool {
111
+ match self {
112
+ MimeType::Text => header_contains(content_encodings, "text/*"),
113
+ MimeType::Image => header_contains(content_encodings, "image/*"),
114
+ MimeType::Application => header_contains(content_encodings, "application/*"),
115
+ MimeType::Audio => header_contains(content_encodings, "audio/*"),
116
+ MimeType::Video => header_contains(content_encodings, "video/*"),
117
+ MimeType::Other(v) => header_contains(content_encodings, v),
118
+ MimeType::All => header_contains(content_encodings, "*"),
119
+ }
120
+ }
121
+ }
122
+
123
+ fn stream_encode<R>(encoder: R) -> BoxBody<Bytes, Infallible>
124
+ where
125
+ R: AsyncRead + Unpin + Sync + Send + 'static,
126
+ {
127
+ let encoded_stream = ReaderStream::new(encoder).map(|res| {
128
+ res.map(Frame::data)
129
+ .map_err(|_| -> Infallible { unreachable!("We handle IO errors above") })
130
+ });
131
+ BoxBody::new(StreamBody::new(encoded_stream))
132
+ }
133
+
134
+ fn update_content_encoding(parts: &mut http::response::Parts, new_encoding: HeaderValue) {
135
+ if let Some(existing) = parts.headers.get(CONTENT_ENCODING) {
136
+ let mut encodings = existing.to_str().unwrap_or("").to_owned();
137
+ if !encodings.is_empty() {
138
+ encodings.push_str(", ");
139
+ }
140
+ encodings.push_str(new_encoding.to_str().unwrap());
141
+ parts
142
+ .headers
143
+ .insert(CONTENT_ENCODING, HeaderValue::from_str(&encodings).unwrap());
144
+ } else {
145
+ parts.headers.insert(CONTENT_ENCODING, new_encoding);
146
+ }
147
+ }
148
+
149
+ #[async_trait]
150
+ impl MiddlewareLayer for Compression {
151
+ /// A the request comes in, take note of the accepted content encodings,
152
+ /// so that we can apply compression on the response, where appropriate.
153
+ ///
154
+ /// We store the temporary state inside the RequestContext.
155
+ async fn before(
156
+ &self,
157
+ req: HttpRequest,
158
+ context: &mut RequestContext,
159
+ ) -> Result<Either<HttpRequest, HttpResponse>> {
160
+ let algo = match find_first_supported(
161
+ &req.headers().get_all(ACCEPT_ENCODING),
162
+ self.algorithms.iter().map(|algo| algo.as_str()),
163
+ ) {
164
+ Some("gzip") => CompressionAlgorithm::Gzip,
165
+ Some("br") => CompressionAlgorithm::Brotli,
166
+ Some("deflate") => CompressionAlgorithm::Deflate,
167
+ Some("zstd") => CompressionAlgorithm::Zstd,
168
+ _ => CompressionAlgorithm::None,
169
+ };
170
+
171
+ if matches!(algo, CompressionAlgorithm::None) {
172
+ return Ok(Either::Left(req));
173
+ }
174
+
175
+ context.set_compression_method(algo);
176
+
177
+ Ok(Either::Left(req))
178
+ }
179
+
180
+ /// We'll apply compression on the response, where appropriate.
181
+ /// This is if:
182
+ /// * The response body is larger than the minimum size.
183
+ /// * The response content type is supported.
184
+ /// * The client supports the compression algorithm.
185
+ async fn after(&self, resp: HttpResponse, context: &mut RequestContext) -> HttpResponse {
186
+ let compression_method;
187
+ if let Some(method) = context.compression_method.get() {
188
+ compression_method = method.clone();
189
+ } else {
190
+ return resp;
191
+ }
192
+
193
+ if matches!(compression_method, CompressionAlgorithm::None) {
194
+ return resp;
195
+ }
196
+
197
+ let body_size = resp.size_hint().exact();
198
+ let resp = resp;
199
+
200
+ if !self
201
+ .mime_types
202
+ .iter()
203
+ .any(|mt| mt.matches(&resp.headers().get_all(CONTENT_TYPE)))
204
+ {
205
+ return resp;
206
+ }
207
+
208
+ if body_size.is_none() && !self.compress_streams {
209
+ return resp;
210
+ }
211
+
212
+ if body_size.is_some_and(|s| s < self.min_size as u64) {
213
+ return resp;
214
+ }
215
+
216
+ let (mut parts, body) = resp.into_parts();
217
+
218
+ let new_body = if let Some(_size) = body_size {
219
+ let full_bytes: Bytes = body
220
+ .into_data_stream()
221
+ .try_fold(BytesMut::new(), |mut acc, chunk| async move {
222
+ acc.extend_from_slice(&chunk);
223
+ Ok(acc)
224
+ })
225
+ .await
226
+ .unwrap()
227
+ .freeze();
228
+
229
+ let cursor = std::io::Cursor::new(full_bytes);
230
+ let reader = BufReader::new(cursor);
231
+ let compressed_bytes = match compression_method {
232
+ CompressionAlgorithm::Gzip => {
233
+ let mut encoder =
234
+ GzipEncoder::with_quality(reader, self.level.to_async_compression_level());
235
+ let mut buf = Vec::new();
236
+ encoder.read_to_end(&mut buf).await.unwrap();
237
+ buf
238
+ }
239
+ CompressionAlgorithm::Brotli => {
240
+ let mut encoder = BrotliEncoder::with_quality(
241
+ reader,
242
+ self.level.to_async_compression_level(),
243
+ );
244
+ let mut buf = Vec::new();
245
+ encoder.read_to_end(&mut buf).await.unwrap();
246
+ buf
247
+ }
248
+ CompressionAlgorithm::Deflate => {
249
+ let mut encoder = DeflateEncoder::with_quality(
250
+ reader,
251
+ self.level.to_async_compression_level(),
252
+ );
253
+ let mut buf = Vec::new();
254
+ encoder.read_to_end(&mut buf).await.unwrap();
255
+ buf
256
+ }
257
+ CompressionAlgorithm::Zstd => {
258
+ let mut encoder =
259
+ ZstdEncoder::with_quality(reader, self.level.to_async_compression_level());
260
+ let mut buf = Vec::new();
261
+ encoder.read_to_end(&mut buf).await.unwrap();
262
+ buf
263
+ }
264
+ CompressionAlgorithm::None => unreachable!(),
265
+ };
266
+ BoxBody::new(Full::new(Bytes::from(compressed_bytes)))
267
+ } else {
268
+ let stream = body
269
+ .into_data_stream()
270
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e));
271
+ let async_read_fut = StreamReader::new(stream);
272
+ let reader = BufReader::new(async_read_fut);
273
+ match compression_method {
274
+ CompressionAlgorithm::Gzip => stream_encode(GzipEncoder::with_quality(
275
+ reader,
276
+ self.level.to_async_compression_level(),
277
+ )),
278
+ CompressionAlgorithm::Brotli => stream_encode(BrotliEncoder::with_quality(
279
+ reader,
280
+ self.level.to_async_compression_level(),
281
+ )),
282
+ CompressionAlgorithm::Deflate => stream_encode(DeflateEncoder::with_quality(
283
+ reader,
284
+ self.level.to_async_compression_level(),
285
+ )),
286
+ CompressionAlgorithm::Zstd => stream_encode(ZstdEncoder::with_quality(
287
+ reader,
288
+ self.level.to_async_compression_level(),
289
+ )),
290
+ CompressionAlgorithm::None => unreachable!(),
291
+ }
292
+ };
293
+
294
+ update_content_encoding(&mut parts, compression_method.header_value());
295
+ parts.headers.remove(CONTENT_LENGTH);
296
+
297
+ Response::from_parts(parts, new_body)
298
+ }
299
+ }
300
+ impl FromValue for Compression {}