How to get nginx to take advantage of http2 with express - node.js

I am using express with node and nginx as a reverse proxy. I'd like to know how to take advantage of http/2 with nginx to serve static content, with all other requests being forwarded to the express API.
At the moment, my express server is being served via http/1 and nginx is accepting http/2 connections, and forwarding them to express. How do I set up nginx so that it uses http/2 to serve everything in my statics folder, but forwards all requests to the API as http1?

I will break your questions into two parts:
How to take advantages of http/2.0 to serve static files from nginx?
How to setup nginx to send http/1.1 request to the backend server in case where nginx act as a reverse proxy?
Answer 1:
For the case of serving static files the major performance benefit can come from using the multiplexing feature of the http/2.0 protocol.
Multiplexing enhances the pipelining feature introduced in http/1.1 and overcomes the problem of HOL blocking. With multiplexing you can use the same underlying TCP connection to load multiple resources in parallel using one http connection. You should also consider the stream prioritisation to assign priority to the resource which you want to load first on the page otherwise loading of some of the critical resources can be delayed since all the resources will contend for same multiplexed connection.
Answer 2:
Sending http/1.1 request to the backend server is the default behaviour. So if you have already configured nginx to use http/2.0 you do not have to do anything special to proxy http/1.1 request to your backend. This is because nginx does not support http/2.0 in proxy module as of now. Refer to this ticket. Also, please check this digital ocean tutorial which will guide you to setup nginx with http/2.0 configured on ubuntu 16.04.

Related

Why is a web server (i.e Nginx) REQUIRED for FastAPI but not Express API?

A typical request gets processed like this:
request -> Nginx reverse proxy -> AWS EC2 -> Express API/FastAPI -> response
but my biggest confusion is why a FastAPI absolutely NEEDS Nginx to work, but an Express API doesn't (despite nodejs and python both having the http module and hence able to make web servers). Why do I need Nginx at all for FastAPI? Can't an AWS EC2 instance act as a web server like Nginx?
This post says it is so we can hide the port number in the url, but that being the only reason sounds unreasonable to me.
Why is a web server (i.e Nginx) REQUIRED for FastAPI
Nginx is not required for FastAPI. You can listen on external ports and handle requests without a reverse proxy. Even FastAPI documentation includes setting up Nginx under the Advanced section (Ref)
This post says it's so we can hide the port number in the url, but this being the only reason seems silly to me.
You can still hide the port number in the url if you run the Express app with sudo and listen on port 80 or 443.
Can't an AWS EC2 instance act as a web server like Nginx?
Yes it can.
There are benefits of using a proxy server like Nginx. Proxy servers can handle load balancing, caching, SSL termination and they can handle large number of concurrent connections efficiently.
Using a proxy server is a best practice. However, it is not a requirement for FastAPI or Express.

AWS Loadbalancer Proxy for Nodejs

