SlideShare a Scribd company logo
Implementing a Fileserver
with Nginx and Lua
techtalk@ferret
Andrii Gakhov
27.02.2017
Current Architecture
Restful API server that communicates with clients using
application/json
Every client is authenticated by a dedicated 3rd party
authentication server and authorized by the API server
End users get benefits from the API via client-side
frontend application
The Problem
At some point, end users request the ability to manage files
using the same client-side application and, consequently,
same API.
allow to process multipart/form-data requests (that will be
proxied from the form on the client-side application)
extract and handle file metadata
provide file storage and access
The Solution
Obviously, such functionality is out of the scope for the API
and the natural decision is to split it across 2
applications:
API is used for meta file management
Fileserver takes care about actual files upload/download
Instead of using Flask/Django etc., we have decided to
implements such functionality in Nginx with Lua scripting
Nginx and Lua
Nginx is a free, open-source, high-performance HTTP server
and reverse proxy, as well as an IMAP/POP3 proxy server.
Lua is a powerful, efficient, lightweight, embeddable
scripting language. It supports procedural programming,
object-oriented programming, functional programming,
data-driven programming, and data description.
The Lua module for Nginx ngx_http_lua_module embeds Lua into
Nginx and by leveraging Nginx's subrequests, allows the
integration of Lua threads into the Nginx event model.
Nginx and Lua
Nginx object is available in Lua as ngx and server variables
are accessible as ngx.var.{name}, requested GET arguments -
as ngx.var.arg_{name}, but unfortunately POST variables Nginx
doesn't parse into variables automatically.
The various *_by_lua_* configuration directives serve as
gateways to the Lua API within the nginx.conf. We use
*_by_lua_block to embed Lua code into Nginx as the most
notable way to do so.
Nginx and Lua
header_filter_by_lua_block - uses Lua code to define an
output header filter (e.g. for overriding or adding a
response header).
access_by_lua_block - acts as an access phase handler and
executes Lua code for every request. as with other access
phase handlers, access_by_lua will not run in subrequests.
content_by_lua_block - acts as a "content handler" and
executes Lua code for every request.
Architecture / Upload
location = /_upload {
limit_except POST { deny all; }
if ($http_content_type !~ "multipart/form-data") {
return 406;
}
lua_need_request_body off;
client_body_buffer_size 200M;
client_max_body_size 200M;
default_type 'application/json';
...
}
Architecture / Download
location ~ ^/_download/(.*)$ {
set $path /$1;
default_type 'application/json';
limit_except GET { deny all; }
...
}
User Authentication
function authenticate_user(access_token)
local params = {
method = ngx.HTTP_GET,
args = {
access_token = access_token
}
}
local res = ngx.location.capture(
"/_auth/access/check", params)
if not res or res.status ~= 200 then
return nil
end
return cjson.decode(res.body)
end
access_by_lua_block {
local cjson = require("cjson")
local access_token = ngx.var.arg_access_token
if not access_token then
return_http_forbidden("Forbidden")
end
-- authenticate user
local credentials = authenticate_user(access_token)
-- if user can't be resolved, return 403 Forbidden
if not credentials or not credentials.data.user.id
then
return_http_forbidden("Forbidden")
end
}
Upload
-- Extract POST variables in a streaming way
-- and store file object in a temporary
-- file (form_data.file.filepath)
-- All.other form variables are in form_data
-- (we expect them to be small)
local helpers = require("utils")
form_data, err = helpers.streaming_multipart_form()
if form_data == nil then
return_http_internal_server_error(err)
end
data = {
status = "not_ready",
title = form.title.value,
filename = form.file.filename,
}
local params = {
method = ngx.HTTP_POST,
args = {access_token = access_token},
body = cjson.encode(data)
}
local res = ngx.location.capture("/_api/v1/file", params)
if not res then
return_http_bad_gateway("No metadata")
end
local create_metadata_resp = res.body
if res and res.status ~= 201 then
ngx.status = res.status
ngx.print(create_metadata_resp)
ngx.exit(res.status)
end
local file_metadata = cjson.decode(create_metadata_resp)
Important part to make it work is to specify
client_body_buffer_size equal to
client_max_body_size.
Otherwise, if the client body is bigger than
client_body_buffer_size, the nginx variable
$request_body will be empty.
Upload
local filex = require("pl.file")
local file_fullpath = "/storage/files/" ..
file_metadata.hash .. file_metadata.path
-- ensure that subdirectories exist (if not, create)
-- store tmp file to its permanent position
is_moved, err = filex.move(
form.file.fullpath, file_fullpath)
-- make it available for download
if is_moved then
local params = {
method = ngx.HTTP_PUT,
args = {access_token = access_token},
body = cjson.encode({status = "ready"})
}
ngx.location.capture(
"/_api/v1/file/" .. file_metadata.id, params)
end
-- provide some headers with metadata
ngx.header["X-File-ID"] = file_metadata.id
ngx.status = ngx.HTTP_CREATED
ngx.print(create_metadata_resp)
ngx.exit(ngx.HTTP_CREATED)
Since we need to manage access to the file, we
use access policy of files’ metadata instead of the
files themselves.
We create metadata in any case (“register the
file”), but allow to download it only if file has status
“ready”, meaning it was successfully created at the
specified location.
Download
local search_by_path = {
filters = {
path = ngx.var.path,
status = "ready"
},
size = 1
}
local params = {
method = ngx.HTTP_POST,
args = {access_token = access_token},
body = cjson.encode(search_by_path)
}
local res = ngx.location.capture(
"/_api/v1/search/files", params)
if not res then
return_http_bad_gateway("Search error")
end
local found = cjson.decode(res.body)
if found.total < 1 then
return_http_not_found("File Not Found")
end
local file_metadata = found.results[1]
ngx.header["X-File-Name"] = file_metadata.name
ngx.header["X-File-Path"] = file_metadata.path
ngx.header["X-File-ID"] = file_metadata.id
ngx.req.set_uri(
"/" .. file_metadata.hash .. file_metadata.path)
ngx.exec("@download_file", download_params)
As soon as the file has been found, its metadata
provides us with all necessary information about
the location and we are ready to respond it to the
user.
Sometimes people recommend to read such file in
Lua and return it to the user with ngx.print that is a
bad idea for big files (Lua virtual machine will just
crash).
Download
location @download_file {
internal;
root /storage/files/;
try_files $uri =404;
header_filter_by_lua_block {
ngx.header["Cache-Control"] = "no-cache"
ngx.header["Content-Disposition"] = "attachment; filename="" .. ngx.header["X-File-Name"] .. """
}
}
The @download_file location is quite simple, but additionally we want to play with response headers to provide
a real filename for download (on our filesystem all files are stored with unique generated names).
It is an internal named location (to prevent unauthorized access) that just serves requested static files from the
desired directory.
How to Use
Upload
curl -XPOST https://files.example.com/_upload?access_token={SOME_TOKEN} 
--form file=@/tmp/file.pdf
--form title="Example title"
-H "Content-Type: multipart/form-data"
Download
curl -XGET https://files.example.com/_download/723533/2338342189083057604.pdf?access_token={SOME_TOKEN}
Thank you
Read it in the web:
https://www.datacrucis.com/research/implementing-api-based-
fileserver-with-nginx-and-lua.html

