Can The Kirby 3 Router Recognise Fetch API Requests?

I’m trying to build a ‘Form Handler’ in Kirby 3, which should receive form submissions sent via ajax, and email them. I have setup a plugin folder+file with a route to catch the submissions. Kirby is registering the plugin, and the route - I can test it by making the route return simple text, and then accessing it directly with my browser.

I’ve setup this kind of form processor successfully before, but using jQuery/Zepto. This time around I wanted to do it all using vanilla javascript, and the new fetch() command, which is widely supported. Unfortunately, whenever I try to submit the form data using fetch(), Kirby returns a 404 - Not Found error.

The error is not being sent by my route script - it seems to be coming directly from the Kirby router itself. If I try to access the route directly, by entering the address in my browser, it works fine. If I try to access it via ajax using fetch(), I get a 404 directly from the Kirby router… This is my fetch() code:

fetch('formhandler',{
      body: new FormData(formObject),
      method: 'POST'
    })
    .then(response => {
      if(response.ok){ 
        return response.json();
      } else {
        // will catch responses with 404, 500 or other errors 
        // - such as when sending an email from the FormHandler fails
        throw new Error(response.status + ' - ' + response.statusText);
      }
    })
    .then(data => {
      /* use data to update form on success */
    })
    .catch(error => {
      /* display error responses to the user */
    });

Has anyone come across this issue before? I’m hoping that there may be something wrong with my fetch() code that might be triggering this…

What does your network console say which url you are fetching? To me it looks like formhandler is just some string instead of a variable or the url.

The console tells me it’s trying to post to http://myserver.com/formhandler, which is correct.

I think I’ve solved part of the issue. I’m going through the documentation for the Uniform plugin, and saw that in their AJAX example, the route array uses a third key: method. I’ve added'method' => 'POST' to my route, and it seems to have started to work.

I’m coming across several other problems with the rest of the request processing code, though. Right now, it’s generating a 500 error…

It seems that when a request is done via fetch, Kirby does not recognise it as an ajax request - ie., doing $request->ajax() on the request returns false. Will report it.

Can you also post the code in your controller/route?

Is the 'HTTP_X_REQUESTED_WITH'-header in the request?

According https://github.com/getkirby/kirby/blob/develop/src/Http/Request.php#L160 kirby is checking for a header HTTP_X_REQUESTED_WITH which is not sent per default in a fetch request.

Try

fetch('formhandler',{
      body: new FormData(formObject),
      method: 'POST',
      headers: {
            'X-Requested-With': 'XMLHttpRequest',
      }
})
.then(response => {
      if(response.ok){ 
        return response.json();
      } else {
        // will catch responses with 404, 500 or other errors 
        // - such as when sending an email from the FormHandler fails
        throw new Error(response.status + ' - ' + response.statusText);
      }
})
.then(data => {
      /* use data to update form on success */
})
.catch(error => {
      /* display error responses to the user */
});

Edit: I tried it on my 3.0 localhost installation. It seems that this header gets lost somewhere on the way :thinking:

Edit 2: With 'X-Requested-With': 'XMLHttpRequest', added to header $request->ajax() returns true on my localhost (laragon).
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest', is lost somewhere on the way…probably a server issue?

@bvdputte Indeed, @moeli is correct - I’ve reported this here.

How does the PHP code for your form handler route look like?

I’m not convinced this is a “bug” tbh. :thinking:

Agreed, the header is probably not added by the native fetch method, but one can easily add it manually as shown by @moeli. And maybe the docs should mention this too.

But there can be other causes for this: the server config (loosing headers somehow) or the controller code (we haven’t seen it yet).

Since there is no other way to detect an Ajax request, this method can still be useful if you want to use a shared controller, otherwise you would have to use separate controllers. But of course, it requires you to set the header.

The issue is that there is no ‘standard’ or ‘built-in’ way to detect in JavaScript whether a request is Ajax. Some JavaScript libraries - like jQuery - add that non-standard header by default to their Ajax methods, but that is not something that is part of vanilla JavaScript. Even if not using the new fetch api, if you’re just using vanilla JavaScript the ajax() method will fail.

Seems like a pretty big omission to the JS specs, right? How can we NOT have a way to check whether a request is Ajax? Where is the security? So, this got me doing a bit of research. In fact, what we are concerned with here - what the ajax() method is supposed to help us with - is precisely this: checking the ORIGIN of the request. And there are, indeed, several ways to do that. Some of those are built-into the fetch api, and in general, they do not require setting of a custom, non-standard header.

So, IMHO having a method like ajax() is actually not very useful: it won’t work as expected with standard, modern JavaScript, and promotes practices which are no longer recommended.

@texnixe I am using a check to see whether the form data has been submitted - and that it has a csrf token - to decide which action to take. So, still able to do it with a single controller.

Well, you assume that the request is a post request that handles form submissions. But that might not be true in other cases where you probably would have to use a custom header (or a separate controller for a content representation).

Very true - if we’re setting up a web app, with lots of Ajax interaction, we’ll probably need different solutions. But that is the point: the solutions are always going to be custom, and unless the programmer is using a third-party library, the ajax() method is neither very reliable, nor useful.

I kind of agree with that too @luxlogica. But it’s there for a long while, so it might be nice to keep it there for legacy reasons (for now), and since a lot of AJAX-libraries still send that header it’s not completely useless?

As said, explaining this in the docs could help a lot imho.

Let’s see whether or not it will be deprecated and if not, we’ll add a note in the docs.

1 Like