inets httpd: не удается заставить работать простой скрипт esi

Вот файл proplist_file, который я использую для настройки сервера httpd:

[
  {modules, [
    mod_alias,
    mod_actions,
    mod_cgi,
    mod_get,
    mod_esi,
    mod_log
  ]},
  {bind_address, "localhost"}, 
  {port,0},
  {server_name,"httpd_test"},
  {server_root,"/Users/7stud/erlang_programs/inets_proj"},
  {document_root,"./htdocs"},
  {script_alias, {"/cgi-bin/", "/Users/7stud/erlang_programs/inets_proj/cgi-bin/"} },
  {erl_script_alias, {"/cgi-bin/example", [httpd_example]} },
  {erl_script_nocache, true},
  {error_log, "./errors.log"},
  {transfer_log, "./requests.log"}
].

Для esi-скриптов ключевым свойством является:

  {erl_script_alias, {"/cgi-bin/example", [httpd_example]} }

Согласно документам inets:

Свойства ESI — требуется mod_esi

{erl_script_alias, {URLPath, [AllowedModule]}}
URLPath = string() и AllowedModule = atom(). erl_script_alias помечает все URL-адреса, соответствующие URL-пути, как сценарии схемы erl. Соответствующий URL-адрес сопоставляется с определенным модулем и функцией, например:

 {erl_script_alias, {"/cgi-bin/example", [httpd_example]}}

Запрос к http://your.server.org/cgi-bin/example/httpd_example:yahoo будет ссылаться на httpd_example:yahoo/3 или, если он не существует, httpd_example:yahoo/2 и http://your.server.org/cgi-bin/example/other:yahoo не будет разрешено выполняться.

Моя структура каталогов:

~/erlang_programs$ tree inets_proj/
inets_proj/
├── cgi-bin
│   ├── 1.pl
│   ├── example
│   │   ├── httpd_example.beam
│   │   └── httpd_example.erl
│   ├── httpd_example.beam
│   └── httpd_example.erl
├── cl.beam
├── cl.erl
├── errors.log
├── htdocs
│   └── file1.txt
├── httpd_example.beam
├── httpd_example.erl
├── mylog.log
├── requests.log
├── s.beam
├── s.erl
└── server.conf

Я не знал, куда поставить модуль httpd_example, поэтому поставил его в нескольких местах.

esi_mod.erl:

-module(esi_mod).
-compile(export_all).

log(Data) ->
    {ok, IoDevice} = file:open(
        "/Users/7stud/erlang_programs/inets_proj/mylog.log",
        [append]
    ),

    ok = file:write(IoDevice, Data),
    file:close(IoDevice).

get_data(SessionID, _Env, _Input) ->
    Headers = "Content-Type: text/html\r\n\r\n",
    Data = "Hello, esi",

    log(["--Inside esi_mod:get_data() ~n"]),

    ok = mod_esi:deliver(SessionID, Headers),  %Headers must be a string.
    ok = mod_esi:deliver(SessionID, Data).     %Data can be an iolist.

Вот информация о сервере, указанная в оболочке:

$ erl
Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.2  (abort with ^G)

1> c(s).         
s.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,s}
2> S = s:start().
<0.86.0>
3> httpd:info(S).
[{mime_types,[{"htm","text/html"},{"html","text/html"}]},
 {server_name,"httpd_test"},
 {erl_script_nocache,true},
 {script_alias,{"/cgi-bin/",
                "/Users/7stud/erlang_programs/inets_proj/cgi-bin/"}},
 {bind_address,{127,0,0,1}},
 {modules,[mod_alias,mod_actions,mod_cgi,mod_get,mod_esi,
           mod_log]},
 {server_root,"/Users/7stud/erlang_programs/inets_proj"},
 {erl_script_alias,{"/cgi-bin/example",[httpd_example]}},
 {port,64470},
 {transfer_log,<0.93.0>},
 {error_log,<0.92.0>},
 {document_root,"./htdocs"}]
4> 

Вот мой запрос:

