osprey 0.1.0
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Cargo.lock +1350 -0
- data/Cargo.toml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +22 -0
- data/exe/osprey +77 -0
- data/ext/osprey/Cargo.toml +28 -0
- data/ext/osprey/extconf.rb +6 -0
- data/ext/osprey/src/.DS_Store +0 -0
- data/ext/osprey/src/lib.rs +1659 -0
- data/ext/osprey/src/structs/mod.rs +2 -0
- data/ext/osprey/src/structs/rack_request.rs +168 -0
- data/ext/osprey/src/structs/rack_response.rs +152 -0
- data/flamegraph.svg +491 -0
- data/lib/osprey/.DS_Store +0 -0
- data/lib/osprey/patches.rb +8 -0
- data/lib/osprey/rack/handler/osprey.rb +22 -0
- data/lib/osprey/version.rb +5 -0
- data/lib/osprey.rb +53 -0
- data/server.cert +0 -0
- data/server.key +0 -0
- data/sig/hummingbird.rbs +4 -0
- data/test.crt +29 -0
- data/test.key +52 -0
- metadata +100 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
use crate::{RackResponse, ARRAY_PROC, ID_CALL, ID_TRANSFORM_VALUES, OSPREY};
|
2
|
+
use dashmap::DashMap;
|
3
|
+
use hyper::{
|
4
|
+
header::{HeaderName, HeaderValue},
|
5
|
+
HeaderMap,
|
6
|
+
};
|
7
|
+
use log::{debug, error};
|
8
|
+
use magnus::{
|
9
|
+
block::Proc,
|
10
|
+
r_string::FString,
|
11
|
+
value::{Opaque, OpaqueId, ReprValue},
|
12
|
+
Class, Error, RHash, Ruby, Value,
|
13
|
+
};
|
14
|
+
use std::{
|
15
|
+
cell::RefCell,
|
16
|
+
collections::HashMap,
|
17
|
+
os::{fd::IntoRawFd, unix::net::UnixStream},
|
18
|
+
sync::{Arc, LazyLock},
|
19
|
+
};
|
20
|
+
use tokio::sync::oneshot;
|
21
|
+
|
22
|
+
pub struct RackRequestInner {
|
23
|
+
pub path: String,
|
24
|
+
pub script_name: String,
|
25
|
+
pub query_string: String,
|
26
|
+
pub method: String,
|
27
|
+
pub version: String,
|
28
|
+
pub protocol: Vec<String>,
|
29
|
+
pub host: String,
|
30
|
+
pub scheme: String,
|
31
|
+
pub headers: HashMap<String, String>,
|
32
|
+
pub remote_addr: String,
|
33
|
+
pub sender: Arc<std::sync::Mutex<Option<oneshot::Sender<RackResponse>>>>,
|
34
|
+
pub port: i32,
|
35
|
+
pub stream_fd: i32,
|
36
|
+
}
|
37
|
+
|
38
|
+
#[magnus::wrap(class = "RackRequest", free_immediately, size)]
|
39
|
+
pub struct RackRequest(pub RefCell<Option<RackRequestInner>>);
|
40
|
+
|
41
|
+
unsafe impl Send for RackRequest {}
|
42
|
+
|
43
|
+
pub fn define_request_info(ruby: &magnus::Ruby) -> Result<(), Error> {
|
44
|
+
ruby.define_class("RackRequest", ruby.class_object())?;
|
45
|
+
Ok(())
|
46
|
+
}
|
47
|
+
|
48
|
+
static INTERNED_RUBY_STRINGS: LazyLock<DashMap<String, Opaque<FString>>> =
|
49
|
+
LazyLock::new(|| DashMap::new());
|
50
|
+
|
51
|
+
fn intern_ruby_str(rust_str: &str, ruby: &Ruby) -> Opaque<FString> {
|
52
|
+
if let Some(entry) = INTERNED_RUBY_STRINGS.get(rust_str) {
|
53
|
+
entry.clone()
|
54
|
+
} else {
|
55
|
+
let rstr = Opaque::from(ruby.str_new(rust_str).to_interned_str());
|
56
|
+
INTERNED_RUBY_STRINGS.insert(rust_str.to_owned(), rstr);
|
57
|
+
rstr
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
impl RackRequest {
|
62
|
+
pub fn process(&self, ruby: &Ruby) -> Result<(), Error> {
|
63
|
+
let req = self.0.take().unwrap();
|
64
|
+
let path_info = req.path.strip_prefix(&req.script_name).unwrap_or("");
|
65
|
+
let sender_clone = req.sender.clone();
|
66
|
+
let fd = req.stream_fd;
|
67
|
+
let hijack_proc = ruby.proc_from_fn(move |ruby, _args, _block| {
|
68
|
+
let (local_fd, remote_fd) = if fd == -1 {
|
69
|
+
let (local, remote) = UnixStream::pair()
|
70
|
+
.map_err(|_| "Failed to create ephemeral socket pair")
|
71
|
+
.unwrap();
|
72
|
+
(local.into_raw_fd() * -1, remote.into_raw_fd())
|
73
|
+
} else {
|
74
|
+
(fd * -1, fd)
|
75
|
+
};
|
76
|
+
|
77
|
+
let response_sender = sender_clone
|
78
|
+
.clone()
|
79
|
+
.lock()
|
80
|
+
.unwrap()
|
81
|
+
.take()
|
82
|
+
.expect("No response sender");
|
83
|
+
if let Err(err) = response_sender.send(RackResponse::from_parts(
|
84
|
+
local_fd as i16,
|
85
|
+
HeaderMap::new(),
|
86
|
+
ruby.qnil().as_value(),
|
87
|
+
)) {
|
88
|
+
debug!(
|
89
|
+
"(PID={}) Failed to send response: {:?}. Connection dropped.",
|
90
|
+
unsafe { libc::getpid() },
|
91
|
+
err
|
92
|
+
);
|
93
|
+
}
|
94
|
+
let io_class: magnus::RClass = magnus::eval::<magnus::RClass>("::IO").unwrap();
|
95
|
+
|
96
|
+
io_class.new_instance((remote_fd, "r+b")).unwrap()
|
97
|
+
});
|
98
|
+
|
99
|
+
let response = ruby.get_inner(&OSPREY).funcall::<OpaqueId, (
|
100
|
+
Vec<&str>,
|
101
|
+
Opaque<FString>,
|
102
|
+
Opaque<FString>,
|
103
|
+
Opaque<FString>,
|
104
|
+
Opaque<FString>,
|
105
|
+
Opaque<FString>,
|
106
|
+
Vec<String>,
|
107
|
+
i32,
|
108
|
+
i32,
|
109
|
+
HashMap<String, String>,
|
110
|
+
Proc,
|
111
|
+
), (i16, RHash, Value)>(
|
112
|
+
*ID_CALL,
|
113
|
+
(
|
114
|
+
vec![&req.query_string, &req.remote_addr, &path_info],
|
115
|
+
intern_ruby_str(&req.script_name, ruby),
|
116
|
+
intern_ruby_str(&req.method, ruby),
|
117
|
+
intern_ruby_str(&req.host, ruby),
|
118
|
+
intern_ruby_str(&req.scheme, ruby),
|
119
|
+
intern_ruby_str(&req.version, ruby),
|
120
|
+
req.protocol,
|
121
|
+
req.port,
|
122
|
+
req.stream_fd,
|
123
|
+
req.headers,
|
124
|
+
hijack_proc,
|
125
|
+
),
|
126
|
+
);
|
127
|
+
|
128
|
+
if let Some(response_sender) = req.sender.lock().unwrap().take() {
|
129
|
+
let (status, headers, body): (i16, HeaderMap, Value) = match response {
|
130
|
+
Ok(res) => {
|
131
|
+
let (status, headers, body) = res;
|
132
|
+
let header_iter = headers
|
133
|
+
.funcall_with_block::<OpaqueId, (), HashMap<String, Vec<String>>>(
|
134
|
+
*ID_TRANSFORM_VALUES,
|
135
|
+
(),
|
136
|
+
ruby.get_inner(&ARRAY_PROC),
|
137
|
+
)
|
138
|
+
.unwrap()
|
139
|
+
.into_iter()
|
140
|
+
.flat_map(|(key, values)| {
|
141
|
+
values.into_iter().map(move |value| {
|
142
|
+
(
|
143
|
+
HeaderName::from_bytes(key.as_bytes()).unwrap(),
|
144
|
+
HeaderValue::from_bytes(value.as_bytes()).unwrap(),
|
145
|
+
)
|
146
|
+
})
|
147
|
+
});
|
148
|
+
|
149
|
+
(status, HeaderMap::from_iter(header_iter), body)
|
150
|
+
}
|
151
|
+
Err(err) => {
|
152
|
+
error!("Error in Ruby response: {:?}", err);
|
153
|
+
(500, HeaderMap::new(), ruby.qnil().as_value())
|
154
|
+
}
|
155
|
+
};
|
156
|
+
|
157
|
+
if let Err(err) = response_sender.send(RackResponse::from_parts(status, headers, body))
|
158
|
+
{
|
159
|
+
debug!(
|
160
|
+
"(PID={}) Failed to send response: {:?}. Connection dropped.",
|
161
|
+
unsafe { libc::getpid() },
|
162
|
+
err
|
163
|
+
);
|
164
|
+
}
|
165
|
+
}
|
166
|
+
Ok(())
|
167
|
+
}
|
168
|
+
}
|
@@ -0,0 +1,152 @@
|
|
1
|
+
use bytes::{Bytes, BytesMut};
|
2
|
+
use http_body_util::{combinators::BoxBody, Full};
|
3
|
+
use hyper::{HeaderMap, Response, StatusCode};
|
4
|
+
use magnus::{
|
5
|
+
function, method, try_convert,
|
6
|
+
value::{LazyId, OpaqueId, ReprValue},
|
7
|
+
Error, IntoValue, Module, Object, Value,
|
8
|
+
};
|
9
|
+
use std::{
|
10
|
+
convert::Infallible,
|
11
|
+
sync::{Arc, Mutex},
|
12
|
+
};
|
13
|
+
|
14
|
+
#[derive(Debug, Clone)]
|
15
|
+
pub struct RackResponse {
|
16
|
+
pub status: i16,
|
17
|
+
pub headers: HeaderMap,
|
18
|
+
pub body: Bytes,
|
19
|
+
}
|
20
|
+
|
21
|
+
static ID_EACH: LazyId = LazyId::new("each");
|
22
|
+
static ID_CALL: LazyId = LazyId::new("call");
|
23
|
+
static ID_CLOSE: LazyId = LazyId::new("close");
|
24
|
+
static ID_TO_ENUM: LazyId = LazyId::new("to_enum");
|
25
|
+
static ID_INJECT: LazyId = LazyId::new("inject");
|
26
|
+
static ID_TO_ARY: LazyId = LazyId::new("to_ary");
|
27
|
+
static ID_CONCAT: LazyId = LazyId::new("<<");
|
28
|
+
|
29
|
+
impl RackResponse {
|
30
|
+
pub fn from_parts(status: i16, headers: HeaderMap, body: Value) -> Self {
|
31
|
+
let body = unsafe { RackResponse::gather_body(&body).unwrap_or_default() };
|
32
|
+
Self {
|
33
|
+
status,
|
34
|
+
headers,
|
35
|
+
body,
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
pub fn into_hyper_response(self) -> Response<BoxBody<Bytes, Infallible>> {
|
40
|
+
let mut response = Response::new(BoxBody::new(Full::new(self.body)));
|
41
|
+
*response.status_mut() = StatusCode::from_u16(self.status as u16).unwrap();
|
42
|
+
*response.headers_mut() = self.headers;
|
43
|
+
response
|
44
|
+
}
|
45
|
+
|
46
|
+
unsafe fn gather_body(body_value: &Value) -> Result<Bytes, Error> {
|
47
|
+
if body_value.respond_to(*ID_EACH, false)? {
|
48
|
+
let bytes: Bytes = body_value
|
49
|
+
.funcall::<OpaqueId, (OpaqueId,), Value>(*ID_TO_ENUM, (*ID_EACH,))
|
50
|
+
.unwrap()
|
51
|
+
.funcall::<OpaqueId, (OpaqueId,), Value>(*ID_INJECT, (*ID_CONCAT,))
|
52
|
+
.unwrap()
|
53
|
+
.to_r_string()
|
54
|
+
.unwrap()
|
55
|
+
.to_bytes();
|
56
|
+
|
57
|
+
body_value.check_funcall::<OpaqueId, (), Value>(*ID_CLOSE, ());
|
58
|
+
return Ok(bytes);
|
59
|
+
}
|
60
|
+
|
61
|
+
// 2) If body responds to `to_ary`, it can be treated like an array of strings
|
62
|
+
if let Some(Ok(array)) = body_value.check_funcall::<_, (), Vec<Bytes>>(*ID_TO_ARY, ()) {
|
63
|
+
body_value.check_funcall::<OpaqueId, (), Value>(*ID_CLOSE, ());
|
64
|
+
return Ok(array.into_iter().flatten().collect());
|
65
|
+
}
|
66
|
+
|
67
|
+
// 3) If body responds to `call`, treat it like a Streaming Body
|
68
|
+
if body_value.respond_to(*ID_CALL, false)? {
|
69
|
+
let collector = make_collector()?;
|
70
|
+
let _: Option<Value> = body_value.funcall(*ID_CALL, (collector,))?;
|
71
|
+
let data = get_collector_bytes(&collector)?;
|
72
|
+
body_value.check_funcall::<OpaqueId, (), Value>(*ID_CLOSE, ());
|
73
|
+
return Ok(data);
|
74
|
+
}
|
75
|
+
|
76
|
+
// 4) If none of the above apply, stringify the body and return it
|
77
|
+
let bytes = body_value.to_r_string().unwrap().to_bytes();
|
78
|
+
body_value.check_funcall::<OpaqueId, (), Value>(*ID_CLOSE, ());
|
79
|
+
Ok(bytes)
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
#[magnus::wrap(class = "BodyCollector")]
|
84
|
+
struct BodyCollector {
|
85
|
+
buffer: Arc<Mutex<BytesMut>>,
|
86
|
+
}
|
87
|
+
|
88
|
+
impl BodyCollector {
|
89
|
+
fn new() -> Self {
|
90
|
+
BodyCollector {
|
91
|
+
buffer: Arc::new(Mutex::new(BytesMut::new())),
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
// e.g. def write(str)
|
96
|
+
fn write(&self, s: Bytes) -> Result<(), Error> {
|
97
|
+
self.buffer
|
98
|
+
.lock()
|
99
|
+
.expect("Unlock body collector buffer")
|
100
|
+
.extend(&s);
|
101
|
+
Ok(())
|
102
|
+
}
|
103
|
+
|
104
|
+
// def <<(str)
|
105
|
+
fn append(&self, s: Bytes) -> Result<Self, Error> {
|
106
|
+
self.buffer
|
107
|
+
.lock()
|
108
|
+
.expect("Unlock body collector buffer")
|
109
|
+
.extend(&s);
|
110
|
+
Ok(Self {
|
111
|
+
buffer: Arc::clone(&self.buffer),
|
112
|
+
})
|
113
|
+
}
|
114
|
+
|
115
|
+
fn flush(&self) -> Result<(), Error> {
|
116
|
+
Ok(())
|
117
|
+
}
|
118
|
+
|
119
|
+
fn close(&self) -> Result<(), Error> {
|
120
|
+
Ok(())
|
121
|
+
}
|
122
|
+
|
123
|
+
fn get_buffer(&self) -> Bytes {
|
124
|
+
self.buffer
|
125
|
+
.lock()
|
126
|
+
.expect("Unlock body collector buffer")
|
127
|
+
.clone()
|
128
|
+
.freeze()
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
// Then to create a Ruby object of this class:
|
133
|
+
fn make_collector() -> Result<Value, Error> {
|
134
|
+
let collector = BodyCollector::new();
|
135
|
+
Ok(collector.into_value())
|
136
|
+
}
|
137
|
+
|
138
|
+
fn get_collector_bytes(collector_val: &Value) -> Result<Bytes, Error> {
|
139
|
+
let collector: &BodyCollector = try_convert::TryConvert::try_convert(*collector_val)?;
|
140
|
+
Ok(collector.get_buffer())
|
141
|
+
}
|
142
|
+
|
143
|
+
pub fn define_collector(ruby: &magnus::Ruby) -> Result<(), Error> {
|
144
|
+
let class = ruby.define_class("BodyCollector", ruby.class_object())?;
|
145
|
+
class.define_singleton_method("new", function!(BodyCollector::new, 0))?;
|
146
|
+
class.define_method("write", method!(BodyCollector::write, 1))?;
|
147
|
+
class.define_method("append", method!(BodyCollector::append, 1))?;
|
148
|
+
class.define_method("flush", method!(BodyCollector::flush, 0))?;
|
149
|
+
class.define_method("close", method!(BodyCollector::close, 0))?;
|
150
|
+
|
151
|
+
Ok(())
|
152
|
+
}
|