erlangerlangweb

Erlang read post request


I'm trying to build a simple web server based on Erlang, so far I'm able to start a server with below code. tutorial ref

-module(helloworld).
-export([
  main/1,
  run_server/0,
  start/0,
  service/3,
]).

main(_) ->
  start(),
  receive
    stop -> ok
  end.

run_server() ->
  ok = inets:start(),
  {ok, _} = inets:start(httpd, [

    {modules, [ 
         mod_alias, 
         mod_auth, 
         mod_esi, 
         mod_actions, 
         mod_cgi, 
         mod_dir,
         mod_get, 
         mod_head, 
         mod_log, 
         mod_disk_log 
      ]}, 
    {port, 8000},
    {server_name,"helloworld"}, 
      {server_root,"/tmp"}, 
      {document_root,"."}, 
      {erl_script_alias, {"/erl", [helloworld]}}, 
      {error_log, "error.log"}, 
      {security_log, "security.log"}, 
      {transfer_log, "transfer.log"}, 

      {mime_types,[ 
         {"html","text/html"}, {"css","text/css"}, {"js","application/x-javascript"} ]} 
   ]). 

start() -> run_server().

service(SessionID, _Env, _Input) -> mod_esi:deliver(SessionID, [ 
   "Content-Type: text/html\r\n\r\n", "<html><body>Hello, World!</body></html>" ]).

I'm trying to get the response but getting the permission error.

You don't have permission to access /erl/hello_world:servie on this server.

I'm aiming to build Erlang based server which can read post request and store the post data in MYSQL.

If anyone can help me with instructions or with some code to start the Erlang server for reading POST request that will be so helpful.

Thanks


