## 4 Servers

This section describes a simple and powerful way of programming client-server applications. Client-server applications are programmed using the `gen_server` behaviour.

Refer to the Reference Manual , the module `gen_server` in `stdlib`, for full details of the behaviour interface.

### 4.1 Client-Server Principles

This section describes several solutions to one sample problem in order to illustrate how to write client-server applications.

The sample problem is a very simple server which acts as a Home Location Register (HLR). We will implement a small sub-set of an HLR which we call VSHLR (Very Simple HLR) in a number of different ways. The Erlang modules which implement our VSHLR will always be called something like `vshlr_XX`. All these modules will export the following functions:

- `vshlr_XX:start() -> true` starts the server.
- `vshlr_XX:stop() -> true` stops the server.
- `vshlr_XX:i_am_at(Person, Position) -> ok` tells the server that `Person` is at the location `Position`.
- `vshrl_XX:find(Person) -> {at, Position} | lost` asks the server where `Person` is. The server responds `{at, Position}`, where `Position` is the last reported location, or `lost` if it does not know where the person is.

The client-server model can be illustrated in the following figure:

The Client-Server Model

The client-server model is characterized by a central server and an arbitrary number of clients. The client-server model is generally used for resource management operations, where several different clients want to share a common resource. The server is responsible for managing this resource.

If we ignore how the server is started and stopped, and ignore all error cases, then it is possible to describe the server by means of a simple function `f`.

