e-Spirit / fsxa-nuxt-module

Apache License 2.0
1 stars 6 forks source link

port issues with module's middleware #76

Open tmountjr opened 2 years ago

tmountjr commented 2 years ago

My team and I have been working on deploying this PWA to Edgio (https://docs.edg.io/guides/nuxt) and are running into some challenges with the serverMiddleware component of the module. High-level, when we deploy to our platform, our CLI first runs nuxt build --standalone, then bundles the result of that and uploads it to our platform. The serverless/SSR components are moved over to an AWS Lambda instance. The platform runs Nuxt in a protected environment using an arbitrary port and then exposes the Nuxt routes on port 443 for TLS requests. We also have the ability to run this configuration locally for quicker development - in that case, Nuxt runs on port 3001 and the CLI essentially proxies it on port 3000.

What we're seeing when we run the standalone build as part of our platform is that any request to the application results in a near-infinite flood of status messages:

Initializing app {
  payload: { defaultLocale: 'de_DE', initialPath: '/api/fsxa/navigation' }
}

I wouldn't call this a "redirect" issue, more like a "request restart." On our infrastructure, the request first hits our proxy layer on :3000, and our layer requests the same route from Nuxt on :3001. Best we can tell at this point, because of the value of proxyApiConfig.serverUrl (templates/plugin.js:36) the request is dispatched back to localhost:3000 which starts the loop over again. I have tried a few ways of getting NUXT_PORT to be 3001 - I've put that value in a .env file, hard-coded it in nuxt.config.ts as part of the publicRuntimeConfig key, and prefixed my build and run commands with NUXT_PORT=3001 and I've been unable to see a change in behavior or to confirm that what I'm doing is making any difference.

As part of the troubleshooting as well, I went into node_modules/fsxa_nuxt_module/templates.plugin.js and hardcoded the host and port value to match the production environment, which gives a clearer picture of the issue, especially in the logs:

Screenshot 2022-10-26 at 10 40 33

It looks like the middleware is repeating the request rather than handling it transparently like I would expect middleware to do (eg. if the middleware is going to dispatch a request to the CMS, why doesn't the middleware simply do that? It appears here that the express server receives the POST request but then re-requests it.). It's possible that I'm misunderstanding how the proxy config is set up but the code makes it appear as though the request is being re-dispatched.

I have confirmed that running npx nuxt build --standalone && npx nuxt start without any special port considerations lets the system work properly. I realize this may not be a bug with the module per se but I was hoping that someone with some more experience with the overall architecture of this module might be able to take a look.

nico-mcalley commented 2 years ago

Hey Tom, You picked a very complex topic and there might be really a bug in it, so take everything I tell you with a grain of salt,

When creating your own FSXA-PWA, the FSXA-NUXT-MODULE creates an fsxa-api (explicitly a FSXARemoteApi). The RemoteApi is registered as server middleware. It contains the apikey for the backend, so we cannot use the RemoteApi to make requests on clientside. To use a FSXA-API on clientside we created the FSXAProxyApi. The ProxyApi than uses the server middleware for communication. We're also using SSR which makes it even more complex. maybe this is the reason you see the same request multiple times in the logs. Some requests are made by the SSR process, some requests are forwarded by the ProxyApi and some requests are logging from the RemoteApi (server middleware).

In general a serverless approach is possible. A former colleague of mine got a fsxa-pwa instance to work in Vercel and he said it worked ootb. I hope my answer could help you a little bit. :-)

tmountjr commented 2 years ago

That helps, thanks. So to summarize what you said in my own words, there are really two APIs - the FSXARemoteApi, which runs as server middleware, and the FSXAProxyApi, which is what actually dispatches the request to the CMS.

It sounds like the request path then is:

Request -> FSXARemoteApi -> FSXAProxyApi -> CMS

Is that accurate?

deangite commented 2 years ago

We first send the request to the proxy and after to the remote api, so the right path is: Request -> FSXAProxyApi -> FSXARemoteApi -> CMS

tmountjr commented 2 years ago

Ah, ok, thanks. I think we need to identify where the request handoff is breaking down - I suspect it's between the Proxy and Remote stages but we'll need to confirm that for sure.

ls-espirit commented 2 years ago

As a debug tip: Check the response you're actually getting via Rest client e.g. postman. Just Send a POST request to [SERVER]/api/fsxa/navigation with the payload from the log.

Looks like you get a html page as response (the "<" character), while json is expected.

nico-mcalley commented 2 years ago

@tmountjr Is this issue still relevant or could you solve it with the given information?

tmountjr commented 2 years ago

It's relevant, I just haven't had the time to dig into this in any detail. I plan on working on it today.

On Wed, Nov 2, 2022 at 4:17 AM henczi-espirit @.***> wrote:

@tmountjr https://github.com/tmountjr Is this issue still relevant or could you solve it with the given information?

— Reply to this email directly, view it on GitHub https://github.com/e-Spirit/fsxa-nuxt-module/issues/76#issuecomment-1299780533, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAJF3W4XE3YX6ADTSKUGQYLWGIPQXANCNFSM6AAAAAARPCMHSA . You are receiving this because you were mentioned.Message ID: @.***>

tmountjr commented 2 years ago

I turned up the logging all the way to 0 in the fsxa config and i'm getting some unexpected results. Locally, when I run a production build with just Nuxt (eg. nuxt build --standalone && nuxt start, the debug output only references the Remote API:

ℹ  INFO  Express-Server | Received POST on route | /navigation
ℹ  DEBUG  Express-Server | POST request body | {"initialPath":"/","locale":"en_GB"}
ℹ  DEBUG  FSXARemoteApi | fetchNavigation | start | {"locale":"en_GB","initialPath":"/"}
ℹ  DEBUG  FSXARemoteApi | [buildNavigationServiceUrl] | {"locale":"en_GB","initialPath":"/"}
ℹ  DEBUG  FSXARemoteApi | [buildNavigationServiceUrl] | Using locale: en_GB
ℹ  DEBUG  FSXARemoteApi | fetchNavigation | url | https://redacted?depth=99&format=caas&language=en_GB
ℹ  DEBUG  FSXARemoteApi | fetchNavigation | response | 200
ℹ  DEBUG  FSXARemoteApi | fetchNavigation | response ok | true

But when I do a production build with our platform, I get additional debug output from the Proxy API and nothing from the Remote API:

DEBUG  FSXAProxyApi | FSXAProxyApi created | {"baseUrl":"http://localhost:3001/api/fsxa"}
DEBUG  FSXAApiSingleton | FSXA-Api initialized with api: | {"mode":"proxy","_baseUrl":"http://localhost:3001/api/fsxa","_method":"POST","_headers":{"Content-Type":"application/json"},"_enableEventStream":false,"_logger":{"_logLevel":"0","_name":"FSXAProxyApi"}}
Initializing app {
  payload: { defaultLocale: 'de_DE', initialPath: '/api/fsxa/navigation' }
}
DEBUG  FSXAProxyApi | fetchNavigation | start | {"locale":"de_DE","initialPath":"/api/fsxa/navigation"}
DEBUG  FSXAProxyApi | fetchNavigation | body | {"body":{"initialPath":"/api/fsxa/navigation","locale":"de_DE"}}
DEBUG  FSXAProxyApi | fetch | start | {"baseUrl":"http://localhost:3001/api/fsxa","url":"/navigation","options":{"method":"POST","headers":{"Content-Type":"application/json"},"body":"{\\"initialPath\\":\\"/api/fsxa/navigation\\",\\"locale\\":\\"de_DE\\"}"},"isServer":true,"isClient":false,"window":"undefined"}

It's at this stage that I enter that restart loop. It's worth noting that using our platform, skipping the production build and simply running things in dev mode shows the same debug output as I get when running nuxt.

(I wonder if this is because the API is trying to ping localhost:3001 which, when we run our dev instance, essentially runs nuxt on port 3001, and then runs our own server on port 3000, so port 3001 is still available and answering requests.)

tmountjr commented 2 years ago

It looks like (and I might be wrong) the FSXAProxyApi doesn't come into play when the only thing running is nuxt itself. Is that accurate? Is there something that triggers the use of the proxy api in between the request and remote based on some condition?

nico-mcalley commented 2 years ago

Hey Tom, Sorry for the late answer.

When I do a production build with our platform, I get additional debug output from the Proxy API and nothing from the Remote API

This is pretty unusual. Could you turn off ssr and try that again? https://github.com/e-Spirit/fsxa-pwa/blob/master/nuxt.config.ts#L5 It seems that you're getting client logs and not the log messages from the server.

Also in the documentation https://github.com/e-Spirit/fsxa-pwa/blob/master/docs/modules/ROOT/pages/Introduction.adoc#running-the-application-in-a-production-environment we link to the Dockerfile.template https://github.com/e-Spirit/fsxa-pwa/blob/master/Dockerfile.template to show developers which steps we do, to get to a production deployment. Do you get any errors when following this or do is everything working when you try to start the PWA locally/in docker?

In our team we were not sure which is your real concern. Maybe you can clarify this for us.

tmountjr commented 2 years ago

Simply disabling SSR caused some additional errors on our platform that may take some time to track down.

Taking a step back, I gave some thought to how the application behaves running natively with nuxt vs. how it behaves using our platform's proxy. Just running nuxt dev or nuxt start, requests to localhost:3000/api/fsxa/... are processed directly, which I would expect. When running under our proxy, the behavior is a little different.

Our routing and edge infrastructure runs locally at :3000 and starts up nuxt at :3001. Requests to :3000/api/fsxa/navigation for example are passed to :3001/api/fsxa/navigation with the same payload, and I can see additional headers that we inject. Now, on our dev instance, because that nuxt installation on port 3001 is wide open, that request succeeds. (In dev mode, we're basically running npx nuxt --port 3001.) In prod mode, though, we're running Nuxt programmatically via http.createServer and following https://nuxtjs.org/docs/internals-glossary/nuxt/ for importing the nuxt core and running loadNuxt('start'). This means that there's no server running at port 3001; however the request is still dispatched to :3001/api/fsxa/navigation which obviously fails.

I still have some testing to do with a bare-bones Nuxt app and some middleware to see if I can compare how requests would be handled using middleware I know I've gotten to work properly.

tmountjr commented 1 year ago

Hey folks, I think I finally have the issue fully understood - let me try to lay out my findings and we can talk about next steps.

Near as I can tell, requests from the PWA to / are expected to be server-side rendered. The SSR component of the application uses the FSXAProxyApi to initiate requests to a few endpoints under /api/fsxa/*, which in turn start up the FSXARemoteApi to dispatch one or more requests to whatever URL is specified in the FSXA_NAVIGATION_SERVICE environment variable. The responses from that call are pieced together, filtered, etc., and then returned as the result of the POST call to the /api/fsxa/* route, which the SSR component uses to render the HTML response and serves it back to the browser.

Assuming all that is reasonably accurate, the part where we started running into problems was the fact that the FSXAProxyApi was by default sending requests to localhost:3000/api/fsxa/*. The defaults are configured in the templates/plugin.js file. Now, I discovered a bug on our platform - we apparently were not reading the nuxt.config.TS file (only nuxt.config.JS) and so no matter what we did the NUXT_HOST and NUXT_PORT variables were never being populated in the publicRuntimeConfig part of the nuxt config file, leading to that localhost address always being used. We fixed that bug and are now processing the typescript config file properly. Based on some previous comments here, it looks like the assumption is that this module will be used in a containerized infrastructure, or at least one where the SSR environment will be able to respond to requests to localhost. Our platform is not set up to allow requests to localhost while in the SSR environment, which I think is the core of the issue here, likely the same with Netlify (though I don't know how their platform works under the hood), and potentially explains why we also had issues usingnuxt-start to bootstrap the application. (The Netlify and nuxt-start parts are speculation, but it would make sense.) So when the request is being sent to localhost, it's immediately being denied. I don't know why this resulted in an infinite loop - perhaps we were trying to proxy the request to localhost:3001 which was being redirected by Nuxt...hard to say. I'm actually less concerned with why things broke down at that point, and more satisfied understanding that's where things broke down.

I did get this "fixed" to a point - after my colleagues fixed the typescript bug, I was able to set the values of NUXT_HOST and NUXT_PORT via the publicRuntimeConfig key. Because setting those values to something that isn't localhost was messing up my local development, here's a snippet from my config file:

if ('REMOTE_API_HOST' in process.env) {
  config.publicRuntimeConfig = {
    ...config.publicRuntimeConfig,
    NUXT_HOST: process.env.REMOTE_API_HOST,
    NUXT_PORT: '443'
  }
}

export default config

In my .env file locally, I just don't specify REMOTE_API_HOST; and deployed, I set it to the same hostname that the site uses. This means the rendering process looks like this:

There is one final piece, and this is the question in #75 - all things being equal, that request will be dispatched to http://[domain]:443 which won't work because HTTP requests aren't being served by port 443. I did try http://[domain]:80 and relying on our platform's ability to respond with a 301 to the HTTPS domain, but those redirects are not being followed. So I had to fork the module, patch it, build it, and push it up to our GH account so that we could use the patched version. Long term that's not sustainable, but I have validated that it works for now.

Ultimately I think the assumption that localhost will always be accessible and/or that traffic to /api/fsxa/* will only ever be over HTTP potentially limits the number of places that this module can be deployed. Our platform developers are looking at the localhost issue internally to see if that's something that we can/would support, but for anyone else in the same boat who can't access localhost from within the SSR context, the hard-coded HTTP is the other barrier to entry.

Happy to discuss further, and if I've gotten the request flow or any assumptions incorrect, please let me know. Thanks!