Redirect to Function - Splat is not working as expected

I am having problems with splats in redirects files:

This is my _redirects file:

/*  /.netlify/functions/image?file=:splat   200

I would expect this to take the full request path and supply it as the file parameter to my function.

My function just outputs whatever it gets in file right now: https://netlify-redirect-foo.netlify.app/.netlify/functions/image?file=foo
If there is no param supplied, it outputs no file parameter supplied: https://netlify-redirect-foo.netlify.app/.netlify/functions/image

Unfortunately the redirect does not seem to pick up the path and supply it to the function:
https://netlify-redirect-foo.netlify.app/lala => This outputs no file parameter supplied.

What am I doing wrong here?
Did I misunderstand how splats work?

(I tried to use placeholders before instead of the splat, but that also did not work unfortunately)

Hey @janpio! :wave:t2:

Welcome to the Community :slight_smile:

Also great timing. I actually just built a little system yesterday that uses a redirect just like that to fall HTML requests back to a function :nerd_face: What I found is that when you’re using a 302 redirect to the function (which causes the function url to flash in the browser address since the browser redirects and calls the function itself), the behavior works like you think. When you use a 200 (‘rewrite’), I’m not sure why, but I don’t think the splat injection to the query string works quite the same… but you do have the initial request path available instead. I ended up just using

/* /.netlify/functions/my-function 200

and inside the function, referencing event.path for the /image-name. That’s worked great for me!

My _redirects and function - both part of an open source project called That’s at… :slight_smile:

Hope that helps!

–
Jon

1 Like

Thanks will test that out and report back if this works for me.

Seems like an inaccuracy in either the implementation or the documentation about it though that cost me quite some time :confused:

Yeah :confused: it may be one of those fringe cases that operates a bit tricky. I happen to be doing dev on a project that has a test function. Let me try that out live real quick and make sure my prior notes are correct :+1:t2:

Yes, that indeed works for me:

https://netlify-redirect-foo.netlify.app/lala outputs lala now with this code:

const file = event.path.substring(1)
1 Like

Alright, so I ran some test cases and did indeed find some interesting results.

Setup

Given the following _redirects record for a splat-injected rewrite:

/test/* /.netlify/functions/test-rewrite-to-function?parm=:splat 200

I ran the following tests (using my favorite CLI tool, httpie:

Standard:

http https://sun.sargesites.com/test/hello

Which yielded the following object for the event argument to the function (I removed some of the headers for brevity; unimportant to test):

{
  "path": "/test/hello",
  "httpMethod": "GET",
  "headers": {
    "accept": "*/*",
    "accept-encoding": "gzip",
    "connection": "keep-alive",
    "user-agent": "HTTPie/2.1.0",
    "via": "https/1.1 Netlify[840aa3b4-795a-4b07-aa09-e6f488912ae4] (ApacheTrafficServer/7.1.11)",
    "x-cdn-domain": "global.netlify.com",
    "x-country": "US",
    "x-forwarded-proto": "https",
  },
  "queryStringParameters": {},
  "multiValueQueryStringParameters": {},
  "body": "",
  "isBase64Encoded": true
}

So there was actually no query string data when the Function was called, but the initial path is reported. :thinking: Okay moving forward.

Call direct with parms:

http https://jonsdomain.com/.netlify/functions/test-rewrite-to-function?parm=hello

Which yielded the following object for the event argument to the function (I removed some of the headers for brevity; unimportant to test):

{
  "path": "/.netlify/functions/test-rewrite-to-function",
  "httpMethod": "GET",
  "headers": {
    "accept": "*/*",
    "accept-encoding": "gzip",
    "user-agent": "HTTPie/2.1.0",
    "via": "https/1.1 Netlify[840aa3b4-795a-4b07-aa09-e6f488912ae4] (ApacheTrafficServer/7.1.11)",
    "x-country": "US",
    "x-forwarded-proto": "https",
  },
  "queryStringParameters": {
    "parm": "hello"
  },
  "multiValueQueryStringParameters": {
    "parm": [
      "hello"
    ]
  },
  "body": "",
  "isBase64Encoded": true
}

So the path matches the origin request and the query string parameter was parsed into event.queryStringParameters :thinking: so the standard Lambda system works! That’s good, at least :stuck_out_tongue:

Call direct with empty param:

http https://jonsdomain.com/.netlify/functions/test-rewrite-to-function?parm=

Which yielded the following object for the event argument to the function (I removed some of the headers for brevity; unimportant to test):

