Erlang:函数在 shell 中有效,但在 YAWS 中无效

Erlang: Functions work in shell but not in YAWS

我唯一的调试方法 (io:format/2) 在 YAWS 中不起作用。我不知所措。我的主管启动了三个进程:ETS Manager、YAWS Init 和 Ratelimiter。这是成功的。我可以在 shell... 中使用速率限制器来调用 YAWS 应该调用的相同函数。不同之处在于 shell 的行为符合我的预期,我不知道 YAWS 中发生了什么。

我知道如果我在 shell 中发送垃圾邮件命令:ratelimiter:limit(IP) 它最终会 return true。我可以执行以下命令,它也会 return true: ratelimiter:lockout(IP), ratelimiter:blacklist(IP)。限制器是 gen_server.

函数执行以下操作:

在我的 arg_rewrite_mod 模块中,我正在做一些检查以确保我收到我期望的 HTTP 请求,即 GET、POST 和 HEAD。我认为这也是进行速率限制的好地方。在 Web 服务器的事件链中尽快执行此操作。

除了使用 "printf"s 和限制器外,我对 arg_rewrite 模块所做的所有更改似乎都有效。我是这门语言的新手,所以我不确定我的错误是否明显。

我的骨架 arg_rewrite_mod:

-module(arg_preproc).
-export([arg_rewrite/1]).

-include("limiter_def.hrl").
-include_lib("/usr/lib/yaws/include/yaws_api.hrl").


is_blacklisted(ID) ->
    case ratelimiter:blacklist(ID) of
    false ->    continue;
    true ->     throw(blacklist)
    end.

is_limited(ID) ->
    case ratelimiter:limit(ID) of
    false ->    continue;
    true ->     throw(limit)
    end.


arg_rewrite(A) ->
    Allow = ['GET','POST', 'HEAD'],

    try
        {IP, _} = A#arg.client_ip_port,

        ID = IP,
        is_blacklisted(ID),

io:format("~p ~p ~n",[ID, is_blacklisted(ID)]),         

        %% === Allow expected HTTP requests
        HttpReq = (A#arg.req)#http_request.method,

        case lists:member(HttpReq, Allow) of
            true ->
                {_,ReqTgt} = (A#arg.req)#http_request.path,
                PassThru = [".css",".jpg",".jpeg",".png",".js"],
                %% ... much more ...
            false ->
                is_limited(ID),
                throw(http_method_denied)
        end
    catch
        throw:blacklist -> %% Send back a 429;
        throw:limit -> %% Same but no Retry-After;
        throw:http_method_denied ->
        %%Only thrown experienced
            AllowedReq = string:join([atom_to_list(M) || M <- Allow], ","),
            A#arg{state=#rewrite_response{status=405,
                        headers=[{header, {"Allow", AllowedReq}},{header, {connection, "close"}}]
            }};
        Type:Reason -> {error, {unhandled,{Type, Reason}}}
    end.

我可以在 bash shell 中尽可能快地发送垃圾邮件 curl -I -X HEAD <<any page>>,而我得到的只是 HTTP 200。 ETS table 也有零条目。使用 PUT 我得到了预期的 HTTP 405 。我可以 ratelimiter:lockout({MY_IP}) 并让网页加载到我的浏览器和 HTTP 200curl.

我很困惑。这是我启动 YAWS 的方式吗?

start() ->
    os:putenv("YAWSHOME", ?HOMEPATH_YAWS),
    code:add_patha(?MODPATH_YAWS),

    ok = case (R = application:start(yaws)) of
        {error, {already_started, _}} -> ok;
        _ -> R
    end,

    {ok,self()}. %% Tell supervisor everything okay in a manner it expects.

我这样做是因为我认为它会是 "easier."

当作为另一个应用程序的一部分启动 Yaws 时,使用它的 embedding support 很重要。 Yaws 嵌入启动代码所做的一件重要事情是将应用程序环境变量 embedded 设置为 true:

application:set_env(yaws, embedded, true),

Yaws 在它的几个代码路径中检查这个变量,特别是在初始化期间,以避免假设它是 运行 作为 stand-alone 守护进程。

关于速率限制,您可以考虑使用 shaper,而不是使用 arg 重写器。 yaws_shaper 模块提供了一种行为,期望其回调模块实现两个功能:

  • check/1:yaws_shaper调用这个让回调模块决定是否允许客户端的请求。它将客户端主机信息作为回调参数传递。您的整形器回调模块 return 要么是允许请求继续的原子 allow,要么是元组 {deny, Status, Message},其中 Status 是到 return 的 HTTP 状态代码给客户端,例如 429 表示客户端发出的请求过多,Message 是任何额外的 HTML 要 returned 给客户端。 (如果 Message 也可以包含回复 header,例如 Retry-After,那可能会更好;这是我会考虑添加到 Yaws 中的东西。)

  • update/3yaws_shaper 在客户端的响应准备好 returned 时调用它。第一个参数是客户端主机信息,第二个参数是"hits"的数量(每个请求的值为1),第三个参数是响应客户端请求而传送的字节数。您的整形器回调模块可以从 update/3 return ok(Yaws 不使用 return 值)。

整形器可以使用此框架来跟踪每个客户端发出的请求数量以及 Yaws 向每个客户端传送的数据量,并使用该信息来限制或拒绝特定客户端。

最后,虽然 "printf debugging" 有效,但它不太理想,尤其是在具有 built-in 跟踪功能的 Erlang 中。你应该考虑学习 dbg module 这样你就可以跟踪你想要的任何函数,看看谁调用了它,看看传递给它的参数是什么,看看它是什么 returns,等等