Overview
During the initial startup process of Nginx, the worker worker process will call the ngx_event_process_init method of the event module to allocate a ngx_connection_t connection for each listening socket ngx_listening_t, and Set up the connection Read event callback method
handler is ngx_event_accept, and mounts the read event to the epoll event mechanism to wait for the read event to occur on the listening socket connection. At this point,
Receive the HTTP request message
Before receiving the HTTP request, the successfully established connection will first be initialized; the function of the ngx_http_init_connection function is to set the callback method for reading and writing events, and the actual writing event The callback method does not perform any operation. The callback method of the read event is to initialize the
ngx_http_init_connection function execution flow:
- Set the callback method handler for the write event on the current connection to ngx_http_empty_handler (actually this method does not perform any operation);
- Set the read event on the current connection The callback method handler is ngx_http_wait_request_handler;
- checks whether the read event on the current connection is ready (i.e. the ready flag is 1):
- If the read event ready flag is 1, it means there is a readable TCP stream on the current connection, then the callback method of the read event will be executed. ngx_http_wait_request_handler;
- If the read event ready flag is 0, indicating that there is no readable TCP stream on the current connection, then the read event will be added to the timer event mechanism (monitoring whether the readable event times out), At the same time, the read event is registered to the epoll event mechanism and waits for the occurrence of a readable event; the
function ngx_http_init_connection is defined in the file src/http/ngx_http_request.c as follows:
void ngx_http_init_connection(ngx_connection_t *c) { ngx_uint_t i; ngx_event_t *rev; struct sockaddr_in *sin; ngx_http_port_t *port; ngx_http_in_addr_t *addr; ngx_http_log_ctx_t *ctx; ngx_http_connection_t *hc; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; ngx_http_in6_addr_t *addr6; #endif /* 分配http连接ngx_http_connection_t结构体空间 */ hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); if (hc == NULL) { ngx_http_close_connection(c); return; } c->data = hc; /* find the server configuration for the address:port */ port = c->listening->servers; if (port->naddrs > 1) { /* * there are several addresses on this port and one of them * is an "*:port" wildcard so getsockname() in ngx_http_server_addr() * is required to determine a server address */ if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { ngx_http_close_connection(c); return; } switch (c->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6) ... #endif default: /* AF_INET */ sin = (struct sockaddr_in *) c->local_sockaddr; addr = port->addrs; /* the last address is "*" */ for (i = 0; i naddrs - 1; i++) { if (addr[i].addr == sin->sin_addr.s_addr) { break; } } hc->addr_conf = &addr[i].conf; break; } } else { switch (c->local_sockaddr->sa_family) { #if (NGX_HAVE_INET6) ... #endif default: /* AF_INET */ addr = port->addrs; hc->addr_conf = &addr[0].conf; break; } } /* the default server configuration for the address:port */ hc->conf_ctx = hc->addr_conf->default_server->ctx; ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); if (ctx == NULL) { ngx_http_close_connection(c); return; } ctx->connection = c; ctx->request = NULL; ctx->current_request = NULL; /* 设置当前连接的日志属性 */ c->log->connection = c->number; c->log->handler = ngx_http_log_error; c->log->data = ctx; c->log->action = "waiting for request"; c->log_error = NGX_ERROR_INFO; /* 设置当前连接读、写事件的handler处理方法 */ rev = c->read; /* 设置当前连接读事件的处理方法handler为ngx_http_wait_request_handler */ rev->handler = ngx_http_wait_request_handler; /* * 设置当前连接写事件的处理方法handler为ngx_http_empty_handler, * 该方法不执行任何实际操作,只记录日志; * 因为处理请求的过程不需要write方法; */ c->write->handler = ngx_http_empty_handler; #if (NGX_HTTP_SPDY) ... #endif #if (NGX_HTTP_SSL) ... #endif if (hc->addr_conf->proxy_protocol) { hc->proxy_protocol = 1; c->log->action = "reading PROXY protocol"; } /* 若读事件准备就绪,则判断是否使用同步锁, * 根据同步锁情况判断决定是否立即处理该事件; */ if (rev->ready) { /* the deferred accept(), rtsig, aio, iocp */ /* * 若使用了同步锁ngx_use_accept_mutex, * 则将该读事件添加到待处理事件队列ngx_post_event中, * 直到退出锁时,才处理该读事件; */ if (ngx_use_accept_mutex) { ngx_post_event(rev, &ngx_posted_events); return; } /* 若没有使用同步锁,则直接处理该读事件; * 读事件的处理函数handler为ngx_http_wait_request_handler; */ rev->handler(rev); return; } /* * 若当前连接的读事件未准备就绪, * 则将其添加到定时器事件机制,并注册到epoll事件机制中; */ /* 将当前连接的读事件添加到定时器机制中 */ ngx_add_timer(rev, c->listening->post_accept_timeout); ngx_reusable_connection(c, 1); /* 将当前连接的读事件注册到epoll事件机制中 */ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_connection(c); return; } }
When connected When a readable event occurs for the first time, the ngx_http_wait_request_handler function will be called. The function of this function is to initialize the
ngx_http_wait_request_handler Function execution flow:
- 首先判断当前读事件是否超时(即读事件的 timedout 标志位是否为1):
- 若 timedout 标志位为1,表示当前读事件已经超时,则调用ngx_http_close_connection 方法关闭当前连接,return 从当前函数返回;
- 若 timedout 标志位为0,表示当前读事件还未超时,则继续检查当前连接的close标志位;
- 若当前连接的 close 标志位为1,表示当前连接要关闭,则调用ngx_http_close_connection 方法关闭当前连接,return 从当前函数返回;
- 若当前连接的 close 标志位为0,表示不需要关闭当前连接,进而调用recv() 函数尝试从当前连接所对应的套接字缓冲区中接收数据,这个步骤是为了确定客户端是否真正的发送请求数据,以免因为客户端不发送实际请求数据,出现初始化请求而导致内存被消耗。根据所读取的数据情况n 来判断是否要真正进行初始化请求工作:
- 若 n = NGX_AGAIN,表示客户端发起连接请求,但是暂时还没发送实际的数据,则将当前连接上的读事件添加到定时器机制中,同时将读事件注册到epoll 事件机制中,return 从当前函数返回;
- 若 n = NGX_ERROR,表示当前连接出错,则直接调用ngx_http_close_connection 关闭当前连接,return 从当前函数返回;
- 若 n = 0,表示客户端已经主动关闭当前连接,所有服务器端调用ngx_http_close_connection 关闭当前连接,return 从当前函数返回;
- 若 n 大于 0,表示读取到实际的请求数据,因此决定开始初始化当前请求,继续往下执行;
- 调用 ngx_http_create_request 方法构造ngx_http_request_t 请求结构体,并设置到当前连接的data 成员;
- 设置当前读事件的回调方法为 ngx_http_process_request_line,并执行该回调方法开始接收并解析请求行;
函数 ngx_http_wait_request_handler 在文件src/http/ngx_http_request.c 中定义如下:
/* 处理连接的可读事件 */ static void ngx_http_wait_request_handler(ngx_event_t *rev) { u_char *p; size_t size; ssize_t n; ngx_buf_t *b; ngx_connection_t *c; ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; /* 获取读事件所对应的连接ngx_connection_t 对象 */ c = rev->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler"); /* 若当前读事件超时,则记录错误日志,关闭所对应的连接并退出 */ if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ngx_http_close_connection(c); return; } /* 若当前读事件所对应的连接设置关闭连接标志位,则关闭该链接 */ if (c->close) { ngx_http_close_connection(c); return; } /* 若当前读事件不超时,且其所对应的连接不设置close标志位,则继续指向以下语句 */ hc = c->data; /* 获取当前读事件请求的相关配置项结构 */ cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); size = cscf->client_header_buffer_size; /* 以下内容是接收缓冲区的操作 */ b = c->buffer; /* 若当前连接的接收缓冲区不存在,则创建该接收缓冲区 */ if (b == NULL) { b = ngx_create_temp_buf(c->pool, size); if (b == NULL) { ngx_http_close_connection(c); return; } c->buffer = b; } else if (b->start == NULL) { /* 若当前接收缓冲区存在,但是为空,则为其分配内存 */ b->start = ngx_palloc(c->pool, size); if (b->start == NULL) { ngx_http_close_connection(c); return; } /* 初始化接收缓冲区各成员指针 */ b->pos = b->start; b->last = b->start; b->end = b->last + size; } /* 在当前连接上开始接收HTTP请求数据 */ n = c->recv(c, b->last, size); if (n == NGX_AGAIN) { if (!rev->timer_set) { ngx_add_timer(rev, c->listening->post_accept_timeout); ngx_reusable_connection(c, 1); } if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_connection(c); return; } /* * We are trying to not hold c->buffer's memory for an idle connection. */ if (ngx_pfree(c->pool, b->start) == NGX_OK) { b->start = NULL; } return; } if (n == NGX_ERROR) { ngx_http_close_connection(c); return; } if (n == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client closed connection"); ngx_http_close_connection(c); return; } /* 若接收HTTP请求数据成功,则调整接收缓冲区成员指针 */ b->last += n; if (hc->proxy_protocol) { hc->proxy_protocol = 0; p = ngx_proxy_protocol_parse(c, b->pos, b->last); if (p == NULL) { ngx_http_close_connection(c); return; } b->pos = p; if (b->pos == b->last) { c->log->action = "waiting for request"; b->pos = b->start; b->last = b->start; ngx_post_event(rev, &ngx_posted_events); return; } } c->log->action = "reading client request line"; ngx_reusable_connection(c, 0); /* 为当前连接创建一个请求结构体ngx_http_request_t */ c->data = ngx_http_create_request(c); if (c->data == NULL) { ngx_http_close_connection(c); return; } /* 设置当前读事件的处理方法为ngx_http_process_request_line */ rev->handler = ngx_http_process_request_line; /* 执行该读事件的处理方法ngx_http_process_request_line,接收HTTP请求行 */ ngx_http_process_request_line(rev); }
接收 HTTP 请求行
ngx_http_process_request_line 处理
- 首先,判断当前请求是否超时,若超时(即读事件的 timedout 标志位为1),则设置当前连接的超时标志位为 1(c->timedout = 1),调用 ngx_http_close_request 方法关闭该请求,并 return 从当前函数返回;
- 若当前请求未超时(读事件的 timedout 标志位为 0),调用 ngx_http_read_request_header 方法开始读取当前请求行,根据该函数的返回值n 进行以下判断:
- 若返回值 n = NGX_AGAIN,表示当前连接上套接字缓冲区不存在可读TCP 流,则需将当前读事件添加到定时器机制,注册到 epoll 事件机制中,等待可读事件发生。return 从当前函数返回;
- 若返回值 n = NGX_ERROR,表示当前连接出错,则调用ngx_http_finalize_request 方法结束请求,return 从当前函数返回;
- 若返回值 n 大于 0,表示读取请求行成功,调用函数 ngx_http_parse_request_line 开始解析由函数ngx_http_read_request_header 读取所返回的请求行,根据函数ngx_http_parse_request_line 函数返回值rc 不同进行判断;
- 若返回值 rc = NGX_ERROR,表示解析请求行时出错,此时,调用ngx_http_finalize_request 方法终止该请求,并return 从当前函数返回;
- 若返回值 rc = NGX_AGAIN,表示没有解析到完整的请求行,即仍需接收请求行,首先根据要求调整接收缓冲区header_in 的内存空间,则继续调用函数ngx_http_read_request_header 读取请求数据进入请求行自动处理机制,直到请求行解析完毕;
- 若返回值 rc = NGX_OK,表示解析到完整的
HTTP 请求行,则设置请求行的成员信息(例如:方法名称、URI 参数、 HTTP 版本等信息); - 若
HTTP 协议版本小于 1.0 版本,表示不需要处理 HTTP 请求头部,则直接调用函数ngx_http_process_request 处理该请求,return 从当前函数返回; - 若HTTP协议版本不小于 1.0 版本,表示需要处理HTTP请求头部:
- 调用函数 ngx_list_init 初始化保存
HTTP 请求头部的结构体 ngx_http_request_t 中成员headers_in 链表容器(该链表缓冲区是保存所接收到的 HTTP 请求数据); - 设置当前读事件的回调方法为 ngx_http_process_request_headers 方法,并调用该方法ngx_http_process_request_headers 开始处理
HTTP 请求头部。return 从当前函数返回;
- 调用函数 ngx_list_init 初始化保存
- 若
函数 ngx_http_process_request_line 在文件src/http/ngx_http_request.c 中定义如下:
/* 处理HTTP请求行 */ static void ngx_http_process_request_line(ngx_event_t *rev) { ssize_t n; ngx_int_t rc, rv; ngx_str_t host; ngx_connection_t *c; ngx_http_request_t *r; /* 获取当前读事件所对应的连接 */ c = rev->data; /* 获取连接中所对应的请求结构 */ r = c->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http process request line"); /* 若当前读事件超时,则进行相应地处理,并关闭当前请求 */ if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); c->timedout = 1; ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); return; } /* 设置NGX_AGAIN标志,表示请求行还没解析完毕 */ rc = NGX_AGAIN; for ( ;; ) { /* 若请求行还没解析完毕,则继续解析 */ if (rc == NGX_AGAIN) { /* 读取当前请求未解析的数据 */ n = ngx_http_read_request_header(r); /* 若没有数据,或读取失败,则直接退出 */ if (n == NGX_AGAIN || n == NGX_ERROR) { return; } } /* 解析接收缓冲区header_in中的请求行 */ rc = ngx_http_parse_request_line(r, r->header_in); /* 若请求行解析完毕 */ if (rc == NGX_OK) { /* the request line has been parsed successfully */ /* 设置请求行的成员,请求行是ngx_str_t类型 */ r->request_line.len = r->request_end - r->request_start; r->request_line.data = r->request_start; /* 设置请求长度,包括请求头部、请求包体 */ r->request_length = r->header_in->pos - r->request_start; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request line: \"%V\"", &r->request_line); /* 设置请求方法名称字符串 */ r->method_name.len = r->method_end - r->request_start + 1; r->method_name.data = r->request_line.data; /* 设置HTTP请求协议 */ if (r->http_protocol.data) { r->http_protocol.len = r->request_end - r->http_protocol.data; } /* 处理请求中的URI */ if (ngx_http_process_request_uri(r) != NGX_OK) { return; } if (r->host_start && r->host_end) { host.len = r->host_end - r->host_start; host.data = r->host_start; rc = ngx_http_validate_host(&host, r->pool, 0); if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid host in request line"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return; } if (rc == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { return; } r->headers_in.server = host; } /* 设置请求协议版本 */ if (r->http_version headers_in.server.len == 0 && ngx_http_set_virtual_server(r, &r->headers_in.server) == NGX_ERROR) { return; } /* 若HTTP版本小于1.0版本,则表示不需要接收HTTP请求头部,则直接处理请求 */ ngx_http_process_request(r); return; } /* 初始化链表容器,为接收HTTP请求头部做准备 */ if (ngx_list_init(&r->headers_in.headers, r->pool, 20, sizeof(ngx_table_elt_t)) != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } c->log->action = "reading client request headers"; /* 若请求行解析完毕,则接下来处理请求头部 */ /* 设置连接读事件的回调方法 */ rev->handler = ngx_http_process_request_headers; /* 开始处理HTTP请求头部 */ ngx_http_process_request_headers(rev); return; } /* 解析请求行出错 */ if (rc != NGX_AGAIN) { /* there was error while a request line parsing */ ngx_log_error(NGX_LOG_INFO, c->log, 0, ngx_http_client_errors[rc - NGX_HTTP_CLIENT_ERROR]); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return; } /* NGX_AGAIN: a request line parsing is still incomplete */ /* 请求行仍然未解析完毕,则继续读取请求数据 */ /* 若当前接收缓冲区内存不够,则分配更大的内存空间 */ if (r->header_in->pos == r->header_in->end) { rv = ngx_http_alloc_large_header_buffer(r, 1); if (rv == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (rv == NGX_DECLINED) { r->request_line.len = r->header_in->end - r->request_start; r->request_line.data = r->request_start; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long URI"); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE); return; } } } }
在接收并解析请求行的过程中会调用 ngx_http_read_request_header 读取请求数据,我们看下该函数是如何读取到请求数据的。
ngx_http_read_request_header 读取请求数据函数执行流程:
- 检测当前请求的接收缓冲区 header_in 是否有数据,若有直接返回该数据n;
- 若接收缓冲区 header_in 没有数据,检查当前读事件是否准备就绪(即判断ready 标志位是否为
0 ):
- 若当前读事件未准备就绪(即当前读事件 ready 标志位为0),则设置返回值 n= NGX_AGAIN;
- 若当前读事件已经准备就绪(即 ready 标志位为 1),则调用 recv() 方法从当前连接套接字中读取数据并保存到接收缓冲区header_in 中,并设置 n 为 recv() 方法所读取的数据的返回值;
- 下面根据 n 的取值执行不同的操作:
- 若 n = NGX_AGAIN(此时,n 的值可能当前事件未准备就绪而设置的NGX_AGAIN,也可能是 recv() 方法返回的 NGX_AGAIN 值,但是只能是其中一种情况),将当前读事件添加到定时器事件机制中, 将当前读事件注册到epoll 事件机制中,等待事件可读,n 从当前函数返回;
- 若 n = 0 或 n = ERROR,则调用 ngx_http_finalize_request 结束请求,并返回NGX_ERROR 退出当前函数;
函数 ngx_http_read_request_header 在文件src/http/ngx_http_request.c 中定义如下:
static ssize_t ngx_http_read_request_header(ngx_http_request_t *r) { ssize_t n; ngx_event_t *rev; ngx_connection_t *c; ngx_http_core_srv_conf_t *cscf; /* 获取当前请求所对应的连接 */ c = r->connection; /* 获取当前连接的读事件 */ rev = c->read; /* 获取当前请求接收缓冲区的数据,header_in 是ngx_buf_t类型 */ n = r->header_in->last - r->header_in->pos; /* 若接收缓冲区有数据,则直接返回该数据 */ if (n > 0) { return n; } /* 若当前接收缓冲区没有数据,首先判断当前读事件是否准备就绪 */ if (rev->ready) { /* 若当前读事件已准备就绪,则从其所对应的连接套接字读取数据,并保存到接收缓冲区中 */ n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last); } else { /* 若接收缓冲区没有数据,且读事件未准备就绪,则设置为NGX_AGAIN */ n = NGX_AGAIN; } /* 若接收缓冲区没有数据,且读事件未准备就绪,则设置为NGX_AGAIN */ /* 将当前读事件添加到定时器机制; * 将当前读事件注册到epoll事件机制; */ if (n == NGX_AGAIN) { if (!rev->timer_set) { cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); /* 将当前读事件添加到定时器机制中 */ ngx_add_timer(rev, cscf->client_header_timeout); } /* 将当前读事件注册到epoll事件机制中 */ if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } return NGX_AGAIN; } if (n == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection"); } if (n == 0 || n == NGX_ERROR) { c->error = 1; c->log->action = "reading client request headers"; ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } r->header_in->last += n; return n; }
接收 HTTP 请求头部
前面已经成功接收并解析了
ngx_http_process_request_headers 处理
- 首先,判断当前请求读事件是否超时,若超时(即读事件的 timedout 标志位为1),则设置当前连接超时标志位为 1(c->timedout = 1),并调用 ngx_http_close_request 方法关闭该请求,并 return 从当前函数返回;
- 若当前请求读事件未超时(即读事件的 timedout 标志位为0),检查接收
HTTP 请求头部的 header_in 缓冲区是否有剩余内存空间,若没有剩余的内存空间,则调用ngx_http_alloc_large_header_buffer 方法分配更大的缓冲区。若有剩余的内存,则无需再分配内存空间。 - 调用 ngx_http_read_request_header 方法开始读取当前请求头部保存到header_in 缓冲区中,根据该函数的返回值 n 进行以下判断:
- 若返回值 n = NGX_AGAIN,表示当前连接上套接字缓冲区不存在可读TCP 流,则需将当前读事件添加到定时器机制,注册到 epoll 事件机制中,等待可读事件发生。return 从当前函数返回;
- 若返回值 n = NGX_ERROR,表示当前连接出错,则调用ngx_http_finalize_request 方法结束请求,return 从当前函数返回;
- 若返回值 n 大于 0,表示读取请求头部成功,调用函数 ngx_http_parse_request_line 开始解析由函数ngx_http_read_request_header 读取所返回的请求头部,根据函数ngx_http_parse_request_line 函数返回值rc不同进行判断;
- 若返回值 rc = NGX_ERROR,表示解析请求行时出错,此时,调用ngx_http_finalize_request 方法终止该请求,并return 从当前函数返回;
- 若返回值 rc = NGX_AGAIN,表示没有解析到完整一行的请求头部,仍需继续接收TCP 字符流才能够是完整一行的请求头部,则 continue 继续调用函数ngx_http_read_request_header 和ngx_http_parse_request_line 方法读取并解析下一行请求头部,直到全部请求头部解析完毕;
- 若返回值 rc = NGX_OK,表示解析出一行
HTTP 请求头部(注意:一行请求头部只是整个请求头部的一部分),判断当前解析出来的一行请求头部是否合法,若非法,则忽略当前一行请求头部,继续读取并解析下一行请求头部。若合法,则调用ngx_list_push 方法将该行请求头部设置到当前请求 ngx_http_request_t 结构体 header_in 缓冲区成员的headers 链表中,设置请求头部名称的 hash 值,并 continue 继续调用函数 ngx_http_read_request_header 和 ngx_http_parse_request_line 方法读取并解析下一行请求头部,直到全部请求头部解析完毕; - 若返回值 rc = NGX_HTTP_PARSE_HEADER_DONE,则表示已经读取并解析出全部请求头部,此时,调用ngx_http_process_request 方法开始处理请求,return 从当前函数返回;
函数 ngx_http_process_request_headers 在文件src/http/ngx_http_request.c 中定义如下:
/* 处理HTTP请求头部 */ static void ngx_http_process_request_headers(ngx_event_t *rev) { u_char *p; size_t len; ssize_t n; ngx_int_t rc, rv; ngx_table_elt_t *h; ngx_connection_t *c; ngx_http_header_t *hh; ngx_http_request_t *r; ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; /* 获取当前读事件所对应的连接 */ c = rev->data; /* 获取当前连接的HTTP请求 */ r = c->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http process request header line"); /* 若当前读事件超时,则关闭该请求,并退出 */ if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); c->timedout = 1; ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); return; } /* 获取ngx_http_core_module模块的main级别配置项结构 */ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); /* 表示当前请求头部未解析完毕 */ rc = NGX_AGAIN; for ( ;; ) { if (rc == NGX_AGAIN) { /* 若当前请求头部未解析完毕,则首先判断接收缓冲区是否有内存空间再次接收请求数据 */ if (r->header_in->pos == r->header_in->end) { /* 若接收缓冲区没有足够内存空间,则分配更大的内存空间 */ rv = ngx_http_alloc_large_header_buffer(r, 0); if (rv == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (rv == NGX_DECLINED) { p = r->header_name_start; r->lingering_close = 1; if (p == NULL) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent too large request"); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); return; } len = r->header_in->end - p; if (len > NGX_MAX_ERROR_STR - 300) { len = NGX_MAX_ERROR_STR - 300; p[len++] = '.'; p[len++] = '.'; p[len++] = '.'; } ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long header line: \"%*s\"", len, r->header_name_start); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); return; } } /* 读取未解析请求数据 */ n = ngx_http_read_request_header(r); /* 若没有可读的数据,或读取失败,则直接退出 */ if (n == NGX_AGAIN || n == NGX_ERROR) { return; } } /* the host header could change the server configuration context */ /* 获取ngx_http_core_module模块的srv级别配置项结构 */ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); /* 开始解析HTTP请求头部 */ rc = ngx_http_parse_header_line(r, r->header_in, cscf->underscores_in_headers); /* 解析出一行请求头部(注意:一行请求头部只是HTTP请求头部的一部分) */ if (rc == NGX_OK) { /* 设置当前请求的长度 */ r->request_length += r->header_in->pos - r->header_name_start; /* * 若当前解析出来的一行请求头部是非法的,或Nginx当前版本不支持, * 则记录错误日志,并继续解析下一行请求头部; */ if (r->invalid_header && cscf->ignore_invalid_headers) { /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid header line: \"%*s\"", r->header_end - r->header_name_start, r->header_name_start); continue; } /* a header line has been parsed successfully */ /* * 若当前解析出来的一行请求头部是合法的,表示成功解析出该行请求头部, * 将该行请求头部保存在当前请求的headers_in的headers链表中; * 接着继续解析下一行请求头部; */ h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } /* 设置请求头部名称的hash值 */ h->hash = r->header_hash; h->key.len = r->header_name_end - r->header_name_start; h->key.data = r->header_name_start; h->key.data[h->key.len] = '\0'; h->value.len = r->header_end - r->header_start; h->value.data = r->header_start; h->value.data[h->value.len] = '\0'; h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (h->key.len == r->lowcase_index) { ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); } else { ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { return; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header: \"%V: %V\"", &h->key, &h->value); continue; } /* 若成功解析所有请求头部,则接下来就开始处理该请求 */ if (rc == NGX_HTTP_PARSE_HEADER_DONE) { /* a whole header has been parsed successfully */ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header done"); r->request_length += r->header_in->pos - r->header_name_start; /* 设置当前请求的解析状态 */ r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; /* * 调用该函数主要目的有两个: * 1、根据HTTP头部的host字段,调用ngx_http_find_virtual_server查找虚拟主机的配置块; * 2、对HTTP请求头部协议版本进行检查,例如http1.1版本,host头部不能为空,否则会返回400 Bad Request错误; */ rc = ngx_http_process_request_header(r); if (rc != NGX_OK) { return; } /* 开始处理当前请求 */ ngx_http_process_request(r); return; } /* 表示当前行的请求头部未解析完毕,则继续读取请求数据进行解析 */ if (rc == NGX_AGAIN) { /* a header line parsing is still not complete */ continue; } /* rc == NGX_HTTP_PARSE_INVALID_HEADER: "\r" is not followed by "\n" */ /* 解析请求头部出错,则关闭该请求,并退出 */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid header line: \"%*s\\r...\"", r->header_end - r->header_name_start, r->header_name_start); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return; } }
处理 HTTP 请求
前面的步骤已经接收到完整的
ngx_http_process_request 处理
- 若当前读事件在定时器机制中,则调用 ngx_del_timer 函数将其从定时器机制中移除,因为在处理
HTTP 请求时不存在接收 HTTP 请求头部超时的问题; - 由于处理
HTTP 请求不需要再接收 HTTP 请求行或头部,则需重新设置当前连接读、写事件的回调方法,读、写事件的回调方法都设置为 ngx_http_request_handler,即后续处理 HTTP 请求的过程都是通过该方法进行; - 设置当前请求 ngx_http_request_t 结构体中的成员read_event_handler 的回调方法为 ngx_http_block_reading,该回调方法实际不做任何操作,即在处理请求时不会对请求的读事件进行任何处理,除非某个HTTP模块重新设置该回调方法;
- 接下来调用函数 ngx_http_handler 开始处理
HTTP 请求; - 调用函数 ngx_http_run_posted_requests 处理post 子请求;
函数 ngx_http_process_request 在文件src/http/ngx_http_request.c 中定义如下:
/* 处理HTTP请求 */ void ngx_http_process_request(ngx_http_request_t *r) { ngx_connection_t *c; /* 获取当前请求所对应的连接 */ c = r->connection; #if (NGX_HTTP_SSL) ... #endif /* * 由于现在不需要再接收HTTP请求头部超时问题, * 则需要把当前连接的读事件从定时器机制中删除; * timer_set为1表示读事件已添加到定时器机制中, * 则将其从定时器机制中删除,0表示不在定时器机制中; */ if (c->read->timer_set) { ngx_del_timer(c->read); } #if (NGX_STAT_STUB) ... #endif /* 重新设置当前连接的读、写事件的回调方法 */ c->read->handler = ngx_http_request_handler; c->write->handler = ngx_http_request_handler; /* * 设置请求读事件的回调方法, * 其实ngx_http_block_reading函数实际对读事件不做任何处理; * 即在处理请求时,不会对读事件任何操作,除非有HTTP模块重新设置处理方法; */ r->read_event_handler = ngx_http_block_reading; /* 开始处理各个HTTP模块的handler方法,该函数定义于ngx_http_core_module.c中*/ ngx_http_handler(r); /* 处理post请求 */ ngx_http_run_posted_requests(c); }
ngx_http_handler 函数的执行流程:
- 检查当前请求 ngx_http_request_t 的 internal 标志位:
- 若 internal 标志位为 0,表示当前请求不需要重定向,判断是否使用 keepalive 机制,并设置phase_handler 序号为 0,表示执行 ngx_http_phase_engine_t 结构成员ngx_http_phase_handler_t *handlers数组中的第一个回调方法;
- 若 internal 标志位为 1,表示需要将当前请求做内部跳转,并将 phase_handler 设置为server_rewriter_index,表示执行 ngx_http_phase_engine_t 结构成员ngx_http_phase_handler_t *handlers 数组在NGX_HTTP_SERVER_REWRITE_PHASE 处理阶段的第一个回调方法;
- 设置当前请求 ngx_http_request_t 的成员写事件write_event_handler 为 ngx_http_core_run_phases;
- 执行n gx_http_core_run_phases 方法;
函数 ngx_http_handler 在文件 src/http/ngx_http_core_module.c 中定义如下:
void ngx_http_handler(ngx_http_request_t *r) { ngx_http_core_main_conf_t *cmcf; r->connection->log->action = NULL; r->connection->unexpected_eof = 0; /* 若当前请求的internal标志位为0,表示不需要重定向 */ if (!r->internal) { /* 下面语句是决定是否使用keepalive机制 */ switch (r->headers_in.connection_type) { case 0: r->keepalive = (r->http_version > NGX_HTTP_VERSION_10); break; case NGX_HTTP_CONNECTION_CLOSE: r->keepalive = 0; break; case NGX_HTTP_CONNECTION_KEEP_ALIVE: r->keepalive = 1; break; } /* 设置延迟关闭标志位 */ r->lingering_close = (r->headers_in.content_length_n > 0 || r->headers_in.chunked); /* * phase_handler序号设置为0,表示执行ngx_http_phase_engine_t结构体成员 * ngx_http_phase_handler_t *handlers数组中的第一个回调方法; */ r->phase_handler = 0; } else { /* 若当前请求的internal标志位为1,表示需要做内部跳转 */ /* 获取ngx_http_core_module模块的main级别的配置项结构 */ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); /* * 将phase_handler序号设为server_rewriter_index, * 该phase_handler序号是作为ngx_http_phase_engine_t结构中成员 * ngx_http_phase_handler_t *handlers回调方法数组的序号, * 即表示回调方法在该数组中所处的位置; * * server_rewrite_index则是handlers数组中NGX_HTTP_SERVER_REWRITE_PHASE阶段的 * 第一个ngx_http_phase_handler_t回调的方法; */ r->phase_handler = cmcf->phase_engine.server_rewrite_index; } r->valid_location = 1; #if (NGX_HTTP_GZIP) r->gzip_tested = 0; r->gzip_ok = 0; r->gzip_vary = 0; #endif /* 设置当前请求写事件的回调方法 */ r->write_event_handler = ngx_http_core_run_phases; /* * 执行该回调方法,将调用各个HTTP模块共同处理当前请求, * 各个HTTP模块按照11个HTTP阶段进行处理; */ ngx_http_core_run_phases(r); }
ngx_http_core_run_phases 函数的执行流程:
- 判断每个 ngx_http_phase_handler_t 处理阶段是否实现checker 方法:
- 若实现 checker 方法,则执行 phase_handler 序号在 ngx_http_phase_handler_t *handlers数组中指定的
checker 方法;执行完 checker 方法,若返回 NGX_OK 则退出;若返回非NGX_OK,则继续执行下一个
HTTP 模块在该阶段的 checker 方法; - 若没有实现 checker 方法,则直接退出;
- 若实现 checker 方法,则执行 phase_handler 序号在 ngx_http_phase_handler_t *handlers数组中指定的
checker 方法;执行完 checker 方法,若返回 NGX_OK 则退出;若返回非NGX_OK,则继续执行下一个
函数 ngx_http_core_run_phases 在文件src/http/ngx_http_core_module.c 中定义如下:
void ngx_http_core_run_phases(ngx_http_request_t *r) { ngx_int_t rc; ngx_http_phase_handler_t *ph; ngx_http_core_main_conf_t *cmcf; /* 获取ngx_http_core_module模块的main级别的配置项结构体 */ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); /* 获取各个HTTP模块处理请求的回调方法数组 */ ph = cmcf->phase_engine.handlers; /* 若实现了checker方法 */ while (ph[r->phase_handler].checker) { /* 执行phase_handler序号在数组中指定的checker方法 */ rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]); /* 成功执行checker方法,则退出,否则继续执行下一个HTTP模块的checker方法 */ if (rc == NGX_OK) { return; } } }
处理子请求
post 子请求是基于 subrequest 机制的,首先看下 post 子请求结构体类型:
/* 子请求的单链表结构 */ typedef struct ngx_http_posted_request_s ngx_http_posted_request_t; struct ngx_http_posted_request_s { /* 指向当前待处理子请求的ngx_http_request_t结构体 */ ngx_http_request_t *request; /* 指向下一个子请求 */ ngx_http_posted_request_t *next; };
在请求结构体 ngx_http_request_t 中有一个与post 子请求相关的成员 posted_requests,该成员把各个 post 子请求按照子请求结构体ngx_http_posted_request_t 的结构连接成单链表的形式,请求结构体ngx_http_request_t 中 main 成员是子请求的原始请求,parent 成员是子请求的父请求。下面是子请求的处理过程。
ngx_http_run_posted_requests 函数执行流程:
- 判断当前连接是否已被销毁(即标志位 destroyed 是否为0),若被销毁则直接 return 退出,否则继续执行;
- 获取原始请求的子请求链表,若子请求链表为空(表示没有 post 请求)则直接return 退出,否则继续执行;
- 遍历子请求链表,执行每个 post 请求的写事件回调方法write_event_handler;
函数 ngx_http_run_posted_requests 在文件src/http/ngx_http_request.c 中定义如下:
void ngx_http_run_posted_requests(ngx_connection_t *c) { ngx_http_request_t *r; ngx_http_log_ctx_t *ctx; ngx_http_posted_request_t *pr; for ( ;; ) { /* 若当前连接已被销毁,则直接退出 */ if (c->destroyed) { return; } /* 获取当前连接所对应的请求 */ r = c->data; /* 获取原始请求的子请求单链表 */ pr = r->main->posted_requests; /* 若子请求单链表为空,则直接退出 */ if (pr == NULL) { return; } /* 将原始请求的posted_requests指向单链表的下一个post请求 */ r->main->posted_requests = pr->next; /* 获取子请求链表中的第一个post请求 */ r = pr->request; ctx = c->log->data; ctx->current_request = r; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http posted request: \"%V?%V\"", &r->uri, &r->args); /* * 调用当前post请求写事件的回调方法write_event_handler; * 子请求不被网络事件驱动,因此不需要调用read_event_handler; */ r->write_event_handler(r); } }
处理 HTTP 请求包体
下面开始要分析
其中有一个很重要的成员就是请求结构体 ngx_http_request_t 中的引用计数count,引用计数是用来决定是否真正结束当前请求,若引用计数为0 时,表示没有其他动作在处理该请求,则可以终止该请求;若引用计数不为0 时,表示当前请求还有其他动作在操作,因此不能结束当前请求,以免发生错误;那怎么样控制这个引用计数呢?例如,当一个请求添加新事件,或是把一些原本从定时器、epoll 事件机制中移除的事件从新加入到其中等等,出现这些情况都是要对引用计数增加1;当要结束请求时,首先会把引用计数减 1,并判断该引用计数是否为 0,再进一步判断是否决定真的结束当前请求。
接收 HTTP 请求包体
/* 存储HTTP请求包体的结构体ngx_http_request_body_t */ typedef struct { /* 存放HTTP请求包体的临时文件 */ ngx_temp_file_t *temp_file; /* * 指向接收HTTP请求包体的缓冲区链表表头, * 因为当一个缓冲区ngx_buf_t无法容纳所有包体时,就需要多个缓冲区形成链表; */ ngx_chain_t *bufs; /* 指向当前保存HTTP请求包体的缓冲区 */ ngx_buf_t *buf; /* * 根据content-length头部和已接收包体长度,计算还需接收的包体长度; * 即当前剩余的请求包体大小; */ off_t rest; /* 接收HTTP请求包体缓冲区链表空闲缓冲区 */ ngx_chain_t *free; /* 接收HTTP请求包体缓冲区链表已使用的缓冲区 */ ngx_chain_t *busy; /* 保存chunked的解码状态,供ngx_http_parse_chunked方法使用 */ ngx_http_chunked_t *chunked; /* * HTTP请求包体接收完毕后执行的回调方法; * 即ngx_http_read_client_request_body方法传递的第 2 个参数; */ ngx_http_client_body_handler_pt post_handler; } ngx_http_request_body_t;
接收
- Original request reference calculationr->main->count increases 1; the management of reference count count is: when the logic starts the process, the reference count increases 1, and when the process ends, The reference count is decremented 1. In the ngx_http_read_client_request_body function, first increase the reference count of the original request by 1. When an abnormal termination is encountered, the reference count will be decremented before the function returns. 1; If it ends normally, the reference count will continue to be maintained by the post_handler callback method;
- Judge whether the current request body has been completely received (r->request_body is 1) or discarded (r- >discard_bodyfor 1), if one of them is met, there is no need to receive the request package body again, and the post_handler callback method is executed directly, and NGX_OK is returned from the current function;
- If you need to receive the
HTTP request package body, first call the ngx_http_test_expect method to check whether the client sends the Expect:100-continue header to expect to send the request package body, and the server will Reply HTTP/1.1 100 Continue Indicates that the client is allowed to send the request body; - Allocate the current request ngx_http_request_t member of the structure request_body, ready to receive the request body;
- Check the content-length header of the request , if the content-length field in the request header is less than 0, it means that there is no need to continue receiving the request packet body (that is, the complete request packet body has been received), and the post_handler callback method is executed directly, and NGX_OK Return from the current function;
- If the content-length field in the request header is greater than 0, it means that the request body needs to continue to be received. First, determine whether the header_in member of the current request ngx_http_request_t contains unprocessed data. If there is unprocessed data, it means that the buffer header_in has pre-received the request packet body during the reception of the request header, because during the reception
HTTP It is possible to pre-receive the request body during the request header period. Since the request header has been received before the request body is received, if there is unprocessed data in the buffer, it is the request body. - If there is unprocessed data in the header_in buffer, which is a pre-received request packet body, first check whether the buffer request packet body length preread is greater than the request packet body length
content-length field, if it is greater than it means that the complete
HTTP Request package body, if there is no need to continue receiving, execute the post_handler callback method; - If there is unprocessed data in the header_in buffer, it is the pre-received request package body, but the buffer request package body The length preread is less than the length of the request packet body
The content-length field indicates that the received request body is incomplete, and you need to continue receiving the request body.Call functions
ngx_http_request_body_filte Parse and mount the received request body to the request
ngx_http_request_t r of request_body->bufs, header_in The remaining space in the buffer is enough to receive the remaining request packet size rest, so there is no need to allocate a new buffer and then set the current request
The read_event_handler read event callback method of ngx_http_request_t is ngx_http_read_client_request_body_handler, and the write event write_event_handler callback method is
ngx_http_request_empty_handler (i.e. do not perform any operation), and then call the method ngx_http_do_read_client_request_body to actually receive the
HTTP request body, which will TCP Read all the character streams in the socket buffer on the connection, and determine whether it needs to be written to a temporary file, and whether to receive all the request packet bodies. At the same time, execute the callback method after receiving the complete packet body. post_handler; - If there is unprocessed data in the header_in buffer, it is the pre-received request packet body, but the buffer request packet body length preread is smaller than the request packet body length
There is no unprocessed data in the content-length field, or the header_in buffer, and the remaining space in header_in is not enough to receive
HTTP Request package body, the buffer for receiving the request package body will be reallocated, and then the current request will be set TNGX_HTTP_REQUEST_T The Read_Event_handler Reading method is ngx_http_read_request_BODY_HANDLER. EST_EMPTY_HANDLER (that is, no operation), and then call the method ngx_http_do_read_client_request_body Really received HTTP Request body; function
is defined in the file - If there is unprocessed data in the header_in buffer, which is a pre-received request packet body, first check whether the buffer request packet body length preread is greater than the request packet body length
content-length field, if it is greater than it means that the complete
as follows: /* Receive HTTP request body */ ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler) { size_t preread; ssize_t size; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out, *cl; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; /* * When there is logic to start the process, the reference count will increase by 1. When the process ends, the reference count will decrease by 1; * In the ngx_http_read_client_request_body method, first increase the original request reference count by 1, * When encountering an abnormal termination, the reference count will be decremented by 1 before the function returns; * If it ends normally, the reference count will continue to be maintained by the post_handler method; */ /* Increase the reference count of the original request by 1 */ r->main->count++; #if (NGX_HTTP_SPDY) if (r->spdy_stream && r == r->main) { rc = ngx_http_spdy_read_request_body(r, post_handler); goto done; } #endif /* When the HTTP request body is not processed, the request_body structure is not allocated and will only be allocated when it is processed */ /* * If the current HTTP request is not an original request, or the HTTP request body has been read or discarded; * Then directly execute the callback method post_handler of the HTTP module and return NGX_OK; */ if (r != r->main || r->request_body || r->discard_body) { post_handler(r); return NGX_OK; } /* * ngx_http_test_expect is used to check whether the client sends the Expect:100-continue header, * If the client has sent this header to indicate that it expects to send the request body data, the server will return

Setting session cookie parameters in PHP can be achieved through the session_set_cookie_params() function. 1) Use this function to set parameters, such as expiration time, path, domain name, security flag, etc.; 2) Call session_start() to make the parameters take effect; 3) Dynamically adjust parameters according to needs, such as user login status; 4) Pay attention to setting secure and httponly flags to improve security.

The main purpose of using sessions in PHP is to maintain the status of the user between different pages. 1) The session is started through the session_start() function, creating a unique session ID and storing it in the user cookie. 2) Session data is saved on the server, allowing data to be passed between different requests, such as login status and shopping cart content.

How to share a session between subdomains? Implemented by setting session cookies for common domain names. 1. Set the domain of the session cookie to .example.com on the server side. 2. Choose the appropriate session storage method, such as memory, database or distributed cache. 3. Pass the session ID through cookies, and the server retrieves and updates the session data based on the ID.

HTTPS significantly improves the security of sessions by encrypting data transmission, preventing man-in-the-middle attacks and providing authentication. 1) Encrypted data transmission: HTTPS uses SSL/TLS protocol to encrypt data to ensure that the data is not stolen or tampered during transmission. 2) Prevent man-in-the-middle attacks: Through the SSL/TLS handshake process, the client verifies the server certificate to ensure the connection legitimacy. 3) Provide authentication: HTTPS ensures that the connection is a legitimate server and protects data integrity and confidentiality.

What’s still popular is the ease of use, flexibility and a strong ecosystem. 1) Ease of use and simple syntax make it the first choice for beginners. 2) Closely integrated with web development, excellent interaction with HTTP requests and database. 3) The huge ecosystem provides a wealth of tools and libraries. 4) Active community and open source nature adapts them to new needs and technology trends.

PHP and Python are both high-level programming languages that are widely used in web development, data processing and automation tasks. 1.PHP is often used to build dynamic websites and content management systems, while Python is often used to build web frameworks and data science. 2.PHP uses echo to output content, Python uses print. 3. Both support object-oriented programming, but the syntax and keywords are different. 4. PHP supports weak type conversion, while Python is more stringent. 5. PHP performance optimization includes using OPcache and asynchronous programming, while Python uses cProfile and asynchronous programming.

PHP is mainly procedural programming, but also supports object-oriented programming (OOP); Python supports a variety of paradigms, including OOP, functional and procedural programming. PHP is suitable for web development, and Python is suitable for a variety of applications such as data analysis and machine learning.

PHP originated in 1994 and was developed by RasmusLerdorf. It was originally used to track website visitors and gradually evolved into a server-side scripting language and was widely used in web development. Python was developed by Guidovan Rossum in the late 1980s and was first released in 1991. It emphasizes code readability and simplicity, and is suitable for scientific computing, data analysis and other fields.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

PhpStorm Mac version
The latest (2018.2.1) professional PHP integrated development tool

SecLists
SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.

Zend Studio 13.0.1
Powerful PHP integrated development environment

Notepad++7.3.1
Easy-to-use and free code editor

SAP NetWeaver Server Adapter for Eclipse
Integrate Eclipse with SAP NetWeaver application server.