Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for express server compression middleware. #2980

Merged
merged 16 commits into from
Feb 11, 2021

Conversation

revanth0212
Copy link
Contributor

Description

The express server that serves the app provides a middleware that applies GZIP compression if not applied already on the responses requested by the storefront. Compression is a very important but sensitive feature to manage because it can be applied at different layers of a system. One can apply it at the proxy layer or application layer and overdoing it can cause performance degradation. Hence the express server compression will be controlled by an environment variable. If someone wants to apply compression at the express layer, they have to set the value of ENABLE_EXPRESS_SERVER_COMPRESSION to true. By default, it will be set to false.

ENABLE_EXPRESS_SERVER_COMPRESSION in a sample project's .env file created using the create-pwa scafolding tool:

image

Related Issue

Closes PWA-1108.

Acceptance

Should apply GZIP compression on responses if ENABLE_EXPRESS_SERVER_COMPRESSION to true.

Verification Stakeholders

@jimbo
@dpatil-magento

Verification Steps

  1. Change/create env variable in the .env file ENABLE_EXPRESS_SERVER_COMPRESSION=true
  2. Run the app. Check both yarn watch:venia and yarn build && yarn stage:venia
  3. Open the app and check the network tab for responses with a header Content-Encoding: gzip.
  4. Change the value of ENABLE_EXPRESS_SERVER_COMPRESSION to false.
  5. Restart the app.
  6. Open the app and check the network tab for responses without a header Content-Encoding: gzip.

Screenshots / Screen Captures (if appropriate)

When ENABLE_EXPRESS_SERVER_COMPRESSION=true

image

When ENABLE_EXPRESS_SERVER_COMPRESSION=false

image

Checklist

  • I have added tests to cover my changes, if necessary.
  • I have added translations for new strings, if necessary.
  • I have updated the documentation accordingly, if necessary.

@revanth0212 revanth0212 added the version: Minor This changeset includes functionality added in a backwards compatible manner. label Jan 29, 2021
@revanth0212 revanth0212 changed the title Revanth/express compression Support for express server compression middleware. Jan 29, 2021
@PWAStudioBot PWAStudioBot added pkg:pwa-buildpack pkg:upward-js Pertains to upward-js reference implementation of UPWARD. pkg:venia-concept labels Jan 29, 2021
@PWAStudioBot
Copy link
Contributor

PWAStudioBot commented Jan 29, 2021

Messages
📖

Associated JIRA tickets: PWA-1108.

📖 DangerCI Failures related to missing labels/description/linked issues/etc will persist until the next push or next nightly build run (assuming they are fixed).
📖

Access a deployed version of this PR here. Make sure to wait for the "pwa-pull-request-deploy" job to complete.

Generated by 🚫 dangerJS against 9d252a3

@fooman
Copy link
Contributor

fooman commented Jan 29, 2021

This project used shrink-ray-current in the past
https://github.com/magento/pwa-studio/pull/2618/files#diff-2d63256fca103d1f1f8c9cc0a8c4adc56738f8a0397261b1240f824c87ba599e
to provide compression which I guess was favoured as it included brotli compression expressjs/compression#71. Was dropping brotli support on purpose?

@tjwiebell
Copy link
Contributor

This project used shrink-ray-current in the past
...
Was dropping brotli support on purpose?

@fooman
Digging through old PRs I can't find the justification from @zetlen, but I recall two big reasons

  1. Ideally a different layer of the application would handle compression, and an Express middleware should be the very last option. We wanted it in place so local development would still give close to production performance scores, but was not intended as a go live solution.
  2. The iltorb Python dependency of shrink-ray was troublesome. Between security alerts and weekly bug reports, the maintenance began to pile up.

This PR ideally solves both of those problems; use a very simple gzip compression middleware and make it opt-in. Paired with this we will also be working on documentation around compression best practices at the web server layer, focusing on AWS/nginx and Magento Cloud (Fastly on Pro/Stage branches, nginx config on Integration branches).

Copy link
Contributor

@tjwiebell tjwiebell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic is sound, I think it just needs moved. Potential to remove unnecessary middleware too. Nice work!

