nginx断点续传

有些时候客户端可能只需要请求nginx服务器上的部分数据, 例如: 我们在看电影时, 常常拖动快进条,跳到指定的位置开始观看。 这其实是nginx的断点续传功能, 从指定位置开始观看,相当于向nginx服务器请求某个位置开始的以后的内容。假设nginx服务器上有一个文件,文件的内容为: “0123456789abcdef”一共16个字节。如果客户端只需要2345共4个字节的数据, 则可以在http请求头部加上range字段, 格式为: range: bytes=2-5;       这样nginx服务器就只会把2345返回给客户端,而不会发送整个文件的数据。当然也可以指定多个区间块,例如: range: bytes=2-5,  8-10   则将会把2345以及89a发送给客户端。

nginx断点续传功能是由ngx_http_range_filter_module实现的。其实这个模块是由两个模块组成的,一个为ngx_http_range_header_filter_module, 用于设置http响应的头部信息,例如: 设置content-range,指定应答的区间块开始结束位置; 设置content-length, 指定断点续传时的应答包体大小; 设置206响应码而不是200响应码等等。 另一个模块为ngx_http_range_body_filter_module, 用于从缓冲区中查找指定区间块内容,并把这个区间块的内容发给客户端。

一、ngx_http_range_header_filter_module模块分析

断点续传时,这个模块用来设置发给客户端的响应头部信息。 例如:设置content-range,指定应答的区间块开始结束位置; 设置content-length, 指定断点续传时的应答包体大小; 设置206响应码而不是200响应码等等。以此同时,这个模块还会把断点续传的区间块保存起来,这样在发送响应数据给客户端时,就能够根据这些区间块,找到相应的数据并发给客户端。这个模块的入口函数为: ngx_http_range_header_filter, 看下这个函数的实现。

//功能: 1、判断是否需要进行断点续传,如果不需要则交给下一个过滤模块处理
// 2、保存各个区间块,使得发送响应数据时,能够知道要发送哪些区间的数据给客户端
// 3、断点续传时,设置http响应头部,例如设置content-range,content-length
static ngx_int_t ngx_http_range_header_filter(ngx_http_request_t *r)
{
time_t if_range;
ngx_http_core_loc_conf_t *clcf;
ngx_http_range_filter_ctx_t *ctx;
//不支持断点续传则跳到下一个过滤模块处理
if (r->http_version < NGX_HTTP_VERSION_10
|| r->headers_out.status != NGX_HTTP_OK
|| r != r->main
|| r->headers_out.content_length_n == -1
|| !r->allow_ranges)
{
return ngx_http_next_header_filter(r);
}

//创建断点续传模块上下文结构
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_range_filter_ctx_t));
ngx_array_init(&ctx->ranges, r->pool, 1, sizeof(ngx_http_range_t));

//函数功能,解析http请求头部的range字段,将多个区间保存到ctx->ranges数组中。
switch (ngx_http_range_parse(r, ctx, clcf->max_ranges))
{
case NGX_OK:
//保存这个模块的上下文结构,在发送区间块数据时,才能知道需要发送哪些区间的数据给客户端
ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);
//206响应码
r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
r->headers_out.status_line.len = 0;
//处理只有一个区间块情况
if (ctx->ranges.nelts == 1)
{
return ngx_http_range_singlepart_header(r, ctx);
}
//处理多个区间块
return ngx_http_range_multipart_header(r, ctx);
}

return ngx_http_next_header_filter(r);
}
ngx_http_range_parse函数用于解析http请求头部的range字段,将多个区间保存到ctx->ranges数组中, 例如:range:byte=0-499,500-1000,1000- 则将这三个区间保存到数组中。函数的实现很简单,就是解析range头部, 找到每一个区间块的开始位置、结束位置。 细看下代码就可以看懂,这里就不罗列代码了。
ngx_http_range_singlepart_header用于设置单个区间块的响应头部信息,看下函数的实现:

//获取一个区间块, 创建一个contnet-range应答头部,并给这个头部设置值。
//格式为: content-range: bites 30-200/1000 以上为在总大小为1000字节的文件中,
//获取30—200这区间的内容
static ngx_int_t ngx_http_range_singlepart_header(ngx_http_request_t *r,
ngx_http_range_filter_ctx_t *ctx)
{
//创建content-range应答头部空间,并加入到响应头部链表中
content_range = ngx_list_push(&r->headers_out.headers);
r->headers_out.content_range = content_range;
ngx_str_set(&content_range->key, “Content-Range”);

content_range->value.data = ngx_pnalloc(r->pool,sizeof(“bytes -/”) – 1 + 3 * NGX_OFF_T_LEN);

range = ctx->ranges.elts;
//设置content-range的内容
content_range->value.len = ngx_sprintf(content_range->value.data,
“bytes %O-%O/%O”,
range->start, range->end – 1,
r->headers_out.content_length_n)
– content_range->value.data;

//重新设置应答包体的长度, 为区间块的长度
r->headers_out.content_length_n = range->end – range->start;
}
ngx_http_range_multipart_header函数用于在多个区间块中,设置响应包体的公共部分,以及根据各个区间块计算出响应包体的总大小。 看下代码的实现:
//功能: 1、设置所有区间块的数据总大小
// 2、设置每一个区间块的公共响应数据部分,例如: Content-Type:xxx, Content-Range: bytes等
// 3、生成一个随机数
//公共的响应头部数据格式为:
/*****************************************************************
* CRLF
* “–0123456789” CRLF
* “Content-Type: image/jpeg” CRLF
* “Content-Range: bytes ”
*****************************************************************/
static ngx_int_t ngx_http_range_multipart_header(ngx_http_request_t *r,
ngx_http_range_filter_ctx_t *ctx)
{
//生成一个随机数
boundary = ngx_next_temp_number(0);

//获取每个块的功能头部
ctx->boundary_header.len = ngx_sprintf(ctx->boundary_header.data,
CRLF “–%0muA” CRLF
“Content-Type: %V” CRLF
“Content-Range: bytes “,
boundary,
&r->headers_out.content_type)
– ctx->boundary_header.data;

//设置类型响应头部字段
r->headers_out.content_type.len =
ngx_sprintf(r->headers_out.content_type.data,
“multipart/byteranges; boundary=%0muA”,
boundary)
– r->headers_out.content_type.data;
r->headers_out.content_type_len = r->headers_out.content_type.len;

//根据每一个区块,计算应答包体总大小
range = ctx->ranges.elts;
for (i = 0; i < ctx->ranges.nelts; i++)
{
range[i].content_range.len = ngx_sprintf(range[i].content_range.data,
“%O-%O/%O” CRLF CRLF,
range[i].start, range[i].end – 1,
r->headers_out.content_length_n)
– range[i].content_range.data;

len += ctx->boundary_header.len + range[i].content_range.len
+ (size_t) (range[i].end – range[i].start);
}

//包体长度
r->headers_out.content_length_n = len;

return ngx_http_next_header_filter(r);
}
二、ngx_http_range_body_filter_module模块分析
这个模块用于根据区间块的位置,在缓冲区中找到数据并发送给客户端。请求一个区间块的数据与请求多个区间块的数据处理方式是不一样的。下面分别看下这两个情况下是如何处理的。

