mirror of
https://github.com/servo/servo.git
synced 2025-06-21 23:59:00 +01:00
Auto merge of #26906 - gterzian:fix_streams_and_re-direct, r=Manishearth
Fix streaming request bodies <!-- Please describe your changes on the following line: --> FIX #26904 At this point I'm not sure if the hyper `Body::wrap_stream` integration by way of the IPC route is broken, or if perhaps there is a problem with the WPT server not handling the requests properly, as I am getting a bunch of: ``` 0:04.54 INFO STDERR: 127.0.0.1 - - [13/Jun/2020 18:22:08] code 400, message Bad request syntax ('4') 0:04.54 INFO STDERR: 127.0.0.1 - - [13/Jun/2020 18:22:08] "4" 400 - ``` In any case, the `start_reading` call in `body.rs` had to be put into the other function, since it should not be called for each chunk(this was actually failing before). I've also added the chunked header in case the source is a stream. Last thing to figure out, why is the request data not actually reaching the server, or not being handled properly by it. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [ ] `./mach build -d` does not report any errors - [ ] `./mach test-tidy` does not report any errors - [ ] These changes fix #___ (GitHub issue number if applicable) <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because ___ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
commit
16a99daa22
6 changed files with 282 additions and 109 deletions
|
@ -40,7 +40,9 @@ use js::rust::wrappers::JS_ParseJSON;
|
|||
use js::rust::HandleValue;
|
||||
use js::typedarray::{ArrayBuffer, CreateWith};
|
||||
use mime::{self, Mime};
|
||||
use net_traits::request::{BodyChunkRequest, BodySource as NetBodySource, RequestBody};
|
||||
use net_traits::request::{
|
||||
BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
|
||||
};
|
||||
use script_traits::serializable::BlobImpl;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
|
@ -49,7 +51,7 @@ use url::form_urlencoded;
|
|||
|
||||
/// The Dom object, or ReadableStream, that is the source of a body.
|
||||
/// <https://fetch.spec.whatwg.org/#concept-body-source>
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum BodySource {
|
||||
/// A ReadableStream comes with a null-source.
|
||||
Null,
|
||||
|
@ -59,6 +61,14 @@ pub enum BodySource {
|
|||
Object,
|
||||
}
|
||||
|
||||
/// The reason to stop reading from the body.
|
||||
enum StopReading {
|
||||
/// The stream has errored.
|
||||
Error,
|
||||
/// The stream is done.
|
||||
Done,
|
||||
}
|
||||
|
||||
/// The IPC route handler
|
||||
/// for <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
|
||||
/// This route runs in the script process,
|
||||
|
@ -69,7 +79,7 @@ struct TransmitBodyConnectHandler {
|
|||
stream: Trusted<ReadableStream>,
|
||||
task_source: NetworkingTaskSource,
|
||||
canceller: TaskCanceller,
|
||||
bytes_sender: Option<IpcSender<Vec<u8>>>,
|
||||
bytes_sender: Option<IpcSender<BodyChunkResponse>>,
|
||||
control_sender: IpcSender<BodyChunkRequest>,
|
||||
in_memory: Option<Vec<u8>>,
|
||||
in_memory_done: bool,
|
||||
|
@ -123,7 +133,14 @@ impl TransmitBodyConnectHandler {
|
|||
BodyChunkRequest::Chunk => body_handler.transmit_source(),
|
||||
// Note: this is actually sent from this process
|
||||
// by the TransmitBodyPromiseHandler when reading stops.
|
||||
BodyChunkRequest::Done => body_handler.stop_reading(),
|
||||
BodyChunkRequest::Done => {
|
||||
body_handler.stop_reading(StopReading::Done);
|
||||
},
|
||||
// Note: this is actually sent from this process
|
||||
// by the TransmitBodyPromiseHandler when the stream errors.
|
||||
BodyChunkRequest::Error => {
|
||||
body_handler.stop_reading(StopReading::Error);
|
||||
},
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
@ -138,7 +155,7 @@ impl TransmitBodyConnectHandler {
|
|||
fn transmit_source(&mut self) {
|
||||
if self.in_memory_done {
|
||||
// Step 5.1.3
|
||||
self.stop_reading();
|
||||
self.stop_reading(StopReading::Done);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -153,29 +170,58 @@ impl TransmitBodyConnectHandler {
|
|||
.bytes_sender
|
||||
.as_ref()
|
||||
.expect("No bytes sender to transmit source.")
|
||||
.send(bytes.clone());
|
||||
.send(BodyChunkResponse::Chunk(bytes.clone()));
|
||||
return;
|
||||
}
|
||||
warn!("Re-directs for file-based Blobs not supported yet.");
|
||||
}
|
||||
|
||||
/// Take the IPC sender sent by `net`, so we can send body chunks with it.
|
||||
fn start_reading(&mut self, sender: IpcSender<Vec<u8>>) {
|
||||
/// Also the entry point to <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||||
fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
|
||||
self.bytes_sender = Some(sender);
|
||||
|
||||
// If we're using an actual ReadableStream, acquire a reader for it.
|
||||
if self.source == BodySource::Null {
|
||||
let stream = self.stream.clone();
|
||||
let _ = self.task_source.queue_with_canceller(
|
||||
task!(start_reading_request_body_stream: move || {
|
||||
// Step 1, Let body be request’s body.
|
||||
let rooted_stream = stream.root();
|
||||
|
||||
// TODO: Step 2, If body is null.
|
||||
|
||||
// Step 3, get a reader for stream.
|
||||
rooted_stream.start_reading().expect("Couldn't acquire a reader for the body stream.");
|
||||
|
||||
// Note: this algorithm continues when the first chunk is requested by `net`.
|
||||
}),
|
||||
&self.canceller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop the IPC sender sent by `net`
|
||||
fn stop_reading(&mut self) {
|
||||
// Note: this should close the corresponding receiver,
|
||||
// and terminate the request stream in `net`.
|
||||
self.bytes_sender = None;
|
||||
fn stop_reading(&mut self, reason: StopReading) {
|
||||
let bytes_sender = self
|
||||
.bytes_sender
|
||||
.take()
|
||||
.expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
|
||||
match reason {
|
||||
StopReading::Error => {
|
||||
let _ = bytes_sender.send(BodyChunkResponse::Error);
|
||||
},
|
||||
StopReading::Done => {
|
||||
let _ = bytes_sender.send(BodyChunkResponse::Done);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The entry point to <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||||
/// Step 4 and following of <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||||
fn transmit_body_chunk(&mut self) {
|
||||
if self.in_memory_done {
|
||||
// Step 5.1.3
|
||||
self.stop_reading();
|
||||
self.stop_reading(StopReading::Done);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -188,7 +234,7 @@ impl TransmitBodyConnectHandler {
|
|||
|
||||
// In case of the data being in-memory, send everything in one chunk, by-passing SpiderMonkey.
|
||||
if let Some(bytes) = self.in_memory.clone() {
|
||||
let _ = bytes_sender.send(bytes);
|
||||
let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
|
||||
// Mark this body as `done` so that we can stop reading in the next tick,
|
||||
// matching the behavior of the promise-based flow
|
||||
self.in_memory_done = true;
|
||||
|
@ -197,27 +243,9 @@ impl TransmitBodyConnectHandler {
|
|||
|
||||
let _ = self.task_source.queue_with_canceller(
|
||||
task!(setup_native_body_promise_handler: move || {
|
||||
// Step 1, Let body be request’s body.
|
||||
//
|
||||
// TODO: We need the handle the body null case,
|
||||
// here assuming body is something and we have the corresponding stream.
|
||||
let rooted_stream = stream.root();
|
||||
let global = rooted_stream.global();
|
||||
|
||||
// TODO: Step 2, If body is null,
|
||||
// then queue a fetch task on request to process request end-of-body
|
||||
// for request and abort these steps.
|
||||
|
||||
// TODO: queuing those "process request ..." tasks means we also need a handle on Request here.
|
||||
|
||||
// Step 3, get a reader for stream.
|
||||
if rooted_stream.start_reading().is_err() {
|
||||
// Note: this can happen if script starts consuming request body
|
||||
// before fetch starts transmitting it.
|
||||
// Not in the spec.
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 4, the result of reading a chunk from body’s stream with reader.
|
||||
let promise = rooted_stream.read_a_chunk();
|
||||
|
||||
|
@ -225,14 +253,19 @@ impl TransmitBodyConnectHandler {
|
|||
// are a combination of the promise native handler here,
|
||||
// and the corresponding IPC route in `component::net::http_loader`.
|
||||
let promise_handler = Box::new(TransmitBodyPromiseHandler {
|
||||
bytes_sender,
|
||||
bytes_sender: bytes_sender.clone(),
|
||||
stream: rooted_stream.clone(),
|
||||
control_sender: control_sender.clone(),
|
||||
});
|
||||
|
||||
let rejection_handler = Box::new(TransmitBodyPromiseRejectionHandler {
|
||||
bytes_sender,
|
||||
stream: rooted_stream,
|
||||
control_sender,
|
||||
});
|
||||
|
||||
let rejection_handler = Box::new(TransmitBodyPromiseRejectionHandler {stream: rooted_stream});
|
||||
|
||||
let handler = PromiseNativeHandler::new(&global, Some(promise_handler), Some(rejection_handler));
|
||||
let handler =
|
||||
PromiseNativeHandler::new(&global, Some(promise_handler), Some(rejection_handler));
|
||||
|
||||
let realm = enter_realm(&*global);
|
||||
let comp = InRealm::Entered(&realm);
|
||||
|
@ -248,7 +281,7 @@ impl TransmitBodyConnectHandler {
|
|||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
struct TransmitBodyPromiseHandler {
|
||||
#[ignore_malloc_size_of = "Channels are hard"]
|
||||
bytes_sender: IpcSender<Vec<u8>>,
|
||||
bytes_sender: IpcSender<BodyChunkResponse>,
|
||||
stream: DomRoot<ReadableStream>,
|
||||
#[ignore_malloc_size_of = "Channels are hard"]
|
||||
control_sender: IpcSender<BodyChunkRequest>,
|
||||
|
@ -278,8 +311,7 @@ impl Callback for TransmitBodyPromiseHandler {
|
|||
Ok(chunk) => chunk,
|
||||
Err(_) => {
|
||||
// Step 5.5, the "otherwise" steps.
|
||||
// TODO: terminate fetch.
|
||||
let _ = self.control_sender.send(BodyChunkRequest::Done);
|
||||
let _ = self.control_sender.send(BodyChunkRequest::Error);
|
||||
return self.stream.stop_reading();
|
||||
},
|
||||
};
|
||||
|
@ -287,7 +319,7 @@ impl Callback for TransmitBodyPromiseHandler {
|
|||
// Step 5.1 and 5.2, transmit chunk.
|
||||
// Send the chunk to the body transmitter in net::http_loader::obtain_response.
|
||||
// TODO: queue a fetch task on request to process request body for request.
|
||||
let _ = self.bytes_sender.send(chunk);
|
||||
let _ = self.bytes_sender.send(BodyChunkResponse::Chunk(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,14 +327,18 @@ impl Callback for TransmitBodyPromiseHandler {
|
|||
/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
struct TransmitBodyPromiseRejectionHandler {
|
||||
#[ignore_malloc_size_of = "Channels are hard"]
|
||||
bytes_sender: IpcSender<BodyChunkResponse>,
|
||||
stream: DomRoot<ReadableStream>,
|
||||
#[ignore_malloc_size_of = "Channels are hard"]
|
||||
control_sender: IpcSender<BodyChunkRequest>,
|
||||
}
|
||||
|
||||
impl Callback for TransmitBodyPromiseRejectionHandler {
|
||||
/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
|
||||
fn callback(&self, _cx: JSContext, _v: HandleValue, _realm: InRealm) {
|
||||
// Step 5.4, the "rejection" steps.
|
||||
// TODO: terminate fetch.
|
||||
let _ = self.control_sender.send(BodyChunkRequest::Error);
|
||||
return self.stream.stop_reading();
|
||||
}
|
||||
}
|
||||
|
@ -376,7 +412,14 @@ impl ExtractedBody {
|
|||
BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
|
||||
// Note: this is actually sent from this process
|
||||
// by the TransmitBodyPromiseHandler when reading stops.
|
||||
BodyChunkRequest::Done => body_handler.stop_reading(),
|
||||
BodyChunkRequest::Done => {
|
||||
body_handler.stop_reading(StopReading::Done);
|
||||
},
|
||||
// Note: this is actually sent from this process
|
||||
// by the TransmitBodyPromiseHandler when the stream errors.
|
||||
BodyChunkRequest::Error => {
|
||||
body_handler.stop_reading(StopReading::Error);
|
||||
},
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue