Nginx

從 nginx 流代理中提取 HTTP 主機頭

  • December 12, 2020

我正在尋找使用 nginx 的流模組來代理 HTTP 流量。這適用於 HTTPS,因為該ngx_stream_ssl_preread模組存在。這允許我從 TLS 握手中提取請求的伺服器名稱,然後我可以使用它來確定我應該將流代理到哪個伺服器。但是,我似乎找不到普通 HTTP 的等價物。

我想這是因為大多數人只是使用普通的 HTTP 代理,因為代理伺服器可以看到 HTTP 請求中的 Host 標頭(因為它是未加密的)。不過,對於我的場景來說,使用流將是一個更好的解決方案,而且它似乎比完整的 HTTP 代理更輕量級。

使用該ngx_stream_ssl_preread模組,您可以訪問名為ssl_preread_server_name. 我正在尋找能夠提供基本相同但從HostHTTP 請求中的標頭派生的東西。這樣的事情存在嗎?

我找不到任何內置方法,所以我自己實現了一個。

使用ngx_stream_js_module模組和一些自定義 JavaScript (njs),我可以將 HTTP Host 標頭中的伺服器名稱讀取到 $preread_server_name 並配置與 HTTPS 非常相似的 HTTP 流量。

首先,在 nginx 中安裝支持 JavaScript 所需的額外模組:

$ apt install nginx-module-njs

在 nginx.conf 中載入模組:

load_module modules/ngx_stream_js_module.so;

/etc/nginx/http_server_name.js 實現讀取伺服器名稱:

var server_name = '-';

/**
* Read the server name from the HTTP stream.
*
* @param s
*   Stream.
*/
function read_server_name(s) {
 s.on('upload', function (data, flags) {
   if (data.length || flags.last) {
     s.done();
   }

   // If we can find the Host header.
   var n = data.indexOf('\r\nHost: ');
   if (n != -1) {
     // Determine the start of the Host header value and of the next header.
     var start_host = n + 8;
     var next_header = data.indexOf('\r\n', start_host);

     // Extract the Host header value.
     server_name = data.substr(start_host, next_header - start_host);

     // Remove the port if given.
     var port_start = server_name.indexOf(':');
     if (port_start != -1) {
       server_name = server_name.substr(0, port_start);
     }
   }
 });
}

function get_server_name(s) {
 return server_name;
}

export default {read_server_name, get_server_name}

這是我的stream.conf

stream {
 # The HTTP map is based on the server name read from the HTTP stream in
 # http_server_name.js.
 js_import main from http_server_name.js;
 js_set $preread_server_name main.get_server_name;

 map $preread_server_name $internal_port {
   foo.example.com 8080;
   bar.example.com 8081;
 }

 # The HTTPS map is based on the server name provided by the
 # ngx_stream_ssl_preread_module module.
 map $ssl_preread_server_name $ssl_internal_port {
   foo.example.com 8443;
   bar.example.com 8444;
 }

 server {
   listen 443;

   # Have $ssl_preread_server_name populated.
   ssl_preread on;

   proxy_protocol on;

   proxy_pass my_host:$ssl_internal_port;
 }

 server {
   listen 80;

   # Read the server name at the preread phase.
   js_preread main.read_server_name;

   proxy_protocol on;

   proxy_pass my_host:$internal_port;
 }
}

在 nginx.conf 中包含 stream.conf:

include /etc/nginx/stream.conf;

通過重新載入 nginx 應用配置:

$ service nginx reload

代理伺服器現在可以配置為從 HTTP 和 HTTPS 的代理協議讀取真實客戶端數據的一種方式:

server {
 listen 80 proxy_protocol;
 listen 443 ssl proxy_protocol;

 set_real_ip_from 10.0.0.0/8;
 set_real_ip_from 172.16.0.0/12;
 set_real_ip_from 192.168.0.0/16;
 set_real_ip_from fc00::/7;
 real_ip_header proxy_protocol;
 real_ip_recursive on;
}

引用自:https://serverfault.com/questions/1015880