itsi-scheduler 0.2.16 → 0.2.18
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 +4 -4
- data/Cargo.lock +1 -1
- data/ext/itsi_acme/Cargo.toml +1 -1
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_server/Cargo.toml +3 -1
- data/ext/itsi_server/src/lib.rs +6 -1
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +4 -4
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +64 -33
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +422 -110
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +62 -15
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
- data/ext/itsi_server/src/server/binds/listener.rs +45 -7
- data/ext/itsi_server/src/server/frame_stream.rs +142 -0
- data/ext/itsi_server/src/server/http_message_types.rs +142 -9
- data/ext/itsi_server/src/server/io_stream.rs +28 -5
- data/ext/itsi_server/src/server/lifecycle_event.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -56
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +5 -7
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +5 -5
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +7 -10
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +4 -6
- data/ext/itsi_server/src/server/mod.rs +1 -0
- data/ext/itsi_server/src/server/process_worker.rs +3 -4
- data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +16 -12
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +83 -31
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +166 -142
- data/ext/itsi_server/src/server/signal.rs +37 -9
- data/ext/itsi_server/src/server/thread_worker.rs +84 -69
- data/ext/itsi_server/src/services/itsi_http_service.rs +43 -43
- data/ext/itsi_server/src/services/static_file_server.rs +28 -47
- data/lib/itsi/scheduler/version.rb +1 -1
- metadata +2 -1
@@ -1,4 +1,9 @@
|
|
1
|
-
use
|
1
|
+
use crate::server::{
|
2
|
+
frame_stream::{BufferedStream, FrameStream},
|
3
|
+
http_message_types::{HttpBody, HttpResponse},
|
4
|
+
serve_strategy::single_mode::RunningPhase,
|
5
|
+
};
|
6
|
+
use bytes::{Buf, Bytes};
|
2
7
|
use derive_more::Debug;
|
3
8
|
use futures::stream::{unfold, StreamExt};
|
4
9
|
use http::{
|
@@ -6,10 +11,11 @@ use http::{
|
|
6
11
|
request::Parts,
|
7
12
|
HeaderMap, HeaderName, HeaderValue, Request, Response, StatusCode,
|
8
13
|
};
|
9
|
-
use http_body_util::
|
10
|
-
use hyper::
|
14
|
+
use http_body_util::Empty;
|
15
|
+
use hyper::upgrade::Upgraded;
|
11
16
|
use hyper_util::rt::TokioIo;
|
12
17
|
use itsi_error::Result;
|
18
|
+
use itsi_rb_helpers::call_without_gvl;
|
13
19
|
use itsi_tracing::error;
|
14
20
|
use magnus::error::Result as MagnusResult;
|
15
21
|
use memchr::{memchr, memchr_iter};
|
@@ -17,105 +23,66 @@ use parking_lot::RwLock;
|
|
17
23
|
use std::{
|
18
24
|
collections::HashMap,
|
19
25
|
io,
|
26
|
+
ops::Deref,
|
20
27
|
os::{fd::FromRawFd, unix::net::UnixStream},
|
21
28
|
str::FromStr,
|
22
29
|
sync::Arc,
|
30
|
+
time::Duration,
|
23
31
|
};
|
24
32
|
use tokio::{
|
25
33
|
io::AsyncReadExt,
|
26
34
|
net::UnixStream as TokioUnixStream,
|
27
|
-
sync::{
|
28
|
-
mpsc::{self},
|
29
|
-
watch,
|
30
|
-
},
|
35
|
+
sync::{mpsc::Sender, oneshot::Sender as OneshotSender, watch},
|
31
36
|
};
|
32
|
-
use tokio_stream::wrappers::ReceiverStream;
|
33
37
|
use tokio_util::io::ReaderStream;
|
34
|
-
use tracing::warn;
|
35
|
-
|
36
|
-
use crate::server::{
|
37
|
-
byte_frame::ByteFrame, http_message_types::HttpResponse,
|
38
|
-
serve_strategy::single_mode::RunningPhase,
|
39
|
-
};
|
38
|
+
use tracing::{info, warn};
|
40
39
|
|
41
40
|
#[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)]
|
42
41
|
#[derive(Debug, Clone)]
|
43
42
|
pub struct ItsiHttpResponse {
|
44
|
-
pub
|
43
|
+
pub inner: Arc<ResponseInner>,
|
44
|
+
}
|
45
|
+
|
46
|
+
impl Deref for ItsiHttpResponse {
|
47
|
+
type Target = Arc<ResponseInner>;
|
48
|
+
|
49
|
+
fn deref(&self) -> &Self::Target {
|
50
|
+
&self.inner
|
51
|
+
}
|
45
52
|
}
|
46
53
|
|
47
54
|
#[derive(Debug)]
|
48
|
-
pub struct
|
55
|
+
pub struct ResponseInner {
|
56
|
+
pub frame_writer: RwLock<Option<Sender<Bytes>>>,
|
49
57
|
pub response: RwLock<Option<HttpResponse>>,
|
50
|
-
pub response_writer: RwLock<Option<mpsc::Sender<ByteFrame>>>,
|
51
|
-
pub response_buffer: RwLock<BytesMut>,
|
52
58
|
pub hijacked_socket: RwLock<Option<UnixStream>>,
|
53
|
-
pub
|
59
|
+
pub response_sender: RwLock<Option<OneshotSender<ResponseFrame>>>,
|
60
|
+
pub shutdown_rx: watch::Receiver<RunningPhase>,
|
61
|
+
pub parts: Arc<Parts>,
|
62
|
+
}
|
63
|
+
|
64
|
+
#[derive(Debug)]
|
65
|
+
pub enum ResponseFrame {
|
66
|
+
HttpResponse(HttpResponse),
|
67
|
+
HijackedResponse(ItsiHttpResponse),
|
54
68
|
}
|
55
69
|
|
56
70
|
impl ItsiHttpResponse {
|
57
|
-
pub
|
58
|
-
|
59
|
-
|
60
|
-
receiver: mpsc::Receiver<ByteFrame>,
|
71
|
+
pub fn new(
|
72
|
+
parts: Arc<Parts>,
|
73
|
+
response_sender: OneshotSender<ResponseFrame>,
|
61
74
|
shutdown_rx: watch::Receiver<RunningPhase>,
|
62
|
-
) ->
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
75
|
+
) -> Self {
|
76
|
+
Self {
|
77
|
+
inner: Arc::new(ResponseInner {
|
78
|
+
parts,
|
79
|
+
shutdown_rx,
|
80
|
+
response_sender: RwLock::new(Some(response_sender)),
|
81
|
+
frame_writer: RwLock::new(None),
|
82
|
+
response: RwLock::new(Some(Response::new(HttpBody::empty()))),
|
83
|
+
hijacked_socket: RwLock::new(None),
|
84
|
+
}),
|
71
85
|
}
|
72
|
-
let mut response = self.data.response.write().take().unwrap();
|
73
|
-
*response.body_mut() = if matches!(first_frame, ByteFrame::Empty) {
|
74
|
-
BoxBody::new(Empty::new())
|
75
|
-
} else if matches!(first_frame, ByteFrame::End(_)) {
|
76
|
-
BoxBody::new(Full::new(first_frame.into()))
|
77
|
-
} else {
|
78
|
-
let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame))));
|
79
|
-
let frame_stream = unfold(
|
80
|
-
(ReceiverStream::new(receiver), shutdown_rx),
|
81
|
-
|(mut receiver, mut shutdown_rx)| async move {
|
82
|
-
if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
|
83
|
-
return None;
|
84
|
-
}
|
85
|
-
loop {
|
86
|
-
tokio::select! {
|
87
|
-
maybe_bytes = receiver.next() => {
|
88
|
-
match maybe_bytes {
|
89
|
-
Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => {
|
90
|
-
return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx)));
|
91
|
-
}
|
92
|
-
_ => {
|
93
|
-
return None;
|
94
|
-
}
|
95
|
-
}
|
96
|
-
},
|
97
|
-
_ = shutdown_rx.changed() => {
|
98
|
-
match *shutdown_rx.borrow() {
|
99
|
-
RunningPhase::ShutdownPending => {
|
100
|
-
warn!("Disconnecting streaming client.");
|
101
|
-
return None;
|
102
|
-
},
|
103
|
-
_ => continue,
|
104
|
-
}
|
105
|
-
}
|
106
|
-
}
|
107
|
-
}
|
108
|
-
},
|
109
|
-
);
|
110
|
-
|
111
|
-
let combined_stream = initial_frame.chain(frame_stream);
|
112
|
-
BoxBody::new(StreamBody::new(combined_stream))
|
113
|
-
};
|
114
|
-
response
|
115
|
-
}
|
116
|
-
|
117
|
-
pub fn close(&self) {
|
118
|
-
self.data.response_writer.write().take();
|
119
86
|
}
|
120
87
|
|
121
88
|
async fn two_way_bridge(upgraded: Upgraded, local: TokioUnixStream) -> io::Result<()> {
|
@@ -163,8 +130,7 @@ impl ItsiHttpResponse {
|
|
163
130
|
&self,
|
164
131
|
) -> Result<(HeaderMap, StatusCode, bool, TokioUnixStream)> {
|
165
132
|
let hijacked_socket =
|
166
|
-
self.
|
167
|
-
.hijacked_socket
|
133
|
+
self.hijacked_socket
|
168
134
|
.write()
|
169
135
|
.take()
|
170
136
|
.ok_or(itsi_error::ItsiError::InvalidInput(
|
@@ -191,9 +157,9 @@ impl ItsiHttpResponse {
|
|
191
157
|
pub async fn process_hijacked_response(&self) -> Result<HttpResponse> {
|
192
158
|
let (headers, status, requires_upgrade, reader) = self.read_hijacked_headers().await?;
|
193
159
|
let mut response = if requires_upgrade {
|
194
|
-
let parts = self.
|
160
|
+
let parts = self.parts.clone();
|
195
161
|
tokio::spawn(async move {
|
196
|
-
let mut req = Request::from_parts(parts, Empty::<Bytes>::new());
|
162
|
+
let mut req = Request::from_parts((*parts).clone(), Empty::<Bytes>::new());
|
197
163
|
match hyper::upgrade::on(&mut req).await {
|
198
164
|
Ok(upgraded) => {
|
199
165
|
Self::two_way_bridge(upgraded, reader)
|
@@ -203,14 +169,14 @@ impl ItsiHttpResponse {
|
|
203
169
|
Err(e) => eprintln!("upgrade error: {:?}", e),
|
204
170
|
}
|
205
171
|
});
|
206
|
-
Response::new(
|
172
|
+
Response::new(HttpBody::empty())
|
207
173
|
} else {
|
208
174
|
let stream = ReaderStream::new(reader);
|
209
175
|
let boxed_body = if headers
|
210
176
|
.get(TRANSFER_ENCODING)
|
211
177
|
.is_some_and(|h| h == "chunked")
|
212
178
|
{
|
213
|
-
|
179
|
+
HttpBody::stream(unfold(
|
214
180
|
(stream, Vec::new()),
|
215
181
|
|(mut stream, mut buf)| async move {
|
216
182
|
loop {
|
@@ -231,7 +197,7 @@ impl ItsiHttpResponse {
|
|
231
197
|
if buf.starts_with(b"\r\n") {
|
232
198
|
buf.drain(..2);
|
233
199
|
}
|
234
|
-
return Some((Ok(
|
200
|
+
return Some((Ok(Bytes::from(data)), (stream, buf)));
|
235
201
|
}
|
236
202
|
match stream.next().await {
|
237
203
|
Some(Ok(chunk)) => buf.extend_from_slice(&chunk),
|
@@ -239,15 +205,11 @@ impl ItsiHttpResponse {
|
|
239
205
|
}
|
240
206
|
}
|
241
207
|
},
|
242
|
-
))
|
208
|
+
))
|
243
209
|
} else {
|
244
|
-
|
245
|
-
|
|
246
|
-
|
247
|
-
.map(Frame::data)
|
248
|
-
.map_err(|e| unreachable!("unexpected io error: {:?}", e))
|
249
|
-
},
|
250
|
-
)))
|
210
|
+
HttpBody::stream(stream.map(|result: std::result::Result<Bytes, io::Error>| {
|
211
|
+
result.map_err(|e| unreachable!("unexpected io error: {:?}", e))
|
212
|
+
}))
|
251
213
|
};
|
252
214
|
Response::new(boxed_body)
|
253
215
|
};
|
@@ -259,59 +221,97 @@ impl ItsiHttpResponse {
|
|
259
221
|
|
260
222
|
pub fn internal_server_error(&self, message: String) {
|
261
223
|
error!(message);
|
262
|
-
self.
|
263
|
-
if let Some(
|
224
|
+
self.close_write().ok();
|
225
|
+
if let Some(mut response) = self.response.write().take() {
|
264
226
|
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
227
|
+
if let Some(sender) = self.response_sender.write().take() {
|
228
|
+
sender.send(ResponseFrame::HttpResponse(response)).ok();
|
229
|
+
}
|
265
230
|
}
|
266
231
|
}
|
267
232
|
|
268
|
-
pub fn
|
269
|
-
self.
|
233
|
+
pub fn service_unavailable(&self) {
|
234
|
+
self.close_write().ok();
|
235
|
+
if let Some(mut response) = self.response.write().take() {
|
236
|
+
*response.status_mut() = StatusCode::SERVICE_UNAVAILABLE;
|
237
|
+
if let Some(sender) = self.response_sender.write().take() {
|
238
|
+
sender.send(ResponseFrame::HttpResponse(response)).ok();
|
239
|
+
}
|
240
|
+
}
|
270
241
|
}
|
271
242
|
|
272
|
-
pub fn
|
273
|
-
|
243
|
+
pub fn send_frame(&self, frame: Bytes) -> MagnusResult<()> {
|
244
|
+
{
|
245
|
+
if self.frame_writer.read().is_none() && self.response.read().is_some() {
|
246
|
+
if let Some(mut response) = self.response.write().take() {
|
247
|
+
let (writer, reader) = tokio::sync::mpsc::channel::<Bytes>(256);
|
248
|
+
let shutdown_rx = self.shutdown_rx.clone();
|
249
|
+
let frame_stream = FrameStream::new(reader, shutdown_rx.clone());
|
250
|
+
|
251
|
+
let buffered =
|
252
|
+
BufferedStream::new(frame_stream, 32 * 1024, Duration::from_millis(10));
|
253
|
+
*response.body_mut() = HttpBody::stream(buffered);
|
254
|
+
self.frame_writer.write().replace(writer);
|
255
|
+
if let Some(sender) = self.response_sender.write().take() {
|
256
|
+
sender.send(ResponseFrame::HttpResponse(response)).ok();
|
257
|
+
}
|
258
|
+
} else {
|
259
|
+
info!("No response!");
|
260
|
+
}
|
261
|
+
}
|
262
|
+
}
|
263
|
+
if let Some(frame_writer) = self.frame_writer.read().as_ref() {
|
264
|
+
call_without_gvl(|| frame_writer.blocking_send(frame))
|
265
|
+
.map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?;
|
266
|
+
}
|
267
|
+
Ok(())
|
274
268
|
}
|
275
269
|
|
276
|
-
pub fn
|
277
|
-
|
270
|
+
pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<()> {
|
271
|
+
if self.frame_writer.read().is_some() {
|
272
|
+
self.send_frame(frame)?;
|
273
|
+
self.close()?;
|
274
|
+
return Ok(());
|
275
|
+
}
|
276
|
+
if let Some(mut response) = self.response.write().take() {
|
277
|
+
if frame.is_empty() {
|
278
|
+
*response.body_mut() = HttpBody::empty();
|
279
|
+
} else {
|
280
|
+
*response.body_mut() = HttpBody::full(frame);
|
281
|
+
}
|
282
|
+
if let Some(sender) = self.response_sender.write().take() {
|
283
|
+
sender.send(ResponseFrame::HttpResponse(response)).ok();
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
Ok(())
|
278
288
|
}
|
279
289
|
|
280
|
-
pub fn
|
281
|
-
self.
|
290
|
+
pub fn close_write(&self) -> MagnusResult<bool> {
|
291
|
+
self.frame_writer.write().take();
|
292
|
+
Ok(true)
|
282
293
|
}
|
283
294
|
|
284
|
-
pub fn
|
285
|
-
|
286
|
-
self.data.response_writer.write().take();
|
287
|
-
result
|
295
|
+
pub fn recv_frame(&self) {
|
296
|
+
// not implemented
|
288
297
|
}
|
289
298
|
|
290
|
-
pub fn
|
291
|
-
|
292
|
-
frame: ByteFrame,
|
293
|
-
writer: &RwLock<Option<mpsc::Sender<ByteFrame>>>,
|
294
|
-
) -> MagnusResult<()> {
|
295
|
-
if let Some(writer) = writer.write().as_ref() {
|
296
|
-
return Ok(writer
|
297
|
-
.blocking_send(frame)
|
298
|
-
.map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?);
|
299
|
-
}
|
300
|
-
Ok(())
|
299
|
+
pub fn is_closed(&self) -> bool {
|
300
|
+
self.response.read().is_none() && self.frame_writer.read().is_none()
|
301
301
|
}
|
302
302
|
|
303
303
|
pub fn is_hijacked(&self) -> bool {
|
304
|
-
self.
|
304
|
+
self.hijacked_socket.read().is_some()
|
305
305
|
}
|
306
306
|
|
307
|
-
pub fn
|
308
|
-
self.
|
309
|
-
|
307
|
+
pub fn close(&self) -> MagnusResult<()> {
|
308
|
+
self.close_write()?;
|
309
|
+
self.close_read()?;
|
310
|
+
Ok(())
|
310
311
|
}
|
311
312
|
|
312
313
|
pub fn accept_str(&self) -> &str {
|
313
|
-
self.
|
314
|
-
.parts
|
314
|
+
self.parts
|
315
315
|
.headers
|
316
316
|
.get(ACCEPT)
|
317
317
|
.and_then(|hv| hv.to_str().ok()) // handle invalid utf-8
|
@@ -330,25 +330,9 @@ impl ItsiHttpResponse {
|
|
330
330
|
Ok(true)
|
331
331
|
}
|
332
332
|
|
333
|
-
pub fn
|
334
|
-
|
335
|
-
|
336
|
-
response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
|
337
|
-
response_writer: RwLock::new(Some(response_writer)),
|
338
|
-
response_buffer: RwLock::new(BytesMut::new()),
|
339
|
-
hijacked_socket: RwLock::new(None),
|
340
|
-
parts,
|
341
|
-
}),
|
342
|
-
}
|
343
|
-
}
|
344
|
-
|
345
|
-
pub fn add_header(&self, name: Bytes, value: Bytes) -> MagnusResult<()> {
|
346
|
-
let header_name: HeaderName = HeaderName::from_bytes(&name).map_err(|e| {
|
347
|
-
itsi_error::ItsiError::InvalidInput(format!("Invalid header name {:?}: {:?}", name, e))
|
348
|
-
})?;
|
349
|
-
if let Some(ref mut resp) = *self.data.response.write() {
|
350
|
-
let headers_mut = resp.headers_mut();
|
351
|
-
self.insert_header(headers_mut, &header_name, value);
|
333
|
+
pub fn reserve_headers(&self, header_count: usize) -> MagnusResult<()> {
|
334
|
+
if let Some(ref mut resp) = *self.response.write() {
|
335
|
+
resp.headers_mut().try_reserve(header_count).ok();
|
352
336
|
}
|
353
337
|
Ok(())
|
354
338
|
}
|
@@ -394,8 +378,22 @@ impl ItsiHttpResponse {
|
|
394
378
|
}
|
395
379
|
}
|
396
380
|
|
381
|
+
pub fn add_header(&self, header_name: Bytes, value: Bytes) -> MagnusResult<()> {
|
382
|
+
if let Some(ref mut resp) = *self.response.write() {
|
383
|
+
let headers_mut = resp.headers_mut();
|
384
|
+
let header_name = HeaderName::from_bytes(&header_name).map_err(|e| {
|
385
|
+
itsi_error::ItsiError::InvalidInput(format!(
|
386
|
+
"Invalid header name {:?}: {:?}",
|
387
|
+
header_name, e
|
388
|
+
))
|
389
|
+
})?;
|
390
|
+
self.insert_header(headers_mut, &header_name, value);
|
391
|
+
}
|
392
|
+
Ok(())
|
393
|
+
}
|
394
|
+
|
397
395
|
pub fn add_headers(&self, headers: HashMap<Bytes, Vec<Bytes>>) -> MagnusResult<()> {
|
398
|
-
if let Some(ref mut resp) = *self.
|
396
|
+
if let Some(ref mut resp) = *self.response.write() {
|
399
397
|
let headers_mut = resp.headers_mut();
|
400
398
|
for (name, values) in headers {
|
401
399
|
let header_name = HeaderName::from_bytes(&name).map_err(|e| {
|
@@ -414,7 +412,7 @@ impl ItsiHttpResponse {
|
|
414
412
|
}
|
415
413
|
|
416
414
|
pub fn set_status(&self, status: u16) -> MagnusResult<()> {
|
417
|
-
if let Some(ref mut resp) = *self.
|
415
|
+
if let Some(ref mut resp) = *self.response.write() {
|
418
416
|
*resp.status_mut() = StatusCode::from_u16(status).map_err(|e| {
|
419
417
|
itsi_error::ItsiError::InvalidInput(format!(
|
420
418
|
"Invalid status code {:?}: {:?}",
|
@@ -428,13 +426,14 @@ impl ItsiHttpResponse {
|
|
428
426
|
pub fn hijack(&self, fd: i32) -> MagnusResult<()> {
|
429
427
|
let stream = unsafe { UnixStream::from_raw_fd(fd) };
|
430
428
|
|
431
|
-
*self.
|
432
|
-
if let Some(
|
433
|
-
|
434
|
-
.
|
435
|
-
.
|
429
|
+
*self.hijacked_socket.write() = Some(stream);
|
430
|
+
if let Some(sender) = self.response_sender.write().take() {
|
431
|
+
sender
|
432
|
+
.send(ResponseFrame::HijackedResponse(self.clone()))
|
433
|
+
.ok();
|
436
434
|
}
|
437
|
-
|
435
|
+
|
436
|
+
self.close()?;
|
438
437
|
Ok(())
|
439
438
|
}
|
440
439
|
}
|