static ngx_int_t ngx_http_range_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
//处理一个区间块
if (ctx->ranges.nelts == 1)
{
return ngx_http_range_singlepart_body(r, ctx, in);
}

//处理多个区间块
return ngx_http_range_multipart_body(r, ctx, in);
}
(1)请求单个区间块的数据

考虑下,如果给客户端响应的应答链表由5个数据结点组成,每一个数据结点都存放了要发给客户端的数据, 则刚开始的内存布局如下;

当客户端指定了range, 只请求这个应答链表中的部分数据时,则可能的内存布局如下图:  从图中可以看出第一个数据结点不需要发给客户端,该缓冲区是没有用了,因此将pos与last都指向缓冲区的末尾。最后一个数据结点也是不需要发送给客户端, 但并没有将pos与last指向缓冲区的末尾,因为这个结点会从链表中脱离(可以看下一张图),因此可以不需要把pos与last都指向缓冲区的末尾。

只有中间的三个结点中的数据需要发送给客户端,内存布局如下:  可以看出这三个区间块中的最后一个结点的next指针为null

对于没有发送给客户端的数据节点,这些节点没有用了,待请求关闭时,这些数据节点会被回收。看下函数ngx_http_range_singlepart_body是如何从缓冲区中查找单个数据区间,并把这个数据区间的内容发送给客户端的。

//单个块应答包体处理逻辑
static ngx_int_t ngx_http_range_singlepart_body(ngx_http_request_t *r,
ngx_http_range_filter_ctx_t *ctx,
ngx_chain_t *in)
{
ngx_chain_t *out, *cl, **ll;
ll = &out;
range = ctx->ranges.elts;

//遍历所有的链表节点,查看数据的开始,结束位置
for (cl = in; cl; cl = cl->next)
{
buf = cl->buf;
start = ctx->offset;
last = ctx->offset + ngx_buf_size(buf);

ctx->offset = last; //ctx->offset当前块之前所有块的总长度(不包括当前块)
//last包括当前块在内的之前所有块的总长度

//当前块不满足要请求的最小区间,则查找下一块。
//以此同时,这一块pos指针指向last,表示这个块不会发送给客户端
if (range->end <= start || range->start >= last)
{
buf->pos = buf->last;
buf->sync = 1;
continue;
}
//当前块满足区间,则查看开始数据位置
if (range->start > start)
{
//改变pos位置,发送响应时从pos位置开始放松,之前的内容不发送
if (ngx_buf_in_memory(buf))
{
buf->pos += (size_t) (range->start – start);
}
}

//当前块位置满足区间,则查看结束数据位置
if (range->end <= last)
{
//改变last位置,发送响应时last之后的内容不在发送
if (ngx_buf_in_memory(buf))
{
buf->last -= (size_t) (last – range->end);
}
//标记为最后一块,即便还有剩余块,这些剩余块也不会发送给客户端
buf->last_buf = 1;
*ll = cl;
cl->next = NULL; //指向清空
break;
}

*ll = cl;
ll = &cl->next;
}

//out链表记录了要发送的分块数据的开始位置
if (out == NULL)
{
return NGX_OK;
}

return ngx_http_next_body_filter(r, out);
}
(2)请求多个区间块的数据
nginx服务器目前的实现方式:  请求多个区间块时,应答链表只能由一个结点组成,而不像请求单个区间,应答链表可以由多个节点组成。

假设有一个文件的内容为: “0123456789abcdef”, 一共16个字节。如果客户端想要请求两个区间块的数据, 则可以在http请求头部加上range字段, 格式为: range: bytes=0-5, 9-13;       这样nginx服务器就只会把012345以及9abcd返回给客户端,而不会发送整个文件的数据。参考下面的这张图:

看下ngx_http_range_multipart_body函数的实现过程: 把一个缓冲区内容分割成多个链表,http请求中range指定了多少个区间,就分割成多少个链表,并把各个链表链接起来。ngixn使用空间换时间的方法,不在原来的只有一个节点的应答链表上进行处理,而是重新开辟链表节点。需要注意的是,最后面还有一个结点,用来存放随机数。

