我们先看看ngx_string中定义的几个数据结构
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
这个结构体就是最基础的nginx中的字符串结构了,其中 len表示这个字符串的长度, *data是一个指向无符号char类型的指针。
从这个结构体可以看出,后续关于字符串长度的操作strlen()都可以直接饮用len这个字段。
除了这个最基本的结构体之外,还定义了另外两个字符串相关的结构体:
typedef struct {
ngx_str_t key;
ngx_str_t value;
} ngx_keyval_t;
这个结构体从定义来看就可以知道,这是一个关于字符串类型的key,value对的子结构,具体再哪能用到,还得往后看才能知道。
typedef struct {
unsigned len:28;
unsigned valid:1;
unsigned no_cacheable:1;
unsigned not_found:1;
unsigned escape:1;
u_char *data;
} ngx_variable_value_t;
这个结构体有点点奇怪,从定义来看,前面5个成员才占用了一个int,看上去只是ngx_str_t的一个扩展版本,其中
len: 只用了28bit来表示长度即最大字符串的长度不超过1-2^29.
valid:
no_cacheable:
not_fount:
escape:
data: 这个字段和ngx_str_t里的对应字段含义一样。
这里就是ngx_string 里所有的关于字符串的结构体了,下面来看一些关于对于字符串操作的接口和宏定义
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
这个宏可以映射到c++中的构造函数,传入一个字符串,然后构造出一个ngx_str_t的对象。
#define ngx_null_string { 0, NULL }
这个宏实际上是上一个宏的特例,构造了一个空的ngx_str_t。#define ngx_str_set(str, text) \
(str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
这个宏也可以看成是一个队ngx_str_t的构造函数,但是与前面ngx_string()宏的区别是,ngx_string()宏里传入的变量必须是一个字符串常量,但是这里的参数text可以是一个变量。
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL
这个宏和之前的ngx_null_string的作用也挺像,也是构造一个空的字符串常量,但是这里是显示的把某一个ngx_str_t对象置空或者可以看出是初始化。
#define ngx_tolower(c) (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)
#define ngx_toupper(c) (u_char) ((c >= 'a' && c <= 'z') ? (c & ~0x20) : c)
这两个宏,顾名思义,是将字符c进行大小写转换。
void
ngx_strlow(u_char *dst, u_char *src, size_t n)
{
while (n) {
*dst = ngx_tolower(*src);
dst++;
src++;
n--;
}
}
这个函数是将字符串src里的字符全部转成小写字符,值得注意的是,这个函数里没有对指针的有效性进行判断,所以需要在外围传入的时候进行保证,否则就会出现段错误。这个函数就使用到了刚刚定义的ngx_tolower这个宏。 同样的办法也可以定义一个转换成大写的函数。
#define ngx_strncmp(s1, s2, n) strncmp((const char *) s1, (const char *) s2, n)
/* msvc and icc7 compile strcmp() to inline loop */
#define ngx_strcmp(s1, s2) strcmp((const char *) s1, (const char *) s2)
#define ngx_strstr(s1, s2) strstr((const char *) s1, (const char *) s2)
#define ngx_strlen(s) strlen((const char *) s)
#define ngx_strchr(s1, c) strchr((const char *) s1, (int) c)
这一系列的宏定义,实际上是对string.h里一些常见的结构进行了封装,这么做的好处就保证了再Nginx中保持了命名风格保持了一致。
static ngx_inline u_char *
ngx_strlchr(u_char *p, u_char *last, u_char c)
{
while (p < last) {
if (*p == c) {
return p;
}
p++;
}
return NULL;
}
这个函数的一个奇怪修饰符 ngx_inline这个是一个宏定义,实际上就是c语言中的inline,同样是为了命名规范。这个函数的作用从代码可以看出从指针p位置到last位置开始循环寻找字符c,如果找到了那么返回指针所在的位置,否则就返回空指针
#define ngx_memzero(buf, n) (void) memset(buf, 0, n)
#define ngx_memset(buf, c, n) (void) memset(buf, c, n)
这两个宏定义,是对memset的封装,主要是对内存初始化的操作。
#if (NGX_MEMCPY_LIMIT)
void *ngx_memcpy(void *dst, const void *src, size_t n);
#define ngx_cpymem(dst, src, n) (((u_char *) ngx_memcpy(dst, src, n)) + (n))
#else
/*
* gcc3, msvc, and icc7 compile memcpy() to the inline "rep movs".
* gcc3 compiles memcpy(d, s, 4) to the inline "mov"es.
* icc8 compile memcpy(d, s, 4) to the inline "mov"es or XMM moves.
*/
#define ngx_memcpy(dst, src, n) (void) memcpy(dst, src, n)
#define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n))
#endif
这段代码首先涉及到在makefile中用宏定义进行条件编译的知识,如果在makefile中编译的时候有 -D NGX_MEMCPY_LIMIT=1 这个参数,那么运行的时候就会执行if后面的声明,否则是使用的else里面的声明。
#if (NGX_MEMCPY_LIMIT)
void *
ngx_memcpy(void *dst, const void *src, size_t n)
{
if (n > NGX_MEMCPY_LIMIT) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "memcpy %uz bytes", n);
ngx_debug_point();
}
return memcpy(dst, src, n);
}
#endif
看到这段代码就明白NGX_MEMCPY_LIMIT这个参数的意思了,它是对内存拷贝长度的一个限制,如果拷贝的长度超过了这个限制,那么是会报错的。这个函数实际上就是对系统函数memcpy的一个封装返回的是dst的起始地址。感兴趣的可以进一步的去考察memcpy这个函数的实现方式,也比较有意思。
#define ngx_cpymem(dst, src, n) (((u_char *) ngx_memcpy(dst, src, n)) + (n))
这个宏定义很有意思,调用了ngx_memcpy,最后宏返回的结果是dst+n,也就是拷贝后的最后一个指针的位置,具体为什么要这么做还需要往后看才知道具体的应用场景。
#define ngx_memcpy(dst, src, n) (void) memcpy(dst, src, n)
#define ngx_cpymem(dst, src, n) (((u_char *) memcpy(dst, src, n)) + (n))
如果编译的时候没有定义NGX_MEMCPY_LIMIT,那么 上面定义的ngx_memcpy以及ngx_cpymem实现是对memcpy的一个简单封装。但是都做到了接口统一。
#if ( __INTEL_COMPILER >= 800 )
/*
* the simple inline cycle copies the variable length strings up to 16
* bytes faster than icc8 autodetecting _intel_fast_memcpy()
*/
static ngx_inline u_char *
ngx_copy(u_char *dst, u_char *src, size_t len)
{
if (len < 17) {
while (len) {
*dst++ = *src++;
len--;
}
return dst;
} else {
return ngx_cpymem(dst, src, len);
}
}
#else
#define ngx_copy ngx_cpymem
#endif
这段代码可以看出,源码作者对于nginx的性能真的是苦心孤诣。从实现来看,如果字符串的长度不超过16个字节并且要符合某个cpu架构的条件下直接循环逐个字符的复制,比直接调用系统函数要快!
#define ngx_memmove(dst, src, n) (void) memmove(dst, src, n)
#define ngx_movemem(dst, src, n) (((u_char *) memmove(dst, src, n)) + (n))
这两个宏是关于内存的移动,其实前面有不少关于内存移动的函数了,比如ngx_memcpy,但是为什么这里还要对memmove进行封装呢? 原因是:当dst和src在内存出现重叠的时候,memmove能保证dst和src一样,但是src的内容会被修改
用一小段代码来说明下:
#include<stdio.h>
#include <string.h>
int main()
{
char s[]="hello word!";
memmove(s,s+3,4);
printf("%s\n",s);
return 0;
}
这个代码的输出为:lo wo word!。
#define ngx_memcmp(s1, s2, n) memcmp((const char *) s1, (const char *) s2, n)
这个宏定义是对两段长度为n的内存的大小比较,是对系统函数memcmp的一个简单封装。
u_char *
ngx_cpystrn(u_char *dst, u_char *src, size_t n)
{
if (n == 0) {
return dst;
}
while (--n) {
*dst = *src;
if (*dst == '\0') {
return dst;
}
dst++;
src++;
}
*dst = '\0';
return dst;
}
这个函数是一个字符串的拷贝函数,从src中拷贝n个字符到dst中。
u_char *
ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src)
{
u_char *dst;
dst = ngx_pnalloc(pool, src->len);
if (dst == NULL) {
return NULL;
}
ngx_memcpy(dst, src->data, src->len);
return dst;
}
这个函数是想从pool中分配一个和str同样长度的空间,然后把src中的数据dump到pool中来。
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,
...);
u_char *ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args);
#define ngx_vsnprintf(buf, max, fmt, args) \
ngx_vslprintf(buf, buf + (max), fmt, args)
这几个函数是一系列的print函数,是通过格式化的方式将一些变量写入到另外的一个字符串中
u_char * ngx_cdecl
ngx_sprintf(u_char *buf, const char *fmt, ...)
{
u_char *p;
va_list args;
va_start(args, fmt);
p = ngx_vslprintf(buf, (void *) -1, fmt, args);
va_end(args);
return p;
}
这个函数是处理动态参数个数的典型例子,关于函数动态参数的可以参考
【函数动态参数处理详解】 。 另外ngx_vslprintf这个函数实际上是对自动机,对于各种格式化输出的处理,可以自行参考代码。
ngx_int_t ngx_strcasecmp(u_char *s1, u_char *s2);
ngx_int_t ngx_strncasecmp(u_char *s1, u_char *s2, size_t n);
这两个函数是比较两个字符串而且是大小写不敏感。
ngx_int_t
ngx_strcasecmp(u_char *s1, u_char *s2)
{
ngx_uint_t c1, c2;
for ( ;; ) {
c1 = (ngx_uint_t) *s1++;
c2 = (ngx_uint_t) *s2++;
c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
if (c1 == c2) {
if (c1) {
continue;
}
return 0;
}
return c1 - c2;
}
}
看下ngx_strcasecmp这个函数,实际上在函数内部把u_char字符转换成了ngx_uint. 而ngx_uint实际上是 uintptr_t. 这里把u_char转换成ngx_unit的原因是:转换后比较操作能稍微快一些。
而uintptr_t的定义在/usr/includ/stdint.h头文件中
#if __WORDSIZE == 64
# ifndef __intptr_t_defined
typedef long int intptr_t;
# define __intptr_t_defined
# endif
typedef unsigned long int uintptr_t;
#else
# ifndef __intptr_t_defined
typedef int intptr_t;
# define __intptr_t_defined
# endif
typedef unsigned int uintptr_t;
#endif
从这段代码可以看出,uintptr_t要么是int,要么是unsigned int。 对于64位的机器是 long 或者unsigned long。
u_char *ngx_strstrn(u_char *s1, char *s2, size_t n);
u_char *ngx_strcasestrn(u_char *s1, char *s2, size_t n);
u_char *ngx_strlcasestrn(u_char *s1, u_char *last, u_char *s2, size_t n);
/*
* ngx_strstrn() and ngx_strcasestrn() are intended to search for static
* substring with known length in null-terminated string. The argument n
* must be length of the second substring - 1.
*/
u_char *
ngx_strstrn(u_char *s1, char *s2, size_t n)
{
u_char c1, c2;
c2 = *(u_char *) s2++;
do {
do {
c1 = *s1++;
if (c1 == 0) {
return NULL;
}
} while (c1 != c2);
} while (ngx_strncmp(s1, (u_char *) s2, n) != 0);
return --s1;
}
这个函数的功能是在s1字符串中找到第一个s2子串出现的位置,没有找到就不返回空指针,否则返回出现的位置。
ngx_strstrn这个函数上面有段注释,很有意思,主要是有两点需要注意
1,s2必须是一个长度已知的静态的字符串。
2, n这个参数必须是s2长度-1,这里为什么要-1呢? 这里还是需要仔细看代码才能明白,因为在内层的do while循环了已经比较了一个字符了,所以就不需要比较开头的字符了,所以需要减掉1,效率,效率,一切都是为了效率。
ngx_strcasestrn这个函数只是一个大小写不敏感的函数而已。
ngx_strlcasestrn这个函数增加了一个参数last,这个函数实现的功能是在s1到last这个区间内找到第一个出现s2子串的位置并且大小写不敏感。
ngx_int_t ngx_rstrncmp(u_char *s1, u_char *s2, size_t n);
ngx_int_t ngx_rstrncasecmp(u_char *s1, u_char *s2, size_t n);
ngx_int_t ngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2);
ngx_int_t ngx_dns_strcmp(u_char *s1, u_char *s2);
这几个函数从函数名的后缀来看就可以知道是比较函数,那么先来看看第一个的实现
ngx_int_t
ngx_rstrncmp(u_char *s1, u_char *s2, size_t n)
{
if (n == 0) {
return 0;
}
n--;
for ( ;; ) {
if (s1[n] != s2[n]) {
return s1[n] - s2[n];
}
if (n == 0) {
return 0;
}
n--;
}
}
从代码可以看出,这是一个字符串的逆向比较函数,在实现上也没有有什么特别的地方。
从这个代码基本可以可以推测出ngx_rstrncasecmp这个函数的实现了,基于上一个函数增加大小写不敏感的处理。
ngx_int_t
ngx_memn2cmp(u_char *s1, u_char *s2, size_t n1, size_t n2)
{
size_t n;
ngx_int_t m, z;
if (n1 <= n2) {
n = n1;
z = -1;
} else {
n = n2;
z = 1;
}
m = ngx_memcmp(s1, s2, n);
if (m || n1 == n2) {
return m;
}
return z;
}
这个函数从实现上来看一开始从n1和n2中取到了一个 较小的数,然后比较s1和s2的前缀的大小,最后的一个if语句实际上处理了前缀相同但是字符串的总长度不相同的情况,这里看到的是 当前缀相同,但是长度不同的时候,较长的大。
ngx_int_t
ngx_dns_strcmp(u_char *s1, u_char *s2)
{
ngx_uint_t c1, c2;
for ( ;; ) {
c1 = (ngx_uint_t) *s1++;
c2 = (ngx_uint_t) *s2++;
c1 = (c1 >= 'A' && c1 <= 'Z') ? (c1 | 0x20) : c1;
c2 = (c2 >= 'A' && c2 <= 'Z') ? (c2 | 0x20) : c2;
if (c1 == c2) {
if (c1) {
continue;
}
return 0;
}
/* in ASCII '.' > '-', but we need '.' to be the lowest character */
c1 = (c1 == '.') ? ' ' : c1;
c2 = (c2 == '.') ? ' ' : c2;
return c1 - c2;
}
}
这是一个域名比较的函数,其中需要特别注意的地方是在遇到 dot和minus这两个符号进行比较的时候,实际需要的是 minus > dot,所以在遇到 dot的时候做了特殊处理,转化成了 space, 这三个符号的ASCII值为 {'.':46, '-':45,' ':32}。
ngx_int_t ngx_atoi(u_char *line, size_t n);
ngx_int_t ngx_atofp(u_char *line, size_t n, size_t point);
ssize_t ngx_atosz(u_char *line, size_t n);
off_t ngx_atoof(u_char *line, size_t n);
time_t ngx_atotm(u_char *line, size_t n);
ngx_int_t ngx_hextoi(u_char *line, size_t n);
这几个函数都是关于字符串到数字的转换。
ngx_int_t
ngx_atofp(u_char *line, size_t n, size_t point)
{
ngx_int_t value;
ngx_uint_t dot;
if (n == 0) {
return NGX_ERROR;
}
dot = 0;
for (value = 0; n--; line++) {
if (point == 0) {
//小数点每往后移动一位,point要减掉1,如果point都变成0了,这个循环还没有结束,那么和这个函数的本意就相违背了。
return NGX_ERROR;
}
if (*line == '.') {
if (dot) {
//多次找到了dot,那么这个浮点数是非法的。
return NGX_ERROR;
}
//字符串中找到了dot,那么就进行标记
dot = 1;
continue;
}
if (*line < '0' || *line > '9') {
return NGX_ERROR;
}
value = value * 10 + (*line - '0');
point -= dot;
}
while (point--) {
value = value * 10;
}
if (value < 0) {
return NGX_ERROR;
} else {
return value;
}
这个函数需要认真琢磨下,先说下函数各个参数的含义
line: 这个比较明了就是这个字符串。
n: 这里是表示输出整数的位数。
point: 是指浮点字符串dot往后移动的位数。
这里可以有一个隐含的条件是n-point应该就是line中整数部分位数,如果不相等,那么参数错误了。
这个函数的场景其实还是很少见的,后续见到了再回过头来看看。
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);
ngx_int_t ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src);
static ngx_int_t ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)
这一组函数是关于base64的编解码的接口
void
ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src)
{
u_char *d, *s;
size_t len;
/*编码后输出的字符集*/
static u_char basis64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
len = src->len;
s = src->data;
d = dst->data;
while (len > 2) {
/*因为3个字节刚好是24位可以组合成4个编码后的字符,所以循环判断的时候是len>2*/
*d++ = basis64[(s[0] >> 2) & 0x3f]; //第一个字符的高6位
*d++ = basis64[((s[0] & 3) << 4) | (s[1] >> 4)]; //第一个字符的低2位和第二个字符的高4位
*d++ = basis64[((s[1] & 0x0f) << 2) | (s[2] >> 6)]; //第二个字符的低4位和第三个字符的高2位
*d++ = basis64[s[2] & 0x3f]; //第三个字符的低6位
s += 3;
len -= 3;
}
if (len) {
//字符长度不是3的倍数
*d++ = basis64[(s[0] >> 2) & 0x3f]; //剩下字符的第一个字符的高6位
if (len == 1) { //字符串的长度膜3余1
*d++ = basis64[(s[0] & 3) << 4]; //剩下一个字符的低2位变成高2位,后面四位补0
*d++ = '='; //特殊标记
} else { //字符串长度模3余2
*d++ = basis64[((s[0] & 3) << 4) | (s[1] >> 4)]; //剩下字符的第一个字符的低2位和第二个字符的高4位
*d++ = basis64[(s[1] & 0x0f) << 2]; //剩下字符的第二个字符的低4位,不足补0
}
*d++ = '='; //特殊标记。
}
//从整个编码来看,如果最后没有'='那么原字符串的长度刚好是3的倍数,如果有1个那么模3余2,如果有1个那么是模3余1.
dst->len = d - dst->data;
}
这个是标准的base64的编码方式,具体说明可以参考代码中的注释。
ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
ngx_int_t ngx_decode_base64url(ngx_str_t *dst, ngx_str_t *src);
static ngx_int_t ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)
前面两个函数,统一调用的是最下面接口,参数唯一区别是解码表有点点区别。先来看看decode函数
static ngx_int_t
ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis)
{
size_t len;
u_char *d, *s;
for (len = 0; len < src->len; len++) {
if (src->data[len] == '=') { //找到'=',那么后面的任何字符都不是编码得到的了。
break;
}
/*字符的有效性验证,因为之前都是把字符串当成bit串然后6位为一组进行编码的,那么得到的编码结果每个字符都应该映射回去,如果映射到77了,那么说明出现了错误。*/
if (basis[src->data[len]] == 77) {
return NGX_ERROR;
}
}
/*这里为什么要判定模4余1为非法,需要先看看编码结尾部分的处理,就很好理解了。*/
if (len % 4 == 1) {
return NGX_ERROR;
}
s = src->data;
d = dst->data;
while (len > 3) {
*d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);//第一个字符的低6位左移2位,和第二个字符的低6位右移4位,这样就还原了第一个字符。
*d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);//第二个字符的
*d++ = (u_char) (basis[s[2]] << 6 | basis[s[3]]);
s += 4;
len -= 4;
}
if (len > 1) {
*d++ = (u_char) (basis[s[0]] << 2 | basis[s[1]] >> 4);
}
if (len > 2) {
*d++ = (u_char) (basis[s[1]] << 4 | basis[s[2]] >> 2);
}
dst->len = d - dst->data;
return NGX_OK;
}
这里再简单介绍下编解码的过程。
解码: 解码的时候需要将字符串看成bit串(高位在左,低位在右),然后从左到右依次6bit的去取,因为6bit的数字大小在0-63之间,所以在编码的时候需要制定一个64个字符的编码表,然后用着6bit的数字作为下标取到对应的字符,需要特别注意的是,最后6bit不够的时候是采用低位补0的办法处理。
解码: 编码的时候有一个编码表,那么解码的时候同样是需要的,比如: 在编码的时候第一个6bit假设都是1,那么得到的下标是63,取到的字符是'/',那么在解码的时候用'/'的ASCII值作为下标在解码表中取到的值应该是63,这样就完成了解码。
需要特别注意的是,在对url进行base64的decode的时候,解码表中值为62,63的位置和标准的解码表位置不一样了,分别是'_' 和'-'ASCII值下标对应的值为63和62. 这是因为当url中传递的字符串在传递之前做base64encode的时候编码表示有差异的,如下:
标准的base64编码表: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
url做base64的编码表: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
之所以对最后两个字符做了变化是因为在'+'和'/'在url中传递的时候是会被urlencode成%xx这样的字符。
这里很奇怪的是,没有提供url参数做base64编码的函数。
uint32_t ngx_utf8_decode(u_char **p, size_t n);
size_t ngx_utf8_length(u_char *p, size_t n);
u_char *ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len);
这几个函数是关于字符串的在utf8编码上的一些处理,这里没有utf8的encode函数,但是提供了utf8的decode函数,要了解utf的decode函数需要先看看utf8的编码规则,可以先参考
维基百科-UTF8, 另外也可以参考一篇写的比较好的blog:
UTF8编码规则
uint32_t
ngx_utf8_decode(u_char **p, size_t n)
{
size_t len;
uint32_t u, i, valid;
u = **p;
if (u >= 0xf0) {
//leading byte = 11110xxx, 第一个字节不小于0xf0的话,说明这个字符是4字节组成
u &= 0x07;
valid = 0xffff;
len = 3;
} else if (u >= 0xe0) {
//leading byte = 1110xxxx, 第一个字节不小于0xe0的话,说明这个字符是3字节组成
u &= 0x0f;
valid = 0x7ff;
len = 2;
} else if (u >= 0xc2) {
//leading byte = 110xxxxx, 第一个字节不小于0xc2的话,说明这个字符是2字节组成,
// 从utf编码的方式来看这里判断条件应该是 u>=0xc0? 已经在nginx论坛提问了 , 持续关注中。
u &= 0x1f;
valid = 0x7f;
len = 1;
} else {
//leading byte = 0xxxxxxx,
(*p)++;
return 0xffffffff;
}
if (n - 1 < len) {
return 0xfffffffe;
}
(*p)++;
//continue byte
while (len) {
i = *(*p)++;
if (i < 0x80) {
return 0xffffffff;
}
u = (u << 6) | (i & 0x3f);
len--;
}
if (u > valid) {
return u;
}
return 0xffffffff;
}
size_t
ngx_utf8_length(u_char *p, size_t n)
{
u_char c, *last;
size_t len;
last = p + n;
for (len = 0; p < last; len++) {
c = *p;
if (c < 0x80) {
p++;
continue;
}
//UTF-8 : [0-0x10FFFF]
if (ngx_utf8_decode(&p, n) > 0x10ffff) {
/* invalid UTF-8 */
return n;
}
}
return len;
}
这个函数是实现,计算一个字符串中含有的字符的个数,以为是utf8编码,所以不能简单计算字节数来计算字符个数。
最后string部分还有几个关于url escape的接口和红黑树的接口,这部分后面其他部分再来看看。