Solution

  • I'm aiming to build Erlang based server which can read post request and store the post data in MYSQL.

    I used rebar3 to create an app:

    $ rebar3 new app myserver
    

    Then I specified mysql-otp as a dependency so that I could use mysql. I basically ignored the OTP app, and I added some source code to start an inets server. See here.

    Here's the module I used to start the inets httpd server, handle the request, and insert the post data into a mysql db:

    my.erl:

    -module(my).
    -compile(export_all).
    
    ensure_inets_start() ->
        case inets:start() of
            ok -> ok;
            {error,{already_started,inets}} -> ok
        end.
    
    start() ->
        ok = my:ensure_inets_start(),
    
        %Construct path to myserver/priv/server.conf:
        PrivDir = code:priv_dir(myserver),
        ServerConfPath = filename:join(PrivDir, "server.conf"),
    
        {ok, Server} = inets:start(httpd, 
            [{proplist_file, ServerConfPath}]
        ),
        Server.
    
    
    stop(Server) ->
        ok = inets:stop(httpd, Server).
    
    log(Data) ->
        {ok, IoDevice} = file:open(
            %"/Users/7stud/erlang_programs/inets_post_request/myserver/logs/mylog.log",
            "./logs/mylog.log",
            [append]
        ),
    
        file:write(IoDevice, Data),
        file:close(IoDevice).
    
    handle_request(SessionID, Env, Input) ->
        Headers = "Content-Type: text/html\r\n\r\n",
        Data = [
            <<"Hello, ">>, 
            "esi!\n"
        ],
    
        log(io_lib:format(
            "Inside my:handle_request()\nSessionId=~p\nEnv=~p\nInput=~p\n", 
            [SessionID, Env, Input]
        )),
    
        PostData = list_to_binary(Input),
        [NamePair, InfoPair] = binary:split(PostData, <<"&">>),
        [<<"name">>, Name] = binary:split(NamePair, <<"=">>),
        [<<"info">>, Info] = binary:split(InfoPair, <<"=">>),
        do_mysql(Name, Info),
    
        mod_esi:deliver(SessionID, Headers),  %Headers must be a string.
        mod_esi:deliver(SessionID, Data).     %Data can be an iolist.
    
    do_mysql(Name, Info) ->
        {ok, MysqlPid} = mysql:start_link(
                      [{host, "localhost"}, 
                       {user, "root"},
                       {password, ""}, 
                       {database, "mydb"}
                      ]
                    ),
    
        %{ok, ColumnNames, Rows} = mysql:query(
        %            Pid, 
        %            <<"SELECT * FROM people">>),
        %io:format("ColumnNames: ~p~nRows: ~p~n", [ColumnNames, Rows]).
    
        ok = mysql:query(
               MysqlPid, 
               "INSERT INTO people (name, info) VALUES (?, ?)", [Name, Info]
            ).
    

    I put that code in the src directory of my app, which was named myserver. Here is my directory structure:

    ~/erlang_programs/inets_post_request$ tree myserver
    
    myserver
    ├── htdocs
    ├── logs
    │   ├── errors.log
    │   ├── mylog.log
    │   └── requests.log
    ├── priv
    │   └── server.conf
    ├── rebar.config  (created by rebar3)
    ├── rebar.lock    (created by rebar3)
    └── src
        ├── my.erl
        ├── myserver.app.src  (created by rebar3)
        ├── myserver_app.erl  (created by rebar3)
        └── myserver_sup.erl  (created by rebar3)
    
    (compiling with rebar3 creates all this stuff:)
    
    ├── _build
    │   └── default
    │       └── lib
    │           ├── myserver
    │           │   ├── ebin
    │           │   │   ├── my.beam
    │           │   │   ├── myserver.app
    │           │   │   ├── myserver_app.beam
    │           │   │   └── myserver_sup.beam
    │           │   ├── include -> ../../../../include
    │           │   ├── priv -> ../../../../priv
    │           │   └── src -> ../../../../src
    │           └── mysql
    │               ├── CHANGELOG.md
    │               ├── COPYING
    │               ├── COPYING.LESSER
    │               ├── Makefile
    │               ├── README.md
    │               ├── changelog.sh
    │               ├── doc
    │               │   └── overview.edoc
    │               ├── ebin
    │               │   ├── mysql.app
    │               │   ├── mysql.beam
    │               │   ├── mysql_cache.beam
    │               │   ├── mysql_encode.beam
    │               │   ├── mysql_protocol.beam
    │               │   ├── mysql_sock_ssl.beam
    │               │   └── mysql_sock_tcp.beam
    │               ├── erlang-mk.build.config
    │               ├── erlang.mk
    │               ├── include
    │               │   ├── protocol.hrl
    │               │   ├── records.hrl
    │               │   └── server_status.hrl
    │               ├── priv
    │               │   └── edoc-style.css
    │               ├── src
    │               │   ├── mysql.app.src
    │               │   ├── mysql.erl
    │               │   ├── mysql_cache.erl
    │               │   ├── mysql_encode.erl
    │               │   ├── mysql_protocol.erl
    │               │   ├── mysql_sock_ssl.erl
    │               │   └── mysql_sock_tcp.erl
    │               └── test
    │                   ├── error_logger_acc.erl
    │                   ├── mock_tcp.erl
    │                   ├── mysql_encode_tests.erl
    │                   ├── mysql_protocol_tests.erl
    │                   ├── mysql_tests.erl
    │                   ├── ssl
    │                   │   ├── Makefile
    │                   │   └── my-ssl.cnf.template
    │                   ├── ssl_tests.erl
    │                   └── transaction_tests.erl
    

    Here is the server.conf file:

    [
      {modules, [
        mod_alias,
        mod_actions,
        mod_esi,
        mod_cgi,
        mod_get,
        mod_log
      ]},
      {bind_address, "localhost"}, 
      {port,0},
      {server_name,"httpd_test"},
      {server_root,"./"},
      {document_root,"./htdocs"},
      {erl_script_alias, {"/erl", [my]} },
      {erl_script_nocache, true},
      {error_log, "./logs/errors.log"},
      {transfer_log, "./logs/requests.log"}
    ].
    

    Then, to run the app I did:

    ~/erlang_programs/inets_post_request/myserver$ rebar3 compile (may not be necessary)
    ...
    ...
    ~/erlang_programs/inets_post_request/myserver$ rebar3 shell (looks like this also will fetch the dependencies, then compile)
    ===> Verifying dependencies...
    ===> Compiling myserver
    /Users/7stud/erlang_programs/inets_post_request/myserver/_build/default/lib/myserver/src/my.erl:2: Warning: export_all flag enabled - all functions will be exported
    
    Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:0] [hipe] [kernel-poll:false]
    
    Eshell V9.2  (abort with ^G)
    1> S = my:start().
    <0.127.0>
    
    2> httpd:info(S). 
    [{mime_types,[{"htm","text/html"},{"html","text/html"}]},
     {server_name,"httpd_test"},
     {erl_script_nocache,true},
     {bind_address,{127,0,0,1}},
     {modules,[mod_alias,mod_actions,mod_esi,mod_cgi,mod_get,
               mod_log]},
     {server_root,"/Users/7stud/erlang_programs/inets_post_request/myserver/src"},
     {erl_script_alias,{"/erl",[my]}},
     {port,55804},
     {transfer_log,<0.134.0>},
     {error_log,<0.133.0>},
     {document_root,"./htdocs"}]
    
    3>
    

    I looked at that output to get the server's port: 55804, which I used to send a post request with curl:

    ~$ curl -v --data "name=Kathy&info=xyz" "http://localhost:55804/erl/my:handle_request"
    *   Trying ::1...
    * TCP_NODELAY set
    * Connection failed
    * connect to ::1 port 55804 failed: Connection refused
    *   Trying 127.0.0.1...
    * TCP_NODELAY set
    * Connected to localhost (127.0.0.1) port 55804 (#0)
    > POST /erl/my:handle_request HTTP/1.1
    > Host: localhost:55804
    > User-Agent: curl/7.58.0
    > Accept: */*
    > Content-Length: 19
    > Content-Type: application/x-www-form-urlencoded
    > 
    * upload completely sent off: 19 out of 19 bytes
    < HTTP/1.1 200 OK
    < Date: Wed, 09 May 2018 00:10:42 GMT
    < Server: inets/6.4.5
    < Cache-Control: no-cache
    < Pragma: no-cache
    < Expires: Wed, 09 May 2018 00:10:42 GMT
    < Transfer-Encoding: chunked
    < Content-Type: text/html
    < 
    Hello, esi!
    * Connection #0 to host localhost left intact
    ~$ 
    

    Then I stopped the inets httpd server:

    4> my:stop(S).
    ok
    
    5> 
    

    Then I checked the mysql db for a new entry:

    mysql> select * from people;
    +----+-------+------+
    | id | name  | info |
    +----+-------+------+
    |  1 | Fred  | abc  |
    |  2 | Alice | xxx  |
    |  3 | Kathy | xyz  |
    +----+-------+------+
    4 rows in set (0.00 sec)
    

    Success!

    Here's my rebar.config file:

    {erl_opts, [debug_info]}.
    {deps, [
      {mysql, {git, "https://github.com/mysql-otp/mysql-otp",
              {tag, "1.3.2"}}}
    ]}.