《深入理解Nginx:模块开发与架构解析》一3.7 发送响应

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本节书摘来自华章出版社《深入理解Nginx:模块开发与架构解析》一书中的第3章,第3.7节,作者 陶辉,更多章节内容可以访问云栖社区“华章计算机”公众号查看

3.7 发送响应

请求处理完毕后,需要向用户发送HTTP响应,告知客户端Nginx的执行结果。HTTP响应主要包括响应行、响应头部、包体三部分。发送HTTP响应时需要执行发送HTTP头部(发送HTTP头部时也会发送响应行)和发送HTTP包体两步操作。本节将以发送经典的“Hello World”为例来说明如何发送响应。

3.7.1 发送HTTP头部

下面看一下HTTP框架提供的发送HTTP头部的方法,如下所示。

ngx_int_t ngx_http_send_header(ngx_http_request_t *r);

调用ngx_http_send_header时把ngx_http_request_t对象传给它即可,而ngx_http_request_t的返回值是多样的,在本节中,可以认为返回NGX_ERROR或返回值大于0就表示不正常,例如:

ngx_int_t  rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
    return rc;
}

下面介绍设置响应中的HTTP头部的过程。
如同headers_in,ngx_http_request_t也有一个headers_out成员,用来设置响应中的HTTP头部,如下所示。

struct ngx_http_request_s {
    …
    ngx_http_headers_in_t             headers_in;
    ngx_http_headers_out_t            headers_out;
    …
};

只要指定headers_out中的成员,就可以在调用ngx_http_send_header时正确地把HTTP头部发出。下面介绍headers_out的结构类型ngx_http_headers

_out_t。
typedef struct {
    //待发送的HTTP头部链表,与headers_in中的headers成员类似
    ngx_list_t                        headers;

/响应中的状态值,如200表示成功。这里可以使用3.6.1节中介绍过的各个宏,如NGX_HTTP_OK /

ngx_uint_t                        status;
    //响应的状态行,如“HTTP/1.1 201 CREATED”
    ngx_str_t                         status_line;

/以下成员(包括ngx_table_elt_t)都是RFC1616规范中定义的HTTP头部,设置后,ngx_http_header_filter_module过滤模块可以把它们加到待发送的网络包中/

ngx_table_elt_t                  *server;
   ngx_table_elt_t                  *date;
   ngx_table_elt_t                  *content_length;
   ngx_table_elt_t                  *content_encoding;
   ngx_table_elt_t                  *location;
   ngx_table_elt_t                  *refresh;
   ngx_table_elt_t                  *last_modified;
   ngx_table_elt_t                  *content_range;
   ngx_table_elt_t                  *accept_ranges;
   ngx_table_elt_t                  *www_authenticate;
   ngx_table_elt_t                  *expires;
   ngx_table_elt_t                  *etag;

   ngx_str_t                        *override_charset;

/可以调用ngx_http_set_content_type(r)方法帮助我们设置Content-Type头部,这个方法会根据URI中的文件扩展名并对应着mime.type来设置Content-Type值/

size_t                            content_type_len;
   ngx_str_t                         content_type;
   ngx_str_t                         charset;
   u_char                           *content_type_lowcase;
   ngx_uint_t                        content_type_hash;

   ngx_array_t                       cache_control;

/在这里指定过content_length_n后,不用再次到ngx_table_elt_t content_ length中设置响应长度*/

off_t                             content_length_n;
    time_t                            date_time;
    time_t                            last_modified_time;
} ngx_http_headers_out_t;

在向headers链表中添加自定义的HTTP头部时,可以参考3.2.3节中ngx_list_push的使用方法。这里有一个简单的例子,如下所示。

ngx_table_elt_t* h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
    return NGX_ERROR;
}
h->hash = 1;
h->key.len = sizeof("TestHead") - 1;
h->key.data = (u_char *) "TestHead";
h->value.len = sizeof("TestValue") - 1;
h->value.data = (u_char *) "TestValue";

这样将会在响应中新增一行HTTP头部:

TestHead: TestValud\r\n

如果发送的是一个不含有HTTP包体的响应,这时就可以直接结束请求了(例如,在ngx_http_mytest_handler方法中,直接在ngx_http_send_header方法执行后将其返回值return即可)。
注意 ngx_http_send_header方法会首先调用所有的HTTP过滤模块共同处理headers_out中定义的HTTP响应头部,全部处理完毕后才会序列化为TCP字符流发送到客户端,相关流程可参见11.9.1节。