Suppose that the internal state of the server is described by the state variable `S` and that the server receives a query `Q`. The server responds by sending a reply `R` back to the client and changes its internal state to `S'`. This can be described as follows:

    {R, S'} = f(Q, S)

Given a function `f`, we can write a very simple universal client server as follows:

    -module(server).
    -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
    -vsn('$Revision: /main/release/2 $').

    -export([start/3, stop/1, loop/3, call/2]).

    start(Name, F, State) ->
        register(Name, spawn(server, loop, [Name, F, State])).

    stop(Name) ->
        exit(whereis(Name), kill).

    call(Name, Query) ->
        Name ! {self(), Query},
        receive
            {Name, Reply} ->
                Reply
        end.

    loop(Name, F, State) ->
        receive
            {Pid, Query} ->
                {Reply, State1} = F(Query, State),
                Pid ! {Name, Reply},
                loop(Name, F, State1)
        end.

`vshlr` can be written using `server`:

    -module(vshlr_1).
    -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
    -vsn('$Revision: /main/release/2 $').

    -export([start/0, stop/0, i_am_at/2, find/1, handle_event/2]).

    start() -> server:start(xx1,
                            fun(Event, State) ->
                                handle_event(Event, State)
                            end,
                            []).

    stop() -> server:stop(xx1).

    i_am_at(Person, Position) -> server:call(xx1, {i_am_at, Person, Position}).
    find(Person)              -> server:call(xx1, {find, Person}).

    handle_event({i_am_at, Person, Position}, State) ->
        State1 = update_position(Person, Position, State),
        {ok, State1};
    handle_event({find, Person}, State) ->
        Location = lookup(Person, State),
        {Location, State}.

    update_position(Key, Value, [{Key, _}|T]) -> [{Key, Value}|T];
    update_position(Key, Value, [H|T])        -> [H|update_position(Key, Value, T)];
    update_position(Key, Value, [])           -> [{Key,Value}].

    lookup(Key, [{Key, Value}|_]) -> {at, Value};
    lookup(Key, [_|T])            -> lookup(Key, T);
    lookup(Key, [])               -> lost.

We can run this as follows:

    1 > vshlr_1:start().
    true
    2> vshlr_1:i_am_at("joe", "home").
    ok
    3> vshlr_1:i_am_at("helen", "work").
    ok
    4> vshlr_1:find("joe").
    {at,"home"}
    5> vshlr_1:find("mike").
    lost
    6> vshlr_1:i_am_at("joe", {building,23}).
    ok
    7> vshlr_1:find("helen").
    {at,"work"}
    8> vshlr_1:find("joe").
    {at,{building,23}}

Even though our VSHLR program is extremely simple, it illustrates and provides simple solutions to a surprisingly large number of design issues.

The reader should note the following:

- The functionality is divided between two different modules. All the code that deals with spawning processes and sending and receiving messages is contained in the module `server`. All the code that has to do with the implementation of the VSHLR is contained in the module `vshrl`. Note also that most of the functions in `vshrl` can be written in a pure, side effect free manner. This division of functionality is good programming practice.
- The code in `server` can be re-used to build many different client-server applications.
- The name of the server, in this example the atom `xx1`, is hidden from the users of the client functions. This means it can be changed without effecting the code that uses the client functions. This point has important consequences for writing distributed systems. Essentially, we can develop programs as non-distributed applications and then turn them into distributed applications by making very small changes to the client stub code. This point will be covered in more detail later.
- We hide the details of the remote procedure call inside the `server` module. This means that we can change how we do the remote procedure call at a later stage. This has consequences for error handling and the recovery from failures which may occur during a remote procedure call.
- We hide the details of the protocol used between the client and the server inside the `server` module. This is good programming practice and allows us to change the protocols without having to make any changes to the functions which use the server.

#### 4.1.1 Extending the Server

Splitting a server into two parts means that we can work on either of the parts without effecting the other. We can illustrate this by extending the server so that it logs the last ten requests and calls the error logger if something goes wrong. This version is called `server1` to distinguish it from `server`.

    -module(server1).
    -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
    -vsn('$Revision: /main/release/2 $').

    -export([start/3, stop/1, loop/4, call/2]).

    start(Name, F, State) ->
        register(Name, spawn(server1, loop, [Name, F, State, []])).

    stop(Name) ->
        exit(whereis(Name), kill).

    call(Name, Query) ->
        Name ! {self(), Query},
        receive
          {Name, error} ->
            exit(server_error);
          {Name, {ok, Reply}} ->
            Reply
        end.

    loop(Name, F, State, Buff) ->
        receive
          {Pid, Query} ->
            Buff1 = trim([Query|Buff]),
            case catch F(Query, State) of
              {'EXIT', Why} ->
                Pid ! {Name, error},
                error_logger:error_msg({server_error, Name, Buff1});
              {Reply, State1} ->
                Pid ! {Name, {ok, Reply}},
                loop(Name, F, State1, Buff1)
            end
        end.

    trim([X1,X2,X3,X4,X5,X6,X7,X8,X9,X10|_]) -> [X1,X2,X3,X4,X5,X6,X7,X8,X9,X10];
    trim(X) -> X.

`server1` has exactly the same function interface as the previous version of `server`.

If we use `server1` together with `vshlr`, we get an improved version of `vshlr`, which has additional error handling facilities.

The improvement to VSHLR was made without any significant change to the code in the module `vshlr`. This is a consequence of dividing the server into two parts, the generic part which is common to all servers, and the specific part which concerns the VSHLR problem.

#### 4.1.2 The Generic Server

The examples shown in the previous sections make it apparent that the server can be extended in a number of different ways. The module `gen_server` provides a number of useful extensions to our simple server. In the following example, `vshrl` is re-implemented using `gen_server`.

The Reference Manual, `stdlib`, module `gen_server` has detailed information about the generic server.

    -module(vshlr_2).
    -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
    -vsn('$Revision: /main/release/2 $').

    -export([start/0, start_link/0, stop/0, i_am_at/2, find/1]).

    -behaviour(gen_server).

    -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).

    %% These are the interface routines

    start() -> gen_server:start({local, xx2}, vshlr_2, [], []).
    start_link() -> gen_server:start_link({local, xx2}, vshlr_2, [], []).

    stop()  -> gen_server:call(xx2, die, 10000).

    i_am_at(Person, Location) ->
        gen_server:call(xx2, {i_am_at, Person, Location}, 10000).

    find(Person)  ->
        gen_server:call(xx2, {find, Person}, 10000).

    %% These Routine MUST be exported since they are called by gen_server

    init(_) -> {ok, []}.

    handle_call({i_am_at, Person, Location}, _, State) ->
        State1 = update_location(Person, Location, State),
        {reply, ok, State1};
    handle_call({find, Person}, _, State) ->
        Location = lookup(Person, State),
        {reply, Location, State};
    handle_call(die, _, State) ->
        %% ok goes back to the user and terminate(normal, State)
        %% will be called
        {stop,  normal, ok, State}.

    handle_cast(Request, State) -> {noreply, State}.

    handle_info(Request, State) -> {noreply, State}.

    terminate(Reason, State) ->
        ok.

    %% sub-functions

    update_location(Key, Value, [{Key, _}|T]) -> [{Key, Value}|T];
    update_location(Key, Value, [H|T])        -> [H|update_location(Key, Value, T)];
    update_location(Key, Value, [])           -> [{Key,Value}].

    lookup(Key, [{Key, Value}|_]) -> {at, Value};
    lookup(Key, [_|T])            -> lookup(Key, T);
    lookup(Key, [])               -> lost.