I have configured the load balancer to route the request to two of Ec2 Instance running a NodeJs server. I need to direct the request coming from both http (port 80) and https (port 443) to http (port 80) of the EC2 instances in NodeJs. I have uploaded the ssl certificate to AWS and configured the load balancer to use ssl certificate. The problem is the request coming from http port doesn't automatically route to https. It has to be a server side script or snipped which I need to write in server.js which should be routing the http to https, i tried to do it and it run into endless redirection. So questions -
Is there any guide to do this from AWS ?
If not then how one can achieve this, any pointers or suggestions would be greatly appreciated.
On the server side you can check the X-Forwarded-Proto
(original request protocol) and if it's heaving value http you can send redirect (http 302) to a url with https protocol..
though with ALB (application load balancer you may specify a set of rules, maybe it's possible to do that there..)
I couldn't find a guide from AWS, but I will keep searching and update the answer in the case I find it.
Usually, when you write applications in Node.js, you specify which port should your app run at. It means that you will need two different servers listening. And when your app receives a request on port 80 (HTTP), it should redirect to your HTTPS server, like in this answer.
Another point that may be relevant to your question is that, in production environments, you don't usually bind a port to your Node.js server, since it's not production ready. You probably want to use a reverse proxy and load balancer like Nginx or HAProxy.
If you are using the AWS ALB (Application Load Balancer) they announced the http->https redirect today. Take a look: https://exampleloadbalancer.com/redirect_demo.html
Put your ELB behind the Cloudfront and in settings of your distribution select forward HTTP to HTTPS.
The following doc will be helpful
https://docs.aws.amazon.com/waf/latest/developerguide/tutorials-ddos-cross-service-ELB.html
This method has two benefit:
1-Your problem will be solve
2-You can use the benefit of the powerful CDN, for more information about Cloudfront read https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html
Update:
You can forward traffic from HTTP to HTTPS by edit your Listeners setting in your ELB.

How does Varnish handle HTTP/2 Server Push?

I have been looking at ways to improve page loads using HTTP2 specifically using server push.
We have a HAProxy => Varnish => Apache configuration.
I know varnish 5 can handle HTTP2 requests, but when the request has server push headers for further resources on a page do those resources come out of the cache or does it just get passed to apache?
My thinking is that if those server push headers don't get handled by varnish it would be to the determent of page loads rather than a net gain...
HAProxy 1.8 only has HTTP/2 support at the front end and then will connect to Varnish using HTTP/1.1 - unless you are using this as a TCP loadbalancer rather than a HTTP loadbalancer? HAproxy 1.9 did add HTTP/2 in the backend (i.e. to downstream systems like Varnish in your setup). I do not believe either supports HTTP/2 Push when using it as a HTTP proxy.
Varnish similarly only supports HTTP/2 over the front end and not push AFAIK.
So basically you cannot use Push in your currently infrastructure. They will connect to Apache (which is the only piece with Push support) as a HTTP/1.1 connection and therefore it will not even attempt to Push resources unless using HTTP/2.
The easiest way to support this would be to simplify your infrastructure. I’m not sure if you have the volume that requires both HAProxy and Varnish or if you have some other reason to set this up this way? If not then you could either get rid of them totally and just use Apache. Alternatively just use HAProxy as a TCP proxy and connect to any Apache instances (either over TLS or not) using HTTP/2.
The other option is to put another instance of Apache in front of HAProxy to handle HTTP/2 and HTTP/2 Push. The back end connections can be over HTTP/1 then and signal to this new Apache to push a resource using Link headers (even over HTTP/1) and it will request the resource from downstream appropriately (which may read it from the Varnish cache if it’s set up that way). But running Apache -> HAProxy -> Varnish -> Apache definitely sounds like overkill for most sites.
HTTP/2 Push is now supported in HAproxy 1.9 with "option http-use-htx"
It can use Varnish (and other non-TLS) as HTTP/2 backend servers with parameter "proto h2".
It can also use TLS HTTP/2 enabled backend servers with parameter "ssl verify none alpn h2"
However I'm strucling with Varnish 6.0 to allow HTTP/2 Push back to another HAproxy Frontend with "proto h2" enabled, as Varnish seems to only support HTTP/1.1 to its backend servers.
Even though varnishlog shows HTTP/2 protocol is used in the request from HAProxy.
I have tested the following and it works all the way with HTTP/2 (end-2-end).
HTTP/2-browser -> Public-HAProxy-h2frontend -> Back-HAProxy-h2frontend-> HTTP/2-SSL-Webserver (IIS)
The following fails after Varnish, as it only uses http/1.1 to the Back-HAProxy-h2frontend, need to be able to force varnish to keep using HTTP/2 to the backend server.
HTTP/2-browser -> Public-HAProxy-h2frontend -> HTTP/2-Varnish -> Back-HAProxy-h2frontend-> HTTP/2-SSL-Webserver (IIS)

Socketio and cloudflare

So I've already done my research and figured out that socket.io only works with cloudflare if you use set ports found that here
So through that research I found that http and https can't use the same port. I'm coming here to as you guys how do you get a socketio server to listen on two ports? So it can support http and https with cloudflare
The common method is referred to as an SSL Termination Proxy (also called SSL off-loading). The proxy accepts incoming messages over HTTPS and passes the decrypted requests to another resource (another server, web service/API, etc.). This would allow your Node.js application utilizing socketio to handle all requests, no matter if the client made an HTTP or HTTPS request. Software like NGINX, Apache, and even Microsoft IIS are capable of providing this functionality.
Here are some links regarding this topic:
General Info: https://en.wikipedia.org/wiki/TLS_termination_proxy
NGINX: https://www.nginx.com/resources/admin-guide/nginx-ssl-termination/
NGINX: https://www.nginx.com/resources/admin-guide/nginx-tcp-ssl-termination/
HAProxy: https://www.digitalocean.com/community/tutorials/how-to-implement-ssl-termination-with-haproxy-on-ubuntu-14-04
IIS: https://blogs.iis.net/wonyoo/ssl-off-loading-in-application-request-routing

Nodejs http2 behind (nginx or otherwise Webserver) with Push in Nodejs

I am trying to update our online shop to use HTTP/2 with Server Push capabilities but I can't find a solution for a webserver like Nginx(for proxying and some other stuff) with upstream HTTP/2. We are using Node.js with the node HTTP module at the moment, but would like to switch to the node spdy module. The spdy module supports HTTP/2 with Server Push. I have tried H2O as an alternative to Nginx, but it doesn't support HTTP/2 upstream either.
I am kind of lost at the moment and need help.
Nginx has only just added support for HTTP/2 Push so unless you are rubbing the latest mainline version you will not be able to do this. Also because it is so new there are still some issues with it. Nginx do not support http2 over backend connections (and have stated they won’t support this). So you cannot directly push from a downstream system all the way up like you suggest.
There is some question as to whether that is the best way to push anyway. A downstream system may push to the upstream proxy server even if the client does not support push for example - which is a wasted push.
So the better way is to push from the proxy and have the downstream system tell the upstream system (via link headers) to do that push. This has several advantages in reducing complexity, allowing the downstream system to push assets it may not control (e.g. static assets like stylesheets, JavaScript, images... etc.), a central store of already pushed assets (cache digests) and also not requiring HTTP/2 to be supported all the way through (link headers can be sent over HTTP/1.1 as easily as HTTP/2).
The main downside to pushing from the upstream proxy via link headers is that you have to wait for the requested resource to be ready as the link headers are read from the response. If the request resource takes some time to generate then it may be more beneficial to start pushing other resources while it’s being processed. This is solved by a new 103 Early Hints HTTP Status code where you can reply back earlier before sending the main 200-status code later. This early message can have link headers which can be read by the upstream proxy and be used to push the resource. I am not sure if Nginx implementation will support this.
Incidentally Apache has supported Push for a while now and has a much more mature implementation. It supports it via direct Apache config or via link headers (including via 103 responses which by default are configured not to be sent on in case on compatibility issues). It even supports proxying to backends via HTTP/2 though does not support direct push over back end connections for reasons described above. Some other less well known servers (e.g. H2O) also support HTTP/2 better than Nginx.
Finally if using a CDN then they may support HTTP/2 Push (often via link headers) without you having to upgrade any of your backend infrastructure. In fact Cloudflare is an Nginx based CDN which has had HTTP/2 Push for a while and I fact it’s two Cloudflare engineers which have back ported their implementation to the base Nginx code.
After NGINX 1.13.9 (just pushed to mainline today) you're able to have HTTP/2 server push out of the box by compiling it with the ngx_http_v2_module.
If you're interested in the recent addition, this is the commit that added most of the functionality: hg.nginx.org: HTTP/2: server push.
Its use is relatively straightforward: add the http2_push_preload directive to the server that is proxying Node and then from node make use of the Link header (as described in the W3 spec - https://www.w3.org/TR/preload/#server-push-http-2) and then NGINX will do the job of sending the h2 frame that indicates a server push.
For instance, assume that you have a / endpoint that serves a regular index.html but also pushes image.svg to the client.
In NGINX you could configure an upstream server and then in the server configuration enable http2_push_preload on the server configuration:
# Add an upstream server to proxy requests to.
upstream sample-http1 {
server localhost:8080;
}
server {
# Listen on port 8443 with http2 support on.
listen 8443 http2;
# Enable TLS such that we can have proper HTTP2
# support using browsers.
ssl on;
ssl_certificate certs/cert_example.com.pem;
ssl_certificate_key certs/key_example.com.pem;
# Enable support for using `Link` headers to indicate
# origin server push.
http2_push_preload on;
# Act as a reverse proxy for requests going to /proxy/*.
#
# Because we don't want to rewrite our endpoints in the
# Node app, rewrite the path such that `/proxy/lol` ends up
# as `/lol`.
location / {
proxy_pass http://sample-http1;
}
}
Then in the NodeJS app, you'd serve / as you'd normally do but add an extra Link header to the response:
response.setHeader('Link', '</image.svg>; rel=preload; as=image');
ps.: yeah, you'd keep those angle brackets; I do not mean that you should replace them.
By the way, the example I just gave (with some debugging tips) is written up in complete here: https://ops.tips/blog/nginx-http2-server-push/ .
You can compile/recompile nginx from source and include the --with-http_v2_module configuration parameter to enable HTTP2 push capabilities.

Resources