Today we’re announcing NGINX Unit 1.8.0, which introduces support for internal routing along with other features. In this post, we’re sharing details about all features and a preview of upcoming developments:
- Internal routing
- New version numbering scheme
- Availability in Remi repositories
- Experimental support for Java Servlet containers
- Upcoming support for static file serving
- Upcoming support for proxying
Introducing Internal Routing
We’re introducing the internal routing feature with a different approach than we normally use – this time it’s example first, description after:
"routes": {
"blogs": [
{
"match": {
"uri": ["/phpmyadmin/*", "/wp-admin/*"]
},
"action": {
"pass": "applications/blogs-admin"
}
},
{
"match": {
"host": "!blog.example.com"
},
"action": {
"pass":"applications/goaway"
}
},
{
"match": {
"method": "POST",
"uri": "/media/upload"
},
"action": {
"pass":"applications/uploader"
}
}
]
}
Now, the details.
When we released the initial version of NGINX Unit, some of our users wondered why the API included a separate listener
object – it perhaps wasn’t clear why it was necessary. In NGINX Unit 1.8.0, we’re introducing the internal routing feature that makes use of the listener
object in a more sophisticated way and enables new functionality in the application server.
Internal routing enables granular control over the target application based on the sets of policies that you define. Sample use cases include:
- Requests to administrative URLs need a different security group and fewer application processes than the main application.
- Requests to a wrong hostname need to be handled by a separate application that does not consume system resources.
POST
requests are handled by a special app, maybe written in a different language.
Full documentation for internal routing is available at https://unit.nginx.org/configuration/#routes.
Introducing the routes
Object
The new routes
object is accessible through the NGINX Unit API, and located inside the /config
object.
If you only need one set of routing rules for all available listeners, you can define the routes
object as a JSON array:
"routes": [ { ... }, { ... }, { ... } ]
If you need more than one set of rules, define routes
as a JSON object. Each named object inside of it is an array of one or more rules:
"routes": {
"blogs-routes": [ { ... } , { ... } ],
"security-routes": [ { ... }, { ... } ],
"misc-routes": [ { ... } ]
}
You use the route names defined here when referring to the routes in other sections of the configuration.
Defining Routing Rules
Each rule in the route
object consists of a match
object and an action
object.
The match
Object
The match
object defines the characteristics a request must have for the corresponding action to happen. You can omit the match
if the rule is unconditional (“match always”). Version 1.8.0 supports three match
objects:
host
– The HTTPHost
header, or the hostname that you usually see in the browser’s address baruri
– The URI without the query stringmethod
– The HTTP method, such asGET
,POST
, orPUT
Version 1.8.0 supports three types of matching:
- Exact match.
- Partial match. Use the
*
character (asterisk) to match any number of characters at the beginning or the end of the value, for example/api/*
,*.php
, orP*
(the last to match thePUT
andPOST
methods). - Negative match. Use the
!
character (exclamation point) in front of the value, for example!api.example.com
.
In future releases, we plan to support partial match at any position in the value, and after that regular expressions.
A match
object can be defined either as a string or as an array.
The action
Object
The other object in routing rules, action
, is mandatory. NGINX Unit 1.8.0 supports only one action, pass
, which passes the request to the specified application or route. We plan to add other actions, such as redirects, HTTP return codes, static files, and maybe more.
You can pass requests to either an application:
"action": {
"pass": "applications/blogs-general"
}
or a route:
"action": {
"pass": "routes/blogs"
}
Creating Routing Chains
Because the action
object in a route
object is the same as the action
object in a listener
object, you can chain routes together, as in the following example. Make sure you don’t loop them!
"routes": {
"host-method": [
{
"match": {
"host": "example.com",
"method": ["GET", "HEAD"]
},
"action": {
"pass": "routes/url-only"
}
},
{
"match": {
"host": "blog.example.com"
},
"action": {
"pass": "applications/blog"
}
}
],
"url-only": [
{
"match": {
"uri": "/api/*"
},
"action": {
"pass": "applications/myapp"
}
}
]
}
Dynamically Changing Routing Rules
Like all other configuration objects in NGINX Unit, the routes
object is accessed through the NGINX Unit API and stored in memory rather than in a file. This means you don’t have to edit a configuration file to change a routing rule, nor do you need to restart NGINX Unit.
As previously mentioned, a route is represented as an array of one or more rules. When you want to change a rule, you reference it by its numerical index in the array, starting with 0
.
In the following examples, we change each of the three routing rules in the example from the beginning of the post. Here’s the example again:
"routes": {
"blogs": [
{
"match": {
"uri": ["/phpmyadmin/*", "/wp-admin/*"]
},
"action": {
"pass": "applications/blogs-admin"
}
},
{
"match": {
"host": "!blog.example.com"
},
"action": {
"pass":"applications/goaway"
}
},
{
"match": {
"method": "POST",
"uri": "/media/upload"
},
"action": {
"pass":"applications/uploader"
}
}
]
}
In the first rule (index 0
), we set the application that processes the main blogs
route to applications/cms
, replacing applications/blogs-admin
:
curl -X PUT -d '"applications/cms"' --unix-socket=/run/control.unit.sock http://localhost/config/routes/blogs/0/action/pass
In the second rule (index 1
), we set the matched hostname to www.example.com
, replacing !blog.example.com
:
curl -X PUT -d '"www.example.com"' --unix-socket=/run/control.unit.sock http://localhost/config/routes/blogs/1/match/host
In the third rule (index 2
), we set the matched methods to both POST
and PUT
, instead of just POST
:
curl -X PUT -d '["POST", "PUT"]' --unix-socket=/run/control.unit.sock http://localhost/config/routes/blogs/2/match/method
Minimal Config for Internal Routing
There are different approaches to system configuration. Many engineers prefer to read full documentation first and create correct configuration from scratch. I usually build my configurations iteratively, starting with the minimal mandatory config and gradually expanding it with the values I want for the options. For this blog, I started with this minimal config (yours might be different):
{
"certificates": {},
"config": {
"listeners": {
"*:8400": {
"pass": "routes/example"
}
},
"applications": {
"blogs": {
"type": "php",
"root": "/home/nick/demo/routing",
"script": "demo.php"
}
},
"routes": {
"example": [
{
"match": {
"host": "*",
"uri": "*",
"method": "*"
},
"action": {
"pass": "applications/blogs"
}
}
]
}
}
}
New Third Number in Versioning
Eagle‑eyed users of NGINX Unit might have noticed that the latest version, 1.8.0, has one more digit than previous version numbers. Starting with version 1.7.1, we added the final (revision) number to the existing major and minor version numbers used for versions 0.1 through 1.7.
This small change allows us to create patch versions for any security fixes and other important updates that do not alter functionality or add major features. Version 1.7.1 was the first example of such an update.
NGINX Unit Available from Remi Repositories
Like any other software project, programming languages grow and evolve. However, it is quite common for operating system maintainers to freeze the version of programming languages that they package with a specific OS distribution. Judging from our experience, enterprise users in turn tend to freeze the operating system version in their stack and refrain from upgrading for as long as possible.
For example, we know of quite a few enterprises still using CentOS 6, which was released in 2011 and is set to EOL in late 2020. The CentOS 6 package includes PHP version 5.3, which is nearly 10 years old and has already reached its EOL.
Besides upgrading to the latest OS version monthly, the most popular solution for this problem is to use a third‑party repository of OS packages that include more recent versions of PHP and other languages.
Remi Collet, a community contributor from France, has been maintaining a number of such PHP repositories for many years. At the beginning of 2019, Remi added NGINX Unit packages to his repositories for CentOS, RHEL, and Fedora. These packages contain NGINX Unit modules that run all versions of PHP that he provides.
Now you can have the best of both worlds – keep your infrastructure maintenance team happy by skipping the operating system upgrade and have your apps run the latest PHP packages on our modern application server. See Remi’s announcement and setup instructions.
Experimental Support for Java Servlet Containers
Java is ubiquitous – there’s not much need to elaborate on that. We are working to build a supported solution for Java application engineers.
Our own Maxim Romanov worked in a separate branch last year on support for running applications leveraging certain technology described in the Java® Servlet 3.1 (JSR‑340) specification. This module is a beta release, as the module is untested and presumed incompatible with the JSR‑340 specification.
Now everybody can easily install it from our packages, try it with their Java applications, and provide feedback.
Java is a registered trademark of Oracle and/or its affiliates.
Upcoming Support for Static File Serving
Soon, we’ll start working on serving static files. The ability to serve static files will make NGINX Unit not only the most versatile application server, but a full‑fledged web server as well.
Upcoming Support for Proxying
We’ve had a recent surge of questions about support for proxying. We’re happy to announce that our engineering team has this issue in their sights as well. When they’re done, NGINX Unit will be able to receive an HTTP request and do one of the following: process it internally with an app in any of the supported languages, deliver a static file from the filesystem, or proxy the request further to another NGINX Unit instance or a different backend.
Conclusion and Next Steps
Internal routing makes your applications more configurable and dynamic. We plan to extend this functionality in future releases to other Layer 7 objects such as HTTP headers and cookies, and to Layer 3 objects such as IP addresses and ports. Try it in your web application configuration, and let us know what configs you come up with. You can ask questions in our GitHub issue tracker (https://github.com/nginx/unit/issues) or the NGINX Unit mailing list (https://mailman.nginx.org/mailman/listinfo/unit).
Full documentation for internal routing is available at https://unit.nginx.org/configuration/#routes.
Want to create the next big feature for NGINX Unit? We are hiring! Check out the C/Unix engineer openings at https://www.nginx.com/careers/current-openings/.