How to write a simple webserver in Erlang? - erlang

Using the default Erlang installation what is the minimum code needed to produce a "Hello world" producing web server?

Taking "produce" literally, here is a pretty small one. It doesn't even read the request (but does fork on every request, so it's not as minimal possible).
-module(hello).
-export([start/1]).
start(Port) ->
spawn(fun () -> {ok, Sock} = gen_tcp:listen(Port, [{active, false}]),
loop(Sock) end).
loop(Sock) ->
{ok, Conn} = gen_tcp:accept(Sock),
Handler = spawn(fun () -> handle(Conn) end),
gen_tcp:controlling_process(Conn, Handler),
loop(Sock).
handle(Conn) ->
gen_tcp:send(Conn, response("Hello World")),
gen_tcp:close(Conn).
response(Str) ->
B = iolist_to_binary(Str),
iolist_to_binary(
io_lib:fwrite(
"HTTP/1.0 200 OK\nContent-Type: text/html\nContent-Length: ~p\n\n~s",
[size(B), B])).

For a web server using only the built in libraries check out inets http_server.
When in need of some more power but still with simplicity you should check out the mochiweb library. You can google for loads of example code.

Do you actually want to write a web server in Erlang, or do you want an Erlang web server so that you can create dynamic web content using Erlang?
If the latter, try YAWS. If the former, have a look at the YAWS source code for inspiration

Another way, similar to the gen_tcp example above but with less code and already offered as a suggestion, is using the inets library.
%%%
%%% A simple "Hello, world" server in the Erlang.
%%%
-module(hello_erlang).
-export([
main/1,
run_server/0,
start/0
]).
main(_) ->
start(),
receive
stop -> ok
end.
run_server() ->
ok = inets:start(),
{ok, _} = inets:start(httpd, [
{port, 0},
{server_name, "hello_erlang"},
{server_root, "/tmp"},
{document_root, "/tmp"},
{bind_address, "localhost"}
]).
start() -> run_server().
Keep in mind, this exposes your /tmp directory.
To run, simply:
$ escript ./hello_erlang.erl

For a very easy to use webserver for building restful apps or such check out the gen_webserver behaviour: http://github.com/martinjlogan/gen_web_server.

Just one fix for Felix's answer and it addresses the issues Martin is seeing. Before closing a socket, all data being sent from the client should be received (using for example do_recv from gen_tcp description).
Otherwise there's a race condition for the browser/proxy sending the HTTP request being quick enough to send the http request before the socket is closed.

Related

Erlang OTP UDP Server