The flow of control in the example shown above is as follows:

- Start a new server by evaluation the expression `gen_server:start({local, xx2}, vshlr_2, Args, Opts).`
  This expression starts a local server with name `xx2` on the local node. The handler module `vshlr_2` is called to initialize the server.
  The generic server calls `vshlr_2:init(Args)` which is expected to return `{ok, S}`. The value of `S` is used as the initial value of the state of the server.
- The client routines use the following type of calls:

      gen_server:call(xx2, {i_am_at, Person, Location}, 10000)

  For communication purposes, `xx2` is the name of the server and must agree with the name used to start the server. `{i_am_at, Person, Location}` is a command which is passed to the server, and 10000 is a timeout value. If the server does not respond within 10000 milliseconds, the call to the server is aborted.
  The previous call corresponds to the clause:

      handle_call({i_am_at, Person, Location}, _, State) ->
          State1 = update_Location(Person, Location, State),
          {reply, ok, State1};

  `handle_call` returns a tuple of the form`{reply, Reply, State1}`. In this tuple, `Reply` is the reply which should be sent back to the client, and `State1` is a new value for the state of the server.
- Stop the server by evaluating `gen_server:call(xx2, die, 10000).`
  This matches the following expression:

      handle_call(die, _, State) ->
          {stop, normal, ok, State}.

  The return value tells the server to stop. The server first evaluates `vshlr_2:terminate(normal, State)`. The reply, which is `ok` in this example, is passed back to the client and the server stops.

#### 4.1.3 Local Client-Server

The example shown in the previous section was a local server. The main points to note were:

- `gen_server:start({local, xx2}, ...)` starts the server.
- `gen_server:call(xx2, ...)` calls the server.

#### 4.1.4 Global Client-Server

To make a global server, the following small changes are made to the access routines:

- `gen_server:start({global, xx2}, ...)` starts the server.
- `gen_server:call({global, xx2}, ...)` calls the server.

With these changes, the client-server model will work in a network of distributed nodes. All nodes in the system are assumed to evaluate identical copies of the code. The server will be placed on the first node which evaluates `gen_server:start`. All other nodes will be coupled to this node automatically.

#### 4.1.5 Anonymous Server

The following calls will start an anonymous server:

- `gen_server:start(Mod, ...) -> {ok, Pid}` starts an anonymous server. All calls to the server must include an explicit reference to the Pid of the server.
- `gen_server:call(Pid, ...)` calls the server.

The user must ensure that the Pid of the server is communicated to all clients which make use of the server.

### 4.2 Notes

- The server can be started with `gen_server:start`, or `gen_server:start_link`. In the case of `start_link`, the server is linked to the process which started the server.
- The `start_link` function must be used if the server is supervised by a supervisor.
- The server does not normally trap exits and will die if it receives an exit signal. If you wish the server to trap exits then you should evaluate `process_flag(trap_exit, true)` in `init/1` before returning `{ok, State}`.
- This section has only described the remote procedure call interface to the server. You can also send a cast to the server. In a cast, the client sends a message to the server and continues since no reply is sent by the server.
- The server can also handle exit messages and information requests from the management system. Refer to the Reference Manual, `stdlib` for more information.

Copyright © 1991-2002 Ericsson Utvecklings AB