queryStringParameters not working with redirect on production

Hi @jimniels,

Yea, I think that was due to the fact that the code that was enabling the pass-through was reverted but has since been re-fixed. Can you confirm that this is the case?

For some context, the post mentions the fix was re-deployed 8 days ago.

@Dennis nope, still not working for me.

Just ran a rebuild of my site above. My build outputs my redirect, which looks like this:

3:35:39 PM:   '/api/v1/search /.netlify/functions/api-v1-search?siteId=macos 200', 

You can see my rule is saying to redirect to another path with a hard-coded query param (i.e. /path to /.netlify-fn?foo=bar) but the query param is not showing up in the lambda.

You can test this at the preview build. Do a GET to:

https://5febaf1576d34a0007ef33aa--macosicongallery.netlify.app/api/v1/search?name=swift

It doesn’t work. You’ll see the siteId parameter is coming back from the lambda function as undefined. If you hard code it in the GET, it works:

https://5febaf1576d34a0007ef33aa--macosicongallery.netlify.app/api/v1/search?name=swift&siteId=macos

So the redirect is still not working. Or maybe I’m missing something?

I feel like I must be missing something really basic here?

All I’m doing is a redirect from a path to a new path with a (hard-coded) query string param.

/path/to/resource /redirect/path?with=query 200

How does that not work?

Following up with more context. Hoping @fool or @Dennis can help me out.

So I’ve been playing with this a bit more to try and figure it out. I’ve found that doing a regular redirect rule works as expected. But using rewrites doesn’t work as I would expect.

Note: all of these examples are using my preview build at: https://5ff7956eba84190008348a88--macosicongallery.netlify.app

Redirects (301s)

Here’s the redirect rule in my _redirects file:

/netlify-support-test-no-200 /404.html?foo=bar

Because I didn’t specify a HTTP code, it uses a 301 and redirects the user from one path to another with the specified query params.

If I curl that resource, I can see I get a 301 redirect to the 404 page with the query parameters.

If I hit that URL in my browser – /netlify-support-test-no-200 – it redirects to the 404 page with the query parameters in the URL bar.

Screen Shot 2021-01-07 at 4.19.04 PM

Cool, that works just as expected.

Rewrites and proxies (200s)

If I setup a URL to rewrite with a 200, then I don’t see the query parameters show up. Here’s the rule in my _redirects file:

/netlify-support-test /404.html?foo=bar 200

If I curl this resource, it returns a 200 with the contents of the 404 page. That makes sense:

If I hit it directly in my browser (/netlify-support-test) I see the contents of the 404 page (same as the redirect above) but my browser’s URL doesn’t change (and doesn’t have the query params in it)

Screen Shot 2021-01-07 at 4.24.22 PM

That all makes sense. Because this is a rewrite, Netlify is making a request to the URL with the query params behind the scenes, so any query params are hidden from the URL bar and from the curl.

But that request that’s happening behind the scenes isn’t working as expected

I have one more rewrite rule setup. This one is designed to be a rewrite that points to a URL with query params. Here’s the rule:

/netlify-support-test-params /.netlify/functions/netlify-support-test?foo=bar 200

Note that this is pointing at a netlify (lambda) function with explicit query parameters. The lambda function’s code is really simple. It’s essentially echoing back the query parameters.

Screen Shot 2021-01-07 at 4.45.14 PM