~$ curl -vv "http://localhost:64470/cgi-bin/example/httpd_example:get_data"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 64470 (#0)
> GET /cgi-bin/example/httpd_example:get_data HTTP/1.1
> Host: localhost:64470
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 404 Object Not Found
< Date: Wed, 28 Feb 2018 10:22:38 GMT
< Server: inets/6.4.5
< Content-Type: text/html
< Content-Length: 245
< 
<HTML>
       <HEAD>
           <TITLE>Object Not Found</TITLE>
      </HEAD>
      <BODY>
      <H1>Object Not Found</H1>
The requested URL &#47;cgi-bin&#47;example&#47;httpd_example:get_data was not found on this server.
</BODY>
      </HTML>
* Connection #0 to host localhost left intact
~$ 

ошибки.лог:

[Временная метка] не удалось получить доступ к /cgi-bin/example/httpd_example:get_data для 127.0.0.1, причина: «httpd_file: невозможно открыть ./htdocs/cgi-bin/example/httpd_example:get_data: файл не найден»

Согласно сообщению об ошибке, путь запроса:

/cgi-bin/example/httpd_example:get_data 

был преобразован в:

./htdocs/cgi-bin/example/httpd_example:get_data

что означает, что путь запроса был привязан к ./htdocs. Хм??


person 7stud    schedule 28.02.2018    source источник


Ответы (1)


Хорошо, у меня все работает. Во-первых, пока я искал вокруг, я наткнулся на:

10 основных инструментов Erlang для Erlang Разработчики

и в этом заключается единственный величайший «инструмент» erlang, который я использовал на сегодняшний день:

История команд, которая не стирается при выходе из оболочки erlang.

Вы можете установить/включить его, следуя инструкциям здесь: https://github.com/ferd/erlang-history

Вернемся к проблеме:

1) Я обнаружил, что порядок модулей httpd важен. mod_esi должно стоять перед mod_get:

сервер.конф:

[
  {modules, [
    mod_alias,
    mod_actions,
    mod_esi,
    mod_cgi,
    mod_get,
    mod_log
  ]},
  {bind_address, "localhost"}, 
  {port,0},
  {server_name,"httpd_test"},
  {server_root,"/Users/7stud/erlang_programs/inets_proj"},
  {document_root,"./htdocs"},
  {script_alias, {"/cgi-bin/", "/Users/7stud/erlang_programs/inets_proj/cgi-bin/"} },
  {erl_script_alias, {"/erl", [mymod]} },
  {erl_script_nocache, true},
  {error_log, "./errors.log"},
  {transfer_log, "./requests.log"}
].

После правильного порядка модулей я перестал получать ошибки «Файл не найден» из-за странных преобразованных путей.

2) Код модуля esi должен находиться в каталоге server_root. Имя моего модуля esi mymod.erl:

~/erlang_programs$ tree inets_proj/
inets_proj/
├── cgi-bin
│   ├── 1.pl
│   ├── example
│   │   ├── httpd_example.beam
│   │   └── httpd_example.erl
│   ├── httpd_example.beam
│   └── httpd_example.erl
├── cl.beam
├── cl.erl
├── errors.log
├── htdocs
│   └── file1.txt
├── mylog.log
├── mymod.beam
├── mymod.erl
├── requests.log
├── s.beam
├── s.erl
├── server.conf
├── xhttpd_example.beam
└── xhttpd_example.erl

3) Потому что я указал:

{erl_script_alias, {"/erl", [mymod]} }

URL-адрес, который мне нужно использовать:

http://localhost:57867/erl/mymod:get_data

Порт должен совпадать с портом сервера. Правильный путь — это любой путь, указанный вами в свойстве erl_script_alias, плюс /modulename:funcname или /modulename/funcname.

Вот mymod.erl:

-module(mymod).
-export([get_data/3]).

log(Data) ->
    {ok, IoDevice} = file:open(
        "/Users/7stud/erlang_programs/inets_proj/mylog.log",
        [append]
    ),

    file:write(IoDevice, Data),
    file:close(IoDevice).

get_data(SessionID, Env, Input) ->
    Headers = "Content-Type: text/html\r\n\r\n",
    Data = [
        <<"Hello, ">>, 
        "esi!\n"
    ],

    log(io_lib:format(
        "Inside mymod:get_data()\nSessionId=~p\nEnv=~p\nInput=~p\n", 
        [SessionID, Env, Input]
    )),

    mod_esi:deliver(SessionID, Headers),  %Headers must be a string.
    mod_esi:deliver(SessionID, Data).     %Data can be an iolist.

Согласно документации mod_esi:

mod_esi:deliver/2 должен использоваться для генерации ответа клиенту, а SessionID — это идентификатор, который должен использоваться при вызове этой функции, не предполагая ничего о типе данных. Эту функцию можно вызывать несколько раз, чтобы разбить данные ответа на части. Обратите внимание, что первый блок данных, отправляемый клиенту, должен как минимум содержать все поля заголовка HTTP, которые будут сгенерированы в ответе. Если первый фрагмент не содержит конец заголовка HTTP, то есть "\r\n\r\n", сервер предполагает, что поля заголовка HTTP не будут созданы.