{
  "path": "/.netlify/functions/test-rewrite-to-function",
  "httpMethod": "GET",
  "headers": {
    "accept": "*/*",
    "accept-encoding": "gzip",
    "connection": "keep-alive",
    "user-agent": "HTTPie/2.1.0",
    "via": "https/1.1 Netlify[c9c5b509-8a45-4a7c-a4b5-03f81d19d15f] (ApacheTrafficServer/7.1.11)",
    "x-cdn-domain": "global.netlify.com",
    "x-country": "US",
    "x-forwarded-proto": "https",
  },
  "queryStringParameters": {
    "parm": ""
  },
  "multiValueQueryStringParameters": {
    "parm": [
      ""
    ]
  },
  "body": "",
  "isBase64Encoded": true
}

Just wanted to run this test to see how the function input is given when the query string parameter is present but empty. This is important, but the key takeaway here is that the event.queryStringParameters does have the parm in it, even though it’s empty.


Conclusions:

  1. I think the path parameter does accurately reflect the path specified in the origin request, both for standard Function calls and Function calls via Rewrite proxy
  2. For whatever reason, the 200-based Rewrite operation ignores query parameters altogether. At first I thought it just wasn’t hydrating the :splat but as test #3 shows, even if the :splat was empty, the ?parm= part would still show in event.queryStringParameters – this indicates to me that the Rewrite engine is actually dropping the the query-string component altogether (the ? and everything after), which seems odd. @Dennis or @Scott do either of you have knowledge on why that could be?
1 Like

Unfortunately, while it does look like the redirect should be parsed correctly from the netlify-redirect-parser source code, the netlify-redirector (the engine that actually executes the redirects) is one of Netlify’s few closed-source projects. Wasm is real :sweat_smile: Hopefully the official team can chime in on this :+1:t2:

–
Jon

1 Like

Hey y’all!

So, I’m pretty sure I’ve had a very similar discussion not too long ago. It’s not the best news but hopefully it’ll give you some answers!

Nice! Thanks for the follow up @Scott! I probably should’ve searched before doing my own digging Community :man_facepalming:t2: but glad to know it’s known and confirmed. Appreciate it! I ended up doing similar - just parsing the path in the Function :grin:

–
Jon

1 Like

I actually read that, but was not sure if it was similar to my problem as it kept talking about query parameters - which I don’t use. But maybe I did not get the gist of the thread at all, rereading it now with what I do know now it seems quite relevant indeed.

Still a bug/limitation in my opinion that should possibly be mentioned in the docs :wink:

I am not sure if this is limited to Rewrites to functions by the way or applies also to rewrites to external URLs or other internal URLs that are not functions. I would expect the same problem to be present there really.

Until this bug (?) gets fixed, you can try the following workaround:

1 Like

thanks for sharing that, @brun0! We’re hoping to have an update on this soon. Its on our radar.

Netlify splat rewrites work with paths but not with query parameters. I ran into this problem myself. It’s documented if you look hard enough, but it’s not immediately clear. One way to work around this is to use the Javascript function btoa to Base64 encode your parameters on the client side and then use atob to decode them from the path on the server size. So, if you want example.com/test?a=1&b=2, you’d instead request example.com/test/YT0xJmI9Mg== and your serverless function can turn that back into a=1&b=2 by decoding it.

Haha that’s clever! Very nice :grin:

Coming back to this one to provide some code for those in the future.

Tl;dr:

Netlify doesn’t support redirects that take the content of a * and inject it as a query-string parameter with :splat a la attempting:

/admin/* /login?admin-path=:splat 302!`

Or anything of the sort. It’s just not supported right now.


Work-Around:

If you want that functionality, just add a new Function with the following code (using your targeted URL / query param):

// jons-splat-redirector.js

exports.handler = async (event) => {
  pathBack = encodeURIComponent(event.path)

  return {
    statusCode: 302,
    headers: {
      Location: `/login?admin-path=${pathBack}`
    }
  }
}

and use the following _redirects record to hit your Function without the user ever seeing it :+1:t2:

/admin-page/* /.netlify/functions/reauthorize 200!

Essentially instead of using :splat the Function will just read the incoming path, whatever it is, and shim it into the query string you need then send back the correct 302. YMMV :slight_smile:

–
Jon

Once this update has ironed out the bug and live, I am assuming it would solve this problem? Just double checking :slight_smile:

1 Like

I don’t believe so; that update doesn’t speak at all to star-to-splat changes / doesn’t hint that functionality there might change. I believe this feature req. still stands :+1:t2:

General update here: Until recently the suggestion from Redirect to Function - Splat is not working as expected - #2 by jonsully worked fine for me and the variable was filled as described. Recently that started to not be the case any more on some function runs, instead the string is just empty. No idea what changed on Netlify side :man_shrugging:

Fortunately I could get rid of the param in my limited use case that was still live. Still confusing.

@jonsully, I think this issue is related to the fact that query param values should probably have any slashes escaped. So your conclusion correct that if * is a path (which usually contains slashes) it will not work as a splat value of a query param.

That said, I think we introduced a change to our URL parser just now that might help resolve this issue. Could one of y’all with a test case check if the behavior has changed?