0

I have a dockerized PHP/Laravel app that's working fine inside a php:7.4-apache container. We had it routed to http://localhost:81 for development purposes.

Now we want this service to be available at http://localhost/foo/bar in our network of microservices. Request to localhost go to an nginx service, and we've set up a rewrite rule in nginx_cors.conf to achieve this:

location /foo/bar {
  rewrite ^/foo/bar/?(.*) /$1 break;
  proxy_pass http://laravel_app;
}

But now some jQuery.ajax requests fail. This is what happens:

// in vendor code
// url is http://localhost/foo/bar/ajax/lib/?someParam=someValue
$.ajax({
  url: url
});

// in Chrome DevTools
// Note that foo/bar/ is gone
jquery.js:4 GET http://localhost/ajax/lib?someParam=someValue 404 (Not Found)

The problem seems to affect all URLs that contain a question mark. We stripped it for a request that contains no params, and the request went through. But that's not possible in the above case.

What is happening here? I'm not sure if the nginx rewrite rule might be to blame as I wouln't expect the browser to notice it's effect at all (and the above error message is from Chrome DevTools).

edit

Looking at the network tab in Chrome, I realized there are two similar requests:

  • One goes to http://localhost/foo/bar/ajax/lib/?someParam=someValue as expected. nginx responds with a 301 and a location header http://localhost/ajax/lib?someParam=someValue.
  • The second one goes to http://localhost/ajax/lib?someParam=someValue, which is precisely the location header from the first request. Again nginx responds, this time with a 404.

So it seems nginx 'forwards' the first request to itself instead of the Laravel app.

Pida
  • 928
  • 9
  • 32
  • 1
    I don't understand the question at all. What exactly is wrong with the `http://localhost/foo/bar/ajax/lib/?someParam=someValue` URL? How it should be passed to the backend? And I don't understand this one, according to your config it should be rewrited as `http://localhost/ajax/lib/?someParam=someValue`, not as the `http://localhost/ajax/lib?someParam=someValue` (note the trailing slash). – Ivan Shatsky Nov 13 '20 at 17:19
  • The URL is fine, the problem is that the request fails, probably because somehow *foo/bar* is being removed from the the URL, which in turn generates a 404. The request is sent by `$.ajax`, which is a jQuery function. I left out the *success* and *error* handlers in my code sample as I don't even get there. I understand your comment about the slash, but all I can say is that's what is happening. – Pida Nov 13 '20 at 18:18
  • 1
    But your rewrite rule is exactly supposed to remove the `/foo/bar` prefix from every URI started with this prefix before proxying the request to the laravel app, isn't it? Or you expect some other behavior from that rule? – Ivan Shatsky Nov 13 '20 at 19:01
  • I expect the following behaviour: nginx reacts to the `foo/bar` part and forwards the request to the Laravel service. It also strips the `foo/bar` part because this service wouldn't know how to handle it. the service has a route for `GET ajax/lib`, but none for `GET foo/bar/ajax/lib`. Please see my edit also. And thank you! – Pida Nov 13 '20 at 20:17
  • 1
    It looks to me than it isn't nginx but the laravel app who makes `/ajax/lib/` to `/ajax/lib` redirection. You can check it with the `curl -i "http://laravel_app/ajax/lib/?someParam=someValue"` command and check what that app would return in response. If I'm right, the next request `http://localhost/ajax/lib?someParam=someValue` made by browser after the redirection won't fall under the `location /foo/bar { ...}` giving you HTTP 404 Not found instead. – Ivan Shatsky Nov 13 '20 at 20:52
  • When I curl `http://laravel_app/ajax/lib` the request gets through and Apache replies with 401 as there is no authenticated user. When I curl `http://laravel_app/ajax/lib/?someParam=someValue` I get a 301 from Apache. Location header is `http://laravel_app/ajax/lib/?someParam=someValue`. When I curl `http://laravel_app/ajax/lib/?someParam=someValue` the request is going through again (401). – Pida Nov 13 '20 at 21:27
  • 1
    Besides you can add any header with `curl` including the authorization ones (see [this](https://stackoverflow.com/questions/3044315/how-to-set-the-authorization-header-using-curl) SO thread), this is going too complicated. Lets try more simple solution, add the `proxy_redirect / /foo/bar;` directive to that location and lets see if it helps. – Ivan Shatsky Nov 13 '20 at 21:49
  • 1
    Maybe it should be `proxy_redirect / /foo/bar/;` or even `proxy_redirect http://laravel_app/ /foo/bar/;`, can you try these? – Ivan Shatsky Nov 14 '20 at 09:00
  • Adding a trailing slash (`proxy_redirect / /foo/bar/;`) has no effect. With the second alternative, I can't open any page in the Laravel app in the browser (*localhost refused to connect.*) – Pida Nov 14 '20 at 12:28
  • 1
    I'm running out of ideas. The last one to try: `proxy_redirect http://laravel_app/ http://localhost/foo/bar/;` – Ivan Shatsky Nov 14 '20 at 12:30
  • That's it! The request is working now, I get a 301 first and then a 200. Now that I know this is the solution, it seems quite comprehensible. Thank you again for your efforts! – Pida Nov 14 '20 at 13:54

1 Answers1

1

It seems that the redirection from /ajax/lib/ to /ajax/lib issued by the laravel app rather than nginx itself. The default nginx behavior for rewriting the Location header is to substitute the backend hostname/protocol with the request one giving you http://laravel_app/ajax/lib rewrited to http://localhost/ajax/lib. That request wouldn't fall under the location /foo/bar { ... } and would be served by some other nginx location giving you HTTP 404 Not found error. You need to override the default proxy_redirect behavior with the following directive:

proxy_redirect http://laravel_app/ http://localhost/foo/bar/;
Ivan Shatsky
  • 13,267
  • 2
  • 21
  • 37