4) Скомпилируйте mymod.erl:

~/erlang_programs/inets_proj$ erlc mymod.erl 
~/erlang_programs/inets_proj$ 

Вы должны перекомпилировать после каждого изменения, которое вы делаете в mymod.erl, а затем перезапускать сервер. Было бы проще, если бы это работало:

5> httpd:reload_config("server.conf", disturbing).
{error,{missing_property,server_name}}

но даже если в моем конфигурационном файле указано свойство server_name, я получаю эту ошибку.

5) Я предлагаю вам сделать логирование ошибок, включив mod_log в список модулей и указав свойство:

{error_log, "./errors.log"}

Затем проверьте этот файл на наличие каких-либо отзывов о том, что произошло при сбое запроса.

6) Когда я вызвал свой пользовательский метод log() (чтобы записать некоторую информацию в файл), без моего ведома метод log() вызывал исключение, из-за которого сервер отклонял запрос и вводил сообщение module traverse failed по ошибке. .бревно:

[Timestamp], module traverse failed: mod_esi:do => 
   Error Type:  exit
   Error:       {mod_esi_linked_process_died,<0.97.0>,normal}
   Stack trace: [{mod_esi,receive_headers,1,[{file,"mod_esi.erl"},{line,428}]},
                 {mod_esi,deliver_webpage_chunk,3,
                     [{file,"mod_esi.erl"},{line,389}]},
                 {mod_esi,erl_scheme_webpage_chunk,5,
                     [{file,"mod_esi.erl"},{line,380}]},
                 {mod_esi,generate_webpage,7,
                     [{file,"mod_esi.erl"},{line,314}]},
                 {httpd_response,traverse_modules,2,
                     [{file,"httpd_response.erl"},{line,77}]},
                 {httpd_response,generate_and_send_response,1,
                     [{file,"httpd_response.erl"},{line,44}]},
                 {httpd_request_handler,handle_response,1,
                     [{file,"httpd_request_handler.erl"},{line,655}]},
                 {gen_server,try_dispatch,4,
                     [{file,"gen_server.erl"},{line,616}]}]

7) Вот модуль, который я использовал для запуска сервера:

-module(s).
-compile(export_all).

%Need to look up port with httpd:info(Server)

ensure_inets_start() ->
    case inets:start() of
        ok -> ok;
        {error,{already_started,inets}} -> ok
    end.


start() ->
    ok = s:ensure_inets_start(),

    {ok, Server} = inets:start(httpd, 
        [{proplist_file, "./server.conf"}]
    ),
    Server.


stop(Server) ->
    ok = inets:stop(httpd, Server).

8) Вот несколько примеров запросов с использованием curl...

Информация о сервере в оболочке:

