Can HAProxy work as web server without other web server (e.g. Apache, NGINX) behind it? I was told for such configuration but I am not sure about the accuracy of the information.
3 Answers
No, HAProxy is not a web server and cannot act like one. From the HAProxy Starter Guide, What HAProxy is and is not:
HAProxy is not a web server:
During startup, it isolates itself inside a chroot jail and drops its privileges, so that it will not perform any single file-system access once started. As such it cannot be turned into a web server. There are excellent open-source software for this such as Apache or Nginx, and HAProxy can be installed in front of them to provide load balancing and high availability.
The accepted answer of "no, it is not designed to do that" is correct.
But software being what it is, if you DO still want to do it, there is a hacky workaround.
You need an ACL pointing the request to a custom backend. For this example lets say you wanted to serve robots.txt
frontend port80
acl is_robotstxt path /robots.txt
use_backend robots if is_robotstxt
backend robots
mode http
errorfile 503 /etc/haproxy/errors/robots.http
Note that there are no servers defined for this backend, so when /robots.txt is requested and haproxy uses it, it serves a 503 error. We specify that 503 errors should serve /etc/haproxy/errors/robots.http, which luckily specifies the full HTTP output headers and all.
So in that file, we put
HTTP/1.0 200 Found
Cache-Control: no-cache
Connection: close
Content-Type: text/plain
Content here
so now what should be a 503 error is actually a 200 Found reply, content and all.
Note that doing this is not really recommended and comes with many limitations. Most obvious is that you can only serve one file this way per backend. Less obvious is that this file must fit inside haproxy's BUFSIZE, which is usually 8 or 16kB. Haproxy also is not doing any kind of sanitization on the file served this way, so its on you to serve the right headers the right way. If your clients need \r\n instead of just \n, thats on you to handle.
You're much better off just setting up a copy of your webserver of choice and just having a "static asset backend" haproxy can route to.
first of all - and this is important - HA-Proxy is not a webserver. This should be abundantly clear by now. However, there are ways to serve files via LUA. I came up with the following solution:
HA-Proxy configuration:
lua-load /etc/haproxy/lua/load-file.lua
...
backend lua-load-file
http-request set-header X-LUA-LOADFILE-DOCROOT /etc/haproxy/docroot
http-request use-service lua.load-file
As you can see I load a lua-file called load-file.lua. The contents of that file are as follows:
core.register_service("load-file", "http", function(applet)
local docroot
local location
local file
local retval
local response
local extension
if(applet.path == nil or applet.headers["x-lua-loadfile-docroot"] == nil or applet.headers["x-lua-loadfile-docroot"][0] == "") then
retval = 500
response = "Internal Server Error"
else
docroot = applet.headers["x-lua-loadfile-docroot"][0]
location = applet.path
if(location == "" or location == "/") then
location = "/index.html"
end
file = io.open(docroot .. location, "r")
if(file == nil) then
retval = 404
response = "File Not Found"
else
retval = 200
response = file:read("*all")
file:close()
end
end
extension = string.match(location, ".(%w+)$")
if extension == "css" then applet:add_header("content-type", "text/css")
elseif extension == "gif" then applet:add_header("content-type", "image/gif")
elseif extension == "htm" then applet:add_header("content-type", "text/html")
elseif extension == "html" then applet:add_header("content-type", "text/html")
elseif extension == "ico" then applet:add_header("content-type", "image/x-icon")
elseif extension == "jpg" then applet:add_header("content-type", "image/jpeg")
elseif extension == "jpeg" then applet:add_header("content-type", "image/jpeg")
elseif extension == "js" then applet:add_header("content-type", "application/javascript; charset=UTF-8")
elseif extension == "json" then applet:add_header("content-type", "application/json")
elseif extension == "mpeg" then applet:add_header("content-type", "video/mpeg")
elseif extension == "png" then applet:add_header("content-type", "image/png")
elseif extension == "txt" then applet:add_header("content-type", "text/plain")
elseif extension == "xml" then applet:add_header("content-type", "application/xml")
elseif extension == "zip" then applet:add_header("content-type", "application/zip")
end
applet:set_status(retval)
if(response ~= nil and response ~= "") then
applet:add_header("content-length", string.len(response))
end
applet:start_response()
applet:send(response)
end)
Basically, this code reads files from a specified document-root location in the filesystem based on the query string and generates appropriate HTTP-responses just as a normal webserver would do. That is why the script needs a document-root do be configured via the HTTP-header X-LUA-LOADFILE-DOCROOT. This header is being set via the haproxy configuration as shown in the examples. Furthermore, the code also does some very basic mimetype handling - extend the list according your needs. I hope someone finds this useful.
cheers!
-
This stops HaProxy from working. According to haproxy's LUA documentation blocking actions like
io.*
are only allowed incore.register_init
. The trick is to split it into 2 parts: First, in initialization phase, read all files into LUA. But respect the memory limit. Second, in service phase, serve the files from memory. (Note that I'd downvote this, but do not have enough reputation yet.)– TinoCommented Jun 16, 2019 at 8:50