Intro to Lua and Openresty, Part 7: Limit HTTP Methods
In Part 6 of this series, we demonstrated how to write a simple but effective data processing sink with redis as the queue to safely store message before and during processing.
In this post, we’ll take a short detour to briefly investigate conditionals based on the HTTP method used in the request.
If you are following along in the code, we are here.
Restricting access based on HTTP method
Let’s say you have an endpoint that accepts GET
and POST
, how do you ensure you only process requests of those type, and block non-allowed methods as early as possible? The purpose of this exercise is to demonstrate how to inspect and react to the specific HTTP method used to access the URI location. While there are multiple ways to accomplish this directly in nginx.conf
, we will use Lua to inspect and take action on these methods.
Micro Example
local http_method = ngx.var.request_method
if http_method == ngx.HTTP_GET then
local cjson = require "cjson"
.status = ngx.HTTP_OK
ngx.say(cjson.encode({method = "GET", status = "allowed"}))
ngxreturn ngx.exit(ngx.HTTP_OK)
else
.status = ngx.HTTP_NOT_ALLOWED
ngx.say(cjson.encode({method = http_method , status = "denied"}))
ngxreturn ngx.exit(ngx.HTTP_NOT_ALLOWED)
end
Note.. in testing this, ngx.HTTP_GET
appears to be 2
, while ngx.HTTP_POST
is 8
, so I have used this instead:
local http_method = ngx.var.request_method
if http_method == "GET" then
local cjson = require "cjson"
.status = ngx.HTTP_OK
ngx.say(cjson.encode({method = "GET", status = "allowed"}))
ngxreturn ngx.exit(ngx.HTTP_OK)
else
.status = ngx.HTTP_NOT_ALLOWED
ngx.say(cjson.encode({method = http_method , status = "denied"}))
ngxreturn ngx.exit(ngx.HTTP_NOT_ALLOWED)
end
Put it into a webapp
With some initial tests using the snippet above, we settle on the following:
worker_processes 1;
error_log error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8000;
charset utf-8;
charset_types application/json;
default_type application/json;
location /get {
content_by_lua '
local cjson = require "cjson"
local http_method = ngx.var.request_method
if http_method == "GET" then
ngx.status = ngx.HTTP_OK
ngx.say(cjson.encode({method = "GET", status = "allowed"}))
return ngx.exit(ngx.HTTP_OK)
else
ngx.status = ngx.HTTP_NOT_ALLOWED
ngx.say(cjson.encode({method = http_method , status = "denied"}))
return ngx.exit(ngx.HTTP_NOT_ALLOWED)
end
';
}
location /post {
content_by_lua '
local cjson = require "cjson"
local http_method = ngx.var.request_method
if http_method == "POST" then
ngx.status = ngx.HTTP_OK
ngx.say(cjson.encode({method = "POST", status = "allowed"}))
return ngx.exit(ngx.HTTP_OK)
else
ngx.status = ngx.HTTP_NOT_ALLOWED
ngx.say(cjson.encode({method = http_method , status = "denied"}))
return ngx.exit(ngx.HTTP_NOT_ALLOWED)
end
';
}
}
}
Here is the Dockerfile
:
FROM openresty/openresty:alpine-fat
EXPOSE 8000
RUN /usr/local/openresty/luajit/bin/luarocks install lua-cjson
ADD nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
The Makefile
:
build:
docker build --tag=app:8 --rm=true ./
run:
docker run -d --name app --net host -p 127.0.0.1:8000:8000 app:8
dev:
docker run --rm -it --name app --net host --entrypoint /bin/sh -v `pwd`/app:/src app:8
clean:
docker stop app || true
docker rm app || true
reload:
docker exec -it app /usr/local/openresty/nginx/sbin/nginx -s reload
logs:
docker exec -it app tail -f /usr/local/openresty/nginx/error.log
test:
curl -i -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' localhost:8000/get
curl -i -H "Content-Type: application/json" localhost:8000/get
curl -i -H "Content-Type: application/json" localhost:8000/post
curl -i -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' localhost:8000/post
Build the Image
ᐅ make build
docker build --tag=app:8 --rm=true ./
Sending build context to Docker daemon 6.144 kB
Step 1 : FROM openresty/openresty:alpine-fat
---> 366babf2b04d
Step 2 : EXPOSE 8000
---> Using cache
---> 35a8c6e42825
Step 3 : RUN /usr/local/openresty/luajit/bin/luarocks install lua-cjson
---> Using cache
---> 88eaefcb0701
Step 4 : ADD nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
---> 0f06fa265a56
Removing intermediate container 17a6406417d9
Successfully built 0f06fa265a56
Run the image
ᐅ make run
docker run -d --name app --net host -p 127.0.0.1:8000:8000 app:8
2f52d4449835fe5bc3cfe3881e45c442d742bf1bfbd784d201e1ef3872615a5d
Run tests on the webapp
ᐅ make test
curl -i -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' localhost:8000/get
HTTP/1.1 405 Not Allowed
Server: openresty/1.11.2.2
Date: Sun, 05 Mar 2017 05:22:31 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
{"status":"denied","method":"POST"}
curl -i -H "Content-Type: application/json" localhost:8000/get
HTTP/1.1 200 OK
Server: openresty/1.11.2.2
Date: Sun, 05 Mar 2017 05:22:31 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
{"status":"allowed","method":"GET"}
curl -i -H "Content-Type: application/json" localhost:8000/post
HTTP/1.1 405 Not Allowed
Server: openresty/1.11.2.2
Date: Sun, 05 Mar 2017 05:22:31 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
{"status":"denied","method":"GET"}
curl -i -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' localhost:8000/post
HTTP/1.1 200 OK
Server: openresty/1.11.2.2
Date: Sun, 05 Mar 2017 05:22:31 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
{"status":"allowed","method":"POST"}