Summary
The target reader of this article is Tengine/Nginx R&D or Operations and Maintenance students. If you are very clear about this logic, you can skip it. If you have the following questions in the process of configuring or developing Tengine/Nginx, this article may be able to answer your questions for many years:
- Which server block does the request arrive at the match?
- Why is the server block explicitly configured, or is it not valid?
- Without the server block of this domain name, which server block is used for the request?
- If you want to match the server block by yourself, where should you start?
......
The problems related to such server blocks, such as Tengine, may often be encountered when using Tengine. It is easier to identify when there are fewer server blocks. But in the case of CDN or cloud platform access layer, there are usually many server blocks, dozens or hundreds of them are few, thousands or thousands of them are possible. So understanding how Tengine finds server blocks is very helpful for routine problem solving.
To configure
Let's start with a few configurations:
server { listen 10.101.192.91:80 default_server; listen 80 default_server; listen 8080 default_server; server_name www.aa.com; default_type text/plain; location / { return 200 "default-server: $server_name, host: $host"; } } server { listen 10.101.192.91:80; server_name www.bb.com; default_type text/plain; location / { return 200 "80server: $server_name, host: $host"; } } server { listen 10.101.192.91:8080; server_name *.bb.com; default_type text/plain; location / { return 200 "8080server: $server_name, host: $host"; } } server { listen 10.101.192.91:8080; server_name www.bb.com; default_type text/plain; location / { return 200 "8080server: $server_name, host: $host"; } }
Four server blocks are configured, and the configuration is very simple. The first server block is configured with default_server parameter, which indicates that this is the meaning of default server block (to be exact, the IP of this listen: the request from Port is the default server block), listens on two ports 80 and 8080, matching the domain name www.a. A.com, the second is to listen to 10.101.192.91:80 and the server block matching the domain name www.bb.com, the third is to listen to 10.101.192.91:8080 and the server block matching the pan-domain name*.bb.com, and the fourth is to listen to 10.101.192.91:8080 and the server block matching the exact domain name www.bb.com. Let's verify that:
It can be seen that:
-
127.0.0.1:80 and 127.0.0.1:8080 have access to the first server block.
- This is because the first server listens on ports: 80 and: 8080, other server blocks do not listen on the corresponding port of 127.0.0.1, and access to 127.0.0.1 can only match the first server block.
-
10.101.192.91:80 access, domain name and server block matching using the corresponding server block, mismatching using the first default server block
- If IP:Port matches, the server block where the domain name is located is matched, and if the domain name does not match the server_name, the default server block is matched.
-
10.101.192.91:8080 access, the domain name first accurately matched to the server block of www.bb.com, and then matched to the server block of the pan-domain name*.bb.com. When mismatched, the third implicit default server block was used.
- This involves pan-domain name and implicit default server block. It is easy to understand that pan-domain name matching is after exact domain name. Implicit default server block is a server block that does not specify default_server parameter after listen. Tengine/Nginx has a default server block for each IP:Port when parsing configuration. If the default_server parameter is explicitly specified after the listen, the server where the listen is located is the default server block of the IP: Port. If the default_server parameter is not explicitly specified, the first server block of the IP: Port is the implicit default server block.
These configurations can derive some debug techniques:
if ($http_x_alicdn_debug_get_server = "on") { return 200 "$server_addr:$server_port, server_name: $server_name"; }
Just bring the request header X-Alicdn-Debug-Get-Server: on to know which server block the request hit. This configuration is very useful for debug, a system with many server blocks. Note that this configuration needs to be placed in a configuration file and loaded with server_auto_include, and then tengine will automatically hit all s. The erver block takes effect (nginx does not have a similar configuration command).
data structure
Let's look at how the configuration of server block, the core module of http, is related to the data structure.
typedef struct { /* array of the ngx_http_server_name_t, "server_name" directive */ ngx_array_t server_names; /* server ctx */ ngx_http_conf_ctx_t *ctx; u_char *file_name; ngx_uint_t line; ngx_str_t server_name; #if (T_NGX_SERVER_INFO) ngx_str_t server_admin; #endif size_t connection_pool_size; size_t request_pool_size; size_t client_header_buffer_size; ngx_bufs_t large_client_header_buffers; ngx_msec_t client_header_timeout; ngx_flag_t ignore_invalid_headers; ngx_flag_t merge_slashes; ngx_flag_t underscores_in_headers; unsigned listen:1; #if (NGX_PCRE) unsigned captures:1; #endif ngx_http_core_loc_conf_t **named_locations; } ngx_http_core_srv_conf_t;
I don't elaborate on what these fields are used for. It mainly depends on how ngx_http_core_srv_conf_t is associated with other data structures. From the configuration above, we can know that server is associated with IP:Port. The relationship in tengine/nginx is as follows:
typedef struct { ngx_http_listen_opt_t opt; ngx_hash_t hash; ngx_hash_wildcard_t *wc_head; ngx_hash_wildcard_t *wc_tail; #if (NGX_PCRE) ngx_uint_t nregex; ngx_http_server_name_t *regex; #endif /* the default server configuration for this address:port */ ngx_http_core_srv_conf_t *default_server; ngx_array_t servers; /* array of ngx_http_core_srv_conf_t */ } ngx_http_conf_addr_t;
As you can see, the default server block default_server and all server block array servers associated with IP:Port are included in the core data structure ngx_http_conf_addr_t. Several other fields are expanded in detail. tengine splits all IP:Port by Port and puts ngx_http_conf_addr_t into ngx_http_conf_port_t:
typedef struct { ngx_int_t family; in_port_t port; ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ } ngx_http_conf_port_t;
Why split IP:Port? This is because if listen's Port does not specify ip, such as listen 80; then tengine/nginx's address when creating listen socket is 0.0.0; if there are other configurations with listen's exact ip and port, such as listen 10.101.192.91:80; then it is impossible to create this in the kernel. Section 2 configures several listeners in a socket to listen on the kernel like this:
Although listened to 80 and 10.101.192.91:80, the kernel is 0.0.0.0:80, so tengine needs to record all exact addresses of the port with ngx_http_conf_port_t. However, this structure is only used in the configuration phase, and converted into structures ngx_http_port_t and ngx_http_in_addr_t when listening to socket s (because ip:port and server blocks are many-to-many relationships and need to be reorganized and optimized):
typedef struct { /* ngx_http_in_addr_t or ngx_http_in6_addr_t */ void *addrs; ngx_uint_t naddrs; } ngx_http_port_t; typedef struct { in_addr_t addr; ngx_http_addr_conf_t conf; } ngx_http_in_addr_t; typdef ngx_http_addr_conf_s ngx_http_addr_conf_t; struct ngx_http_addr_conf_s { /* the default server configuration for this address:port */ ngx_http_core_srv_conf_t *default_server; ngx_http_virtual_names_t *virtual_names; unsigned ssl:1; unsigned http2:1; unsigned proxy_protocol:1; };
Among them, ngx_http_port_t records all the exact addresses of the port and the corresponding server blocks. And ngx_http_port_t is placed in the core structure ngx_listening_t of monitored socket:
typedef struct ngx_listening_s ngx_listening_t; struct ngx_listening_s { ngx_socket_t fd; struct sockaddr *sockaddr; socklen_t socklen; /* size of sockaddr */ size_t addr_text_max_len; ngx_str_t addr_text; // Eliminate... /* handler of accepted connection */ ngx_connection_handler_pt handler; void *servers; /* array of ngx_http_in_addr_t, for example */ // Eliminate... }; struct ngx_connection_s { // Eliminate... ngx_listening_t *listening; // Eliminate... };
So a connection can find a matching server block from C - > listening - > servers.
The general relationship between ip:port and server in tengine is as follows:
(You can use this diagram to understand how tengine looks for server blocks)
From request to server block
This section describes the logic of tengine from processing requests to matching server s. ngx_http_init_connection is a function that initializes connections. In this function, we see the following logic:
void ngx_http_init_connection(ngx_connection_t *c) { // Eliminate... ngx_http_port_t *port; ngx_http_in_addr_t *addr; ngx_http_connection_t *hc; // Eliminate... /* find the server configuration for the address:port */ port = c->listening->servers; if (port->naddrs > 1) { // Eliminate... sin = (struct sockaddr_in *) c->local_sockaddr; addr = port->addrs; /* the last address is "*" */ for (i = 0; i < port->naddrs - 1; i++) { if (addr[i].addr == sin->sin_addr.s_addr) { break; } } hc->addr_conf = &addr[i].conf; // Eliminate... } else { // Eliminate... addr = port->addrs; hc->addr_conf = &addr[0].conf; // Eliminate... } /* the default server configuration for the address:port */ hc->conf_ctx = hc->addr_conf->default_server->ctx; // Eliminate... }
As you can see, when initializing, you get the ip:port of the socket and match the most appropriate configuration. It is stored in the hc-> addr_conf pointer, which is the data structure ngx_http_addr_conf_t pointer mentioned above. It stores all the core configuration of the server block associated with the ip:port, and then receives the HTTP request header. When processing the request line or the Host header, the real server block is matched in hc-> addr_conf according to the domain name:
static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host) { // Eliminate... ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; // Eliminate... hc = r->http_connection; // Eliminate... rc = ngx_http_find_virtual_server(r->connection, hc->addr_conf->virtual_names, host, r, &cscf); //When r is created, r - > srv_conf and r - > loc_conf are the default configurations for HC - > conf_ctx //If the matching server block is not found, there is no need to set r->srv_conf and r->loc_conf if (rc == NGX_DECLINED) { return NGX_OK; } // Find the matching server and use the configuration of the real server block r->srv_conf = cscf->ctx->srv_conf; r->loc_conf = cscf->ctx->loc_conf; // Eliminate... }
The function ngx_http_find_virtual_server is the server block interface for finding the domain name (another call to this function is when dealing with the SSL handshake encountering SNI, because it also needs to find the certificate configured in the matching server block when shaking hands).
At this point, the lookup logic of server block configuration ends, and the server/location block configuration of one's own module can be found from r-> srv_conf and r-> loc_conf in subsequent module processing.
(End of the text)