Nodejs http2 behind (nginx or otherwise Webserver) with Push in Nodejs - node.js

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.

Related

NodeJS - Determine if a target website uses HTTP/2

I'm using NodeJS and trying to make a simple server request. I'd like to determine if the server is HTTP/2 or not. If it is, I'd want to use the 'http2' module to make the request. Otherwise, I'll fall back to the 'http' / 'https' modules to make the request.
With NodeJS, how can I determine if a target remote server uses HTTP/2?
TIA
The general idea is that if you want to use http2 (if available), then you make your request from node.js using the http2 module. As part of that connection and part of the SSL/TLS negotiation, the two sides will exchange whether http2 can be used or not (via an extension called ALPN). If both sides agree on http2, then it will be used. If not, it will fall back to regular https.
Some relevant references:
How to check if website has http/2 protocol support
ALPN negotiation using node.js http2 library
If you're curious, this website: https://tools.keycdn.com/http2-test will tell you if any site supports HTTP/2 and the ALPN extension.

How can I check that HTTP2/Server Push works properly?

I've been testing HTTP2 Multiplexing and HTTP2/Server Push in Node.js locally and inspected it in the waterfall network graph in Chrome Dev Tools.
While using my own Node.js Server Push with res.stream.pushStream I got an "Initiator: Push (index)" in DevTools like this:
The change was noticeable as below:
I did some more research and figured I should use a reverse proxy to do the job (for example Nginx instead) and connect to my Node.js via HTTP1.1 instead as the upstream and serve HTTP2 from my reverse proxy.
After setting up the nginx.conf with http2_push_preload on; i sent some headers like these from my Node.js backend:
res.setHeader("Link","</picture.jpg>; as=image; rel=preload");
To my suprise I didn't see the "Push / (index)" indicator but "Other" like on the screenshot below and the asset listed in the link header seemed to show up on the waterfall graph quicker with a slightly lower TTFB time compared to the rest of assets.
I've been also looking for a solution to serve HTTP/2 as the reverse proxy and to download the assets via HTTP/2 from the service directly without the TLS (HTC) but it seems there's nothing like this.
Getting back to my question: How should I go about testing the HTTP/2 Server Push? Is the "Initiator: Other" a misinterpretation from the devtools? It seems to be working but it doesn't report as Server Push.
Also, are there any projects/solutions that would let for a connection to a backend upstream via HTTP2 directly?
I'm pretty sure the latter image shows slower access because of the overhead of using a reverse proxy instead of connecting to the server directly and it's magnified by the HTTP1.1 usage on the target server.
Thanks to #Barry I figured out it's actually Chrome itself as the "Other" initiator and it indeed speeds up the process a bit by using the Resource Hints Link header and not HTTP/2 Server Push itself.
The problem was actually a bug I can't seem to reproduce and it works well after a restart of the OS. Afterall Nginx was the culprit of not using the headers correctly, which kicked in when parsed in Chrome.

What is difference between httpS and http/2?

I'm trying to understand what is the difference between HTTPS and http/2?
If I'm going to build a Node.js/express app, what should I use?
Can I use HTTPS with http/2?
Maybe if I use HTTPS, I don't need http/2 because it's the same, or HTTPS use http/2 under the hood?
I'm confused.
Someone is linked to me "difference between HTTP 1.1 and HTTP 2.0 [closed]", but I understand the difference between HTTP and HTTP2. I'm asking about HTTPS and HTTP/2
HTTP - A protocol used by clients (e.g. web browsers) to request resources from servers (e.g. web servers).
HTTPS - A way of encrypting HTTP. It basically wraps HTTP messages up in an encrypted format using SSL/TLS. The web is moving towards HTTPS more and more and web browsers are starting to put more and more warnings when a website is served over unencrypted HTTP. Unless you have a very good reason not to, use HTTPS on any websites you create now.
Digging into HTTP more we have:
HTTP/1.1 - this was the prevalent format of HTTP until recently. It is a text-based protocol and has some inefficiencies in it - especially when requesting lots of resources like a typical web page. HTTP/1.1 messages can be unencrypted (where web site addresses start http://) or encrypted with HTTPS (where web site address start with https://). The client uses the start of the URL to decide which protocol to use, usually defaulting to http:// if not provided.
HTTP/2 - a new version of HTTP released in 2015 which addresses some of the performance issues by moving away from a text based protocol to a binary protocol where each byte is clearly defined. This is easier to parse for clients and servers, leaves less room for errors and also allows multiplexing. HTTP/2, like HTTP/1.1, is available over unencrypted (http://) and encrypted (https://) channels but web browsers only support it over HTTPS, where it is decided whether to use HTTP/1.1 or HTTP/2 as part of the HTTPS negotiation at the start of the connection.
HTTP/2 is used by about a third of all websites at the time of writing (up to 50% of websites as of Jan 2020, and 67% of website requests). However not all clients support HTTP/2 so you should support HTTP/1.1 over HTTPS and HTTP/2 over HTTPS where possible (I believe node automatically does this for you when using the http module). I do not believe HTTP/1.1 will be retired any time soon. You should also consider supporting HTTP/1.1 over unencrypted HTTP and then redirect to HTTPS version (which will then use HTTP/1.1 or HTTP/2 as appropriate). A web server like Apache or Nginx in front of Node makes this easy.
HTTP/3 - the next version of HTTP, currently under development. It is expected to be finalised in 2020 though it will likely be late 2020 or even 2021 before you see this widely available in web servers and languages like node. It will be built on top of a UDP-based transport called QUIC (rather than the TCP-based protocol that HTTP/1.1 and HTTP/2 are based on top of). It will include part of HTTPS in the protocol so HTTP/3 will only be available over HTTPS.
In short you should use HTTP/1.1 over HTTPS, should consider HTTP/2 as well if easy to implement (not always possible as not quite ubiquitous yet - but getting there) and in future you might be using HTTP/3.
I suggest you get a firm understanding of all of these technologies (except maybe HTTP/3 just yet) if you want to do web development. It will stand you in good stead.

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)

How to get nginx to take advantage of http2 with express

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.

Resources