More Related Content

PDF
Food Safety - History & Economic Impact
PDF
biological hazards in food processing
PPTX
Designer foods as advanced nutrition to meet population demandon
PDF
Mycotoxin monitoring program SGS
PPTX
FOOD PACKAGING AND LABELLING (Food Safety and Standards).pptx
PPT
Aesthetic awareness of food
PPT
Halal ingredients in food processing
PDF
Emerging Trends in Food Safety
Food Safety - History & Economic Impact
biological hazards in food processing
Designer foods as advanced nutrition to meet population demandon
Mycotoxin monitoring program SGS
FOOD PACKAGING AND LABELLING (Food Safety and Standards).pptx
Aesthetic awareness of food
Halal ingredients in food processing
Emerging Trends in Food Safety

What's hot (20)

PPTX
Halal vs kosher
PPTX
All about shelflife
PDF
Introduction to Food Proteins
PPTX
Ppt agri.engg362 (bablu)
PDF
Food Receiving process
PDF
High pressure processing .pdf
PDF
Introduction to Kotlin coroutines
PPTX
FOOD SANITATION, SAFETY & HYGIENE - CHAPTER 1
PPTX
Food safety and sanitation management
PPTX
Fruits and Vegetables Processing Technology
PPTX
Food preservation time line 1.1
PPTX
FOOD SAFETY AND QUALITY CONTROL
PPTX
Food Labelling - Full Information about the topic
PPT
Lesson 1 Introduction to Food Safety
PPTX
Impact of Microscopy in the food industry
PPTX
HACCP verification and documentation
PPTX
Health issues with food packaging
PDF
Vinegar production
PDF
Cucumber and Spock Primer
Halal vs kosher
All about shelflife
Introduction to Food Proteins
Ppt agri.engg362 (bablu)
Food Receiving process
High pressure processing .pdf
Introduction to Kotlin coroutines
FOOD SANITATION, SAFETY & HYGIENE - CHAPTER 1
Food safety and sanitation management
Fruits and Vegetables Processing Technology
Food preservation time line 1.1
FOOD SAFETY AND QUALITY CONTROL
Food Labelling - Full Information about the topic
Lesson 1 Introduction to Food Safety
Impact of Microscopy in the food industry
HACCP verification and documentation
Health issues with food packaging
Vinegar production
Cucumber and Spock Primer
Ad