//多个块包体处理逻辑, 对于多个区间块,则只能针对一个缓冲区进行处理
static ngx_int_t ngx_http_range_multipart_body(ngx_http_request_t *r,
ngx_http_range_filter_ctx_t *ctx,
ngx_chain_t *in)
{
for (i = 0; i < ctx->ranges.nelts; i++)
{
//获取区间的公共头部
b = ngx_calloc_buf(r->pool);
b->memory = 1;
b->pos = ctx->boundary_header.data;
b->last = ctx->boundary_header.data + ctx->boundary_header.len;
hcl = ngx_alloc_chain_link(r->pool);
hcl->buf = b;

//获取区间的开始、结束位置
b = ngx_calloc_buf(r->pool);
b->pos = range[i].content_range.data;
b->last = range[i].content_range.data + range[i].content_range.len;
rcl = ngx_alloc_chain_link(r->pool);
rcl->buf = b;

//数据区间的真正数据
b = ngx_calloc_buf(r->pool);
b->pos = buf->pos + (size_t) range[i].start;
b->last = buf->pos + (size_t) range[i].end;
dcl = ngx_alloc_chain_link(r->pool);
dcl->buf = b;

//将上面的三个结点构成链表
*ll = hcl;
hcl->next = rcl;
rcl->next = dcl;
ll = &dcl->next;
}

//所有块的结束内容,例如:–00000000005–
//也就是最后一个链表节点
b = ngx_calloc_buf(r->pool);
b->last_buf = 1;
b->pos = ngx_pnalloc(r->pool, sizeof(CRLF “–“) – 1 + NGX_ATOMIC_T_LEN
+ sizeof(“–” CRLF) – 1);
b->last = ngx_cpymem(b->pos, ctx->boundary_header.data,
sizeof(CRLF “–“) – 1 + NGX_ATOMIC_T_LEN);
*b->last++ = ‘-‘; *b->last++ = ‘-‘;
*b->last++ = CR; *b->last++ = LF;
hcl = ngx_alloc_chain_link(r->pool);
hcl->buf = b;
hcl->next = NULL;
*ll = hcl;

return ngx_http_next_body_filter(r, out);
}
———————
作者:ApeLife
来源:CSDN
原文:https://blog.csdn.net/apelife/article/details/73611716
版权声明:本文为博主原创文章,转载请附上博文链接!

URL 链接中 井号#、问号?、连接符& 分别有什么作用?

#,井号:表示网页中的一个位置,被称之为锚点,常用于某个网页间不同位置的跳转,简单的说就是在一个网页中,URL 不变的情况下,通过添加“#buy”的字符在 URL 最后可以跳转到当前网页中已经定义好的锚点(id=”buy”)位置;同样#的改变也会增加浏览器的历史记录,也就是说我们可以通过“后退”按钮回到上一个位置,而熟悉网页开发的朋友们可能也会用于 ajax 的一些操作中,以此来实现不同的访问状态和改变页面访问内容,从而也可以实现那种无刷新载入的效果。

例如:https://zhan.leiue.com/fanly-mip.html#buy (访问该链接就可以直接跳转到 Fanly MIP 主题页面的购买位置哦)

?,问号:常用于动态网站,实现不同的参数值而生成不同的页面或者返回不同的结果,例如 WordPress 的动态链接就是/?p=ID,其中的 p 就表示 post 文章,ID 就表示文章的 ID,从而可以通过文章的 ID 来访问不同的文章。当然我们还常用的就是通过问号+任意参数来实现页面的刷新,从而获得最新的页面或者缓存的刷新。

例如:https://i.leiue.com/avatar/?size=100 (访问该链接就可以获得泪雪用户中心默认的用户头像,并且其 size 就是图像像素大小,所以就会是一个 100px 的头像显示)

&,连接符:既然被叫做是链接符号,那就是连接的作用,也可以说是不同参数的间隔符,一般与问号结合使用,一个动态 URL 链接中以问号开始第一个参数,同&连接符来串联多个参数和值。

例如:https://i.leiue.com/avatar/?size=100&time=20171120 (还是以泪雪个人中心的头像举例,因为头像是有缓存功能的,如果用户修改了头像后,访问原来的地址可能就会存在头像未修改的情况,那么我们在保证要获得 100px 大小的头像并且要刷新缓存就可以使用连接符&多添加一个任意的参数,以此来获取最新的头像)

简单总结:本来子凡是想分享一下“URL 链接中 井号#、问号?、连接符& 与 SEO 有什么关系”这么一个话题,但是发现泪雪博客之前好像没有具体的介绍过这三个在 URL 中常见的符号的作用,所以为了让大家知其然并知其所以然,子凡就只好分开来写这两篇文章啦!

URL中“#” “?” &“”号的作用

1. #

10年9月,twitter改版。一个显著变化,就是URL加入了”#!”符号。比如,改版前的用户主页网址为http://twitter.com/username改版后,就变成了http://twitter.com/#!/username
这是主流网站第一次将”#”大规模用于重要URL中。这表明井号(Hash)的作用正在被重新认识。本文根据HttpWatch的文章,整理与井号有关的所有重要知识点。
一、#的涵义
#代表网页中的一个位置。其右面的字符,就是该位置的标识符。比如,http://www.example.com/index.html#print就代表网页index.html的print位置。浏览器读取这个URL后,会自动将print位置滚动至可视区域。
为网页位置指定标识符,有两个方法。一是使用锚点,比如<a name=”print”></a>,二是使用id属性,比如<div id=”print”>。

二、HTTP请求不包括#
#是用来指导浏览器动作的,对服务器端完全无用。所以,HTTP请求中不包括#。
比如,访问下面的网址,http://www.example.com/index.html#print,浏览器实际发出的请求是这样的:

