Welcome back to our Mongoose Embedded Web Server examples blog post series! In this edition, we will look at an HTTP load balancer. This is a fairly advanced example that will showcase Mongoose usage as both a server and a client.
As always, you can download Mongoose Embedded Web Server and work through the example yourself.
An HTTP load balancer is a piece of serving infrastructure; an additional stage between a user’s browser and a server that is introduced when there are multiple servers to choose from. Which one should be used to handle client’s request? Load balancer takes the user’s request, makes the decision and forwards it to one of the servers. These are commonly called backends. More advanced load balancers will keep track of the load of the backends and pick the least loaded, but our example load balancer is very basic: it will just alternate between the configured backends. We have two backends configured, the first request will go to backend 1, the second will go to backend 2, the third will go to backend 1 again and so forth.
So let’s test it first and then look take a look under the hood. For our example we will need the balancer itself and backends. We will use a restful_server as the backend. We’ll need at least two to see balancing going on, so let’s compile and start two on different ports:
mongoose/examples/restful_server$ make cc restful_server.c ../../mongoose.c -o restful_server -g -W -Wall -I../.. -Wno-unused-function -DMG_ENABLE_THREADS -DMG_DISABLE_HTTP_WEBSOCKET -lpthread mongoose/examples/restful_server$ ./restful_server -p 8910 Starting RESTful server on port 8910, serving .
In another terminal window - ran simultaneously:
mongoose/examples/restful_server$ ./restful_server -p 8911 Starting RESTful server on port 8910, serving .
Now compile and run the load_balancer example:
mongoose/examples/load_balancer master$ make cc load_balancer.c ../../mongoose.c -o load_balancer -W -Wall -pthread rojer@nbt:~/go/src/cesanta.com/mongoose/examples/load_balancer master$ ./load_balancer -p 8000 -l - -b / 127.0.0.1:8910 -b / 127.0.0.1:8911 Adding backend for / : 127.0.0.1:8910 [redirect=0,prefix_replacement=/] Adding backend for / : 127.0.0.1:8911 [redirect=0,prefix_replacement=/] Starting LB on port 8000
Arguments are documented in the README file, but briefly: -p 8000 tells it to listen for incoming connection on port 8000, -l - sets the log file to “-”, which is a special value for stdout and the two -b options add our backends.
With that done, you should see the same index page when you point your browser to http://127.0.0.1:8000/. But, each time you reload, the page will be served by a different instance of the backend, and you will see records in the balancer log:
1465822060 GET / backend=127.0.0.1:8910 1465822061 GET / backend=127.0.0.1:8911 1465822062 GET / backend=127.0.0.1:8910
Under the Hood
Now, let’s check out how it works internally. There are a few hundred lines of code in the main file, but as usual, most of the action happens in the event handler (ev_handler). When the client’s request arrives (ev == MG_EV_HTTP_REQUEST), it picks a backend to use, connects to it and forwards the client’s request. When the server responds (ev == MG_EV_HTTP_REPLY), the response is forwarded to the client’s connection. The rest of the code is error handling, dealing with HTTP keep-alive and idle backends.
That’s it for now, we hope this helps you get an idea of how to combine serving and making HTTP requests in the same program.