laravel-json-api / laravel

JSON:API for Laravel applications
MIT License
549 stars 43 forks source link

Laravel running inside NGINX web server in docker #235

Closed genyded closed 1 year ago

genyded commented 1 year ago

So, we're running Laravel inside an nginx web server and they are both running in Docker containers in a dev environment. The issue we're running into is that in order to access the JSON API resources in something like say an Auth Controller is via the Laravel (Guzzle) HTTP client. That client will not see the web server so if localhost is used it will fail:

$user = Http::get('http://localhost/api/users/1');

This times out waiting on a response event though the seb server is up and running in the docker container.

We can work around that by specifying host.docker.internal as the host:

$user = Http::get('http://host.docker.internal/api/users/1');

That works except that then the JSON API links all have that as the host:

{ "jsonapi": { "version": "1.0" } "links": { "self": "http://**host.docker.internal**/api/users/1" } "data": { "type": "users" "id": "1" "attributes": { "email": "user1@email.com" "firstName": "User" "lastName": "One" "updatedAt": null } "links": { "self": "http://**host.docker.internal**/api/users/1" } } }

There is also no way to easily toggle that when in prod vs dev if hard coding the url. So, is there a direct way to access the JSON API resources aside from an HTTP client inside something like an auth controller that is not a JSONAPI Controller? Our guess is no and if not, any thoughts on how best to resolve this?

BenWalters commented 1 year ago

Hey @genyded I'm not fully sure I understand the issue you're facing but I'll throw some info & questions here and see if it can help.

We use this package and run Docker with locally and Kubernetes for our hosted environments. Let me start by asking where you are hardcoding this URL?

The host part of the URL (host.docker.internal/localhost) for us is env driven. We have our routes for the jsonapi available at a subdomain of api. and then the Server we specified appends /v1/ to the path.

I don't think we have any backend calls to our own API... but we run an SPA which obviously hits the API and that works fine.

Hope this helps in some way, and if not, please provide some further info and I'll see if I can be of any help.

genyded commented 1 year ago

Thanks @BenWalters,

We have a very similar setup to you and we normally use env hosts as well. For us it works fine for all our JOSNAPI resources in the browser, Postman, etc using localhost.

However, we have an Auth Controller that manages issuing/refreshing JWT's for login and such. That Controller in not a JSONAPI controller. Upon successful login though, we need to get the JSONAPI based user object to add it to the payload of the JWT before sending the response. We do that by making a Laravel internal 'api' call using Laravel HTTP (Guzzle) from the Auth Controller to the /api/users/{id} endpoint. That fails (times out) if localhost is the host.

We determined that if the host is set to host.docker.internal, all that works, but then all the 'links' in the returned JSONAPI user also point there. Those then will not work externally in the browser, Postman, etc. If we were using something like Laravel api resources, we'd just new up a UserResource for the model, but you can't do something like that with JSONAPI resources. Thus the internal no JSONAPI Auth Controller making the pseudo-api call the the JSONAPI User endpoint.

We were able to sort hack around this since the original post by hard-coding host.docker.internal in the Auth Controller pseudo-api calls and then replacing it with localhost in the returned JSON prior to sending the response. However, string parsing is not the fastest thing on the planet and this really seems sort of hacky.

This issue aside, it seems like there should be some internal way to directly access JSONAPII resources from inside Laravel (like you can Laravel API resources) without making an internal API call. We get it's the way it is because of the 'document' handling though, so were likely stuck with our hacky work around for now.

BenWalters commented 1 year ago

Yeah okay, I understand the issue. We faced a similar problem with testing using Dusk with docker because of how it communicates internally. We also had to implement a hacky workaround manipulating host files within the containers.

Because of how docker works... I'm not sure there is an easy way around this. I'd wait for @lindyhopchris to add his thoughts around internally accessing the JSONAPI resources.

genyded commented 1 year ago

OK, after spending most of the weekend debugging this, we have determined the root cause to be the open for over a year now WSL2 issue https://github.com/microsoft/WSL/issues/4983. In short, WSL2 only binds to IPv6. So any 'app' (like the Laravel cURL based HTTP client) that either only binds to IPv4 or bind to IPv4 before IPv6 will fail to localhost.

Localhost (or it's IP) will work in all the browsers, Postman, etc. just fine because they all bind IPv6 first. But for JSONAPI to work inside Laravel with internal HTTP(s) requests, you will need to do one of the many mostly complex work-arounds. We are using the one that uses 'host.docker.internal' as the host for internal HTTP request in our Controllers to get the JSONAPI resources and then replacing that with 'localhost' (or whatever the env host is set to) in all the returned data so that the links and such are correct aft wards. The WSL2 issue linked above has some other ideas that may work better for your scenarios as well.

Since this is not a JSONAPI issue per se, closing it for now. However it would be nice if there was a direct way in Laravel for get JSONAPI resources across internal controllers without cURL. Note this all only applies to Windows systems running Docker in WSL2. Mac and *nix clients should work fine out of the box.

We find it a bit ironic that the only OS that fails is Microsoft's and it's the only one that requires WSL2. But hey at the end of the day is only money right.

lindyhopchris commented 1 year ago

Yeah agree I need to add something to allow people to programmatically encode resources. Closing this in favour of https://github.com/laravel-json-api/laravel/issues/144 which is exactly that issue.