GET /index.html HTTP/1.1
Host: www.example.com

 

三、#后的字符
在第一个#后面出现的任何字符,都会被浏览器解读为位置标识符。这意味着,这些字符都不会被发送到服务器端。
比如,下面URL的原意是指定一个颜色值:http://www.example.com/?color=#fff,但是,浏览器实际发出的请求是:

GET /?color= HTTP/1.1
Host: www.example.com

 

四、改变#不触发网页重载
单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。
比如,从http://www.example.com/index.html#location1改成http://www.example.com/index.html#location2,浏览器不会重新向服务器请求index.html。

 

五、改变#会改变浏览器的访问历史
每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置。这对于ajax应用程序特别有用,可以用不同的#值,表示不同的访问状态,然后向用户给出可以访问某个状态的链接。值得注意的是,上述规则对IE 6和IE 7不成立,它们不会因为#的改变而增加历史记录。
六、window.location.hash读取#值
window.location.hash这个属性可读可写。读取时,可以用来判断网页状态是否改变;写入时,则会在不重载网页的前提下,创造一条访问历史记录。
七、onhashchange事件
这是一个HTML 5新增的事件,当#值发生变化时,就会触发这个事件。IE8+、Firefox 3.6+、Chrome 5+、Safari 4.0+支持该事件。
它的使用方法有三种:

  • window.onhashchange = func;
  • <body onhashchange=”func();”>
  • window.addEventListener(“hashchange”, func, false);

对于不支持onhashchange的浏览器,可以用setInterval监控location.hash的变化。

八、Google抓取#的机制
默认情况下,Google的网络蜘蛛忽视URL的#部分。
但是,Google还规定,如果你希望Ajax生成的内容被浏览引擎读取,那么URL中可以使用”#!”,Google会自动将其后面的内容转成查询字符串_escaped_fragment_的值。
比如,Google发现新版twitter的URL:http://twitter.com/#!/username
就会自动抓取另一个URL:http://twitter.com/?_escaped_fragment_=/username
通过这种机制,Google就可以索引动态的Ajax内容。

AJAX = 异步 JavaScriptXML标准通用标记语言的子集)。AJAX 是一种用于创建快速动态网页的技术。

2. ?

1)连接作用:比如

http://www.xxx.com/Show.asp?id=77&nameid=2905210001&page=1

2)清除缓存:比如

http://www.xxxxx.com/index.html 
http://www.xxxxx.com/index.html?test123123

两个url打开的页面一样,但是后面这个有问号,说明不调用缓存的内容,而认为是一个新地址,重新读取。

3. &

不同参数的间隔符

nginx-upload-module模块实现文件断点续传原理

每当我们想简单的实现文件上传功能,而又不使用其他的语言(比如PHP、Java),或者想实现文件的断点续传。这个时候Nginx的一个模块nginx-upload-module就能满足我们的需求。
模块安装
下载模块:

cd /tmp
wget https://codeload.github.com/vkholodkov/nginx-upload-module/zip/2.2
unzip 2.2
安装模块:

.configure –add-module=/tmp/nginx-upload-module-2.2/
multipart/form-data表单上传示例
nginx.conf配置:

server {
[…]
location /upload {
upload_pass @uploadHandler;
upload_store /usr/local/nginx/upload_temp 1;
upload_set_form_field $upload_field_name.path “$upload_tmp_path”;
}

location @uploadHandler {
proxy_pass http://backend-host;
}
[…]
}
这里在server里定义了upload location,这个location是上传的接口,还有@uploadHandler location,是当文件上传完成后,nginx模块会对这个location发送一些必要的信息,如文件上传的路径,这里涉及了几个指令:

upload_pass @uploadHandler:上传完成后会发送必要的数据到@uploadHandler;
upload_store /usr/local/nginx/upload_temp 1: 文件上传的临时目录;
upload_set_form_field $upload_field_name.path “$upload_tmp_path”: 设置文件上传完成后,把文件临时路径发送给upload_pass指定的location。

断点续传示例
nginx.conf配置

server {
[…]
location /resumable_upload {
upload_resumable on;
upload_state_store /usr/local/nginx/upload_temp ;
upload_pass @drivers_upload_handler;
upload_store /usr/local/nginx/upload_temp;
upload_set_form_field $upload_field_name.path “$upload_tmp_path”;
}

location @resumable_upload_handler {
proxy_pass http://localhost:8002;
}
[…]
}
与上一步multipart/form-data表单上传示例配置不同的地方有:
upload_resumable on: 开启断点续传功能;
upload_state_store /usr/local/nginx/upload_temp: 设置断点续传状态文件存储的目录。

上传文件第一个片段
POST /upload HTTP/1.1
Host: example.com
Content-Length: 51201
Content-Type: application/octet-stream
Content-Disposition: attachment; filename=”big.TXT”
X-Content-Range: bytes 0-51200/511920
Session-ID: 1111215056

<0-51200的字节文件数据>
上传文件第一个片段服务器响应
HTTP/1.1 201 Created
Date: Thu, 02 Sep 2010 12:54:40 GMT
Content-Length: 14
Connection: close
Range: 0-51200/511920

0-51200/511920
上传文件最后一个片段
POST /upload HTTP/1.1
Host: example.com
Content-Length: 51111
Content-Type: application/octet-stream
Content-Disposition: attachment; filename=”big.TXT”
X-Content-Range: bytes 460809-511919/511920
Session-ID: 1111215056

<460809-511919字节文件数据>
上传文件最后一个片段服务器响应
HTTP/1.1 200 OK
Date: Thu, 02 Sep 2010 12:54:43 GMT
Content-Type: text/html
Connection: close
Content-Length: 2270

