NGINX支持中文目录索引(Win32)

Windows平台上在使用Nginx的时候无法支持中文路径,哪怕是直接访问一个中文路径,也无法访问到,访问文件是404,访问目录是500。

解决方案思考

正常情况下先访问一个内部有中文文件或目录的目录,Nginx会显示为乱码(调整网页编码能正确),而且其中的链接是直接编码的,什么意思,就是这个链接是GBK字符集的URL编码,所以肯定是访问不了的了。不过即使手动将链接改为UTF-8字符集的URL编码,也依然访问不了。

基于上面的一些现象和实际情况分析,基本可获得以下结论

  • 输出必须是UTF-8编码的网页,特别是URL链接
  • 内部本地系统是GBK编码,必须把请求路径最终转换成本地字符编码

当然,在Linux等系统上搞定字符集很简单,Win32就很难了,基本没有成功的可能性,不过网络上提供了一些解决该问题的方法,比如添加 charset gbk,utf-8;,确实是页面能显示中文了,但是并不能下载,原因同上。

修改源码实现

思索再三,发现还是只能去修改源码了,既然是字符集的对应关系,那么就是需要将本地和UTF-8字符集进行转换,增加这么一层逻辑不就可以了么。其中目录浏览功能是个文件遍历功能,这个咱很熟悉,在Win上就是 FindFirstFile 系列函数,看源码,在 http\modules\ngx_http_autoindex_modele.c 文件中 ngx_http_autoindex_handler 函数中有详细的逻辑,基本是2个函数 ngx_open_dirngx_read_dir ,看实现确实和我们想象的是一致的。

首先看一下具体的定义吧,其中外部统一的定义结构体是 ngx_dir_t

typedef struct {
HANDLE dir;
WIN32_FIND_DATA finddata;

unsigned valid_info:1;
unsigned type:1;
unsigned ready:1;
} ngx_dir_t;

里面使用的是ANSI版本的API,如果依然要使用该版本的API,那么就需要修改输入和输出了,也就是在 ngx_open_dir 函数内把字符集从UTF-8转为本地字符集,然后在 ngx_read_dir 函数里面再将字符集转成UTF-8,转什么字符集呢,从源码中可以看到,主要是 WIN32_FIND_DATA 结构体里面的 cFileName,不过在Windows上转换字符集非常麻烦,竟然没有一步到位的,比如要将本地转UTF-8的步骤是先把本地转UTF-16,然后再转UTF-8,需要2步,此处我打算使用宽字符版进行。

首先,在 ngx_dir_t 类型中增加一个宽字符版本的结构体

WIN32_FIND_DATAW                finddataw;

为什么是增加呢,而不是直接修改呢,因为在输出的时候还需要转换!

然后在 ngx_open_dir 里面实现,大概如下

ngx_int_t
ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir)
{
u_short utf16[NGX_UTF16_BUFLEN] = { 0 };
ngx_cpystrn(name->data + name->len, NGX_DIR_MASK, NGX_DIR_MASK_LEN + 1);
size_t len = name->len + NGX_DIR_MASK_LEN + 1;
u_short* u = ngx_utf8_to_utf16(utf16, name->data, &len);

dir->dir = FindFirstFileW((const wchar_t *)utf16, &dir->finddataw);
ngx_win32_find_data_w2a(&dir->finddataw, &dir->finddata);
name->data[name->len] = '\0';

if (dir->dir == INVALID_HANDLE_VALUE) {
return NGX_ERROR;
}

dir->valid_info = 1;
dir->ready = 1;

return NGX_OK;
}

然后在 ngx_read_dir 里面实现,如下

ngx_int_t
ngx_read_dir(ngx_dir_t *dir)
{
if (dir->ready) {
dir->ready = 0;
return NGX_OK;
}

if (FindNextFileW(dir->dir, &dir->finddataw) != 0) {
dir->type = 1;
ngx_win32_find_data_w2a(&dir->finddataw, &dir->finddata);
return NGX_OK;
}

return NGX_ERROR;
}

基本想法就是使用宽字符版的API,然后将结果转为多字节版,这个转换函数实现如下

static void ngx_win32_find_data_w2a(WIN32_FIND_DATAW* w, WIN32_FIND_DATAA* a) {
a->dwFileAttributes = w->dwFileAttributes;
a->dwReserved0 = w->dwReserved0;
a->dwReserved1 = w->dwReserved1;
a->ftCreationTime = w->ftCreationTime;
a->ftLastAccessTime = w->ftLastAccessTime;
a->ftLastWriteTime = w->ftLastWriteTime;
a->nFileSizeHigh = w->nFileSizeHigh;
a->nFileSizeLow = w->nFileSizeLow;

//memset(a->cAlternateFileName, 0, 14);
//WideCharToMultiByte(CP_UTF8, 0, w->cAlternateFileName, -1, a->cAlternateFileName, 14, NULL, NULL);
memset(a->cFileName, 0, MAX_PATH);
WideCharToMultiByte(CP_UTF8, 0, w->cFileName, -1, a->cFileName, MAX_PATH, NULL, NULL);
}

显示字符集调整

上面的修改基本搞定了目录浏览功能,但是还有个小问题,浏览器并没有自动的使用UTF-8,每次都要手动在浏览器上选择字符编码,很麻烦,因为我们已经确定了字符集,只需要在代码写死即可。

在文件 http\modules\ngx_http_autoindex_modele.c 文件实现如下

static ngx_buf_t *
ngx_http_autoindex_html(ngx_http_request_t *r, ngx_array_t *entries)
{
...
static u_char header[] =
"</title></head>" CRLF
"<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">" CRLF
"<body bgcolor=\"white\">" CRLF
"<h1>Index of "
;
...
}

就是简单增加了一个 <meta> 标记。

下载试用

本来不想放的,不过总有人图省事,不想自己编译的话,就直接拿我编译的试用吧

NGINX 1.9.11 32位,VC2017编译 https://pan.baidu.com/s/1I1fWvW7wVOB7pEmoVoj_xA

最近的文章

禁用PIP安装到用户目录

当前使用的 CentOS 7 系统上, 在某些脚本中自动安装一些软件竟然安装到root的用户的目录下了,其他用户根本用不了,因此针对该现象进行了分析。 问题基本分析正常情况下软件包是安装到系统目录下的,也就是安装目录下的 site-packages 目录,但是不知道为啥会安装到用户目录,不管什么情 …

技术 继续阅读
更早的文章

VC编译安装SOCI(SQLITE后端)

SOCI 是C++中为数不多的非常方便访问数据库的工具,Poco.Data 库基本也是参考了该库实现,SOCI 在 Linux 各个版本均有开发包,VisualStudio 上还需要自己编译。 以下为 SOCI 库添加 sqlite 后端为例的编译方法。 编译前需求 sqlite 开发包 参考前文的 …

技术 继续阅读