Viewers also liked (20)

PPTX
Pecha Kucha: Ukrainian Food Traditions
PDF
Recurrent Neural Networks. Part 1: Theory
ODP
Open Source Software You Can Use
PDF
14 Skip Lists
PDF
Вероятностные структуры данных
PPTX
Time series predictions using LSTMs
PDF
Bloom filter
PDF
Probabilistic data structures. Part 3. Frequency
PDF
Probabilistic data structures. Part 2. Cardinality
PDF
Probabilistic data structures. Part 4. Similarity
PPTX
Nginx Conference 2016 - Learnings and State of the Industry
PDF
Microservices designing deploying
PPTX
Machine Learning 101
PPTX
Trello介紹&操作說明
PPT
skip list
PDF
Microservices with Spring Cloud
PPTX
New File Server Features Of Windows Server 2008
PDF
Roll Your Own API Management Platform with nginx and Lua
PPTX
Accelerating Your Web Application with NGINX
PDF
Apache Big Data Europe 2015: Selected Talks
Pecha Kucha: Ukrainian Food Traditions
Recurrent Neural Networks. Part 1: Theory
Open Source Software You Can Use
14 Skip Lists
Вероятностные структуры данных
Time series predictions using LSTMs
Bloom filter
Probabilistic data structures. Part 3. Frequency
Probabilistic data structures. Part 2. Cardinality
Probabilistic data structures. Part 4. Similarity
Nginx Conference 2016 - Learnings and State of the Industry
Microservices designing deploying
Machine Learning 101
Trello介紹&操作說明
skip list
Microservices with Spring Cloud
New File Server Features Of Windows Server 2008
Roll Your Own API Management Platform with nginx and Lua
Accelerating Your Web Application with NGINX
Apache Big Data Europe 2015: Selected Talks
Ad

Similar to Implementing a Fileserver with Nginx and Lua (20)