3.7.2 将内存中的字符串作为包体发送

调用ngx_http_output_filter方法即可向客户端发送HTTP响应包体,下面查看一下此方法的原型,如下所示。
ngx_int_t ngx_http_output_filter(ngx_http_request_t r, ngx_chain_t in);
ngx_http_output_filter的返回值在mytest例子中不需要处理,通过在ngx_http_mytest_handler方法中返回的方式传递给ngx_http_finalize_request即可。ngx_chain_t结构已经在3.2.6节中介绍过,它仅用于容纳ngx_buf_t缓冲区,所以需要先了解一下如何使用ngx_buf_t分配内存。下面介绍Nginx的内存池是如何分配内存的。
为了减少内存碎片的数量,并通过统一管理来减少代码中出现内存泄漏的可能性,Nginx设计了ngx_pool_t内存池数据结构。本章我们不会深入分析内存池的实现,只关注内存池的用法。在ngx_http_mytest_handler处理方法传来的ngx_http_request_t对象中就有这个请求的内存池管理对象,我们对内存池的操作都可以基于它来进行,这样,在这个请求结束的时候,内存池分配的内存也都会被释放。

struct ngx_http_request_s {
    …
    ngx_pool_t *pool;
    …
};

实际上,在r中可以获得许多内存池对象,这些内存池的大小、意义及生存期各不相同。第3部分会涉及许多内存池,本章使用r->pool内存池即可。有了ngx_pool_t对象后,可以从内存池中分配内存。例如,下面这个基本的申请分配内存的方法:

void *ngx_palloc(ngx_pool_t *pool, size_t size);

其中,ngx_palloc函数将会从pool内存池中分配到size字节的内存,并返回这段内存的起始地址。如果返回NULL空指针,则表示分配失败。还有一个封装了ngx_palloc的函数ngx_pcalloc,它多做了一件事,就是把ngx_palloc申请到的内存块全部置为0,虽然,多数情况下更适合用ngx_pcalloc来分配内存。
假如要分配一个ngx_buf_t结构,可以这样做:

ngx_buf_t* b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

这样,ngx_buf_t中的成员指向的内存仍然可以继续分配,例如:

b->start = (u_char*)ngx_pcalloc(r->pool, 128);
b->pos = b->start;
b->last = b->start;
b->end = b->last + 128;
b->temporary = 1;

实际上,Nginx还封装了一个生成ngx_buf_t的简便方法,它完全等价于上面的6行语句,如下所示。

ngx_buf_t *b = ngx_create_temp_buf(r->pool, 128);

分配完内存后,可以向这段内存写入数据。当写完数据后,要让b->last指针指向数据的末尾,如果b->last与b->pos相等,那么HTTP框架是不会发送一个字节的包体的。
最后,把上面的ngx_buf_t *b用ngx_chain_t传给ngx_http_output_filter方法就可以发送HTTP响应的包体内容了。例如:

ngx_chain_t out;
out.buf = b;
out.next = NULL;

return ngx_http_output_filter(r, &out);

注意 在向用户发送响应包体时,必须牢记Nginx是全异步的服务器,也就是说,不可以在进程的栈里分配内存并将其作为包体发送。当ngx_http_output_filter方法返回时,可能由于TCP连接上的缓冲区还不可写,所以导致ngx_buf_t缓冲区指向的内存还没有发送,可这时方法返回已把控制权交给Nginx了,又会导致栈里的内存被释放,最后就会造成内存越界错误。因此,在发送响应包体时,尽量将ngx_buf_t中的pos指针指向从内存池里分配的内存。

3.7.3 经典的“Hello World”示例

下面以经典的返回“Hello World”为例来编写一个最小的HTTP处理模块,以此介绍完整的ngx_http_mytest_handler处理方法。

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
    //必须是GET或者HEAD方法,否则返回405 Not Allowed
    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    //丢弃请求中的包体
    ngx_int_t rc = ngx_http_discard_request_body(r);
    if (rc != NGX_OK) {
        return rc;
    }

/设置返回的Content-Type。注意,ngx_str_t有一个很方便的初始化宏ngx_string,它可以把ngx_str_t的data和len成员都设置好/