Here is a simple UDP server:
-module(kvstore_udpserver).
-author("mylesmcdonnell").
%% API
-export([start/0]).
start() ->
spawn(fun() -> server(2346) end).
server(Port) ->
{ok, Socket} = gen_udp:open(Port, [binary]),
loop(Socket).
loop(Socket) ->
receive
{udp, Socket, Host, Port, Bin} ->
case binary_to_term(Bin) of
{store, Value} ->
io:format("kvstore_udpserver:{store, Value}~n"),
gen_udp:send(Socket,Host,Port,term_to_binary(kvstore:store(Value)));
{retrieve, Key} ->
io:format("kvstore_udpserver:{retrieve, Value}~n"),
gen_udp:send(Socket,Host,Port,term_to_binary(kvstore:retrieve(Key)))
end,
loop(Socket)
end.
How can I restructure this so that
a) It, or at least the relevant part of it, is a gen_server so that I can add to the supervision tree
b) increase concurrency by handling each message in a separate process.
I have reimplemented the sockserv example from Learn You Some Erlang for my TCP server but I'm struggling to determine a similar model for UDP.
For a):
You need to delcare the gen_server behaviour and implement all the callback functions (this is obvious, but it's worth calling out explicitly). If you have rebar installed, you can use the command rebar create template=simplesrv srvid=your_server_name to add the boilerplate functions.
You'd probably want to move the server starting business logic (the gen_udp:open/2 call) to your server's init/1 function. (The init is required by the gen_server behaviour. You can also start your loop/1 function there.
You'd probably want to make sure the udp server is closed by the module's terminate/2 function.
Move the business logic for handling requests that come in from parsing the messages to your loop/1 function into handle_call/3 or handle_cast/2 in your module (see below).
For b):
You have a few options, but basically, whenever you receive a message, you can use gen_server:cast/2 (if you don't care about the response) or gen_server:call/2,3 if you do. The casts or calls will be handled by the handle_cast/2 or handle_call/3 functions in your module.
Casts are inherently non-blocking and the answers to this question have a good design pattern for handling call operations asynchronously in gen_servers. You can crib from that.

Best way to log access to web pages

One of my website is using Nitrogen with a Cowboy server.
I would like to log every access to web pages just like Apache does with access.log.
What would be the best way to do that ?
You can use cowboy middlewares https://ninenines.eu/docs/en/cowboy/1.0/guide/middlewares/
Just create a simple log module:
-module(app_web_log).
-behaviour(cowboy_middleware).
-export([execute/2]).
execute(Req, Env) ->
{{Peer, _}, Req2} = cowboy_req:peer(Req),
{Method, Req3} = cowboy_req:method(Req2),
{Path, Req4} = cowboy_req:path(Req3),
error_logger:info_msg("~p: [~p]: ~p ~p", [calendar:universal_time(), Peer, Method, Path]),
{ok, Req4, Env}.
and add it in list of middlwares:
{ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
{env, [{dispatch, Dispatch}]},
{middlewares, [cowboy_router, app_web_log, cowboy_handler]}]).
Try using Nitrogen on top of the Yaws web server instead, since it performs access logging by default.
Each underlying webserver does it differently (or not at all) - this is something simple_bridge does not yet have abstracted.
So in the case of cowboy, you'll likely have to rig it up yourself.
If you're using a newer build of Nitrogen (if you have the file site/src/nitrogen_main_handler.erl), then you can edit that file to manually log yourself. For example, using erlang's error handler, you could add something simple like:
log_request() ->
error_logger:info_msg("~p: [~p]: ~p", [{date(), time()}, wf:peer_ip(), wf:url()]).
run() ->
handlers(),
log_request(), %% <--- insert before wf_core:run()
wf_core:run().
Then whatever happens with the log can be handled by configuring error_logger to write to disk (http://erldocs.com/17.0/kernel/error_logger.html?i=13&search=error_logger#logfile/1)
If you use an older Nitrogen (which would have site/src/nitrogen_cowboy.erl), then you would similarly edit that file, once again before the wf_core:run() call.
Alternatively, your hooks option with cowboy could work as well. I've not worked with them, so you're on your own there :)

httpc + many request in spawn not worked

I tried to use Erlang's httpc module for high concurrent requests
My code for many requests in spawn hasn't worked:
-module(t).
-compile(export_all).
start() ->
ssl:start(),
inets:start( httpc, [{profile, default}] ),
httpc:set_options([{max_sessions, 200}, {pipeline_timeout, 20000}], default),
{ok, Device} = file:open("c:\urls.txt", read),
read_each_line(Device).
read_each_line(Device) ->
case io:get_line(Device, "") of
eof -> file:close(Device);
Line -> go( string:substr(Line, 1,length(Line)-1)),
read_each_line(Device)
end.
go(Url)->
spawn(t,geturl, [Url] ).
geturl(Url)->
UrlHTTP=lists:concat(["http://www.", Url]),
io:format(UrlHTTP),io:format("~n"),
{ok, RequestId}=httpc:request(get,{UrlHTTP,[{"User-Agent", "Opera/9.80 (Windows NT 6.1; U; ru) Presto/2.8.131 Version/11.10"}]}, [],[{sync, false}]),
receive
{http, {RequestId, {_HttpOk, _ResponseHeaders, Body}}} -> io:format("ok"),ok
end.
httpc:request is not received in html Body - if I can use spawn in
go(Url)->
spawn(t,geturl, [Url] ).
http://erlang.org/doc/man/httpc.html
Note
If possible the client will keep its connections alive and use
persistent connections with or without pipeline depending on
configuration and current circumstances. The HTTP/1.1 specification
does not provide a guideline for how many requests would be ideal to
be sent on a persistent connection, this very much depends on the
application. Note that a very long queue of requests may cause a user
perceived delay as earlier requests may take a long time to complete.
The HTTP/1.1 specification does suggest a limit of 2 persistent
connections per server, which is the default value of the max_sessions
option
urls.txt contain different urls - for example
google.com
amazon.com
alibaba.com
...
What's wrong?
Your code never actually starts the httpc service (and inets, the application that it depends on), and the confusion probably comes from the unfortunate overloading of the inets:start/[0,1,2,3] function:
inets:start/[0,1] starts the inets application itself and the httpc service with the default profile (called default).
inets:start/[2,3] (which should be called start_service) starts one of the services that can run atop inets (viz. ftpc, tftp, httpc, httpd) once the inets application has already started.
start() ->
start(Type) -> ok | {error, Reason}
    Starts the Inets application.
start(Service, ServiceConfig) -> {ok, Pid} | {error, Reason}
start(Service, ServiceConfig, How) -> {ok, Pid} | {error, Reason}
    Dynamically starts an Inets service after
the Inets application has been started
    (with inets:start/[0,1]).
So your spawned process simply crashed when trying to call httpc:request/4 as the service itself was not running. To illustrate, inets:start( httpc, [{profile, default}] ) from your start/0 function would fail to start inets and the httpc service:
Eshell V10.7 (abort with ^G)
1> inets:start(httpc, [{profile, default}]).
{error,inets_not_started}
You should check the returned value of application start to track potential problems:
...
ok = ssl:start(),
ok = inets:start(),
...
Or, if the application could be already started, use a function like this:
...
ok = ensure_start(ssl),
ok = ensure_start(inets),
...
ensure_start(M) ->
case M:start() of
ok -> ok;
{error,{already_started,M}} -> ok;
Other -> Other
end.
[edit 2 - small code enhancement]
I have tested this code and it works on my PC. Note that you are using a '' in the string for file access, this is an escape sequence that make the line to fail.
-module(t).
-compile(export_all).
start() -> start(2000).
% `To` is a parameter which is passed to `getUrl`
% to change the timeout value. You can play with
% it to see the request queue effect, and how
% much the response times of each site varies.
%
% The default timeout value is set to 2 seconds.
start(To) ->
ok = ensure_start(ssl),
ok = ensure_start(inets),
ok = httpc:set_options([{max_sessions, 200}, {pipeline_timeout, 20000}], default),
{ok, Device} = file:open("D:/urls.txt", read),
read_each_line(Device,To).
read_each_line(Device,To) ->
case io:get_line(Device, "") of
eof -> file:close(Device);
Line -> go( string:substr(Line, 1,length(Line)-1),To),
read_each_line(Device,To)
end.
go(Url,To)->
spawn(t,geturl, [Url,To] ).
geturl(Url,To)->
UrlHTTP=lists:concat(["http://www.", Url]),
io:format(UrlHTTP), io:format("~n"),
{ok, RequestId}=httpc:request(get,{UrlHTTP,[{"User-Agent", "Opera/9.80 (Windows NT 6.1; U; ru) Presto/2.8.131 Version/11.10"}]}, [],[{sync, false}]),
M = receive
{http, {RequestId, {_HttpOk, _ResponseHeaders, _Body}}} -> ok
after To ->
not_ok
end,
io:format("httprequest to ~p: ~p~n",[UrlHTTP,M]).
ensure_start(M) ->
case M:start() of
ok -> ok;
{error,{already_started,M}} -> ok;
Other -> Other
end.
and in the console:
1> t:start().
http://www.povray.org
http://www.google.com
http://www.yahoo.com
ok
httprequest to "http://www.google.com": ok
httprequest to "http://www.povray.org": ok
httprequest to "http://www.yahoo.com": ok
2> t:start().
http://www.povray.org
http://www.google.com
http://www.yahoo.com
ok
httprequest to "http://www.google.com": ok
httprequest to "http://www.povray.org": ok
httprequest to "http://www.yahoo.com": ok
3>
Note that thanks to the ensure_start/1 you can launch the application twice.
I have tested also with a bad url and it is detected.
My test include only 3 urls, and I guess that if there are many urls, the time to get the response will increase, because the loop to spawn processes is faster to execute than the request themselves. So you must expect at some point some timeout issue. There may be also some limitation in the http client, I didn't check the doc for this particular point.

Erlang newbie: why do I have to restart to load new code

I am trying to write a first program in Erlang that effects message communication between a client and server. In theory the server exits when it receives no message from the client, but every time I edit the client code and run the server again, it executes the old code. I have to ^G>q>erl>[re-enter command] to get it to see the new code.
-module(srvEsOne).
%%
%% export functions
%%
-export([start/0]).
%%function definition
start()->
io:format("Server: Starting at pid: ~p \n",[self()]),
case lists:member(serverEsOne, registered()) of
true ->
unregister(serverEsOne); %if the token is present, remove it
false ->
ok
end,
register(serverEsOne,self()),
Pid = spawn(esOne, start,[self()]),
loop(false, false,Pid).
%
loop(Prec, Nrec,Pd)->
io:format("Server: I am waiting to hear from: ~p \n",[Pd]),
case Prec of
true ->
case Nrec of
true ->
io:format("Server: I reply to ~p \n",[Pd]),
Pd ! {reply, self()},
io:format("Server: I quit \n",[]),
ok;
false ->
receiveLoop(Prec,Nrec,Pd)
end;
false ->
receiveLoop(Prec,Nrec,Pd)
end.
receiveLoop(Prec,Nrec,Pid) ->
receive
{onPid, Pid}->
io:format("Server: I received a message to my pid from ~p \n",[Pid]),
loop(true, Nrec,Pid);
{onName,Pid}->
io:format("Server: I received a message to name from ~p \n",[Pid]),
loop(Prec,true,Pid)
after
5000->
io:format("Server: I received no messages, i quit\n",[]),
ok
end.
And the client code reads
-module(esOne).
-export([start/1, func/1]).
start(Par) ->
io:format("Client: I am ~p, i was spawned by the server: ~p \n",[self(),Par]),
spawn(esOne, func, [self()]),
io:format("Client: Now I will try to send a message to: ~p \n",[Par]),
Par ! {self(), hotbelgo},
serverEsOne ! {self(), hotbelgo},
ok.
func(Parent)->
io:format("Child: I am ~p, i was spawned from ~p \n",[self(),Parent]).
The server is failing to receive a message from the client, but I can't sensibly begin to debug that until I can try changes to the code in a more straightforward manner.
When you make modification to a module you need to compile it.
If you do it in an erlang shell using the command c(module) or c(module,[options]), the new compiled version of the module is automatically loaded in that shell. It will be used by all the new process you launch.
For the one that are alive and already use it is is more complex to explain and I think it is not what you are asking for.
If you have several erlang shells running, only the one where you compile the module loaded it. That means that in the other shell, if the module were previously loaded, basically if you already use the module in those shell, and even if the corresponding processes are terminated, the new version is ignored.
Same thing if you use the command erlc to compile.
In all these cases, you need to explicitly load the module with the command l(module) in the shell.
Your server loop contain only local function calls. Running code is changed only if there is remote (or external) function call. So you have to export your loop function first:
-export([loop/3]).
and then you have to change all loop/3 calls in function receiveLoop/3 to
?MODULE:loop(...)
Alternatively you can do same thing with receiveLoop/3 instead. Best practice for serious applications is doing hot code swapping by demand so you change loop/3 to remote/external only after receiving some special message.

How to test gen server in erlang?

I am a beginner with erlang, and i write a basic gen server program as follows, I want to know, how to test the server so i can know it works well.
-module(gen_server_test).
-behaviour(gen_server).
-export([start_link/0]).
-export([alloc/0, free/1]).
-export([init/1, handle_call/3, handle_cast/2]).
start_link() ->
gen_server:start_link({local, gen_server_test}, ch3, [], []).
alloc() ->
gen_server:call(gen_server_test, alloc).
free(Ch) ->
gen_server:cast(gen_server_test, {free, Ch}).
init(_Args) ->
{ok, channels()}.
handle_call(alloc, _From, Chs) ->
{Ch, Chs2} = alloc(Chs),
{reply, Ch, Chs2}.
handle_cast({free, Ch}, Chs) ->
io:format(Ch),
io:format(Chs),
Chs2 = free(),
{noreply, Chs2}.
free() ->
io:format("free").
channels() ->
io:format("channels").
alloc(chs) ->
io:format("alloc chs").
BTW: The program can be compiled, and it is not a good program, I just want to print something to make sure it works :)
The beauty of a gen_server implementing module is that it is just a callback module. One need not even have to spawn the underlying gen_server process to test it.
All you need to do is have your test framework (usually eunit) to invoke all the handle_call/cast/info functions by injecting it with different inputs (different gen_server states, different input messages) etc. and ensure it returns the correct response tuple (for eg. {reply, ok, NewState} or {noreply, NewState} etc.)
Ofcourse, this wouldnt work perfectly if your callback functions arn't pure functions. For eg., in your handle_call function, if you are sending a message to another process for eg., or if you are modifying an ets table. In which case, you have to ensure that all the required processes and tables are pre-created before running the test.
You could try one of the following:
Use the erlang shell and invoke the commands manually. Make sure the source or .beam file is in the Erlang path (parameter -pz, like this: erl -pz <path here>)
Write an EUnit test case
PS: I think you have an error in your code, because you seem to start the module ch3 as the server, not the gen_server_test module.

Resources