Nginx stream模块的执行阶段
744 次浏览
发表于 2020-09-08 18:27
转载

Nginx的stream模块提供了TCP负载均衡的功能,最初的stream模块比较简单,在nginx-1.11.4后也开始采用类似HTTP模块中分阶段处理请求的方式。

stream模块的处理阶段

在ngx_stream.h中定义了stream模块的7个阶段。如下面所示

  1. typedef enum {
  2. NGX_STREAM_POST_ACCEPT_PHASE = 0,
  3. NGX_STREAM_PREACCESS_PHASE,
  4. NGX_STREAM_ACCESS_PHASE,
  5. NGX_STREAM_SSL_PHASE,
  6. NGX_STREAM_PREREAD_PHASE,
  7. NGX_STREAM_CONTENT_PHASE,
  8. NGX_STREAM_LOG_PHASE
  9. } ngx_stream_phases;

与HTTP模块相同,每个阶段有相应的checker检查方法和handler回调方法,每个阶段都有零个或多个ngx_stream_phase_handler_t结构体。

  1. typedef struct ngx_stream_phase_handler_s ngx_stream_phase_handler_t;
  2. typedef ngx_int_t (*ngx_stream_phase_handler_pt)(ngx_stream_session_t *s,
  3. ngx_stream_phase_handler_t *ph);
  4. typedef ngx_int_t (*ngx_stream_handler_pt)(ngx_stream_session_t *s);
  5. typedef void (*ngx_stream_content_handler_pt)(ngx_stream_session_t *s);
  6. struct ngx_stream_phase_handler_s {
  7. ngx_stream_phase_handler_pt checker;
  8. ngx_stream_handler_pt handler;
  9. ngx_uint_t next;
  10. };

stream模块接到请求后,初始化连接后调用ngx_stream_core_run_phases依次执行各个阶段的处理函数。

  1. void
  2. ngx_stream_core_run_phases(ngx_stream_session_t *s)
  3. {
  4. ngx_int_t rc;
  5. ngx_stream_phase_handler_t *ph;
  6. ngx_stream_core_main_conf_t *cmcf;
  7. cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
  8. ph = cmcf->phase_engine.handlers;
  9. while (ph[s->phase_handler].checker) {
  10. rc = ph[s->phase_handler].checker(s, &ph[s->phase_handler]);
  11. if (rc == NGX_OK) {
  12. return;
  13. }
  14. }
  15. }

POST_ACCEPT、PREACCESS、ACCESS阶段

POST_ACCEPT、PREACCESS、ACCESS阶段的checker检查方法都是ngx_stream_core_generic_phase,这三个阶段主要进行访问控制的一些工作。因为stream模块处理TCP请求,阶段之间关系比较简单,将模块挂载在哪个阶段只会影响执行的顺序。

下面是ngx_stream_core_generic_phase函数

  1. ngx_int_t
  2. ngx_stream_core_generic_phase(ngx_stream_session_t *s,
  3. ngx_stream_phase_handler_t *ph)
  4. {
  5. ngx_int_t rc;
  6. /*
  7. * generic phase checker,
  8. * used by all phases, except for preread and content
  9. */
  10. ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
  11. "generic phase: %ui", s->phase_handler);
  12. rc = ph->handler(s);
  13. if (rc == NGX_OK) {
  14. s->phase_handler = ph->next;
  15. return NGX_AGAIN;
  16. }
  17. if (rc == NGX_DECLINED) {
  18. s->phase_handler++;
  19. return NGX_AGAIN;
  20. }
  21. if (rc == NGX_AGAIN || rc == NGX_DONE) {
  22. return NGX_OK;
  23. }
  24. if (rc == NGX_ERROR) {
  25. rc = NGX_STREAM_INTERNAL_SERVER_ERROR;
  26. }
  27. ngx_stream_finalize_session(s, rc);
  28. return NGX_OK;
  29. }

与HTTP模块类似,根据rc = ph->handler(s)结果进行处理。

  • rc为NGX_OK表示这一阶段的工作完成,进入下一个阶段
  • rc为NGX_DECLINED表示进入下一个模块处理
  • rc为NGX_AGAIN表示当前请求暂时无法完成,返回NGX_OK
  • rc为NGX_DONE表示当前请求告一段落,会被再次调用,返回NGX_OK
  • rc为NGX_ERROR表示出现错误,结束请求
  • rc为其他值时表示处理完成,此时rc为状态码

注意,TCP请求中没有HTTP请求那样的状态码,这里的状态码只是表示连接处理的信息,如源超时状态码为502。

SSL阶段

SSL阶段的checker检查方法也是ngx_stream_core_generic_phase,这里挂载了ngx_stream_ssl_module,开启SSL时这里进行SSL握手的处理。

PREREAD阶段

这个阶段的特点是会读取下游的请求包体。读取后调用handler回调函数处理。读取的数据会保存在c->buffer中。如CONTENT阶段中ngx_stream_proxy_module在处理时会将c->buffer中的内容发送给上游源服务器。如果你在CONTENT阶段之前读取了下游的数据又想将这些数据通过proxy发送给上游,就可以加数据放到c->buffer中。

这里官方挂载了ngx_stream_ssl_preread_module模块,当TCP连接采用SSL通信时用这个模块可以解析client hello握手包,从extensions字段中得到server_name赋值给变量$ssl_preread_server_name中。这个模块只能在监听地址没有开启SSL,但是上下游却是通过SSL进行通信的情况下使用。

