Serving Your App with HTTP/2 in Phoenix 1.4
History
One of the next big developments in how we build web apps is HTTP/2. This has been a long time coming. We’ve been using HTTP/1.1 since 1997 (citation). When the first versions of the protocol were implemented, the landscape of the web looked very different (just think about what the web looked like back in the mid-nineties). This is betrayed by the name “hypertext transfer protocol.” The earliest versions of HTTP were basically a system to share text documents. Obviously, the internet looks very different today. Rather than a few blindingly ugly fan sites about obscure topics, our entire lives are served over the internet.
HTTP/2 is a massive step forward in how we serve the assets for our sites. Here are some ways in which HTTP/2 improves over the previous version:
-
Multiplexing: Multiple different server requests are allowed simultaneously, on the same connection. With HTTP/1.1, each additional requests for assets would have to wait until the previous transfer in the queue completed. This decreases complexity in development, not necessitating things like asset bundling to decrease to number of server requests.
-
Binary. HTTP/2 transfers information in binary, which is more efficient and less error prone than plain-text.
-
Many other enhancements, like header compression, prioritization, and others. More information here: HTTP/2 Info
Setup
This is all theoretical; Let’s get down to the practical stuff. Can I use this today? Before now, the answer to this question has been convoluted. HTTP/2 has been around for a few years now, but it’s been complicated to implement. Fortunately for us, this is changing, and support for HTTP/2 has been more or less baked into the Pheonix 1.4 update.
Setting this up in Phoenix 1.4:
Let’s try this out by setting up a new project in Phoenix. We’ll run this command to create a new project:
mix phx.new http2_test
The most important line of this new project for our purposes here is this: { :plug_cowboy, "~> 2.0" }
. This brings in the 2.0 version of the Cowboy package, which supports HTTP/2 by default. If a browser doesn’t support the protocol (which basically all the major ones for many iterations have), it reverts to HTTP/1.1.
To serve our app over HTTP/2, we’ll need to run our local server with https. The command mix phx.gen.cert
generates a self-signed certificate that allows you to do this locally and a set of instructions to set this up.
This is basically all we have to do to set this up. HTTP/2 support is baked into the app and super user friendly.
We can see our app is serving over HTTP/2!
Assets
Just because the app is running, doesn’t mean we’re getting all the benefit that we can from this setup. Take for example, our assets: by default the webpack config in Phoenix 1.4 is configured to compile everything in the assets/js and assets/css folders and combine them into a single file in the priv/static folder for each. This process is a relic best practice for serving your app over HTTP/1.1. The browser could only load one file at a time, so it was helpful to pack all of your assets for a given site into one file (hence the name “webpack”). But this all changes with HTTP/2.
With HTTP/2, however, we can make multiple simultaneous requests, so it benefits us to have more small files that will resolve and show up in the browser more quickly, rather than waiting on one large file to download, as before — this is what’s known as multiplexing. So what do we do? Should we continue to use webpack and bundle the assets of our apps into one massive files as before, go back to adding scripts to an html file one by one like the old days, or something else entirely? As with all things, this depends on your use case.
A common use case, in my experience, is to have specific code that will be appropriate for parts of the app. You might have, for instance, some css and javascipt that controls the admin section of your app, and a different set of scripts for your user-facing site. In this example, we’ll split the code into bundles and load it only on appropriate pages.
The first thing we have to do is modify the webpack config, with these lines:
// assets/webpack.config.js
entry: {
'app': './js/app.js',
'admin': './js/admin/admin.js'
},
output: {
path: path.resolve(__dirname, '../priv/static/js'),
filename: '[name].js'
}
In this setup, we’ve added an ‘admin’ folder in our assets/js
directory to hold the scripts for our admin section.
// /assets/js/admin/admin.js
// Import local files
import './admin_sidebar'
import './form_validation'
This is a second entry point that only loads the relevant scripts for this section. You can probably see how this process could be repeated and organized to load only relevant scripts for each section of your app.
All that is left is adding the relevant script to the html of your app. In our setup, webpack will build these bundles and store them in the priv/static/js
directory. To bring them into an eex file, you would add a script pointing to these paths:
src=" Routes.static_path(@conn, "/js/app.js")
src="<%= Routes.static_path(@conn, "/js/admin.js")
For this basic example we might add them to this page lib/your_app/templates/layouts/app.html.eex
to pull them into the main template for the app.
Conclusion
And there you have it. Setting up HTTP/2 in a Phoenix 1.4 app is fairly straightforward, but it does require you to reconsider some parts of the build process for you app. This new process has major implications for performance of web applications and complexity of build systems. We’ve only scratched the surface of the differences that HTTP/2 will have on how we build web applications in the next few years.