ngx_str_t type = ngx_string("text/plain");
 //返回的包体内容
 ngx_str_t response = ngx_string("Hello World!");
 //设置返回状态码
 r->headers_out.status = NGX_HTTP_OK;
 //响应包是有包体内容的,需要设置Content-Length长度
 r->headers_out.content_length_n = response.len;
 //设置Content-Type
 r->headers_out.content_type = type;

 //发送HTTP头部
 rc = ngx_http_send_header(r);
 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
     return rc;
 }

 //构造ngx_buf_t结构体准备发送包体
 ngx_buf_t *b;
 b = ngx_create_temp_buf(r->pool, response.len);
 if (b == NULL) {
     return NGX_HTTP_INTERNAL_SERVER_ERROR;
 }
 //将Hello World复制到ngx_buf_t指向的内存中
 ngx_memcpy(b->pos, response.data, response.len);
 //注意,一定要设置好last指针
 b->last = b->pos + response.len;
 //声明这是最后一块缓冲区
 b->last_buf = 1;

 //构造发送时的ngx_chain_t结构体
 ngx_chain_t out;
 //赋值ngx_buf_t
 out.buf = b;
 //设置next为NULL
 out.next = NULL;

/最后一步为发送包体,发送结束后HTTP框架会调用ngx_http_finalize_request方法结束请求/

return ngx_http_output_filter(r, &out);
}
相关文章
|
14天前
|
IDE Android开发 iOS开发
深入解析Android与iOS的系统架构及开发环境差异
本文旨在探讨Android和iOS两大主流移动操作系统在系统架构、开发环境和用户体验方面的显著差异。通过对比分析,我们将揭示这两种系统在设计理念、技术实现以及市场策略上的不同路径,帮助开发者更好地理解其特点,从而做出更合适的开发决策。
47 2
|
20天前
|
负载均衡 5G 网络性能优化
深入解析LTE(长期演进技术)的基本架构及其关键组件
深入解析LTE(长期演进技术)的基本架构及其关键组件
103 2
|
1月前
|
Shell
HTTP状态码解析:在Haskell中判断响应成功与否
HTTP状态码解析:在Haskell中判断响应成功与否
|
14天前
|
Java 对象存储 开发者
解析Spring Cloud与Netflix OSS:微服务架构中的左右手如何协同作战
Spring Cloud与Netflix OSS不仅是现代微服务架构中不可或缺的一部分,它们还通过不断的技术创新和社区贡献推动了整个行业的发展。无论是对于初创企业还是大型组织来说,掌握并合理运用这两套工具,都能极大地提升软件系统的灵活性、可扩展性以及整体性能。随着云计算和容器化技术的进一步普及,Spring Cloud与Netflix OSS将继续引领微服务技术的发展潮流。
30 0
|
18天前
|
传感器 C# Android开发
深度解析Uno Platform中的事件处理机制与交互设计艺术:从理论到实践的全方位指南,助您构建响应迅速、交互流畅的跨平台应用
Uno Platform 是一款开源框架,支持使用 C# 和 XAML 开发跨平台原生 UI 应用,兼容 Windows、iOS、Android 及 WebAssembly。本文将介绍 Uno Platform 中高效的事件处理方法,并通过示例代码展示交互设计的核心原则与实践技巧,帮助提升应用的用户体验。事件处理让应用能响应用户输入,如点击、触摸及传感器数据变化。通过 XAML 或 C# 添加事件处理器,可确保及时反馈用户操作。示例代码展示了一个按钮点击事件处理过程。此外,还可运用动画和过渡效果进一步增强应用交互性。
128 57
|
25天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
210 37
|
1天前
|
安全 Android开发 iOS开发
深入解析:安卓与iOS的系统架构及其对应用开发的影响
本文旨在探讨安卓与iOS两大主流操作系统的架构差异,并分析这些差异如何影响应用开发的策略和实践。通过对比两者的设计哲学、安全机制、开发环境及性能优化等方面,本文揭示了各自的特点和优势,为开发者在选择平台和制定开发计划时提供参考依据。
|
4天前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
|
11天前
|
物联网 5G SDN
5G 网络架构全解析:RAN、核心网和接入网
5G 网络架构全解析:RAN、核心网和接入网
46 8
|
24天前
|
算法 Linux 调度
操作系统的心脏:现代操作系统架构的深度解析
本文深入探讨了现代操作系统的架构设计,重点分析了进程管理、内存管理和文件系统等核心组件。通过对Linux和Windows两大主流操作系统的比较,揭示了不同设计哲学在实际应用中的表现。旨在为操作系统开发者提供参考,同时帮助普通用户更好地理解其设备背后的软件机制。 ##
37 2

推荐镜像

更多