So my expectation is that if I curl /netlify-support-test-params I’ll get a 200 and it will echo back {"query": {"foo": "bar"}. But it does not. The lambda receives no query parameters:

However, if I do include some query params myself in the request, it echoes those back. For example, if I curl /netlify-support-test-params?hello=world, I get a 200 with {"query":{"hello":"world"}} echoed back to me:

What gives?

Is my expectation incorrect here? It appears Netlify’s rewrites/proxy engine is not actually making a request with query params in tow. To restate:

Given this rule: /path /rewrite?with=params 200

I expect: a request to /path will result in Netlify making a request to /rewrite?with=params behind the scenes and then returning a 200 result to /path.

But what I get is that a request to /path results in, apparently, Netlify making a request to /rewrite without any query params and returning a 200 result to /path.

1 Like

Hey there,
We put this question to the team who works on our proxy and they explained that rewrites to functions work differently than rewrites to other paths. They suggested that you could accomplish what you want by grabbing the incoming path (/.netlify/functions/netlify-support-test?foo=bar) directly in your function using event.path and then parsing the param you’re interested in within your function. Sorry for the confusion here. Let us know if that solution would work for you.

rewrites to functions work differently than rewrites to other paths

Well that’s interesting. That must be the source of my problem.

They suggested that you could accomplish what you want by grabbing the incoming path…directly in your function using event.path and then parsing the param you’re interested in within your function.

That doesn’t work. Or maybe I’m misunderstanding the suggestion?

Let’s say I have the following redirect:

/thing /.netlify/functions/thing?foo=bar 200

If I do a GET to /thing that results in the invoked function’s event.path being /thing, which as I type that I guess makes sense in the context of function calls. Given that case, however, there are no query parameters present and it appears impossible for a function call to know about its /.netlify/functions/path along with any query params that might be present, unless you do a GET to it directly.

So—and I’m writing out loud here—it sounds like my expectation is completely wrong. Given the redirect above, I would expect a GET to /thing to proxy a call to the netlify function URL, which would result in the function having even.path be /.netlify/functions/thing?foo=bar, which I would then be able to parse. But that’s not how it’s working. The location that is being proxied to—the /.netlify/... url—is completely hidden from the invoked function.

Thus, from what I can tell in the implementation, it seems impossible to add query string parameters to a redirect for a function call, i.e. even given a hard-coded redirect of

/path /.netlify/functions/path?foo=bar 200

It’s impossible for the invoked function to know anything about foo=bar.

Hi!

I suggested the workaround of using the request path in the function, and I’ll try to explain it a bit better.

Context

Yes, the event.path in a function will always be the path of the original incoming request.
This is mostly legacy behaviour that was implemented like this once and we can’t really change it now without breaking people’s existing functions.

Primary use case: This means that you can use redirects to make Netlify invoke a function for any path. Great, right?

Guessing your use case

I’m guessing what you want to do is rewrite to a function from different paths and pass some data along in the different redirects based on which rule was matched, so that the function can behave differently using that added context.
Yes, this is where the redirects mechanism falls a bit short.

Solution 1: Do the matching inside your function

Since our redirect mechanism is not able to provide you the data of which rule it matched, you could try to match the original request in your function to determine what it should do.

Example:

_redirects

/first-thing /.netlify/functions/the-only-function 200!
/second-thing /.netlify/functions/the-only-function 200!

the-only-function.js

exports.handler = async function(event, context) {
    if (event.path === "/first-thing") {
        // do one thing
    } else if (event.path === "/second-thing") {
        // do the other thing
    } else {
        // return 400 status, because the function was invoked raw
    }
}

Solution 2: Proxy headers hack

When you rewrite something you can add custom proxy headers (click for docs).

The same example solved using those:

netlify.toml

[[redirects]]
from = "/first-thing"
to = "/.netlify/functions/the-only-function"
status = 200
force = true
headers = {X-Variant = "first-thing"}

[[redirects]]
from = "/second-thing"
to = "/.netlify/functions/the-only-function"
status = 200
force = true
headers = {X-Variant = "second-thing"}

the-only-function.js

exports.handler = async function(event, context) {
    if (event.headers["X-Variant"] === "first-thing") {
        // do one thing
    } else if (event.headers["X-Variant"] === "second-thing") {
        // do the other thing
    } else {
        // return 400 status, because the function was invoked raw
    }
}

Hope this helps you solve your specific issue!

1 Like

Greetings!

Wanted to chime in here too with another option that may make for some better flexibility — in addition to @marcus’s premise of having your rewrite/redirect entry points being unique paths, e.g. /first-thing and /second-thing, you can use query string parameters on the entry-point side of even a rewrite to a Function and those will be available within the Function.

Meaning I can have a rewrite of

/only-thing /.netlify/functions/my-one-function 200!

and send a request to example.com/only-thing?image=the_big_one.jpg

then my function’s event.path will be /only-thing and event.queryStringParameters will be { "image": "the_big_one.jpg" }.

Just more options for you :slight_smile:


Jon

Thanks for the feedback. Going to add some context for anybody who ends up here in the future.

Clarifying My Misunderstanding

I can see now that my original confusion was around the redirect functionality as it applies to functions. To quote @Marcus

a function will always be the path of the original incoming request

So if I have a redirect to a function like this:

/foo /.netlify/functions/bar?with=param 200

And I do a GET to /foo, the lambda will be involved with event.path as foo. Hard-coded query params in the rewrite don’t show up.

Honestly, now as I type it out, it makes sense. I had mistakenly assumed the function call would note its path as being its canonical URL (i.e. always the /.netlify/functions/...) rather than whatever path it was originally called at, which makes sense. I could see use cases on either side, but I would venture my case is the outlier.

Solution to My Problem

What I need is some extra data about the environment. The suggestion from @Marcus for headers is more aligned with what I need.

The production host name contains a piece of information I need. When I call a function, that shows up in event.headers.host, so I can parse that and find the info I need. The problem there is that when I’m on localhost that piece of info is gone. So I need a solution for local testing. This is easy enough now to just hard-code. But in the future, I’ll probably look into a more dynamic solution. Something along the lines of the using a custom header, i.e. X-My-Value. The complexity there is that I need that value to be based on an env variable and it looks like you can use env variables in netlify.toml but it requires extra complexity.

Thanks everyone for the clarification!

1 Like

Hey folks, I happened across an alternative workaround that might be helpful for some of you. It involves setting an additional redirect in front of the rewrite to the function.

For example, say you wanted to take a path like /img/kitten.jpg?width=640 and send it to your img-resizer function with the filename and width passed in event.queryStringParameters.

You could first transform the path with a 301 redirect with placeholders:

/img/:filename width=:width  /resized-img?file=:filename&w=:width&q=75   301

That rule changes /img/kitten.jpg?w=640 to /resized-img?file=kitten.jpg&width=640. Note that I was able to do three things with this rule:

  • converted part of the path to a query parameter
  • changed the name of an existing parameter
  • added an additional hard-coded parameter

Now you can pass that transformed path to the function endpoint:

/resized-img  /.netlify/functions/img-resizer  200

There’s no need to specify the query parameters because they are passed to the function event automatically.

:warning: Note that while this second rule can be a 200 rewrite, the first one cannot. The first rule must be a 301 (or 302) redirect because the URL has to change in order for the redirect parser to be triggered a second time.

Now, with those two rules in place, If I enter https://my-site.netlify.app/img/kitten.jpg?width=640 into a browser address bar, the following will happen:

  1. The address bar URL will change to
    https://my-site.netlify.app/resized-img?file=kitten.jpg&w=640&q=75
    
  2. My img-resizer will be invoked with the following values passed in the event parameter:
    {
      path: '/resized-img',
      queryStringParameters: { file: 'kitten.jpg', q: '75', w: '640' },
      ... other values ...
    }
    
  3. When the img-resizer function finishes, it will return its result to the browser. (Presumably the function has been written to return a resized image. :wink: )

With this image example, it’s worth noting that the whole double-redirect thing doesn’t require the path to be entered in the browser address bar. It works with inline references in code, too. So if I had an img tag in my page, like

<img src="/img/kitten.jpg?width=640"/>

It will still return the result of the img-resizer function in that img tag. You could do the same thing with, for example, a json file request in client-side JavaScript.

2 Likes

Interesting! :thinking: @verythorough have you happened to check if the fetch api follows 301s by default so that this approach could be used for JS calls?


Jon

The default redirect policy on fetch is follow.

A RequestRedirect enum value, which can be one the following strings:

  • follow
  • error
  • manual

If not specified when the request is created, it takes the default value of follow .

See Request.redirect - Web APIs | MDN

1 Like

Great! Seems like that may be a viable work-around! Thanks @verythorough :raised_hands:t2: