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
@@ -68,9 +68,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
68
68
 
69
69
  [[package]]
70
70
  name = "cc"
71
- version = "1.2.16"
71
+ version = "1.2.15"
72
72
  source = "registry+https://github.com/rust-lang/crates.io-index"
73
- checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
73
+ checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
74
74
  dependencies = [
75
75
  "shlex",
76
76
  ]
@@ -186,9 +186,9 @@ dependencies = [
186
186
 
187
187
  [[package]]
188
188
  name = "itoa"
189
- version = "1.0.15"
189
+ version = "1.0.14"
190
190
  source = "registry+https://github.com/rust-lang/crates.io-index"
191
- checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
191
+ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
192
192
 
193
193
  [[package]]
194
194
  name = "itsi-scheduler"
@@ -507,9 +507,9 @@ dependencies = [
507
507
 
508
508
  [[package]]
509
509
  name = "redox_syscall"
510
- version = "0.5.10"
510
+ version = "0.5.9"
511
511
  source = "registry+https://github.com/rust-lang/crates.io-index"
512
- checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
512
+ checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
513
513
  dependencies = [
514
514
  "bitflags",
515
515
  ]
@@ -560,9 +560,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
560
560
 
561
561
  [[package]]
562
562
  name = "ring"
563
- version = "0.17.11"
563
+ version = "0.17.14"
564
564
  source = "registry+https://github.com/rust-lang/crates.io-index"
565
- checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
565
+ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
566
566
  dependencies = [
567
567
  "cc",
568
568
  "cfg-if",
@@ -586,9 +586,9 @@ checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
586
586
 
587
587
  [[package]]
588
588
  name = "ryu"
589
- version = "1.0.20"
589
+ version = "1.0.19"
590
590
  source = "registry+https://github.com/rust-lang/crates.io-index"
591
- checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
591
+ checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
592
592
 
593
593
  [[package]]
594
594
  name = "scopeguard"
@@ -624,9 +624,9 @@ dependencies = [
624
624
 
625
625
  [[package]]
626
626
  name = "serde_json"
627
- version = "1.0.140"
627
+ version = "1.0.139"
628
628
  source = "registry+https://github.com/rust-lang/crates.io-index"
629
- checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
629
+ checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
630
630
  dependencies = [
631
631
  "itoa",
632
632
  "memchr",
@@ -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 {