Functional programming with Elixir

Big Brother

Listing 5 shows the proxy configuration (Figure 1). It resides in the mix.exs file in the htdist project directory. In contrast to the HTTP server, the proxy does without Plug and uses the Httpoison module (line 18) to forward HTTP requests to the web server on the local network. Additionally, the configuration starting in line 11 has an application callback, which runs application() when the application is executed. The mod: { Htdist.Supervisor, []} (line 12) statement in the return value of the function in turn calls start() (Listing 6, line 5) from the Htdist.Supervisor module.

Listing 5

htdist/mix.exs (Proxy Configuration)

 

Listing 6

htdist/lib/httpd.supervisor.ex (Supervisor)

 

Elixir achieves fault tolerance and scalability through its process model. Lightweight Elixir threads handle the work. If an error occurs in a thread, Elixir terminates the thread and not the application. The error message is typically handled by a supervisor that runs within the application process. The supervisor is also responsible for restarting the thread. Listing 6 shows the code for the supervisor from the Htdist.Supervisor module, which is enabled in line 12 of Listing 5 when the system starts.

The Application macro (line 2) promotes the module to an application; Supervisor (line 3) integrates additional functionality. The start_link() function in line 6 wakes up the supervisor. The first parameter in the call hands over the supervisor's name, which is stored in the __MODULE__ variable.

Once activated, the supervisor in turn calls the init() function (line 9). From within its function body, supervise() in line 10 starts the proxy (Listing 7), which runs in its own thread and is monitored by the supervisor.

Listing 7

htdist/lib/htdist.ex (Proxy Module)

 

If the proxy were to terminate with an error, the supervisor would simply dust it down, and get it running again "one for one."

Controls

When Elixir loads a module, the supervisor by default calls the start_link() function (Listing 7, line 2). In the next line, the compile() function creates a router by calling the Cowboy_router module. It stores a router in the dispatch variable. In this case, it fields HTTP requests much like the plugs :match and :dispatch from Listing 4.

The router picks up its configuration from the list at the end of line 3. It uses the configuration to evaluate requests for various IP addresses and port numbers. The pattern :_ in the first tuple matches any conceivable IP address and port number, and the /[...] pattern matches any URL. Finally, line 3 calls the Distr module. In line 4 of Listing 7, start_http() fires up the web server. Thanks to the dispatch variable assigned at the start, the router ends up in the parameter list along with its IP address and port number.

The Distr module (Listing 8) becomes active when called by line 3 in Listing 7. It creates the HTTP response in the style of a load balancer. The module's init() and terminate() functions are mandatory, but the handle() callback function takes care of the HTTP requests (lines 10-18). For each HTTP request that arrives, the function stores the request object in the req variable and the request processing status in state.

Listing 8

htdist/lib/distr.ex (Load Balancer)

 

Line 11 copies the requested URL to the request object and stores the value in the url variable. Line 12 forwards the HTTP request to a local HTTP server. To do so, it calls request() from the Httpoison module; the proxy restricts itself to getting requests (:get).

The next function called, next_url(), resides in lines 20-23. It translates the URL of the HTTP request to an address on the local network. In line 21, the parse() function from the standard URI module breaks down the incoming URL into its address components. The next line reproduces the URL based on the address components, but without the IP component.

The next_host() function (lines 25-29) then replaces the IP component with the IP address of a HTTP server on the local network; the server is chosen randomly. This choice is handled by a pseudo-random-number generator, which is seeded in line 26. In the following line, the next variable accepts the header of the shuffled list of target addresses. Line 28 then returns the result, which is then evaluated in lines 13 and 14. These lines push the response to the HTTP request through a case distinguisher with two possible results.

If successful, the pattern matches the first case. In response to this, line 13 copies the status code, the body, and all the other metadata from the HTTP response to the three variables code, body, and headers (line 12). In case of an error, the code in line 14 assigns constant values to the three variables – again, the _ operator plays a prominent role.

Based on the three variables, the call to the reply() function in line 16 finally creates an HTTP response and stores the response in the reply variable, which returns the next line along with the :ok literal and the state to the web server as a response.

Launch the proxy application by again calling a shell and loading the dependencies; then, build the application before typing the iex command:

mix deps.get
mix deps.compile
sudo iex -S mix

To complete the application illustrated in Figure 1, you then need to open another shell and initialize an additional HTTP server with the new IP address 127.0.0.4. To do so, enter the following:

cd httpd
sudo iex -S mix
Plug.Adapters.Cowboy.http Httpd, [], \
  ip: {127, 0, 0, 4}, port: 80

Figure 5 shows the load balancer on the right in a practical application on Ubuntu 14.04. When http://127.0.0.2/info is requested using the Firefox browser, the application responds with the reply from Figure 4. However, the value of the host field varies randomly between the IP addresses of the two web servers.

Figure 5: The two shells top left launch the web server; the one at the bottom launches the proxy. You can see the load balancer in action on the right.

Conclusions

Elixir can conveniently use the Erlang ecosystem. At the same time, Elixir dumps the muddle of academic concepts that typically make functional languages inaccessible to mere mortals. This means that programmers can develop easily maintainable and fault-tolerant applications quickly. Moreover, macros help extend Elixir's language set and simplify the program code. This is pretty much what the future of programming might come to be.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Elixer

    The Elixir programming language on a Raspberry Pi lets you create distributed projects in just a few lines of code.

  • Crystal

    Crystal is an open source project that seeks to combine the best of two worlds: the simplicity of a language syntax similar to Ruby and the speed and capabilities of the LLVM platform.

  • Secure Online Passwords

    Securely storing passwords online can be a complex task. With a few tools, websites can offer better security, but users still need to choose their passwords wisely.

  • Nginx

    The fast and practical Nginx web server is easy to configure and extend.

  • Docker with OwnCloud

    Run your application smoothly and portably in the cloud with the Docker container system. This workshop takes a practical look deploying Docker with the OwnCloud cloud environment.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News