PREREAD阶段checker检查方法是ngx_stream_core_preread_phase。如下所示

  1. ngx_int_t
  2. ngx_stream_core_preread_phase(ngx_stream_session_t *s,
  3. ngx_stream_phase_handler_t *ph)
  4. {
  5. size_t size;
  6. ssize_t n;
  7. ngx_int_t rc;
  8. ngx_connection_t *c;
  9. ngx_stream_core_srv_conf_t *cscf;
  10. c = s->connection;
  11. c->log->action = "prereading client data";
  12. cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
  13. if (c->read->timedout) {
  14. rc = NGX_STREAM_OK;
  15. } else if (c->read->timer_set) {
  16. rc = NGX_AGAIN;
  17. } else {
  18. rc = ph->handler(s);
  19. }
  20. while (rc == NGX_AGAIN) {
  21. if (c->buffer == NULL) {
  22. c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size);
  23. if (c->buffer == NULL) {
  24. rc = NGX_ERROR;
  25. break;
  26. }
  27. }
  28. size = c->buffer->end - c->buffer->last;
  29. if (size == 0) {
  30. ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full");
  31. rc = NGX_STREAM_BAD_REQUEST;
  32. break;
  33. }
  34. if (c->read->eof) {
  35. rc = NGX_STREAM_OK;
  36. break;
  37. }
  38. if (!c->read->ready) {
  39. if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
  40. rc = NGX_ERROR;
  41. break;
  42. }
  43. if (!c->read->timer_set) {
  44. ngx_add_timer(c->read, cscf->preread_timeout);
  45. }
  46. c->read->handler = ngx_stream_session_handler;
  47. return NGX_OK;
  48. }
  49. n = c->recv(c, c->buffer->last, size);
  50. if (n == NGX_ERROR) {
  51. rc = NGX_STREAM_OK;
  52. break;
  53. }
  54. if (n > 0) {
  55. c->buffer->last += n;
  56. }
  57. rc = ph->handler(s);
  58. }
  59. if (c->read->timer_set) {
  60. ngx_del_timer(c->read);
  61. }
  62. if (rc == NGX_OK) {
  63. s->phase_handler = ph->next;
  64. return NGX_AGAIN;
  65. }
  66. if (rc == NGX_DECLINED) {
  67. s->phase_handler++;
  68. return NGX_AGAIN;
  69. }
  70. if (rc == NGX_DONE) {
  71. return NGX_OK;
  72. }
  73. if (rc == NGX_ERROR) {
  74. rc = NGX_STREAM_INTERNAL_SERVER_ERROR;
  75. }
  76. ngx_stream_finalize_session(s, rc);
  77. return NGX_OK;
  78. }

CONTENT阶段

CONTENT阶段的检查方法是ngx_stream_core_content_phase

  1. ngx_int_t
  2. ngx_stream_core_content_phase(ngx_stream_session_t *s,
  3. ngx_stream_phase_handler_t *ph)
  4. {
  5. ngx_connection_t *c;
  6. ngx_stream_core_srv_conf_t *cscf;
  7. c = s->connection;
  8. c->log->action = NULL;
  9. cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);
  10. if (c->type == SOCK_STREAM
  11. && cscf->tcp_nodelay
  12. && ngx_tcp_nodelay(c) != NGX_OK)
  13. {
  14. ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
  15. return NGX_OK;
  16. }
  17. cscf->handler(s);
  18. return NGX_OK;
  19. }

很显然这一阶段只能有一个处理方法,如进行TCP代理时需要ngx_stream_proxy_module,这里挂载的就是proxy模块的handler函数

LOG阶段

LOG阶段虽然是一个独立的阶段,却是在连接结束时调用的。在CONTENT阶段结束时就会调用ngx_stream_finalize_session结束请求。这部分在ngx_stream_handler.c中

  1. void
  2. ngx_stream_finalize_session(ngx_stream_session_t *s, ngx_uint_t rc)
  3. {
  4. ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
  5. "finalize stream session: %i", rc);
  6. s->status = rc;
  7. ngx_stream_log_session(s);
  8. ngx_stream_close_connection(s->connection);
  9. }
  10. static void
  11. ngx_stream_log_session(ngx_stream_session_t *s)
  12. {
  13. ngx_uint_t i, n;
  14. ngx_stream_handler_pt *log_handler;
  15. ngx_stream_core_main_conf_t *cmcf;
  16. cmcf = ngx_stream_get_module_main_conf(s, ngx_stream_core_module);
  17. log_handler = cmcf->phases[NGX_STREAM_LOG_PHASE].handlers.elts;
  18. n = cmcf->phases[NGX_STREAM_LOG_PHASE].handlers.nelts;
  19. for (i = 0; i < n; i++) {
  20. log_handler[i](s);
  21. }
  22. }

转载于:https://my.oschina.net/u/2539854/blog/1047240

如果您觉得不错,就打赏支持一下吧〜
已有 1 人进行打赏
  • lbuu_58
点击标签,发现更多精彩
发表评论
发表者

守望

彪悍的人生,不需要解释

  • 89

    文章

  • 18

    关注

  • 12

    粉丝

活动推荐
版权所有©F5 Networks,Inc.保留所有权利。京ICP备16013763号-5