Don't throw for fetch()/Request() with duplex: "half" for streaming requests to non-HTTP/2 TCP servers

423 views
Skip to first unread message

guest271314

unread,
Apr 28, 2024, 5:09:47 PM4/28/24
to Chromium-dev
I'm experimenting with Direct Sockets TCPServerSocket.

What I found is Node.js and Deno convert streaming requests to Transfer-Encoding: chunked when duplex: "half" is set in fetch() options and the server does not respond with HTTP/2 headers. 

I understand that no browser implements h2c with prior knowledge or clear text.

It should be possible to POST a ReadableStream to a Direct Sockets TCPServerSocket without TLS (Direct Sockets doesn't implement TLS) or HTTP/2 server.

Consider this code run from Node.js, Deno and Chrome-For-Testing. node and deno will convert the uploaded ReadableStream to a Transfer-Encoding: chunked request. Chrome-For-Testing 126 will throw

VM721:15
       
       
       POST http://0.0.0.0:8080/ net::ERR_H2_OR_QUIC_REQUIRED

POST / HTTP/1.1
access-control-request-private-network: true
accept: */*
accept-language: *
user-agent: Deno/1.42.4
accept-encoding: gzip, br
host: 0.0.0.0:8080
transfer-encoding: chunked 12
test another test
0
script.js:205 POST / HTTP/1.1
access-control-request-private-network: true
accept: */*
accept-language: *
user-agent: Deno/1.42.4
accept-encoding: gzip, br
host: 0.0.0.0:8080
transfer-encoding: chunked

POST / HTTP/1.1
host: 0.0.0.0:8080
connection: keep-alive
Access-Control-Request-Private-Network: true
accept: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate
transfer-encoding: chunked 5
test
script.js:205 POST / HTTP/1.1
host: 0.0.0.0:8080
connection: keep-alive
Access-Control-Request-Private-Network: true
accept: */*
accept-language: *
sec-fetch-mode: cors
user-agent: node
accept-encoding: gzip, deflate
transfer-encoding: chunked

var {readable, writable} = new TransformStream({}, {}, {highWaterMark: Infinity}); // new Blob(["a"]).stream();
var writer = writable.getWriter();
var encoder = new TextEncoder();
var decoder = new TextDecoder();

async function fn(body = "test") {
/*
let data;
if (body instanceof ReadableStream) {
data = await new Response(body).arrayBuffer();
} else {
data = body
}
*/
return fetch("http://0.0.0.0:8080", {
duplex: "half",
method: "post",
body, // : data,
headers: {
"Access-Control-Request-Private-Network": true,
}
})
.then((r) => {
console.log(...r.headers, r.body);
r.body.pipeTo(new WritableStream({
write(v) {
console.log(decoder.decode(v));
},
close() {
console.log("Stream closed");
}
})).catch(console.warn);
}).catch(console.error);
}

fn(readable);
await writer.ready;
await writer.write(encoder.encode("test "));
await writer.write(encoder.encode("another test "));
await writer.close();
Reply all
Reply to author
Forward
0 new messages