PPT
nodejs_at_a_glance.ppt
PPT
nodejs_at_a_glance, understanding java script
ODT
Language Resource Processing Configuration and Run
PPT
Rapid java backend and api development for mobile devices
KEY
Using and scaling Rack and Rack-based middleware
PDF
Airflow tutorials hands_on
PPTX
Building Web Apps with Express
PDF
JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)
PPTX
Introducing Node.js in an Oracle technology environment (including hands-on)
PPTX
Local SQLite Database with Node for beginners
PPTX
Cloudbase.io MoSync Reload Course
PPT
Frontend Servers and NGINX: What, Where and How
PPTX
Reverse proxies & Inconsistency
PPT
Nginx internals
PDF
WebTalk - Implementing Web Services with a dedicated Java daemon
PPTX
U4-01-Node JS.pptxweasrdtfyhg[]"Piuytrhedfyguhijokpl
PDF
The Magic Revealed: Four Real-World Examples of Using the Client Object Model...
PPTX
Overview of RESTful web services
ODP
Asynchronous I/O in NodeJS - new standard or challenges?
PPTX
Scalable network applications, event-driven - Node JS
nodejs_at_a_glance.ppt
nodejs_at_a_glance, understanding java script
Language Resource Processing Configuration and Run
Rapid java backend and api development for mobile devices
Using and scaling Rack and Rack-based middleware
Airflow tutorials hands_on
Building Web Apps with Express
JDD 2017: Nginx + Lua = OpenResty (Marcin Stożek)
Introducing Node.js in an Oracle technology environment (including hands-on)
Local SQLite Database with Node for beginners
Cloudbase.io MoSync Reload Course
Frontend Servers and NGINX: What, Where and How
Reverse proxies & Inconsistency
Nginx internals
WebTalk - Implementing Web Services with a dedicated Java daemon
U4-01-Node JS.pptxweasrdtfyhg[]"Piuytrhedfyguhijokpl
The Magic Revealed: Four Real-World Examples of Using the Client Object Model...
Overview of RESTful web services
Asynchronous I/O in NodeJS - new standard or challenges?
Scalable network applications, event-driven - Node JS

More from Andrii Gakhov (20)

PDF
Let's start GraphQL: structure, behavior, and architecture
PDF
Exceeding Classical: Probabilistic Data Structures in Data Intensive Applicat...
PDF
Too Much Data? - Just Sample, Just Hash, ...
PDF
DNS Delegation
PDF
Swagger / Quick Start Guide
PDF
API Days Berlin highlights
PDF
ELK - What's new and showcases
PDF
Apache Spark Overview @ ferret
PDF
Data Mining - lecture 8 - 2014
PDF
Data Mining - lecture 7 - 2014
PDF
Data Mining - lecture 6 - 2014
PDF
Data Mining - lecture 5 - 2014
PDF
Data Mining - lecture 4 - 2014
PDF
Data Mining - lecture 3 - 2014
PDF
Decision Theory - lecture 1 (introduction)
PDF
Data Mining - lecture 2 - 2014
PDF
Data Mining - lecture 1 - 2014
PDF
Buzzwords 2014 / Overview / part2
PDF
Buzzwords 2014 / Overview / part1
PDF
Elasticsearch
Let's start GraphQL: structure, behavior, and architecture
Exceeding Classical: Probabilistic Data Structures in Data Intensive Applicat...
Too Much Data? - Just Sample, Just Hash, ...
DNS Delegation
Swagger / Quick Start Guide
API Days Berlin highlights
ELK - What's new and showcases
Apache Spark Overview @ ferret
Data Mining - lecture 8 - 2014
Data Mining - lecture 7 - 2014
Data Mining - lecture 6 - 2014
Data Mining - lecture 5 - 2014
Data Mining - lecture 4 - 2014
Data Mining - lecture 3 - 2014
Decision Theory - lecture 1 (introduction)
Data Mining - lecture 2 - 2014
Data Mining - lecture 1 - 2014
Buzzwords 2014 / Overview / part2
Buzzwords 2014 / Overview / part1
Elasticsearch

Recently uploaded (20)

