可以使用 nginx location 指令发送 http 请求吗?

Can a http request be sent with the nginx location directive?

也许这是微不足道的,但我没有发现任何有意义的东西,或者我不知道去哪里找...

(如何)可以在请求特定路径后立即发送 curl / 任何命令?

按照这些思路,但实际上可行:

location / {
curl --data 'v=1&t=pageview&tid=UA-XXXXXXXX-X&cid=123&dp=hit'  https://google-analytics.com/collect
}

(评论中指出),ngx_http_lua_module可以做到!

location / {
          access_by_lua_block  {
            os.execute("/usr/bin/curl --data 'v=1&t=pageview&tid=UA-XXXXXXXX-X&cid=123&dp=hit'  https://google-analytics.com/collect >/dev/null 2>/dev/null") 
        }
}

请注意,执行会暂停页面加载,直到 curl 完成。要 运行 在后台卷曲并立即继续页面加载,请在末尾添加一个 space 和一个 &,使其看起来像

>/dev/null 2>/dev/null &")

您正在尝试做的事情 — 对服务器上的每个 URL 请求为 Google Analytics 执行一个新的 curl 实例 — 是解决问题的错误方法:

  1. Nginx 本身很容易在任何给定时间为 10k+ 并发连接提供服务作为下限,也就是说,如果你做对了,至少,请参阅 https://en.wikipedia.org/wiki/C10k_problem

  2. 另一方面,fork, the underlying system call that creates a new process, which would be necessary if you want to run curl for each request, is very slow, on the order 1k forks per second as an upper limit, e.g., if you do things right, that's the fastest it'll ever go, see Faster forking of large processes on Linux?的表现。


具有更好架构的最佳替代解决方案是什么?

  • 我的建议是通过批处理来执行此操作。通过实时进行 Google 分析,您并没有真正获得任何收益,5 分钟的统计延迟应该绰绰有余。您可以用您选择的编程语言编写一个简单的脚本来查看相关的 http://nginx.org/r/access_log, collect the data for the required time period, and make a single batch request (and/or multiple individual requests from within a single process) to Google Analytics with the requisite information about each visitor in the last 5 minutes. You can run this as a daemon process, or as a script from a cron job, see crontab(5) and crontab(1).

  • 或者,如果您仍然希望实时处理 Google 分析(我不推荐这样做,因为大多数这些服务本身都是在 eventual consistency basis,意思是,GA本身不一定能保证最后XXseconds/minutes/hours/etc)的准确实时统计,那么你可能想实现某种守护进程来处理真实的统计时间:

    • 我的建议仍然是在这样的守护进程中使用 access_log,例如,通过您最喜欢的编程语言中的 tail -f /var/www/logs/access_log 等价物,您将在其中打开access_log 文件作为流,并在数据到来时处理数据。

    • 或者,您可以实现此守护程序以使其本身具有 HTTP 请求接口,并将每个传入请求复制到您的实际后端以及此额外服务器。 您可以在非默认构建 auth_request or add_after_body to make a "free" subrequest for each request. This subrequest would go to your server, for example, written in Go 的帮助下通过 nginx 对其进行多路复用。服务器将至少有两个 goroutines:一个会将传入的请求处理到队列中(通过缓冲字符串通道实现),立即向客户端发出回复,以确保不会延迟nginx 上游;另一个将通过第一个 chan string 接收第一个的请求,处理它们并将适当的请求发送到 Google Analytics。

最终,无论采用哪种方式,您可能仍希望实施某种程度的批处理 and/or 节流,因为我想在某一时刻,Google 分析本身会如果您在非常过度的基础上继续从同一 IP 地址发送请求,而没有任何类型的批处理实施受到威胁,则可能会受到限制。根据 as well as https://developers.google.com/analytics/devguides/collection/protocol/v1/limits-quotas,似乎大多数图书馆都对每秒向 Google.

发送的请求数量实施了内部限制。

如果您只需要向 Google Analytics 提交命中,那么它可以更容易地完成:Nginx 可以动态修改页面 HTML,在关闭前嵌入 GA 代码 </body> 标签:

sub_filter_once on;

sub_filter '</body>' "<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-XXXXXXXX-X', 'auto');
ga('send', 'pageview');
</script></body>";

location / {
}

这个 Nginx 模块叫做 sub

这是我最终的做法 - proxy_pass 而不是 curl - 基于此:https://github.com/vorodevops/nginx-analytics-measurement-protocol/tree/master/lua。该代码假设安装了 openresty 或 lua。不确定评论格式是否兼容(未测试)所以最好在使用前删除它们。

# pick your location 

location /example {

    # invite lua to the party

    access_by_lua_block  {

        # set request parameters

        local request = {
            v = 1,
            t = "pageview",

            # don' forget to put your own property here

            tid = "UA-XXXXXXX-Y",

            # this is a "unique" user id based on a hash of ip and user agent, not too reliable but possibly best that one can reasonably do without cookies

            cid = ngx.md5(ngx.var.remote_addr .. ngx.var.http_user_agent),
            uip = ngx.var.remote_addr,
            dp = ngx.var.request_uri,
            dr = ngx.var.http_referer,
            ua = ngx.var.http_user_agent,

            # here you truncate the language string to make it compatible with the javascript format - you'll want either the first two characters like here (e.g. en) or the first five (e.g en_US) with ...1, 5

            ul = string.sub(ngx.var.http_accept_language, 1, 2)
            }

        # use the location.capture thingy to send everything to a proxy

        local res = ngx.location.capture(  "/gamp",  {
        method = ngx.HTTP_POST,
        body = ngx.encode_args(request)
        })
    }
}


# make a separate location block to proxy the request away

location = /gamp {
    internal;
    expires epoch;
    access_log off;
    proxy_pass_request_headers off;
    proxy_pass_request_body on;
    proxy_pass https://google-analytics.com/collect;
}