nginx的location指令能否发送HTTP请求?

4
也许这很琐碎,但我没有找到任何有意义的东西或者不知道该去哪里找...当请求某个特定路径时,如何立即发送curl/其他命令?类似于以下内容,但实际上可以起作用:
location / {
curl --data 'v=1&t=pageview&tid=UA-XXXXXXXX-X&cid=123&dp=hit'  https://google-analytics.com/collect
}

2
我不确定是否可以使用“纯”nginx完成此操作,但如果您可以使用OpenResty(或ngx_http_lua_module),我可以为您提供如何完成此操作的方法。 - Ivan Shatsky
如果它能完成工作,为什么不呢? - lucian
4个回答

6
(如评论所指出),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命令会阻塞页面加载,直到curl命令执行完成。如果想要在后台运行curl命令并立即继续页面加载,请在命令结尾添加一个空格和一个&符号,使其看起来像这样:

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

是的,它正在工作! 必须安装openresty,现在暂时没有http/2支持 - 希望他们很快会发布基于nginx >1.13.9的版本... 有什么办法将现有的标头作为参数传递进去吗? - lucian
3
@LucianDavidescu 我真的无法相信这个解决方案甚至被认为是可以接受的,即使是用于“测试”目的,更不用说任何生产环境了。在后台生成一个新的进程,同时立即返回客户端,使得一个单一的使用单一TCP连接的客户端可以轻松地通过耗尽和过载进程表,仅用几秒钟的时间就可以完全使您的整个计算机崩溃,是的,您的整个计算机。这个答案中提出的解决方案与forkbomb几乎没有区别! - cnst
2
@LucianDavidescu,这可能是任何问题。如果DNS出现故障,或者Google决定限制您的访问,或者IPv6被配置但无法正常工作怎么办?每个curl实例将持续3分钟以上,并且每个请求都会有更多的实例。您将无法使用shell登录系统,因为进程表已耗尽。如果您使用共享主机和/或fork速度很慢(确实如此),并且每秒仅限于20到100个forks,这需要2/3的CPU功率,从而减慢了站点的其余部分。我认为您没有完全意识到fork的成本有多高。 - cnst
2
@LucianDavidescu 看起来你可以通过运行 local headers, err = ngx.resp.get_headers(); 获取请求头的数组(由浏览器发送),并使用 local headers, err = ngx.req.get_headers() 获取响应头的数组(由nginx发送)- 但是你应该使用 log_by_lua_block 而不是 access_by_lua_block - hanshenrik
1
发现这个 - https://github.com/vorodevops/nginx-analytics-measurement-protocol/tree/master/lua 它使用proxy_pass,目前运行得非常好。 - lucian
显示剩余7条评论

5
你试图做的事情——在服务器上每个URL请求执行一个新的curl实例,以便于Google Analytics操作——是解决问题的一种错误方式:
  1. Nginx本身可以轻松地在任何给定时间服务于10k+并发连接,作为下限,也就是说,如果您做得正确,请参见https://en.wikipedia.org/wiki/C10k_problem

  2. 另一方面,fork的性能,即创建新进程的底层系统调用,如果您想为每个请求运行curl,则非常慢,上限为每秒1k个分支,例如,如果您做得正确,那就是它最快的速度,请参见Faster forking of large processes on Linux?


什么是更好的架构替代方案?
  • 我的建议是通过批处理来执行此操作。实时进行Google Analytics并没有什么优势,统计数据的5分钟延迟应该足够了。您可以使用编程语言编写一个简单的脚本,查找相关的http://nginx.org/r/access_log,收集所需时间段的数据,并向Google Analytics发出单个批量请求(和/或从单个进程内发出多个单独的请求)以提供有关过去5分钟内每个访问者的必要信息。您可以将其作为守护进程或从cron作业中运行脚本,请参见crontab(5)crontab(1)

  • 或者,如果您仍然希望进行Google Analytics的实时处理(我不建议这样做,因为大多数这些服务本身都是基于“最终一致性”实现的,这意味着GA本身不会保证最后XX秒/分钟/小时等的准确实时统计数据),则您可能需要实现某种形式的守护进程来处理实时统计:

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

    • 或者,您可以实现此守护进程具有自己的HTTP请求接口,并将每个传入的请求复制到您的实际后端以及此额外服务器。您可以通过nginx的帮助进行多路复用,使用默认未构建的auth_requestadd_after_body来为每个请求创建一个“免费”子请求。该子请求将发送到您的服务器,例如,使用Go编写。该服务器将具有至少两个goroutines:一个将将传入的请求处理为队列(通过缓冲字符串通道实现),立即向客户端发出回复,以确保不会延迟nginx上游;另一个将通过第一个chan string从第一个接收请求,随着其进行处理并向Google Analytics发送适当的请求。

无论你选择哪种方式,你可能仍然想要实现一定程度的批处理和/或限流,因为我想在没有任何批处理实现的情况下,如果你从相同的IP地址过于频繁地向Google Analytics发送请求,它本身很可能会进行限流。根据Google Analytics测量协议API的直接使用速率限制是多少?https://developers.google.com/analytics/devguides/collection/protocol/v1/limits-quotas,大多数库似乎实现了内部限制,以确定它们每秒钟将发送多少个请求到Google。


的确,那似乎是可扩展的长期解决方案。然而,一方面,对于大多数用例来说,同时连接和速率限制已经相当高了(除非还存在性能问题),另一方面,我认为“快速而肮脏”的方法可能至少在测试目的上会很有用。 - lucian
1
顺便说一下,我写了一些用PHP解析nginx访问日志的代码,可以在这里的第58行看到:https://github.com/divinity76/http_log_parser/blob/master/create_database.php#L58(但是那段代码是2015年的,没有维护,不知道自2015年以来有没有进行过任何更改)。 - hanshenrik

3
如果您只需要将一项任务提交到Google Analytics,那么可以更轻松地完成:Nginx可以即时修改页面HTML,在关闭的</body>标签之前嵌入GA代码。
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


非HTML文件中包含短语“</body>”会发生什么?例如XML文件? - hanshenrik
1
@hanshenrik 这是可配置的。默认配置是替换文件中的 text/html MIME 类型,也可以允许其他类型。 - Alexander Azarov
如果我想要的话,我本来就可以访问该网站并将JavaScript放在那里。 - lucian

1
这是我最终的做法 - 使用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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接