在nginx源碼目錄的src/core下面的ngx_string.h|c里面,包含了字符串的封裝以及字符串相關操作的api。nginx提供了一個帶長度的字符串結構ngx_str_t,它的原型如下:
[](http:// "點擊提交Issue,反饋你的意見...")
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
在結構體當中,data指向字符串數據的第一個字符,字符串的結束用長度來表示,而不是由’\0’來表示結束。所以,在寫nginx代碼時,處理字符串的方法跟我們平時使用有很大的不一樣,但要時刻記住,字符串不以’\0’結束,盡量使用nginx提供的字符串操作的api來操作字符串。 那么,nginx這樣做有什么好處呢?首先,通過長度來表示字符串長度,減少計算字符串長度的次數。其次,nginx可以重復引用一段字符串內存,data可以指向任意內存,長度表示結束,而不用去copy一份自己的字符串(因為如果要以’\0’結束,而不能更改原字符串,所以勢必要copy一段字符串)。我們在ngx_http_request_t結構體的成員中,可以找到很多字符串引用一段內存的例子,比如request_line、uri、args等等,這些字符串的data部分,都是指向在接收數據時創建buffer所指向的內存中,uri,args就沒有必要copy一份出來。這樣的話,減少了很多不必要的內存分配與拷貝。 正是基于此特性,在nginx中,必須謹慎的去修改一個字符串。在修改字符串時需要認真的去考慮:是否可以修改該字符串;字符串修改后,是否會對其它的引用造成影響。在后面介紹ngx_unescape_uri函數的時候,就會看到這一點。但是,使用nginx的字符串會產生一些問題,glibc提供的很多系統api函數大多是通過’\0’來表示字符串的結束,所以我們在調用系統api時,就不能直接傳入str->data了。此時,通常的做法是創建一段str->len + 1大小的內存,然后copy字符串,最后一個字節置為’\0’。比較hack的做法是,將字符串最后一個字符的后一個字符backup一個,然后設置為’\0’,在做完調用后,再由backup改回來,但前提條件是,你得確定這個字符是可以修改的,而且是有內存分配,不會越界,但一般不建議這么做。 接下來,看看nginx提供的操作字符串相關的api。
#define ngx_string(str) ? ? { sizeof(str) - 1, (u_char *) str }
ngx_string(str)是一個宏,它通過一個以’\0’結尾的普通字符串str構造一個nginx的字符串,鑒于其中采用sizeof操作符計算字符串長度,因此參數必須是一個常量字符串。
#define ngx_null_string ? ? { 0, NULL }
定義變量時,使用ngx_null_string初始化字符串為空字符串,符串的長度為0,data為NULL。
#define ngx_str_set(str, text) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? \
? ?(str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
ngx_str_set用于設置字符串str為text,由于使用sizeof計算長度,故text必須為常量字符串。
#define ngx_str_null(str) ? (str)->len = 0; (str)->data = NULL
ngx_str_null用于設置字符串str為空串,長度為0,data為NULL。
上面這四個函數,使用時一定要小心,ngx_string與ngx_null_string是“{,}”格式的,故只能用于賦值時初始化,如:
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_str_t str = ngx_string("hello world");
ngx_str_t str1 = ngx_null_string;
如果向下面這樣使用,就會有問題,這里涉及到c語言中對結構體變量賦值操作的語法規則,在此不做介紹。
ngx_str_t str, str1;str = ngx_string("hello world"); // 編譯出錯str1 = ngx_null_string; // 編譯出錯
這種情況,可以調用ngx_str_set與ngx_str_null這兩個函數來做:
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_str_t str, str1;
ngx_str_set(&str, "hello world");
ngx_str_null(&str1);
按照C99標準,您也可以這么做:
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_str_t str, str1;
str = (ngx_str_t) ngx_string("hello world");
str1 = (ngx_str_t) ngx_null_string;
另外要注意的是,ngx_string與ngx_str_set在調用時,傳進去的字符串一定是常量字符串,否則會得到意想不到的錯誤(因為ngx_str_set內部使用了sizeof(),如果傳入的是u_char*,那么計算的是這個指針的長度,而不是字符串的長度)。如:
ngx_str_t str;u_char *a = "hello world";ngx_str_set(&str, a); // 問題產生
此外,值得注意的是,由于ngx_str_set與ngx_str_null實際上是兩行語句,故在if/for/while等語句中單獨使用需要用花括號括起來,例如:
ngx_str_t str;if (cond)
ngx_str_set(&str, "true"); // 問題產生else
ngx_str_set(&str, "false"); // 問題產生
[](http:// "點擊提交Issue,反饋你的意見...")
void ngx_strlow(u_char *dst, u_char *src, size_t n);
將src的前n個字符轉換成小寫存放在dst字符串當中,調用者需要保證dst指向的空間大于等于n,且指向的空間必須可寫。操作不會對原字符串產生變動。如要更改原字符串,可以:
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_strlow(str->data, str->data, str->len);
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_strncmp(s1, s2, n)
區分大小寫的字符串比較,只比較前n個字符。
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_strcmp(s1, s2)
區分大小寫的不帶長度的字符串比較。
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2);
不區分大小寫的不帶長度的字符串比較。
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);
不區分大小寫的帶長度的字符串比較,只比較前n個字符。
[](http:// "點擊提交Issue,反饋你的意見...")
u_char * ngx_cdecl ngx_sprintf(u_char *buf, const char *fmt, ...);
u_char * ngx_cdecl ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...);
u_char * ngx_cdecl ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...);
上面這三個函數用于字符串格式化,ngx_snprintf的第二個參數max指明buf的空間大小,ngx_slprintf則通過last來指明buf空間的大小。推薦使用第二個或第三個函數來格式化字符串,ngx_sprintf函數還是比較危險的,容易產生緩沖區溢出漏洞。在這一系列函數中,nginx在兼容glibc中格式化字符串的形式之外,還添加了一些方便格式化nginx類型的一些轉義字符,比如%V用于格式化ngx_str_t結構。在nginx源文件的ngx_string.c中有說明:
[](http:// "點擊提交Issue,反饋你的意見...")
/*
* supported formats:
* %[0][width][x][X]O off_t
* %[0][width]T time_t
* %[0][width][u][x|X]z ssize_t/size_t
* %[0][width][u][x|X]d int/u_int
* %[0][width][u][x|X]l long
* %[0][width|m][u][x|X]i ngx_int_t/ngx_uint_t
* %[0][width][u][x|X]D int32_t/uint32_t
* %[0][width][u][x|X]L int64_t/uint64_t
* %[0][width|m][u][x|X]A ngx_atomic_int_t/ngx_atomic_uint_t
* %[0][width][.width]f double, max valid number fits to %18.15f
* %P ngx_pid_t
* %M ngx_msec_t
* %r rlim_t
* %p void *
* %V ngx_str_t *
* %v ngx_variable_value_t *
* %s null-terminated string
* %*s length and string
* %Z '\0'
* %N '\n'
* %c char
* %% %
*
* reserved:
* %t ptrdiff_t
* %S null-terminated wchar string
* %C wchar
*/
這里特別要提醒的是,我們最常用于格式化ngx_str_t結構,其對應的轉義符是%V,傳給函數的一定要是指針類型,否則程序就會coredump掉。這也是我們最容易犯的錯。比如:
ngx_str_t str = ngx_string("hello world");char buffer[1024];ngx_snprintf(buffer, 1024, "%V", &str); // 注意,str取地址
[](http:// "點擊提交Issue,反饋你的意見...")
void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);
ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
這兩個函數用于對str進行base64編碼與解碼,調用前,需要保證dst中有足夠的空間來存放結果,如果不知道具體大小,可先調用ngx_base64_encoded_length與ngx_base64_decoded_length來預估最大占用空間。
[](http:// "點擊提交Issue,反饋你的意見...")
uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size,
ngx_uint_t type);
對src進行編碼,根據type來按不同的方式進行編碼,如果dst為NULL,則返回需要轉義的字符的數量,由此可得到需要的空間大小。type的類型可以是:
#define NGX_ESCAPE_URI ? ? ? ? 0
#define NGX_ESCAPE_ARGS ? ? ? ?1
#define NGX_ESCAPE_HTML ? ? ? ?2
#define NGX_ESCAPE_REFRESH ? ? 3
#define NGX_ESCAPE_MEMCACHED ? 4
#define NGX_ESCAPE_MAIL_AUTH ? 5
[](http:// "點擊提交Issue,反饋你的意見...")
void ngx_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type);
對src進行反編碼,type可以是0、NGX_UNESCAPE_URI、NGX_UNESCAPE_REDIRECT這三個值。如果是0,則表示src中的所有字符都要進行轉碼。如果是NGX_UNESCAPE_URI與NGX_UNESCAPE_REDIRECT,則遇到’?’后就結束了,后面的字符就不管了。而NGX_UNESCAPE_URI與NGX_UNESCAPE_REDIRECT之間的區別是NGX_UNESCAPE_URI對于遇到的需要轉碼的字符,都會轉碼,而NGX_UNESCAPE_REDIRECT則只會對非可見字符進行轉碼。
[](http:// "點擊提交Issue,反饋你的意見...")
uintptr_t ngx_escape_html(u_char *dst, u_char *src, size_t size);
對html標簽進行編碼。
當然,我這里只介紹了一些常用的api的使用,大家可以先熟悉一下,在實際使用過程中,遇到不明白的,最快最直接的方法就是去看源碼,看api的實現或看nginx自身調用api的地方是怎么做的,代碼就是最好的文檔。
- 上篇:nginx模塊開發篇
- nginx平臺初探
- 初探nginx架構
- nginx基礎概念
- connection
- request
- keepalive
- pipe
- lingering_close
- 基本數據結構
- ngx_str_t
- ngx_pool_t
- ngx_array_t
- ngx_hash_t
- ngx_hash_wildcard_t
- ngx_hash_combined_t
- ngx_hash_keys_arrays_t
- ngx_chain_t
- ngx_buf_t
- ngx_list_t
- ngx_queue_t
- nginx的配置系統
- 指令參數
- 指令上下文
- nginx的模塊化體系結構
- 模塊的分類
- nginx的請求處理
- handler模塊
- handler模塊簡介
- 模塊的基本結構
- 模塊配置結構
- 模塊配置指令
- 模塊上下文結構
- 模塊的定義
- handler模塊的基本結構
- handler模塊的掛載
- handler的編寫步驟
- 示例: hello handler 模塊
- handler模塊的編譯和使用
- 更多handler模塊示例分析
- http access module
- http static module
- http log module
- 過濾模塊
- 過濾模塊簡介
- 過濾模塊的分析
- upstream模塊
- upstream模塊
- upstream模塊接口
- memcached模塊分析
- 本節回顧
- 負載均衡模塊
- 配置
- 指令
- 鉤子
- 初始化配置
- 初始化請求
- peer.get和peer.free回調函數
- 本節回顧
- 其他模塊
- core模塊
- event模塊
- 模塊開發高級篇
- 變量
- 下篇:nginx原理解析篇
- nginx架構詳解
- nginx的源碼目錄結構
- nginx的configure原理
- 模塊編譯順序
- nginx基礎設施
- 內存池
- nginx的啟動階段
- 概述
- 共有流程
- 配置解析
- nginx的請求處理階段
- 接收請求流程
- http請求格式簡介
- 請求頭讀取
- 解析請求行
- 解析請求頭
- 請求體讀取
- 讀取請求體
- 丟棄請求體
- 多階段處理請求
- 多階段執行鏈
- POST_READ階段
- SERVER_REWRITE階段
- FIND_CONFIG階段
- REWRITE階段
- POST_REWRITE階段
- PREACCESS階段
- ACCESS階段
- POST_ACCESS階段
- TRY_FILES階段
- CONTENT階段
- LOG階段
- Nginx filter
- header filter分析
- body filter分析
- ngx_http_copy_filter_module分析
- ngx_http_write_filter_module分析
- subrequest原理解析
- https請求處理解析
- 附錄A 編碼風格
- 附錄B 常用API
- 附錄C 模塊編譯,調試與測試