< 响应的内容>
请求头说明
请求头 说明
Content-Disposition attachment, filename=“上传的文件名”
Content-Type 待上传文件的mime type,如application/octet-stream(注:不能为multipart/form-data)
X-Content-Range 待上传文件字节范围,如第一片段bytes 0-51200/511920,最后一个片段bytes 460809-511919/511920(注:文件第一个字节标号为0,最后一个字节标号为n-1,其中n为文件字节大小)
X-Session-ID 上传文件的标识,由客户端随机指定.因为是断点续传,客户端必须确保同一个文件的所有片段上传标识一致
Content-Length 上传片段的大小
Python上传demo
#!/usr/bin/python
# -*- coding: utf-8 -*-

import os.path
import requests
import hashlib

# 待上传文件路径
FILE_UPLOAD = “/tmp/testfile”
# 上传接口地址
UPLOAD_URL = “http://host/drivers_upload”
# 单个片段上传的字节数
SEGMENT_SIZE = 1048576

def upload(fp, file_pos, size, file_size):
session_id = get_session_id()
fp.seek(file_pos)
payload = fp.read(size)
content_range = “bytes {file_pos}-{pos_end}/{file_size}”.format(file_pos=file_pos,
pos_end=file_pos+size-1,file_size=file_size)
headers = {‘Content-Disposition’: ‘attachment; filename=”big.TXT”‘,’Content-Type’: ‘application/octet-stream’,
‘X-Content-Range’:content_range,’Session-ID’: session_id,’Content-Length’: size}
res = requests.post(UPLOAD_URL, data=payload, headers=headers)
print(res.text)

# 根据文件名hash获得session id
def get_session_id():
m = hashlib.md5()
file_name = os.path.basename(FILE_UPLOAD)
m.update(file_name)
return m.hexdigest()

def main():
file_pos = 0
file_size = os.path.getsize(FILE_UPLOAD)
fp = open(FILE_UPLOAD,”r”)

while True:
if file_pos + SEGMENT_SIZE >= file_size:
upload(fp, file_pos, file_size – file_pos, file_size)
fp.close()
break
else:
upload(fp, file_pos, SEGMENT_SIZE, file_size)
file_pos = file_pos + SEGMENT_SIZE

if __name__ == “__main__”:
main()
———————
作者:工程师WWW
来源:CSDN
原文:https://blog.csdn.net/weiwangchao_/article/details/76039480
版权声明:本文为博主原创文章,转载请附上博文链接!

谈Nginx的Location匹配优先级(最大前缀匹配)

Nginx的配置文件中的Location用于匹配特定的URI,如果location后面的规则匹配了URI,并且匹配在此处停止向后匹配,则应用该location下面的规则,反之继续进行匹配,直到匹配到相应的规则或匹配到默认的规则。
这篇文章单就location的定位进行讨论,其它的部分不进行解析。
本文解决的问题:
location为什么重要?
location 相当于选择,选中。当我们处理某件事情,往往必须选中,然后再进行进一步的动作。比如处理word时,对某段文字应用字体,肯定是先选中,然后再应用规则。location就是这个意思,选择的对象就是URI,根据不同的URI,使用不同的规则,location就是第一步,只有这一步正确,其余的部分才能保证顺利进行。
location该如何配置?
  1. location的几种匹配方式
普通匹配
location = URI { configuration } #精确匹配
location ^~ URI { configuration } #非正则匹配
location [space] URI { configuration} # # 前缀匹配
正则匹配
location ~ URI { configuration } #大小写敏感匹配
location ~* URI { configuration } #大小写不敏感匹配
  1. 几种匹配方式的优先级
整体规则按照先普通匹配,然后再正则匹配,如果正则不匹配,则回退至上一个普通匹配。其中普通匹配没有顺序之分,哪个匹配最精确,就使用哪个location,正则匹配按照规则的书写顺序进行。
= 精确匹配,匹配后停止后续匹配,直接执行该匹配后的configuration.
[空格] 前缀匹配,匹配后,继续更长前缀匹配和正则匹配。
^~ 非正则匹配,匹配该规则后,停止继续正则匹配。
~ 区分大小写的正则匹配,按顺序匹配,一旦匹配上即停止后续匹配。
~* 不区分大小写的匹配,一旦匹配即停止后续匹配。
  1. 几种匹配规则的相同点和不同点
首先对几种匹配符号进行编号:

 

符号 编号
= 1
空格 2
^~ 3
~ 4
~* 5
1 vs 2
相同点: 2的特殊情况(隐式精确匹配)会等于1
不同点: 1匹配后停止后续的正则匹配,2 匹配后还要看有没有更长的前缀可以匹配,和有没有后续的正则匹配。
1 vs 3
相同点:都停止后续的正则匹配,即即使有正则表达式可以匹配的上,也不会匹配。
不同点: 3 匹配后还要继续进行最大前缀匹配,如果有更精确的匹配可以用,就使用该条匹配。
1 和 4 5 是两类不同的匹配方式,匹配了1 就意味着,45都忽略。
2 vs 3 :
相同点:匹配后都执行最大前缀匹配,
不同点: 2 进行最大前缀匹配后还要进行正则匹配,3 匹配后停止正则匹配。

示例:
如果URI相同,则location [] /test 和 location ^~ /test  相同,报错如下:
nginx: [emerg] duplicate location “/test” in /data/nginx/conf/nginx.conf:42
由此可见, ^~ 和 空格 在URI相同的情况下,是同样的意思,所以报如上错误。

演示:

在以下路径中
/data/nginx/zzp/exact/
/data/nginx/zzp/regex/
创建同名但不同内容的文件,然后使用curl -v http://host/a.html
发现,正则表达式部分会生效,由此证明,经过普通匹配后的URI,再继续进行regex匹配,regex匹配上之后,regex生效。
location  /zzp/ {
            alias /data/nginx/zzp/exact/;
        }
