-
-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Non-GET requests don't work when passed as a single Request argument #19
Comments
Hi @sbking thanks for reporting this. I'm going to look into this. Do you have an example how you call it with a |
Hi @zirkelc, I'm trying to use this with a REST API client generated by https://github.com/hey-api/openapi-ts/blob/main/packages/client-fetch/src/index.ts#L62-L69 And a little example on MDN: https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch#examples Something like this is a minimal example: const request = new Request("https://example.com", { method: "POST" });
fetch(request); |
Hi @sbking, I have fixed this issue and released a new version v4.0.1. Could you please check if it works for you? |
@zirkelc Unfortunately now I'm getting this when sending a POST request:
|
@sbking I had the same issue, however, it looks like this is specific to Node.js implementation of fetch(). I can reproduce the same error on Node.js v20 and plain (async () => {
const stream = new ReadableStream({
async start(controller) {
controller.enqueue("This ");
controller.enqueue("is ");
controller.enqueue("a ");
controller.enqueue("slow ");
controller.enqueue("request.");
controller.close();
},
}).pipeThrough(new TextEncoderStream());
const request = new Request("https://example.com", {
method: "POST",
body: stream,
// duplex: "half",
});
const response = await fetch(request);
console.log(response);
})(); 2024-08-21_08-55-46.mp4If your So maybe it is streaming the body even though you supply only a string. Can you set the |
@zirkelc I don't really have control over |
@sbking could you set up a small repository with some code so I can debug this issue on my side? No sensitive data or secret keys, just the code you are using and a few comments where I can insert my own AWS credentials to investigate it further |
Hey @zirkelc I can try to set up a small reproduction repo when I have some time. For now, I'm using this instead which seems to be working perfectly. I believe the trick is the call to export async function sigv4Fetch(request: Request) {
const newRequest = request.clone();
const url = new URL(request.url);
const body = await request.text();
const headers = {
...Object.fromEntries(request.headers.entries()),
// host is required by AWS Signature V4: https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
host: url.host,
};
const smithyRequest = new HttpRequest({
hostname: url.hostname,
path: url.pathname,
protocol: url.protocol,
port: url.port ? Number(url.port) : undefined,
username: url.username,
password: url.password,
method: request.method.toUpperCase(),
body,
query: Object.fromEntries(url.searchParams.entries()),
fragment: url.hash,
headers,
});
const signer = new SignatureV4({
credentials: fromNodeProviderChain(),
service: "lambda",
region: process.env.AWS_REGION ?? "us-east-1",
sha256: Sha256,
});
const signedRequest = await signer.sign(smithyRequest);
for (const [key, value] of Object.entries(signedRequest.headers)) {
newRequest.headers.set(key, value);
}
return fetch(newRequest);
} |
Hi @sbking good that you found a workaround. I will investigate this further. I assume the |
Hey @sbking I'm planning to give this issue another go. I want to try your solution and copy the signed headers instead of using the As I can't really reproduce the issue on my side, would you be able to try it? I will publish a release candidate package which you can temporarily install. |
@sbking I've released v4.1.0 which only copies the headers to the request. Could you try if that works for you? |
@zirkelc This does not solve the issue I'm afraid. And the spec change introduced here: whatwg/fetch#1457 Not sure why they did not use a default value as there is only one option, but you need to add You can repro this rather easily: Run fetch post call to an api gateway (or basically any server/endpoint) on Node@22 with a body and omit the duplex option. |
@florianbepunkt thank you very much for these links, they have finally put me on the right track. I think there are two different issues here: 1. Fetching with a
|
Thanks. Spent half of my day on this. Currently I got it working with this: import { SignatureV4 } from "@smithy/signature-v4";
import { Sha256 } from "@aws-crypto/sha256-js";
import { HttpRequest } from "@smithy/protocol-http";
async function signRequest2(request: Request) {
const url = new URL(request.url);
const headers = new Headers(request.headers);
const body = request.method !== "GET" && request.method !== "HEAD" ? await request.text() : undefined;
const credentials = {
// redacted
}
// Construct the HttpRequest for SigV4
const awsRequest = new HttpRequest({
protocol: url.protocol.slice(0, -1), // "https:"
hostname: url.hostname,
port: url.port ? parseInt(url.port) : undefined,
method: request.method,
path: url.pathname,
query: Object.fromEntries(url.searchParams),
headers: Object.fromEntries(headers),
body, // Only include the body for non-GET/HEAD requests
});
// Create the SigV4 signer
const signer = new SignatureV4({
credentials: credentials,
sha256: Sha256,
region: "eu-central-1",
service: "execute-api",
});
// Sign the request
const signedRequest = await signer.sign(awsRequest);
// Convert the signed request back to a fetch-compatible format
const signedHeaders = new Headers(signedRequest.headers);
return new Request(request.url, {
method: signedRequest.method,
headers: signedHeaders,
body: signedRequest.body, // Include the body if it exists
});
} Interestingly enough, this works without specifying duplex: "half" in the request. Not sure why. Will test your PR tomorrow. |
In which environment / Node.js version did you try it? Maybe
Thank you! |
@zirkelc Sorry, took me a bit longer. Tried your PR, but still got a 403. I went with my solution above for the time being. |
@florianbepunkt it's not working for me either, and I'm trying to figure out the issue. I'll let you know when I know more. |
Hey @florianbepunkt I think I finally managed to fix it. I now always create a If you get a chance to test it, you can install it from the PR: #25 (comment) Also in response to your previous comment #19 (comment):
I think this works without setting const body = request.method !== "GET" && request.method !== "HEAD" ? await request.text() : undefined; Not sure if this works with all body types the same way. |
Hey @sbking and @florianbepunkt I released v4.2.0 which should close finally this issue 🙏 Thanks to @florianbepunkt I identified the root cause: when provided with a Thank you for your help! |
When passing a non-GET
Request
object as a single argument, the method is automatically changed to "GET".The text was updated successfully, but these errors were encountered: