Nginx开发HTTP模块(六):发送响应

本文详细介绍了在Nginx中如何发送HTTP响应,包括设置HTTP头部、发送包体以及内存池的使用。通过示例代码解析了`ngx_http_send_header`和`ngx_http_output_filter`函数的用法,展示了如何添加自定义头部和发送内存中的字符串作为包体。此外,还讲解了Nginx内存池的分配以及如何通过`ngx_palloc`和`ngx_pcalloc`进行内存管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



前言

请求处理完毕后,需要向用户发送HTTP响应,告知客户端Nginx的执行结果。HTTP响应主要包括响应行、响应头部、包体三部分。发送HTTP响应时需要执行发送HTTP头部(发送HTTP头部时也会发送响应行)和发送HTTP包体两步操作。


一、发送HTTP头部

1. HTTP框架提供的发送HTTP头部的方法

ngx_int_t ngx_http_send_header(ngx_http_request_t *r);

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

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

2. 设置响应中的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;
    ngx_list_t                        trailers;

	// 响应中的状态值,如200表示成功。
    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;
    ngx_array_t                       link;

	/* 在这里指定过content_length_n后,不用再次到ngx_table_elt_t *content_length中设置响应长度 */
    off_t                             content_length_n;
    off_t                             content_offset;
    time_t                            date_time;
    time_t                            last_modified_time;
} ngx_http_headers_out_t;

使用代码如下:

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:TestValue\r\n。如果发送的是一个不含有HTTP包体的响应,这是就可以直接结束请求了。
ngx_http_send_header方法会首先调用所有的HTTP过滤模块共同处理headers_out中定义的HTTP响应头部,全部处理完毕后才会序列化为TCP字符流发送到客户端。

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

1. 向客户端发送HTTP响应包体的方法

ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *chain);
  • ngx_http_output_filter的返回值在mytest例子中无需处理,通过在ngx_http_mytest_handler方法中返回的方式传递给ngx_http_finalize_request即可。

2. Nginx的内存池如何分配

为了减少内存碎片的数量,并通过统一管理来减少代码中出现内存泄漏的可能性,Nginx设计了ngx_pool_t内存池数据结构。
那么内存池该如何使用呢?

  • 在ngx_http_mytest_handler处理方法传来的ngx_http_request_t对象中就有这个请求的内存池管理对象,我们对内存池的操作都可以基于它来进行,这样,在这个请求结束的时候,内存池分配的内存也都会被释放。
    struct ngx_http_request_s {
    	...
    	ngx_pool_t *pool;
    	...
    }
    
  • 在r中可以获得许多内存池对象,这些内存池的大小、意义及生存期各不相同。这里我们使用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);
          

3. 经典的“Hello World”示例

我们以经典的“Hello World”为例写一个最小的HTTP处理模块,以此介绍完整的ngx_http_mytest_handler处理方法。

static ngx_int_t ngx_http_mytest_hadnler(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);
}

总结

本文简要介绍了如何nginx如何发送HTTP响应,并完整介绍了ngx_http_mytest_handler处理方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值