location ~ ^/zzp/(.*\.html)$ {
        alias /data/nginx/zzp/regex/$1;
}
使用取消正则表达式符号 ^~,然后再请求相同的链接,发现exact路径下的文件被访问,证明,普通匹配执行后不再进行正则表达式匹配。
location ^~ /zzp/ {
            alias /data/nginx/zzp/exact/;
        }
location ~ ^/zzp/(.*\.html)$ {
            alias /data/nginx/zzp/regex/$1;
}

Nginx location模块整理(最大前缀匹配)

location模块

Nginx location

location 指令的作用是根据用户请求的URI来执行不同的应用,URI就是根据用户请求到的网址URL进行匹配,匹配成功了进行相关的操作。

location语法

下面是官网的语法结构:

Syntax:    location [ = | ~ | ~* | ^~ ] uri { … }

location @name { … }

Default:   —

Context:   server, location

官网解释翻译和理解

下面会结合官网原文进行解释,以下英文部分均从官网摘抄:

http://nginx.org/en/docs/http/ngx_http_core_module.html#location

(翻译的不好勿喷)

Sets configuration depending on a request URI.

根据请求的URI进行配置

URI 变量是待匹配的请求字符串,

A location can either be defined by a prefix string, or by a regular expression.

Regular expressions are specified with the preceding “~*” modifier (for case-insensitive matching), or the “~” modifier (for case-sensitive matching)

一个location可以用prefix string(前缀字符串)定义,也可以通过regular expression(正则表达式来定义)

通俗的说也就是:我们可以通过使用不同的前缀,表达不同的含义,对于不同的前缀可以分为两大类:普通location和正则location

符号:”~”表示uri包含正则,并且区分大小写

符号:“~*”表示uri包含正则,但不区分大小写

注意:如果你的uri用正则,则你的正则前面必须添加~或者~*,之前我在这里存在误区,以为可以不加~或者~*

To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

Nginx服务器会首先会检查多个location中是否有普通的uri匹配,如果有多个匹配,会先记住匹配度最高的那个。然后再检查正则匹配,这里切记正则匹配是有顺序的,从上到下依次匹配,一旦匹配成功,则结束检查,并就会使用这个location块处理此请求。如果正则匹配全部失败,就会使用刚才记录普通uri匹配度最高的那个location块处理此请求。

If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.

当普通匹配的最长前缀匹配有符号“^~”的时候,就不会在匹配正则

直接使用当前匹配的这个location块处理此请求

Also, using the “=” modifier it is possible to define an exact match of URI and location. If an exact match is found, the search terminates. For example, if a “/” request happens frequently, defining “location = /” will speed up the processing of these requests, as search terminates right after the first comparison. Such a location cannot obviously contain nested locations.

使用符号“=”修饰符可以定义一个精确匹配的URI和位置,如果找到了一个精确的匹配,则搜索终止,例如,如果一个”/”请求频繁发生,定义“location =/”将加快这些请求的处理,一旦精确匹配只有就结束,这样的location显然不能包含嵌套location

这里我们说一下location / {} 和location =/ {}的区别:

“location / {}”是普通的最大前缀匹配,任何的uri肯定是以“/”开头,所以location / {} 可以说是默认匹配,当其他都不匹配了,则匹配默认匹配

根据上述官网内容进行总结

a. ”=”用于普通uri前,要求精确匹配,如果匹配成功,则停止搜索并用当前location处理此请求

b. ”~” 表示uri包含正则,并且区分大小写

c. “~*”表示uri包含正则,但不区分大小写

d. ”^~”表示在普通uri前要求Nginx服务器找到普通uri匹配度最高的那个location后,立即处理此请求,并不再进行正则匹配

e. ”^~”和“=”都可以阻止继续匹配正则location两者的区别:“^~”依然遵守最大前缀原则,然后“=”是需要严格匹配

关于location网上的一些误解

location 的匹配顺序是“先匹配正则,再匹配普通”

这是一个错误的结论,从上面官网的文章中我们可以知道:

先匹配普通uri,然后记住匹配度最高的那个(官网原话:To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered.)然后匹配正则,如果正则匹配则结束查找,如果正则不匹配,则匹配之前普通匹配中匹配度最高的那个将执行该请求(官网原话:Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.)

所以:location 的匹配顺序是“先匹配正则,再匹配普通” 这句话肯定是错误的,况且这里并没有包含”^~”和“=”

location 的执行逻辑跟 location 的编辑顺序无关。

这也是一种错误的理解,我们根据上述内容可以知道:

如果是普通uri 匹配,这个时候是没有顺序的,但是正则匹配则是有顺序的,是从上到下依次匹配,一旦有匹配成功,则停止后面的匹配。

 

那么顺序到底是怎么匹配呢?

我画了一个location匹配的逻辑图便于理解匹配的顺序规则

 

通过实验来验证出结果

对www.conf配置如下:

[root@nginx extra]# cat www.conf

server {

listen       80;

server_name  www.zhaofan.com;

access_log      logs/access_www.log;

root    html/www;

location / {

return 401;

}

location = / {

return 402;

}

 

location  /documents/ {

return 403;

}

location  ^~ /images/ {

return 404;

}

location ~* \.(gif|jpg|jpeg)$ {

return 500;

}

}

[root@nginx extra]#

注意:

location ~* \.(gif|jpg|jpeg)$ {

return 500;

这个部分$前面不能有空格,否则会提示如下错误:

[root@nginx extra]# ../../sbin/nginx -s reload

nginx: [emerg] invalid location modifier “~*\.(gif|jpg|jpeg)” in /application/nginx1.6.2/conf/extra/www.conf:19

如果$后面没有空格,则会提示如下错误:

[root@nginx extra]# ../../sbin/nginx -s reload

nginx: [emerg] directive “location” has no opening “{” in /application/nginx1.6.2/conf/extra/www.conf:23

这些都是细节问题,一定要注意

实验一:登录nginx网站,我这里的直接打开:http://192.168.8.105/

 

可以看出这里是精确匹配

location = / {

return 402;

}

 

实验二:打开http://192.168.8.105/aaa/

 

这里可以看出因为都不匹配,所以最后匹配了location / {}

location / {

return 401;

}

 

实验三:打开http://192.168.8.105/1.gif

 

这里可以看出是匹配了正则

location ~* \.(gif|jpg|jpeg)$ {

return 500;

}

 

实验四:打开http://192.168.8.105/aaa/1.gif

 

这里依然是匹配正则

location ~* \.(gif|jpg|jpeg)$ {

return 500;

}

 

实验五:打开http://192.168.8.105/images/1.gif

 

location / {

return 401;

}

location = / {

return 402;

}

 

location  /document/ {

return 403;

}

location  ^~ /images/ {

return 404;

}

location ~* \.(gif|jpg|jpeg)$ {

return 500;

}

这里通过配置把实验三和实验四的对比就可以看出来因为普通匹配里有“^~”,并且匹配到了images,所以这个就是不进行正则匹配

location  ^~ /images/ {

return 404;

}

实验六:“^~”遵守最大前缀原则

配置如下:

location / {

return 401;

}

location = / {

return 402;

}

 

location  = /document/ {

return 403;

}

        location  ^~ /images/ {

            return 404;

        }

        location   /images/1/ {

            return 501;

        }

location ~* \.(gif|jpg|jpeg)$ {

return 500;

}

还是关注标红的地方,这个时候我们登陆:http://192.168.1.19/images/

结果如下:

 

从这里可以看出匹配了:

        location  ^~ /images/ {

            return 404;

        }

但是如果我们登陆:http://192.168.1.19/images/1/

结果如下;

 

这里匹配了:

        location   /images/1/ {

            return 501;

        }

从这里我们可以看出“^~”遵守最大匹配原则。

实验七:当最长匹配和精确匹配相同时

配置如下:

location / {

return 401;

}

location = / {

return 402;

}

 

location  = /document/ {

return 403;

}

location  ^~ /images/ {

return 404;

}

        location   /images/1/ {

            return 501;

        }

 

        location  =  /images/1/ {

            return 502;

        }

location ~* \.(gif|jpg|jpeg)$ {

return 500;

}

登陆:http://192.168.1.19/images/1/

结果如下:

 

但是如果这个时候登陆:http://192.168.1.19/images/1/aaa/

结果如下:

 

从这里我们可以看出当精确匹配和最长匹配相同时,匹配的是精确匹配。

 

不在过多的做实验,根据上面的逻辑图应该可以解决碰到的问题了

带问号的url重定向和参数保留–nginx rewrite

http://host/demo/sub/z10?x=95&y=27=>http://host/demo/sub/10/95_27.png

问题:

1、重定向前url带有问号,且需要保留问号后的参数;

2、重定向后的参数以下划线连接。

解决:

http://host/demo/sub/z10?x=95&y=27=>http://host/demo/sub/10/95/27=>http://host/demo/sub/10/95_27.png

1、nginx rewrite正则匹配不会匹配问号后的参数,因此需要使用$arg_{参数名}来保留参数,且匹配规则要以问号结尾;

但如果直接重定向到最终结果,两个参数之间用下划线连接,会造成参数解析错误,因此第一步先去掉问号,生成中间结果:

rewrite ^/(\w+)/(\w+)/z(\d+) /$1/$2/$3/$arg_x/$arg_y? permanent;

2、第一步重定向去掉了问号,下面只需要正则匹配参数就ok了:

rewrite ^/(\w+)/(\w+)/(\d+)/(\d+)/(\d+) /$1/$2/$3/$4_$5.png permanent;
———————
作者:matche
来源:CSDN
原文:https://blog.csdn.net/liznse/article/details/52958813
版权声明:本文为博主原创文章,转载请附上博文链接!

nginx配置proxy_pass后,访问时路径丢失怎么办

应用场景
当我访问blog.first-blood.cn的时候,proxy_pass转发到jonny023.github.io这个域名下去了,而jonny023.github.io/upload/hello.jpg这个文件我通过blog.first-blood.cn/upload/hello.jpg访问就出现404

解决办法
此时在nginx的配置文件下的location配置下面添加一句
proxy_set_header Host jonny023.github.io;
1
server {
listen 80;
server_name blog.first-blood.cn;
charset utf-8,gbk;
root html;
index index.html index.htm;
location / {
proxy_set_header Host jonny023.github.io;
#proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass https://jonny023.github.io;
}

location ^~/archives/ {
proxy_pass https://jonny023.github.io/archives/;
}

location ^~/uploads/ {
proxy_pass https://jonny023.github.io/uploads/;
}

location /favico.ico {
proxy_pass https://jonny023.github.io;
charset utf-8,gbk;
expires 12h;
}

location ~ .*\.(js|css|eot|otf|ttf|woff|woff2)?$ {
proxy_pass https://jonny023.github.io;
charset utf-8,gbk;
expires 12h;
}

location ~* \.(eot|otf|ttf|woff|woff2)$ {
add_header Access-Control-Allow-Origin *;
}
}
———————
作者:LiJonny
来源:CSDN
原文:https://blog.csdn.net/qq_31584291/article/details/83478399
版权声明:本文为博主原创文章,转载请附上博文链接!

http协议的状态码——400,401,403,404,500,502,503,301,302等常见网页错误代码

http协议的状态码

1xx(临时响应)

表示临时响应并需要请求者继续执行操作的状态码。

100(继续)

请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。

101(切换协议)

请求者已要求服务器切换协议,服务器已确认并准备切换。

2xx(成功)
表示成功处理了请求的状态码。

200(成功)

服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。如果是对您的 robots.txt 文件显示此状态码,则表示 Googlebot 已成功检索到该文件。

201(已创建)

请求成功并且服务器创建了新的资源。

202(已接受)

服务器已接受请求,但尚未处理。

203(非授权信息)

服务器已成功处理了请求,但返回的信息可能来自另一来源。

204(无内容)

服务器成功处理了请求,但没有返回任何内容。

205(重置内容)

服务器成功处理了请求,但没有返回任何内容。与 204 响应不同,此响应要求请求者重置文档视图(例如,清除表单内容以输入新内容)。

206(部分内容)

服务器成功处理了部分 GET 请求。

3xx(重定向)

要完成请求,需要进一步操作。通常,这些状态码用来重定向。Google 建议您在每次请求中使用重定向不要超过 5 次。您可以使用网站管理员工具查看一下 Googlebot 在抓取重定向网页时是否遇到问题。诊断下的网络抓取页列出了由于重定向错误导致 Googlebot 无法抓取的网址。

300(多种选择)

针对请求,服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。

301(永久移动)

请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。您应使用此代码告诉 Googlebot 某个网页或网站已永久移动到新位置。

302(临时移动)

服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。此代码与响应 GET 和 HEAD 请求的 301 代码类似,会自动将请求者转到不同的位置,但您不应使用此代码来告诉 Googlebot 某个网页或网站已经移动,因为 Googlebot 会继续抓取原有位置并编制索引。

303(查看其他位置)

请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。对于除 HEAD 之外的所有请求,服务器会自动转到其他位置。

304(未修改)

自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。
如果网页自请求者上次请求后再也没有更改过,您应将服务器配置为返回此响应(称为 If-Modified-Since HTTP 标头)。服务器可以告诉 Googlebot 自从上次抓取后网页没有变更,进而节省带宽和开销。
.

305(使用代理)

请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理。

307(临时重定向)

服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。此代码与响应 GET 和 HEAD 请求的 <a href=answer.py?answer=>301</a> 代码类似,会自动将请求者转到不同的位置,但您不应使用此代码来告诉 Googlebot 某个页面或网站已经移动,因为 Googlebot 会继续抓取原有位置并编制索引。

 

4xx(请求错误)
这些状态码表示请求可能出错,妨碍了服务器的处理。

400(错误请求)

服务器不理解请求的语法。

401(未授权)

请求要求身份验证。对于登录后请求的网页,服务器可能返回此响应。

403(禁止)

服务器拒绝请求。如果您在 Googlebot 尝试抓取您网站上的有效网页时看到此状态码(您可以在 Google 网站管理员工具诊断下的网络抓取页面上看到此信息),可能是您的服务器或主机拒绝了 Googlebot 访问。

404(未找到)

服务器找不到请求的网页。例如,对于服务器上不存在的网页经常会返回此代码。
如果您的网站上没有 robots.txt 文件,而您在 Google 网站管理员工具“诊断”标签的 robots.txt 页上看到此状态码,则这是正确的状态码。但是,如果您有 robots.txt 文件而又看到此状态码,则说明您的 robots.txt 文件可能命名错误或位于错误的位置(该文件应当位于顶级域,名为 robots.txt)。
如果对于 Googlebot 抓取的网址看到此状态码(在”诊断”标签的 HTTP 错误页面上),则表示 Googlebot 跟随的可能是另一个页面的无效链接(是旧链接或输入有误的链接)。

405(方法禁用)

禁用请求中指定的方法。

406(不接受)

无法使用请求的内容特性响应请求的网页。

407(需要代理授权)

此状态码与 <a href=answer.py?answer=35128>401(未授权)</a>类似,但指定请求者应当授权使用代理。如果服务器返回此响应,还表示请求者应当使用代理。

408(请求超时)

服务器等候请求时发生超时。

409(冲突)

服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。服务器在响应与前一个请求相冲突的 PUT 请求时可能会返回此代码,以及两个请求的差异列表。

410(已删除)

如果请求的资源已永久删除,服务器就会返回此响应。该代码与 404(未找到)代码类似,但在资源以前存在而现在不存在的情况下,有时会用来替代 404 代码。如果资源已永久移动,您应使用 301 指定资源的新位置。

411(需要有效长度)

服务器不接受不含有效内容长度标头字段的请求。

412(未满足前提条件)

服务器未满足请求者在请求中设置的其中一个前提条件。

413(请求实体过大)

服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。

414(请求的 URI 过长)

请求的 URI(通常为网址)过长,服务器无法处理。

415(不支持的媒体类型)

请求的格式不受请求页面的支持。

416(请求范围不符合要求)

如果页面无法提供请求的范围,则服务器会返回此状态码。

417(未满足期望值)

服务器未满足”期望”请求标头字段的要求。

5xx(服务器错误)
这些状态码表示服务器在处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错。

500(服务器内部错误)

服务器遇到错误,无法完成请求。

501(尚未实施)

服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。

502(错误网关)

服务器作为网关或代理,从上游服务器收到无效响应。

503(服务不可用)

服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。

504(网关超时)

服务器作为网关或代理,但是没有及时从上游服务器收到请求。

505(HTTP 版本不受支持)

服务器不支持请求中所用的 HTTP 协议版本。