itsi 0.1.8 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +11 -2
  3. data/Rakefile +6 -2
  4. data/crates/itsi_rb_helpers/src/lib.rs +27 -4
  5. data/crates/itsi_server/Cargo.toml +4 -1
  6. data/crates/itsi_server/src/lib.rs +74 -1
  7. data/crates/itsi_server/src/request/itsi_request.rs +32 -11
  8. data/crates/itsi_server/src/response/itsi_response.rs +14 -4
  9. data/crates/itsi_server/src/server/bind.rs +16 -12
  10. data/crates/itsi_server/src/server/itsi_server.rs +146 -95
  11. data/crates/itsi_server/src/server/listener.rs +10 -10
  12. data/crates/itsi_server/src/server/process_worker.rs +10 -3
  13. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +15 -9
  14. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +134 -115
  15. data/crates/itsi_server/src/server/signal.rs +4 -0
  16. data/crates/itsi_server/src/server/thread_worker.rs +55 -24
  17. data/crates/itsi_server/src/server/tls.rs +11 -8
  18. data/crates/itsi_tracing/src/lib.rs +18 -1
  19. data/gems/scheduler/Cargo.lock +12 -12
  20. data/gems/scheduler/ext/itsi_rb_helpers/src/lib.rs +27 -4
  21. data/gems/scheduler/ext/itsi_server/Cargo.toml +4 -1
  22. data/gems/scheduler/ext/itsi_server/src/lib.rs +74 -1
  23. data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +32 -11
  24. data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +14 -4
  25. data/gems/scheduler/ext/itsi_server/src/server/bind.rs +16 -12
  26. data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +146 -95
  27. data/gems/scheduler/ext/itsi_server/src/server/listener.rs +10 -10
  28. data/gems/scheduler/ext/itsi_server/src/server/process_worker.rs +10 -3
  29. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +15 -9
  30. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +134 -115
  31. data/gems/scheduler/ext/itsi_server/src/server/signal.rs +4 -0
  32. data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +55 -24
  33. data/gems/scheduler/ext/itsi_server/src/server/tls.rs +11 -8
  34. data/gems/scheduler/ext/itsi_tracing/src/lib.rs +18 -1
  35. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  36. data/gems/scheduler/test/test_address_resolve.rb +0 -1
  37. data/gems/scheduler/test/test_file_io.rb +0 -1
  38. data/gems/scheduler/test/test_kernel_sleep.rb +3 -4
  39. data/gems/server/Cargo.lock +11 -2
  40. data/gems/server/Rakefile +8 -1
  41. data/gems/server/exe/itsi +53 -23
  42. data/gems/server/ext/itsi_rb_helpers/src/lib.rs +27 -4
  43. data/gems/server/ext/itsi_server/Cargo.toml +4 -1
  44. data/gems/server/ext/itsi_server/src/lib.rs +74 -1
  45. data/gems/server/ext/itsi_server/src/request/itsi_request.rs +32 -11
  46. data/gems/server/ext/itsi_server/src/response/itsi_response.rs +14 -4
  47. data/gems/server/ext/itsi_server/src/server/bind.rs +16 -12
  48. data/gems/server/ext/itsi_server/src/server/itsi_server.rs +146 -95
  49. data/gems/server/ext/itsi_server/src/server/listener.rs +10 -10
  50. data/gems/server/ext/itsi_server/src/server/process_worker.rs +10 -3
  51. data/gems/server/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +15 -9
  52. data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +134 -115
  53. data/gems/server/ext/itsi_server/src/server/signal.rs +4 -0
  54. data/gems/server/ext/itsi_server/src/server/thread_worker.rs +55 -24
  55. data/gems/server/ext/itsi_server/src/server/tls.rs +11 -8
  56. data/gems/server/ext/itsi_tracing/src/lib.rs +18 -1
  57. data/gems/server/lib/itsi/request.rb +29 -21
  58. data/gems/server/lib/itsi/server/Itsi.rb +127 -0
  59. data/gems/server/lib/itsi/server/config.rb +36 -0
  60. data/gems/server/lib/itsi/server/options_dsl.rb +401 -0
  61. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +18 -7
  62. data/gems/server/lib/itsi/server/rack_interface.rb +75 -0
  63. data/gems/server/lib/itsi/server/scheduler_interface.rb +21 -0
  64. data/gems/server/lib/itsi/server/signal_trap.rb +23 -0
  65. data/gems/server/lib/itsi/server/version.rb +1 -1
  66. data/gems/server/lib/itsi/server.rb +71 -101
  67. data/gems/server/test/helpers/test_helper.rb +30 -0
  68. data/gems/server/test/test_itsi_server.rb +294 -3
  69. data/lib/itsi/version.rb +1 -1
  70. data/location_dsl.rb +381 -0
  71. data/sandbox/deploy/main.tf +1 -0
  72. data/sandbox/itsi_itsi_file/Itsi.rb +119 -0
  73. data/sandbox/itsi_sandbox_async/Gemfile +1 -1
  74. data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
  75. data/sandbox/itsi_sandbox_rails/Gemfile.lock +2 -2
  76. data/tasks.txt +25 -8
  77. metadata +21 -14
  78. data/gems/server/lib/itsi/signals.rb +0 -23
  79. data/gems/server/test/test_helper.rb +0 -7
  80. /data/gems/server/lib/itsi/{index.html.erb → index.html} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6963234dfc04b727baa4c6c9bbb2760040091de0f685e0b5412ff272a27463fc
4
- data.tar.gz: 78a28fb8d43775141e8d27e05aec9ee345493065e6820b3a4301509712e21007
3
+ metadata.gz: 59369e9fbde937f7f81412211e2003f3537bb0b3adcfe5ac65f141cdb74bd150
4
+ data.tar.gz: a0bc192989bcfd5da5223ee5d1602da8e70775383cb0e04db8a7ab5f759ba602
5
5
  SHA512:
6
- metadata.gz: 2a003e7e28a989c2566b53b03d5289267fcbf8038899d89edf8909dec50367fd62682a820b0825368b8a017f24330ecc9d1e75e24ce746750feddc54763e6e1f
7
- data.tar.gz: efd97ee4f193fd8303b382b03d0bde8f60456b4f722c925cf8178af35e847af5d4fe01f5f44f89c3dcfbd5e68082f25210d90cac408ff3d7e41c4fec805837be
6
+ metadata.gz: 2f0a6aa10401ce95b78701ee49761449b10091cbe7262337fb2ff07812866852bca54c43bb17c530c5f6a403058b4bca930cdf756713efdf2a5652636906b737
7
+ data.tar.gz: 23547b8969bb3b2086f59ea1d3b25c9414bea53410ae50f21d928f0d95edda7a25e979ca5b2860f3e71453632d9c32d1c74c548d0c834970177b8f7a17eaaf3b
data/Cargo.lock CHANGED
@@ -1011,6 +1011,7 @@ dependencies = [
1011
1011
  "crossbeam",
1012
1012
  "derive_more",
1013
1013
  "dirs",
1014
+ "fnv",
1014
1015
  "fs2",
1015
1016
  "futures",
1016
1017
  "http",
@@ -1027,7 +1028,9 @@ dependencies = [
1027
1028
  "pin-project",
1028
1029
  "rb-sys",
1029
1030
  "rcgen",
1031
+ "regex",
1030
1032
  "ring",
1033
+ "route-recognizer",
1031
1034
  "rustls",
1032
1035
  "rustls-pemfile",
1033
1036
  "socket2",
@@ -1758,6 +1761,12 @@ dependencies = [
1758
1761
  "windows-sys 0.52.0",
1759
1762
  ]
1760
1763
 
1764
+ [[package]]
1765
+ name = "route-recognizer"
1766
+ version = "0.3.1"
1767
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1768
+ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
1769
+
1761
1770
  [[package]]
1762
1771
  name = "rustc-demangle"
1763
1772
  version = "0.1.24"
@@ -2170,9 +2179,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2170
2179
 
2171
2180
  [[package]]
2172
2181
  name = "tokio"
2173
- version = "1.43.0"
2182
+ version = "1.44.1"
2174
2183
  source = "registry+https://github.com/rust-lang/crates.io-index"
2175
- checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
2184
+ checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
2176
2185
  dependencies = [
2177
2186
  "backtrace",
2178
2187
  "bytes",
data/Rakefile CHANGED
@@ -58,6 +58,7 @@ task :sync_crates do
58
58
  Dir.chdir('crates') do
59
59
  to_sync = Dir['*'].each do |to_sync|
60
60
  system("rsync -q -av #{to_sync}/ ../#{gem_info[:dir]}/ext/#{to_sync} --delete")
61
+ system("cp ../Cargo.lock ../#{gem_info[:dir]}/Cargo.lock")
61
62
  end
62
63
  end
63
64
  end
@@ -82,13 +83,16 @@ end
82
83
  task :test_env_down do
83
84
  system("terraform -chdir=sandbox/deploy destroy")
84
85
  end
85
-
86
86
  %i[itsi puma iodine falcon unicorn].each do |server|
87
87
  namespace server do
88
88
  %i[hanami roda async rack rack_lint rails sinatra].each do |sandbox|
89
89
  namespace sandbox do
90
90
  task :serve do |args|
91
- sh "(cd sandbox/itsi_sandbox_#{sandbox} && bundle exec #{server} #{ARGV[2..]&.join(" ")} )"
91
+ begin
92
+ system("(cd sandbox/itsi_sandbox_#{sandbox} && bundle exec #{server} #{ARGV[2..]&.join(" ")} )")
93
+ rescue Interrupt
94
+ # Suppress the stacktrace and message for Interrupt
95
+ end
92
96
  end
93
97
  end
94
98
  end
@@ -1,7 +1,8 @@
1
- use std::{os::raw::c_void, ptr::null_mut, sync::Arc};
1
+ use std::{os::raw::c_void, ptr::null_mut};
2
2
 
3
3
  use magnus::{
4
4
  RArray, Ruby, Thread, Value,
5
+ block::Proc,
5
6
  rb_sys::FromRawValue,
6
7
  value::{LazyId, ReprValue},
7
8
  };
@@ -17,6 +18,7 @@ static ID_LIST: LazyId = LazyId::new("list");
17
18
  static ID_EQ: LazyId = LazyId::new("==");
18
19
  static ID_ALIVE: LazyId = LazyId::new("alive?");
19
20
  static ID_THREAD_VARIABLE_GET: LazyId = LazyId::new("thread_variable_get");
21
+ static ID_BACKTRACE: LazyId = LazyId::new("backtrace");
20
22
 
21
23
  pub fn schedule_thread() {
22
24
  unsafe {
@@ -120,20 +122,30 @@ where
120
122
  *result_box
121
123
  }
122
124
 
123
- pub fn fork(after_fork: Arc<Option<impl Fn()>>) -> Option<i32> {
125
+ pub fn fork(after_fork: Option<HeapValue<Proc>>) -> Option<i32> {
124
126
  let ruby = Ruby::get().unwrap();
125
127
  let fork_result = ruby
126
128
  .module_kernel()
127
129
  .funcall::<_, _, Option<i32>>(*ID_FORK, ())
128
130
  .unwrap();
129
131
  if fork_result.is_none() {
130
- if let Some(f) = &*after_fork {
131
- f()
132
+ if let Some(proc) = after_fork {
133
+ call_proc_and_log_errors(proc)
132
134
  }
133
135
  }
134
136
  fork_result
135
137
  }
136
138
 
139
+ pub fn call_proc_and_log_errors(proc: HeapValue<Proc>) {
140
+ if let Err(e) = proc.call::<_, Value>(()) {
141
+ if let Some(value) = e.value() {
142
+ print_rb_backtrace(value);
143
+ } else {
144
+ eprintln!("Error occurred {:?}", e);
145
+ }
146
+ }
147
+ }
148
+
137
149
  pub fn kill_threads<T>(threads: Vec<T>)
138
150
  where
139
151
  T: ReprValue,
@@ -176,3 +188,14 @@ pub fn terminate_non_fork_safe_threads() {
176
188
 
177
189
  kill_threads(non_fork_safe_threads);
178
190
  }
191
+
192
+ pub fn print_rb_backtrace(rb_err: Value) {
193
+ let backtrace = rb_err
194
+ .funcall::<_, _, Vec<String>>(*ID_BACKTRACE, ())
195
+ .unwrap_or_default();
196
+
197
+ eprintln!("Ruby exception {:?}", rb_err);
198
+ for line in backtrace {
199
+ eprintln!("{}", line);
200
+ }
201
+ }
@@ -24,7 +24,7 @@ rcgen = { version = "0.13.2", features = ["x509-parser", "pem"] }
24
24
  base64 = "0.22.1"
25
25
  http-body-util = "0.1.2"
26
26
  hyper = { version = "1.5.0", features = ["full", "server", "http1", "http2"] }
27
- tokio = { version = "1", features = ["full"] }
27
+ tokio = { version = "1.44.1", features = ["full"] }
28
28
  hyper-util = { version = "0.1.10", features = ["full"] }
29
29
  derive_more = { version = "2.0.1", features = ["debug"] }
30
30
  http = "1.3.1"
@@ -45,3 +45,6 @@ fs2 = "0.4.3"
45
45
  ring = "0.17.14"
46
46
  async-trait = "0.1.87"
47
47
  dirs = "6.0.0"
48
+ regex = "1.11.1"
49
+ route-recognizer = "0.3.1"
50
+ fnv = "1.0.7"
@@ -1,5 +1,8 @@
1
1
  use body_proxy::itsi_body_proxy::ItsiBodyProxy;
2
- use magnus::{error::Result, function, method, value::Lazy, Module, Object, RClass, RModule, Ruby};
2
+ use magnus::{
3
+ error::Result, function, method, value::Lazy, Module, Object, RClass, RHash, RModule, Ruby,
4
+ };
5
+ use regex::{Regex, RegexSet};
3
6
  use request::itsi_request::ItsiRequest;
4
7
  use response::itsi_response::ItsiResponse;
5
8
  use server::{itsi_server::Server, signal::reset_signal_handlers};
@@ -54,6 +57,70 @@ pub fn log_error(msg: String) {
54
57
  error!(msg);
55
58
  }
56
59
 
60
+ const ROUTES: [&str; 39] = [
61
+ r"(?-u)^/organisations/(?<organisation_id>\d+)/users/(?<user_id>\d+)$",
62
+ r"(?-u)^/projects/(?<project_id>\d+)/tasks/(?<task_id>\d+)$",
63
+ r"(?-u)^/products/(?<product_id>\d+)(?:/reviews/(?<review_id>\d+))?$",
64
+ r"(?-u)^/orders/(?<order_id>\d+)/items(?:/(?<item_id>\d+))?$",
65
+ r"(?-u)^/posts/(?<post_id>\d+)/comments(?:/(?<comment_id>\d+))?$",
66
+ r"(?-u)^/teams/(?<team_id>\d+)(?:/members/(?<member_id>\d+))?$",
67
+ r"(?-u)^/categories/(?<category_id>\d+)/subcategories(?:/(?<subcategory_id>\d+))?$",
68
+ r"(?-u)^/departments/(?<department_id>\d+)/employees/(?<employee_id>\d+)$",
69
+ r"(?-u)^/events/(?<event_id>\d+)(?:/sessions/(?<session_id>\d+))?$",
70
+ r"(?-u)^/invoices/(?<invoice_id>\d+)/payments(?:/(?<payment_id>\d+))?$",
71
+ r"(?-u)^/tickets/(?<ticket_id>\d+)(?:/responses/(?<response_id>\d+))?$",
72
+ r"(?-u)^/forums/(?<forum_id>\d+)(?:/threads/(?<thread_id>\d+))?$",
73
+ r"(?-u)^/subscriptions/(?<subscription_id>\d+)/plans(?:/(?<plan_id>\d+))?$",
74
+ r"(?-u)^/profiles/(?<profile_id>\d+)/settings$",
75
+ r"(?-u)^/organizations/(?<organization_id>\d+)/billing(?:/(?<billing_id>\d+))?$",
76
+ r"(?-u)^/vendors/(?<vendor_id>\d+)/products(?:/(?<product_id>\d+))?$",
77
+ r"(?-u)^/courses/(?<course_id>\d+)/modules(?:/(?<module_id>\d+))?$",
78
+ r"(?-u)^/accounts/(?<account_id>\d+)(?:/transactions/(?<transaction_id>\d+))?$",
79
+ r"(?-u)^/warehouses/(?<warehouse_id>\d+)/inventory(?:/(?<inventory_id>\d+))?$",
80
+ r"(?-u)^/campaigns/(?<campaign_id>\d+)/ads(?:/(?<ad_id>\d+))?$",
81
+ r"(?-u)^/applications/(?<application_id>\d+)/stages(?:/(?<stage_id>\d+))?$",
82
+ r"(?-u)^/notifications/(?<notification_id>\d+)$",
83
+ r"(?-u)^/albums/(?<album_id>\d+)/photos(?:/(?<photo_id>\d+))?$",
84
+ r"(?-u)^/news/(?<news_id>\d+)/articles(?:/(?<article_id>\d+))?$",
85
+ r"(?-u)^/libraries/(?<library_id>\d+)/books(?:/(?<book_id>\d+))?$",
86
+ r"(?-u)^/universities/(?<university_id>\d+)/students(?:/(?<student_id>\d+))?$",
87
+ r"(?-u)^/banks/(?<bank_id>\d+)/branches(?:/(?<branch_id>\d+))?$",
88
+ r"(?-u)^/vehicles/(?<vehicle_id>\d+)/services(?:/(?<service_id>\d+))?$",
89
+ r"(?-u)^/hotels/(?<hotel_id>\d+)/rooms(?:/(?<room_id>\d+))?$",
90
+ r"(?-u)^/doctors/(?<doctor_id>\d+)/appointments(?:/(?<appointment_id>\d+))?$",
91
+ r"(?-u)^/gyms/(?<gym_id>\d+)/memberships(?:/(?<membership_id>\d+))?$",
92
+ r"(?-u)^/restaurants/(?<restaurant_id>\d+)/menus(?:/(?<menu_id>\d+))?$",
93
+ r"(?-u)^/parks/(?<park_id>\d+)/events(?:/(?<event_id>\d+))?$",
94
+ r"(?-u)^/theaters/(?<theater_id>\d+)/shows(?:/(?<show_id>\d+))?$",
95
+ r"(?-u)^/museums/(?<museum_id>\d+)/exhibits(?:/(?<exhibit_id>\d+))?$",
96
+ r"(?-u)^/stadiums/(?<stadium_id>\d+)/games(?:/(?<game_id>\d+))?$",
97
+ r"(?-u)^/schools/(?<school_id>\d+)/classes(?:/(?<class_id>\d+))?$",
98
+ r"(?-u)^/clubs/(?<club_id>\d+)/events(?:/(?<event_id>\d+))?$",
99
+ r"(?-u)^/festivals/(?<festival_id>\d+)/tickets(?:/(?<ticket_id>\d+))?$",
100
+ ];
101
+ use std::sync::LazyLock;
102
+
103
+ static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| RegexSet::new(ROUTES).unwrap());
104
+ static REGEXES: LazyLock<Vec<Regex>> =
105
+ LazyLock::new(|| ROUTES.iter().map(|&r| Regex::new(r).unwrap()).collect());
106
+
107
+ fn match_route(input: String) -> Result<Option<(usize, Option<RHash>)>> {
108
+ if let Some(index) = REGEX_SET.matches(&input).iter().next() {
109
+ let regex = &REGEXES[index];
110
+ if let Some(captures) = regex.captures(&input) {
111
+ let params = RHash::with_capacity(captures.len());
112
+ for name in regex.capture_names().flatten() {
113
+ if let Some(value) = captures.name(name) {
114
+ params.aset(name, value.as_str()).ok();
115
+ }
116
+ }
117
+ return Ok(Some((index, Some(params))));
118
+ }
119
+ return Ok(Some((index, None)));
120
+ }
121
+ Ok(None)
122
+ }
123
+
57
124
  #[magnus::init]
58
125
  fn init(ruby: &Ruby) -> Result<()> {
59
126
  itsi_tracing::init();
@@ -62,6 +129,7 @@ fn init(ruby: &Ruby) -> Result<()> {
62
129
  .ok();
63
130
 
64
131
  let itsi = ruby.get_inner(&ITSI_MODULE);
132
+ itsi.define_singleton_method("match_route", function!(match_route, 1))?;
65
133
  itsi.define_singleton_method("log_debug", function!(log_debug, 1))?;
66
134
  itsi.define_singleton_method("log_info", function!(log_info, 1))?;
67
135
  itsi.define_singleton_method("log_warn", function!(log_warn, 1))?;
@@ -71,6 +139,7 @@ fn init(ruby: &Ruby) -> Result<()> {
71
139
  server.define_singleton_method("new", function!(Server::new, -1))?;
72
140
  server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
73
141
  server.define_method("start", method!(Server::start, 0))?;
142
+ server.define_method("stop", method!(Server::stop, 0))?;
74
143
 
75
144
  let request = ruby.get_inner(&ITSI_REQUEST);
76
145
  request.define_method("path", method!(ItsiRequest::path, 0))?;
@@ -86,6 +155,8 @@ fn init(ruby: &Ruby) -> Result<()> {
86
155
  request.define_method("port", method!(ItsiRequest::port, 0))?;
87
156
  request.define_method("body", method!(ItsiRequest::body, 0))?;
88
157
  request.define_method("response", method!(ItsiRequest::response, 0))?;
158
+ request.define_method("json?", method!(ItsiRequest::is_json, 0))?;
159
+ request.define_method("html?", method!(ItsiRequest::is_html, 0))?;
89
160
 
90
161
  let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY);
91
162
  body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?;
@@ -102,6 +173,8 @@ fn init(ruby: &Ruby) -> Result<()> {
102
173
  response.define_method("close_read", method!(ItsiResponse::close_read, 0))?;
103
174
  response.define_method("close", method!(ItsiResponse::close, 0))?;
104
175
  response.define_method("hijack", method!(ItsiResponse::hijack, 1))?;
176
+ response.define_method("json?", method!(ItsiResponse::is_json, 0))?;
177
+ response.define_method("html?", method!(ItsiResponse::is_html, 0))?;
105
178
 
106
179
  Ok(())
107
180
  }
@@ -13,10 +13,11 @@ use crate::{
13
13
  use bytes::Bytes;
14
14
  use derive_more::Debug;
15
15
  use futures::StreamExt;
16
- use http::{request::Parts, Response, StatusCode};
16
+ use http::{request::Parts, HeaderValue, Response, StatusCode};
17
17
  use http_body_util::{combinators::BoxBody, BodyExt, Empty};
18
18
  use hyper::{body::Incoming, Request};
19
19
  use itsi_error::from::CLIENT_CONNECTION_CLOSED;
20
+ use itsi_rb_helpers::print_rb_backtrace;
20
21
  use itsi_tracing::{debug, error};
21
22
  use magnus::{
22
23
  error::{ErrorType, Result as MagnusResult},
@@ -33,7 +34,6 @@ use tokio::sync::{
33
34
  };
34
35
  static ID_CALL: LazyId = LazyId::new("call");
35
36
  static ID_MESSAGE: LazyId = LazyId::new("message");
36
- static ID_BACKTRACE: LazyId = LazyId::new("backtrace");
37
37
 
38
38
  #[derive(Debug)]
39
39
  #[magnus::wrap(class = "Itsi::Request", free_immediately, size)]
@@ -49,6 +49,7 @@ pub struct ItsiRequest {
49
49
  pub server: Arc<Server>,
50
50
  pub response: ItsiResponse,
51
51
  pub start: Instant,
52
+ pub content_type: String,
52
53
  }
53
54
 
54
55
  impl fmt::Display for ItsiRequest {
@@ -82,6 +83,14 @@ impl ItsiRequest {
82
83
  }
83
84
  }
84
85
 
86
+ pub fn is_json(&self) -> bool {
87
+ self.content_type.eq("application/json")
88
+ }
89
+
90
+ pub fn is_html(&self) -> bool {
91
+ self.content_type.eq("text/html")
92
+ }
93
+
85
94
  pub fn process(
86
95
  self,
87
96
  ruby: &Ruby,
@@ -106,14 +115,7 @@ impl ItsiRequest {
106
115
  debug!("Connection closed by client");
107
116
  response.close();
108
117
  } else if let Some(rb_err) = err.value() {
109
- let backtrace = rb_err
110
- .funcall::<_, _, Vec<String>>(*ID_BACKTRACE, ())
111
- .unwrap_or_default();
112
-
113
- error!("Error occurred in Handler: {:?}", rb_err);
114
- for line in backtrace {
115
- error!("{}", line);
116
- }
118
+ print_rb_backtrace(rb_err);
117
119
  response.internal_server_error(err.to_string());
118
120
  } else {
119
121
  response.internal_server_error(err.to_string());
@@ -175,8 +177,27 @@ impl ItsiRequest {
175
177
  server,
176
178
  listener,
177
179
  version: format!("{:?}", &parts.version),
178
- response: ItsiResponse::new(parts.clone(), response_channel.0),
180
+ response: ItsiResponse::new(
181
+ parts.clone(),
182
+ response_channel.0,
183
+ parts
184
+ .headers
185
+ .get("Accept")
186
+ .unwrap_or(&HeaderValue::from_static("text/html"))
187
+ .to_str()
188
+ .unwrap()
189
+ .to_string(),
190
+ ),
179
191
  start: Instant::now(),
192
+ content_type: parts
193
+ .headers
194
+ .get("Content-Type")
195
+ .unwrap_or(&HeaderValue::from_static(
196
+ "application/x-www-form-urlencoded",
197
+ ))
198
+ .to_str()
199
+ .unwrap()
200
+ .to_string(),
180
201
  parts,
181
202
  },
182
203
  response_channel.1,
@@ -37,6 +37,7 @@ use crate::server::serve_strategy::single_mode::RunningPhase;
37
37
  #[derive(Debug, Clone)]
38
38
  pub struct ItsiResponse {
39
39
  pub data: Arc<ResponseData>,
40
+ pub accept: String,
40
41
  }
41
42
 
42
43
  #[derive(Debug)]
@@ -76,7 +77,6 @@ impl ItsiResponse {
76
77
  (ReceiverStream::new(receiver), shutdown_rx),
77
78
  |(mut receiver, mut shutdown_rx)| async move {
78
79
  if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
79
- warn!("Disconnecting streaming client.");
80
80
  return None;
81
81
  }
82
82
  loop {
@@ -279,7 +279,8 @@ impl ItsiResponse {
279
279
  if let Some(writer) = writer.write().as_ref() {
280
280
  writer
281
281
  .blocking_send(Some(frame))
282
- .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?;
282
+ .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)
283
+ .ok();
283
284
  }
284
285
  Ok(0)
285
286
  }
@@ -293,11 +294,19 @@ impl ItsiResponse {
293
294
  Ok(true)
294
295
  }
295
296
 
297
+ pub fn is_html(&self) -> bool {
298
+ self.accept.starts_with("text/html")
299
+ }
300
+
301
+ pub fn is_json(&self) -> bool {
302
+ self.accept.starts_with("application/json")
303
+ }
304
+
296
305
  pub fn close_read(&self) -> MagnusResult<bool> {
297
- todo!();
306
+ Ok(true)
298
307
  }
299
308
 
300
- pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes>>) -> Self {
309
+ pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes>>, accept: String) -> Self {
301
310
  Self {
302
311
  data: Arc::new(ResponseData {
303
312
  response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
@@ -306,6 +315,7 @@ impl ItsiResponse {
306
315
  hijacked_socket: RwLock::new(None),
307
316
  parts,
308
317
  }),
318
+ accept,
309
319
  }
310
320
  }
311
321
 
@@ -101,7 +101,7 @@ impl FromStr for Bind {
101
101
  "IPv6 addresses must use [ ] when specifying a port".to_owned(),
102
102
  ));
103
103
  } else {
104
- (h, None) // Treat as a hostname
104
+ (h, p.parse::<u16>().ok()) // Treat as a hostname
105
105
  }
106
106
  } else {
107
107
  (url, None)
@@ -110,18 +110,22 @@ impl FromStr for Bind {
110
110
  let address = if let Ok(ip) = host.parse::<IpAddr>() {
111
111
  BindAddress::Ip(ip)
112
112
  } else {
113
- resolve_hostname(host)
114
- .map(BindAddress::Ip)
115
- .ok_or(ItsiError::ArgumentError(format!(
116
- "Failed to resolve hostname {}",
117
- host
118
- )))?
113
+ match protocol {
114
+ BindProtocol::Https | BindProtocol::Http => resolve_hostname(host)
115
+ .map(BindAddress::Ip)
116
+ .ok_or(ItsiError::ArgumentError(format!(
117
+ "Failed to resolve hostname {}",
118
+ host
119
+ )))?,
120
+ BindProtocol::Unix | BindProtocol::Unixs => BindAddress::UnixSocket(host.into()),
121
+ }
119
122
  };
120
- let (port, address) = match protocol {
121
- BindProtocol::Http => (port.or(Some(80)), address),
122
- BindProtocol::Https => (port.or(Some(443)), address),
123
- BindProtocol::Unix => (None, BindAddress::UnixSocket(host.into())),
124
- BindProtocol::Unixs => (None, BindAddress::UnixSocket(host.into())),
123
+
124
+ let port = match protocol {
125
+ BindProtocol::Http => port.or(Some(80)),
126
+ BindProtocol::Https => port.or(Some(443)),
127
+ BindProtocol::Unix => None,
128
+ BindProtocol::Unixs => None,
125
129
  };
126
130
 
127
131
  let tls_config = match protocol {