PDF
NewMind AI Monthly Chronicles - July 2025
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
CIFDAQ's Market Insight: SEC Turns Pro Crypto
PDF
KodekX | Application Modernization Development
PPTX
MYSQL Presentation for SQL database connectivity
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Electronic commerce courselecture one. Pdf
PDF
Encapsulation theory and applications.pdf
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Machine learning based COVID-19 study performance prediction
PPTX
Big Data Technologies - Introduction.pptx
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPTX
Cloud computing and distributed systems.
NewMind AI Monthly Chronicles - July 2025
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
CIFDAQ's Market Insight: SEC Turns Pro Crypto
KodekX | Application Modernization Development
MYSQL Presentation for SQL database connectivity
“AI and Expert System Decision Support & Business Intelligence Systems”
Diabetes mellitus diagnosis method based random forest with bat algorithm
Electronic commerce courselecture one. Pdf
Encapsulation theory and applications.pdf
The AUB Centre for AI in Media Proposal.docx
Advanced methodologies resolving dimensionality complications for autism neur...
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Dropbox Q2 2025 Financial Results & Investor Presentation
Machine learning based COVID-19 study performance prediction
Big Data Technologies - Introduction.pptx
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Digital-Transformation-Roadmap-for-Companies.pptx
Building Integrated photovoltaic BIPV_UPV.pdf
Cloud computing and distributed systems.