Comment on lines 55 to 62
if (env.ENABLE_EXPRESS_SERVER_COMPRESSION === 'true') {
app.use(
compression({
threshold: 0
})
);
app.use(gzipValidationMiddleware(env));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've already implemented inversion of control here with the before prop, so let's just use that. Move this logic to PWADevServer, and follow the same pattern as addImgOptMiddleware. With the environment variable being defined in buildpack, it would also make more sense for buildpack to be the one to use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having it in the PWADevServer will only run this as part of the Dev mode. If someone wants to have compression in other modes/environments as well, that wouldn't be possible with it being in PWADevServer. That's the reason why I have left this in the serve module.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me rephrase; we cannot add API to upward-js without also updating the upward-spec and upward-php implementations. If I had confidence UPWARD was going to remain in the stack, this would be an option, but it would likely be better to use upward.yml config as opposed to an environment variable.

Given this, please move the middleware to PWADevServer and utilities/serve in buildpack. Sorry I didn't specify to also put it in the serve utility as well, was just pointing out where to find an example of the IOC pattern.


if (contentEncoding !== 'gzip') {
log(
'\nGzip compression is supported by the Client. For better performance please enable gzip compression.\n'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this getting logged on every request. Oddly enough though, when I switched to this branch compression was enabled, even though I have nothing set in my .env. If this was working as intended, it also doesn't make sense this middleware is only injected alongside compression with that flag set.

I propose removing this middleware entirely, and we focus on adding a blurb to the getting started docs that highlights the real purpose of this middleware.

If you want to run performance metrics on a local development instance, it is suggested that you replicate your production environment as closely as possible to get more accurate measurements. To simulate web server layer compression, you can pass this flag to enable an Express middleware that performs gzip compression of assets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not if you haven't set the ENV variable. Can you check?

Also, I have added a check to not render this warning in a production environment but there is a known bug in the serve module that does not let it work if NODE_ENV is production. I have created a ticket in our backlog to handle this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked again, always compresses with yarn watch:venia, while yarn stage:venia seems to work as intended. Regardless, I would still get rid of the warning. If someone is purposely not using compression or using a different algorithm, there's no reason to annoy them.

packages/venia-concept/package.json Show resolved Hide resolved
}
}
);

let envPort;
if (process.env.PORT) {
Copy link
Contributor Author

@revanth0212 revanth0212 Feb 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been a false-positive all along. Since we dint have tests, we never found it. I have added covering tests for this change.

tjwiebell
tjwiebell previously approved these changes Feb 4, 2021
Copy link
Contributor

@tjwiebell tjwiebell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion for test improvement but not a blocker to send this to QA. Awesome work covering this with tests too 👌

test('should create upward server', async () => {
const server = await serve('pwa-buildpack');

expect(createUpwardServer.mock.calls).toMatchSnapshot();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion looks fragile; has a lot of cruft in it that I'm not sure we need to check (eg. some devDependency stuff). Can we slim this down?

* null and undefined are represented as strings in the env
* so we have to match using strings instead.
*/
if (process.env.PORT !== 'null' && process.env.PORT !== 'undefined') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to propose parseInt here, but I guess 0 is a valid port number. I'm fine with this, but a little type checking wouldn't hurt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you are right, 0 is a valid port number.

tjwiebell
tjwiebell previously approved these changes Feb 4, 2021
@dpatil-magento
Copy link
Contributor

@revanth0212 Condition update to port validation is failing. When you run yarn stage:venia with STAGING_SERVER_PORT set in .env. Either its ignored or entering if condition before this and ends up running server on random port. Same issue on AWS so PR deploy instance is not accessible.

yarn run v1.22.4
$ yarn venia start
$ yarn workspace @magento/venia-concept start
$ buildpack serve .
  ℹ  PORT is set in environment: undefined
  ℹ  Launching UPWARD server
https://magento-venia-concept-ub-0n.local.pwadev:9536/

@dpatil-magento
Copy link
Contributor

QA Approved.

@@ -16,4 +16,4 @@ UPWARD_JS_LOG_URL=1
CHECKOUT_BRAINTREE_TOKEN=redacted
GOOGLE_MAPS_API_KEY=redacted
MAGENTO_BACKEND_EDITION=EE
ENABLE_EXPRESS_SERVER_COMPRESSION=false
ENABLE_EXPRESS_SERVER_COMPRESSION=true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our official guidance should be to do compression at the web server or proxy layer, not the application layer. Any reason you opted for this config instead of adding compression to nginx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dev wanted to test it out and have it enabled on our AWS instances which are not served by Nginx (I presume).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right @tjwiebell. Our app is served using nginx but the compression is not enabled.

For instance, this is a PR instance without
image

And here is the same network req from this PR instance with compression enabled,
image

@dpatil-magento do you know if we can enable compression at nginx level, if so, how long does it take. Till then this can be a stop-gap solution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this change I'm assuming we just deploy these Docker images directly to EBS, so nginx is involved. It sounds like this isn't as straight forward as it should be with the jwilder/nginx-proxy container, but is possible. Not going to block this PR, but we should consider doing this at the correct layer if our goal is performance.

Where to add config: https://github.com/nginx-proxy/nginx-proxy#proxy-wide
Sample of config needed: https://github.com/h5bp/server-configs-nginx/blob/master/h5bp/web_performance/compression.conf

@dpatil-magento dpatil-magento merged commit 5e38b33 into develop Feb 11, 2021
@dpatil-magento dpatil-magento deleted the revanth/express_compression branch February 11, 2021 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg:pwa-buildpack pkg:upward-js Pertains to upward-js reference implementation of UPWARD. pkg:venia-concept Progress: done version: Minor This changeset includes functionality added in a backwards compatible manner.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants