Supercharge your Nginx with Openresty and Lua
Turn your Nginx into an API Gateway. Modify requests and responses from Nginx itself using Openresty and Lua.
Objectives
- To deploy Openresty on a development/production environment
- As a reverse proxy, to modify incoming request params and pass it on to another server
- As a reverse proxy, to modify response from proxied server before returning to user
Openresty
Openresty is a open-sourced NGINX bundle that combines NGINX and various 3rd-party NGINX modules and Lua Libraries.
Openresty brings with it the power of the programming language Lua to write actual code directly on NGINX itself. Allowing NGINX administrators to add logic without having to write a seperate application to perform more complex routing activities.
Deploying Openresty
Thanks to Docker, there’s a quick way to get started with Openresty.
Depending on your prefered distribution, you can choose from a variety of supported distributions by the Openresty team.
The quickest way would be to copy the following:
$ docker run -p 80:80 openresty:trusty
For advanced users: Nginx configuration file is located in
/usr/local/openresty/nginx/conf
in your container. For a better experience, I’d recommend duplicating the openresty/nginx/conf directory locally and mounting it as a data volume so that you may change the nginx config from outside the container.
You can checkout Lomotif’s bootstrap.sh script that takes your container’s
nginx/conf
file and expose it outside.
Modifying your Nginx with Lua
Now that your Openresty is setup, lets get to the meaty part.
In your nginx.conf
, you now have access to the following new directives:
access_by_lua
, content_by_lua
, init_by_lua
and rewrite_by_lua
.
Each of these directives also a _file
, _block
suffix available. Like access_by_lua_file
, content_by_lua_block
.
The official NGINX documentation has a pretty informative explaination on the differences.
Modifying incoming Requests
Lets say you are running a reverse proxy server that proxies all requests to another API server for processing.
You have certain parameters that you’d like to add/modify before passing that on.
How do we do that?
It should look something like:
# nginx.conf
server {
listen 80;
server_name yo.app.us;
location / {
access_by_lua_file /path/to/file.lua;
proxy_pass http://url_to_proxy
}
}
-- file.lua
function mask_content()
nginx.req.read_body()
local oldbody = ngx.req.get_body_data()
local new_variables = string.gsum(oldbody, "string_to_change", "changed_string")
ngx.req.set_body_data(new_variables)
end
if ngx.req.get_method() ~= "GET"
then
mask_content()
end
Just with these changes, you can get nginx to read incoming parameters, make changes to it and then continue on and pass those same variables to the url defined in proxy_pass
.
Note: We use access_by_lua_file and not content_by_lua_file because content_by_lua doesn’t allow proxy_pass to be in the same location block.
Modifying response from proxied server
With the proxy server sending new variables to the API server above, now we want to modify the response such as to remove any identification about where the actual API server lives or to remove sensitive data.
Modifying returned data is usually done on the content_by_lua
directive which we would not have access to if we’re using proxy_pass
.
So how do we do it?
We will have to modify our code a little bit and make use of a cool function called ngx.location.capture.
# nginx.conf
server {
listen 80;
server_name yo.app.us;
location / {
content_by_lua_file /path/to/content_change.lua;
}
location /legacy {
access_by_lua_file /path/to/file.lua;
proxy_pass http://url_to_proxy
}
}
We can reuse the old file.lua
.
-- file.lua
function mask_content()
nginx.req.read_body()
local oldbody = ngx.req.get_body_data()
local new_variables = string.gsum(oldbody, "string_to_change", "changed_string")
ngx.req.set_body_data(new_variables)
end
if ngx.req.get_method() ~= "GET"
then
mask_content()
end
But lets create a new content_change.lua
-- content_change.lua
res = ngx.location.capture("/legacy")
local response_body = res.body
-- make changes to the response_body here
ngx.say(new_body)
And there you have it!
What’s next?
I’ve heard quite a bit about Lapis which is a Lua framework that runs inside of Openresty.
Some fun experiments for next week!