Implementing a Fileserver with Nginx and Lua

  • 1. Implementing a Fileserver with Nginx and Lua techtalk@ferret Andrii Gakhov 27.02.2017
  • 2. Current Architecture Restful API server that communicates with clients using application/json Every client is authenticated by a dedicated 3rd party authentication server and authorized by the API server End users get benefits from the API via client-side frontend application
  • 3. The Problem At some point, end users request the ability to manage files using the same client-side application and, consequently, same API. allow to process multipart/form-data requests (that will be proxied from the form on the client-side application) extract and handle file metadata provide file storage and access
  • 4. The Solution Obviously, such functionality is out of the scope for the API and the natural decision is to split it across 2 applications: API is used for meta file management Fileserver takes care about actual files upload/download Instead of using Flask/Django etc., we have decided to implements such functionality in Nginx with Lua scripting
  • 5. Nginx and Lua Nginx is a free, open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server. Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description. The Lua module for Nginx ngx_http_lua_module embeds Lua into Nginx and by leveraging Nginx's subrequests, allows the integration of Lua threads into the Nginx event model.
  • 6. Nginx and Lua Nginx object is available in Lua as ngx and server variables are accessible as ngx.var.{name}, requested GET arguments - as ngx.var.arg_{name}, but unfortunately POST variables Nginx doesn't parse into variables automatically. The various *_by_lua_* configuration directives serve as gateways to the Lua API within the nginx.conf. We use *_by_lua_block to embed Lua code into Nginx as the most notable way to do so.
  • 7. Nginx and Lua header_filter_by_lua_block - uses Lua code to define an output header filter (e.g. for overriding or adding a response header). access_by_lua_block - acts as an access phase handler and executes Lua code for every request. as with other access phase handlers, access_by_lua will not run in subrequests. content_by_lua_block - acts as a "content handler" and executes Lua code for every request.
  • 8. Architecture / Upload location = /_upload { limit_except POST { deny all; } if ($http_content_type !~ "multipart/form-data") { return 406; } lua_need_request_body off; client_body_buffer_size 200M; client_max_body_size 200M; default_type 'application/json'; ... }
  • 9. Architecture / Download location ~ ^/_download/(.*)$ { set $path /$1; default_type 'application/json'; limit_except GET { deny all; } ... }
  • 10. User Authentication function authenticate_user(access_token) local params = { method = ngx.HTTP_GET, args = { access_token = access_token } } local res = ngx.location.capture( "/_auth/access/check", params) if not res or res.status ~= 200 then return nil end return cjson.decode(res.body) end access_by_lua_block { local cjson = require("cjson") local access_token = ngx.var.arg_access_token if not access_token then return_http_forbidden("Forbidden") end -- authenticate user local credentials = authenticate_user(access_token) -- if user can't be resolved, return 403 Forbidden if not credentials or not credentials.data.user.id then return_http_forbidden("Forbidden") end }
  • 11. Upload -- Extract POST variables in a streaming way -- and store file object in a temporary -- file (form_data.file.filepath) -- All.other form variables are in form_data -- (we expect them to be small) local helpers = require("utils") form_data, err = helpers.streaming_multipart_form() if form_data == nil then return_http_internal_server_error(err) end data = { status = "not_ready", title = form.title.value, filename = form.file.filename, } local params = { method = ngx.HTTP_POST, args = {access_token = access_token}, body = cjson.encode(data) } local res = ngx.location.capture("/_api/v1/file", params) if not res then return_http_bad_gateway("No metadata") end local create_metadata_resp = res.body if res and res.status ~= 201 then ngx.status = res.status ngx.print(create_metadata_resp) ngx.exit(res.status) end local file_metadata = cjson.decode(create_metadata_resp) Important part to make it work is to specify client_body_buffer_size equal to client_max_body_size. Otherwise, if the client body is bigger than client_body_buffer_size, the nginx variable $request_body will be empty.
  • 12. Upload local filex = require("pl.file") local file_fullpath = "/storage/files/" .. file_metadata.hash .. file_metadata.path -- ensure that subdirectories exist (if not, create) -- store tmp file to its permanent position is_moved, err = filex.move( form.file.fullpath, file_fullpath) -- make it available for download if is_moved then local params = { method = ngx.HTTP_PUT, args = {access_token = access_token}, body = cjson.encode({status = "ready"}) } ngx.location.capture( "/_api/v1/file/" .. file_metadata.id, params) end -- provide some headers with metadata ngx.header["X-File-ID"] = file_metadata.id ngx.status = ngx.HTTP_CREATED ngx.print(create_metadata_resp) ngx.exit(ngx.HTTP_CREATED) Since we need to manage access to the file, we use access policy of files’ metadata instead of the files themselves. We create metadata in any case (“register the file”), but allow to download it only if file has status “ready”, meaning it was successfully created at the specified location.
  • 13. Download local search_by_path = { filters = { path = ngx.var.path, status = "ready" }, size = 1 } local params = { method = ngx.HTTP_POST, args = {access_token = access_token}, body = cjson.encode(search_by_path) } local res = ngx.location.capture( "/_api/v1/search/files", params) if not res then return_http_bad_gateway("Search error") end local found = cjson.decode(res.body) if found.total < 1 then return_http_not_found("File Not Found") end local file_metadata = found.results[1] ngx.header["X-File-Name"] = file_metadata.name ngx.header["X-File-Path"] = file_metadata.path ngx.header["X-File-ID"] = file_metadata.id ngx.req.set_uri( "/" .. file_metadata.hash .. file_metadata.path) ngx.exec("@download_file", download_params) As soon as the file has been found, its metadata provides us with all necessary information about the location and we are ready to respond it to the user. Sometimes people recommend to read such file in Lua and return it to the user with ngx.print that is a bad idea for big files (Lua virtual machine will just crash).
  • 14. Download location @download_file { internal; root /storage/files/; try_files $uri =404; header_filter_by_lua_block { ngx.header["Cache-Control"] = "no-cache" ngx.header["Content-Disposition"] = "attachment; filename="" .. ngx.header["X-File-Name"] .. """ } } The @download_file location is quite simple, but additionally we want to play with response headers to provide a real filename for download (on our filesystem all files are stored with unique generated names). It is an internal named location (to prevent unauthorized access) that just serves requested static files from the desired directory.
  • 15. How to Use Upload curl -XPOST https://files.example.com/_upload?access_token={SOME_TOKEN} --form file=@/tmp/file.pdf --form title="Example title" -H "Content-Type: multipart/form-data" Download curl -XGET https://files.example.com/_download/723533/2338342189083057604.pdf?access_token={SOME_TOKEN}
  • 16. Thank you Read it in the web: https://www.datacrucis.com/research/implementing-api-based- fileserver-with-nginx-and-lua.html