~/erlang_programs/inets_proj$ erl
Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V9.2  (abort with ^G)
1> c(s).         
s.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,s}
2> S = s:start().
<0.86.0>
3> httpd:info(S).
[{mime_types,[{"htm","text/html"},{"html","text/html"}]},
 {server_name,"httpd_test"},
 {erl_script_nocache,true},
 {script_alias,{"/cgi-bin/",
                "/Users/7stud/erlang_programs/inets_proj/cgi-bin/"}},
 {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_proj"},
 {erl_script_alias,{"/erl",[mymod]}},
 {port,59202},
 {transfer_log,<0.93.0>},
 {error_log,<0.92.0>},
 {document_root,"./htdocs"}]
4> 

Запрос 1 (запрос esi get):

~$  curl -v "http://localhost:57867/erl/mymod:get_data"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 57867 (#0)
> GET /erl/mymod:get_data HTTP/1.1
> Host: localhost:57867
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Wed, 28 Feb 2018 13:28:09 GMT
< Server: inets/6.4.5
< Cache-Control: no-cache
< Pragma: no-cache
< Expires: Wed, 28 Feb 2018 13:28:09 GMT
< Transfer-Encoding: chunked
< Content-Type: text/html
< 
Hello, esi!
* Connection #0 to host localhost left intact
~$ 

мой лог.лог:

~/erlang_programs/inets_proj$ cat mylog.log
...
...
Inside mymod:get_data()
SessionId=<0.99.0>
Env=[{server_software,"inets/6.4.5"},
     {server_name,"httpd_test"},
     {host_name,"ChristophersMBP"},
     {gateway_interface,"CGI/1.1"},
     {server_protocol,"HTTP/1.1"},
     {server_port,59202},
     {request_method,"GET"},
     {remote_addr,"127.0.0.1"},
     {peer_cert,undefined},
     {script_name,"/erl/mymod:get_data"},
     {http_host,"localhost:59202"},
     {http_user_agent,"curl/7.58.0"},
     {http_accept,"*/*"}]
Input=[]
-------

Запрос 2 (esi получить запрос со строкой запроса):

~$  curl -v "http://localhost:59202/erl/mymod:get_data?a=1&b=2"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 59202 (#0)
> GET /erl/mymod:get_data?a=1&b=2 HTTP/1.1
> Host: localhost:59202
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Wed, 28 Feb 2018 13:47:41 GMT
< Server: inets/6.4.5
< Cache-Control: no-cache
< Pragma: no-cache
< Expires: Wed, 28 Feb 2018 13:47:41 GMT
< Transfer-Encoding: chunked
< Content-Type: text/html
< 
Hello, esi!
* Connection #0 to host localhost left intact

мой лог.лог:

~/erlang_programs/inets_proj$ cat mylog.log
...
...
Inside mymod:get_data()
SessionId=<0.105.0>
Env=[{server_software,"inets/6.4.5"},
     {server_name,"httpd_test"},
     {host_name,"ChristophersMBP"},
     {gateway_interface,"CGI/1.1"},
     {server_protocol,"HTTP/1.1"},
     {server_port,59202},
     {request_method,"GET"},
     {remote_addr,"127.0.0.1"},
     {peer_cert,undefined},
     {script_name,"/erl/mymod:get_data?a=1&b=2"},
     {http_host,"localhost:59202"},
     {http_user_agent,"curl/7.58.0"},
     {http_accept,"*/*"},
     {query_string,"a=1&b=2"}]
Input="a=1&b=2"

Запрос 3 (почтовый запрос esi):

~$  curl -v --data "a=1&b=2" "http://localhost:59202/erl/mymod:get_data"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 59202 (#0)
> POST /erl/mymod:get_data HTTP/1.1
> Host: localhost:59202
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Length: 7
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 7 out of 7 bytes
< HTTP/1.1 200 OK
< Date: Wed, 28 Feb 2018 13:51:44 GMT
< Server: inets/6.4.5
< Cache-Control: no-cache
< Pragma: no-cache
< Expires: Wed, 28 Feb 2018 13:51:44 GMT
< Transfer-Encoding: chunked
< Content-Type: text/html
< 
Hello, esi!
* Connection #0 to host localhost left intact

мой лог.лог:

Inside mymod:get_data()
SessionId=<0.108.0>
Env=[{server_software,"inets/6.4.5"},
     {server_name,"httpd_test"},
     {host_name,"ChristophersMBP"},
     {gateway_interface,"CGI/1.1"},
     {server_protocol,"HTTP/1.1"},
     {server_port,59202},
     {request_method,"POST"},
     {remote_addr,"127.0.0.1"},
     {peer_cert,undefined},
     {script_name,"/erl/mymod:get_data"},
     {http_host,"localhost:59202"},
     {http_user_agent,"curl/7.58.0"},
     {http_accept,"*/*"},
     {http_content_length,"7"},
     {http_content_type,"application/x-www-form-urlencoded"}]
Input="a=1&b=2"
-------

Запрос 4 (запрос на получение cgi):

~$  curl -v "http://localhost:59202/cgi-bin/1.pl"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 59202 (#0)
> GET /cgi-bin/1.pl HTTP/1.1
> Host: localhost:59202
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Wed, 28 Feb 2018 13:41:43 GMT
< Server: inets/6.4.5
< Transfer-Encoding: chunked
< Content-Type: text/html
< 
Hello, Perl.
* Connection #0 to host localhost left intact

Запрос 5 (получить запрос на обычный файл из каталога document_root):

~$  curl -v "http://localhost:59202/file1.txt"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 59202 (#0)
> GET /file1.txt HTTP/1.1
> Host: localhost:59202
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Wed, 28 Feb 2018 13:42:15 GMT
< Server: inets/6.4.5
< Content-Type: text/plain
< Etag: nCZT0114
< Content-Length: 14
< Last-Modified: Mon, 26 Feb 2018 02:51:52 GMT
< 
Hello, world!
* Connection #0 to host localhost left intact
person 7stud    schedule 28.02.2018