osprey 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|