一、OSI模型和TCP/IP模型

  • OSI(Open System Interconnect),即开放式系统互联。 一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互连模型
  • TCP/IP协议族是一个四层协议系统,自底而上分别是数据链路层、网络层、传输层和应用层。每一层完成不同的功能,且通过若干协议来实现,上层协议使用下层协议提供的服务
OSI模型和TCP/IP模型
  • OSI模型
    • 应用层:是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,POP3、SMTP等
    • 表示层:提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一
    • 会话层:负责建立、管理和终止表示层实体之间的通信会话
    • 传输层:建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP UDP就是在这一层
    • 网络层:通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。这一层就是我们经常说的IP协议层
    • 数据链路层:将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测
    • 物理层:实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质
  • TCP/IP模型
    • 应用层:负责处理特定的应用程序细节。简单网络管理SNMP协议,简单网络传输SMTP,域名解析DNS,文件下载FTP协议,远程协助Telnet协议,超文本传输HTTP等等
    • 传输层:主要为两台主机上的应用提供端到端的通信。TCP协议和UDP协议
    • 网络层:处理分组在网络中的活动,比如分组的选路。IP协议等
    • 网络接口层:包括操作系统中的设备驱动程序、计算机中对应的网络接口卡

二、HTTP与TCP/IP

HTTP通信是由TCP/IP协议承载的,因此HTTP协议的需要进行三次握手才能建立连接,经过四次挥手才能断开连接。

2.1 建立连接

  • 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认
  • 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态
  • 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了
TCP/IP建立连接

2.2 断开连接

  • 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态
  • 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态
  • 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态
  • 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手
TCP/IP断开连接

2.3 浏览器对同一域名下的并发限制

各版本浏览器对同一域名下的并发连接数有一定限制,具体详情如下:

浏览器 HTTP/1.1 HTTP/1.0
IE 11 6 6
IE 10 6 6
IE 9 10 10
IE 8 6 6
IE 6, 7 2 4
Firefox 6 6
Safari 3,4 4 4
Chrome 4+ 6 6

http协议目前有如下几个版本:

  • HTTP/0.9    HTTP的1991原型版本。这个版本的设计有许多严重的缺陷,几乎没有人在使用了
  • HTTP/1.0    第一个得到广泛应有的HTTP协议,详情见https://tools.ietf.org/html/rfc1945
  • HTTP/1.0+  在1.0版本基础上增加Keep Alive连接,虚拟机支持已经代理连接支持等特性。也称之为1.1的非正式版本,详情见https://tools.ietf.org/html/rfc2068
  • HTTP/1.1    当前比较流行的HTTP协议版本,该版本校正了之前版本的结构设计缺陷,明确了语义,引入了性能优化特性,删除了一些不友好的特性,详情见https://tools.ietf.org/html/rfc2616
  • HTTP/NG    又称HTTP/2.0,该版本重点关注性能的优化,具有二进制分帧,首部压缩,多路复用,服务器推送等新特性。详情见https://tools.ietf.org/html/rfc7540

发展历史图如下:

HTTP发展历史

注:

Google在2012年时推出了自己SPDY协议,该协议优化了HTTP/1.x中的请求延迟,解决了安全性等问题,大部分特性在HTTP/2.0中均已包含进去了

一、HTTP1.0和HTTP1.1的常见区别

HTTP/1.1是目前最广泛应用的协议,它与HTTP/1.0的几个明显在应用上差异如下:

  • 长连接与短连接
    • HTTP/1.0仅支持短连接,即每次请求都要经历一次建立连接(三次握手)的过程,效率很低,有些时候我们看到有些基于HTTP/1.0的协议,使用Connection: keep-alive来打开长连接,这里的HTTP/1.0是通常指HTTP/1.0+,真正的HTTP/1.0协议是没有这个特性的
    • HTTP/1.1默认就是长连接,只要在Keep Alive的时间内,再次发起请求不需要再重新建立一个连接,直接使用上一次建立好的连接进行传输,减少了建立和关闭连接的资源消耗和延迟
  • 缓存控制
    • HTTP/1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准
    • HTTP/1.1引入了更多的缓存控制策略例如ETag,If-Unmodified-Since, If-Match, If-None-Match,Cache-Control等更多可供选择的HEAD头来控制缓存策略
  • 错误通知
    • 在HTTP/1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除
  • Host头
    • 在HTTP/1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP/1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
  • 断点续传
    • HTTP/1.1在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content)

总结图:

HTTP/1.0与HTTP/1.1的区别

二、URL

  • URI:统一资源标识符(Uniform Resource Identifier)
  • URL: 统一资源定位符(Uniform Resource Locator)
  • URN: 统一资源名(Uniform Resource Name)

URI = URL + URN

  • URL完整定义:
<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

举例:

mailto: president@example.com
ftp://ftp.example.com/pub/books.xls
rtsp://www.example.com:554/videos/cook.rmvb
ftp://joe:joepassword@www.joes.com:23/pub/gun;type=5;graphic=6?item=123&mode=test#dog 
  • URL 字符集与转义

由于 URL 常常会包含 ASCII 集合之外的字符,URL 必须转换为有效的 ASCII 格式。URL 编码使用 “%” 其后跟随两位的十六进制数来替换非 ASCII 字符。URL 不能包含空格。URL 编码通常使用 + 来替换空格

  • URL的最大长度

在HTTP协议中并没规定URL的长度限制,但各个浏览器厂商及Web服务软件是有这个限制的,具体如下:

浏览器最大长度(字节)
IE浏览器 2,083
Firefox65,536
Chrome8,182
Safari80,000

Web服务软件:

Web Service最大长度(字节)备注
Apache8,192
IIS16,384configuration/system.webServer/security/requestFiltering/requestLimits@maxQueryStringsetting.
Nginx1,000client_header_buffer_size 1k
large_client_header_buffers 4 4k/8k

三、常见状态码

  • 100 – 199 信息性状态码
  • 200 – 299 成功状态码
  • 300 – 399 重定向状态码
  • 400 – 499 客户端错误状态码
  • 500 – 599 服务器错误状态码

常见状态码列表:

状态码含义HTTP/1.0是否支持HTTP/1.1是否支持
200OK
206Partial Content
301Moved Permanently
304Not Modified
400Bad Request
403Forbidden
404Not Found
500Internal Server Error
502Bad Gateway
503Service Unavailable
504Gateway Timeout

四、METHOD

HTTP协议定义了一组安全的方法:GET方法和HEAD方法,即这两个方法不会让服务产生新的内容或影响。

常见METHOD列表:

方法名含义HTTP/1.0是否支持HTTP/1.1是否支持
GET获取资源
HEAD获取报文HEAD
POST向服务器提交数据
PUT向服务器提交资源
DELETE请求服务器删除资源
TRACE网络跟踪
CONNECT与PROXY之间的连接管理
OPTIONS查询能力

五、HEAD

HEAD头分为通用HEAD, 请求HEAD, 响应HEAD和实体HEAD四个部分

  • 通用HEAD
HEAD字段 描述 备注
Connection 允许客户端和服务器指定与请求/响应连接有关的选项 HTTP/1.1
Date 创建报文的日期时间 HTTP/1.0、HTTP/1.1
Transfer-Encoding 指定报文主体的传输编码方式 HTTP/1.1
Via 代理服务器的相关信息 HTTP/1.1
Cache-Control 控制缓存的行为 HTTP/1.1
  • 请求HEAD
HEAD字段 描述 备注
From 客户端用户的E-Mail地址 HTTP/1.0、HTTP/1.1
Host 接收请求的服务器的主机名和端口号 HTTP/1.1
Referer 当前请求URI的文档的URL HTTP/1.0、HTTP/1.1
User-Agent HTTP客户端程序的信息 HTTP/1.0、HTTP/1.1
Accept 告诉服务器客户端能接收哪些媒体类型 HTTP/1.1
Accept-Charset 告诉服务器客户端能接收哪些字符集 HTTP/1.1
Accept-Encoding 告诉服务器客户端能接收哪些内容编码 HTTP/1.1
Accept-Language 告诉服务器客户端能接收哪些语言 HTTP/1.1
Authorization Web 认证信息 HTTP/1.0、HTTP/1.1
If-Match 比较实体标记(ETag) HTTP/1.1
If-Modified-Since 比较资源的更新时间 HTTP/1.0、HTTP/1.1
If-None-Match 比较实体标记(与 If-Match 相反) HTTP/1.1
Range 实体的字节范围请求 HTTP/1.1
  • 响应HEAD
HEAD字段 描述 备注
Age 响应持续时间 HTTP/1.1
Server Web Service的名称和版本 HTTP/1.0、HTTP/1.1
Location 客户端重定向至指定 URI HTTP/1.0、HTTP/1.1
Accept-Ranges 服务器可接受的范围类型 HTTP/1.1
Vary 代理服务器缓存的管理信息 HTTP/1.1
WWW-Authenticate 服务器对客户端的认证信息 HTTP/1.0、HTTP/1.1
  • 实体HEAD
HEAD字段 描述 备注
Content-Encoding 对Body执行的编码方式 HTTP/1.0、HTTP/1.1
Content-Language 对Body执行的自然语言 HTTP/1.0、HTTP/1.1
Content-Length Body的长度 HTTP/1.0、HTTP/1.1
Content-Range Body的位置范围 HTTP/1.1
Content-Type Body的媒体类型 HTTP/1.0、HTTP/1.1
Expires Body的过期日期时间 HTTP/1.0、HTTP/1.1
Last-Modified Body的最后一次修改的日期和时间 HTTP/1.0、HTTP/1.1
ETag Body的标记 HTTP/1.1

六、参考文献

ngx.req.get_body_data() 读请求体,会偶尔出现读取不到直接返回 nil 的情况。

如果请求体尚未被读取,请先调用 ngx.req.read_body (或打开 lua_need_request_body 选项强制本模块读取请求体,此方法不推荐)。

如果请求体已经被存入临时文件,请使用 ngx.req.get_body_file 函数代替。

如需要强制在内存中保存请求体,请设置 client_body_buffer_size 和 client_max_body_size 为同样大小。

1、ngx.req.read_body

语法: ngx.req.read_body()
作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*

开启获取请求中的body内容。

结论:

1、lua_need_request_body 开启后,该函数不执行。
2、如果在该函数前执行 ngx.req.discard_body(),该函数不执行。

2、ngx.req.discard_body

语法: ngx.req.discard_body()
作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*

禁止获取请求中的body内容。

3、ngx.req.get_body_data

语法: data = ngx.req.get_body_data()
作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*, log_by_lua*

获取请求body的内容,返回字符串。

注意:如果使用 ngx.req.get_post_args(),则返回一个lua table。

以下三种情况返回 nil

  1. 未使用 ngx.req.read_body 读取请求 body,或未打开lua_need_request_body 。
  2. 请求 body 的内容已经写入临时文件;此时使用 ngx.req.get_body_file 获取,或设置 client_body_buffer_size  client_max_body_size同等大小,来避免请求body写入临时文件。
  3. 请求体的内容为空。

4、ngx.req.get_body_file

语法: file_name = ngx.req.get_body_file()
作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*

返回请求body写入的临时文件。例如:返回

/usr/local/openresty/nginx/client_body_temp/0000000019

注意以下2种情况会写入临时文件:

1、请求body的长度超过nginx.conf定义的 client_body_buffer_size 大小。
2、nginx.conf开启 client_body_in_file_only on (请求body始终写入临时文件)

5、ngx.req.set_body_data

语法: ngx.req.set_body_data(data)
作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*

设置请求body的内容,一般用于转发请求到子请求。

注意:

1、在使用该方法时,必需确保当前请求已经使用 ngx.req.read_body() 或者 lua_need_read_body on,否则,在使用该方法会报 500 错误。

例如:

ngx.req.read_body() --如果该行省略,将直接报错。
ngx.req.set_body_data("abc")
ngx.exec("/app/detail")

6、ngx.req.set_body_file

语法: ngx.req.set_body_file(file_name, auto_clean?)
作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*

7、ngx.req.init_body

语法: ngx.req.init_body(buffer_size?)
作用域: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*

8、ngx.req.append_body

语法: ngx.req.append_body(data_chunk)
作用域: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*

9、ngx.req.finish_body

语法: ngx.req.finish_body()
作用域: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*

Link: http://www.hangdaowangluo.com/archives/2746

tcpdump是一个用于截取网络分组,并输出分组内容的工具。tcpdump凭借强大的功能和灵活的截取策略,使其成为类UNIX系统下用于网络分析和问题排查的首选工具。 

tcpdump提供了源代码,公开了接口,因此具备很强的可扩展性,对于网络维护和入侵者都是非常有用的工具。tcpdump存在于基本的Linux系统中,由于它需要将网络界面设置为混杂模式,普通用户不能正常执行,但具备root权限的用户可以直接执行它来获取网络上的信息。因此系统中存在网络分析工具主要不是对本机安全的威胁,而是对网络上的其他计算机的安全存在威胁。

选项

  • -A 以ASCII格式打印出所有分组,并将链路层的头最小化。 
  • -c 在收到指定的数量的分组后,tcpdump就会停止。 
  • -C 在将一个原始分组写入文件之前,检查文件当前的大小是否超过了参数file_size 中指定的大小。如果超过了指定大小,则关闭当前文件,然后在打开一个新的文件。参数 file_size 的单位是兆字节(是1,000,000字节,而不是1,048,576字节)。 
  • -d 将匹配信息包的代码以人们能够理解的汇编格式给出。 
  • -dd 将匹配信息包的代码以c语言程序段的格式给出。 
  • -ddd 将匹配信息包的代码以十进制的形式给出。 
  • -D 打印出系统中所有可以用tcpdump截包的网络接口。 
  • -e 在输出行打印出数据链路层的头部信息。 
  • -E 用spi@ipaddr algo:secret解密那些以addr作为地址,并且包含了安全参数索引值spi的IPsec ESP分组。 
  • -f 将外部的Internet地址以数字的形式打印出来。 
  • -F 从指定的文件中读取表达式,忽略命令行中给出的表达式。 
  • -i 指定监听的网络接口。 
  • -l 使标准输出变为缓冲行形式,可以把数据导出到文件。 
  • -L 列出网络接口的已知数据链路。 
  • -m 从文件module中导入SMI MIB模块定义。该参数可以被使用多次,以导入多个MIB模块。 
  • -M 如果tcp报文中存在TCP-MD5选项,则需要用secret作为共享的验证码用于验证TCP-MD5选选项摘要(详情可参考RFC 2385)。 
  • -b 在数据-链路层上选择协议,包括ip、arp、rarp、ipx都是这一层的。
  • -n 不把网络地址转换成名字。
  • -nn 不进行端口名称的转换。
  • -N 不输出主机名中的域名部分。例如,‘nic.ddn.mil‘只输出’nic‘。 
  • -t 在输出的每一行不打印时间戳。 
  • -O 不运行分组分组匹配(packet-matching)代码优化程序。 
  • -P 不将网络接口设置成混杂模式。 
  • -q 快速输出。只输出较少的协议信息。 
  • -r 从指定的文件中读取包(这些包一般通过-w选项产生)。 
  • -S 将tcp的序列号以绝对值形式输出,而不是相对值。 
  • -s 从每个分组中读取最开始的snaplen个字节,而不是默认的68个字节。 
  • -T 将监听到的包直接解释为指定的类型的报文,常见的类型有rpc远程过程调用)和snmp(简单网络管理协议;)。 
  • -t 不在每一行中输出时间戳。 
  • -tt 在每一行中输出非格式化的时间戳。 
  • -ttt 输出本行和前面一行之间的时间差。 
  • -tttt 在每一行中输出由date处理的默认格式的时间戳。 
  • -u 输出未解码的NFS句柄。 
  • -v 输出一个稍微详细的信息,例如在ip包中可以包括ttl和服务类型的信息。 
  • -vv 输出详细的报文信息。 
  • -w 直接将分组写入文件中,而不是不分析并打印出来。

proxy_set_header官网说明

官网关于proxy_set_header的说明如下:

Syntax:   proxy_set_header field value;
Default:  proxy_set_header Host $proxy_host;
          proxy_set_header Connection close;
Context:  httpserverlocation

Allows redefining or appending fields to the request header passed to the proxied server. The value can contain text, variables, and their combinations. These directives are inherited from the previous level if and only if there are no proxy_set_header directives defined on the current level. By default, only two fields are redefined:

proxy_set_header Host       $proxy_host;
proxy_set_header Connection close;

If caching is enabled, the header fields “If-Modified-Since”, “If-Unmodified-Since”, “If-None-Match”, “If-Match”, “Range”, and “If-Range” from the original request are not passed to the proxied server.

An unchanged “Host” request header field can be passed like this:

proxy_set_header Host       $http_host;

However, if this field is not present in a client request header then nothing will be passed. In such a case it is better to use the $host variable – its value equals the server name in the “Host” request header field or the primary server name if this field is not present:

proxy_set_header Host       $host;

In addition, the server name can be passed together with the port of the proxied server:

proxy_set_header Host       $host:$proxy_port;

f the value of a header field is an empty string then this field will not be passed to a proxied server:

proxy_set_header Accept-Encoding "";
几个需要注意的地方:
  • $host、$proxy_host和$http_host区别
  • Http协议header头中的host对Nginx的影响

$host、$proxy_host和$http_host区别

为了更方便的说明这三者之间的区别,我们使用一个转发范例进行说明:

    upstream myapp1 {
        server app.example.com;
    }

    server {
        listen 80;
        server_name  proxy1.test.com;
        location / {
            proxy_pass http://myapp1;
            proxy_set_header Host $host;
        }
    }

    server {
        listen 80;
        server_name  proxy2.test.com;
        location / {
            proxy_pass http://myapp1;
            proxy_set_header Host $proxy_host;
        }
    }

   server {
        listen 80;
        server_name  proxy3.test.com;
        location / {
            proxy_pass http://myapp1;
            proxy_set_header Host $http_host;
        }
    }
  • $host 按照如下优先级获得
    • 请求行中的host
    • 请求头中的Host头部
    • 与一条请求匹配的server name
  • $proxy_host 默认值,即代理的和转发的host
  • $http_host 请求头中读取到的Host

范例1:

curl -X GET \
  http://proxy1.test.com/ \
  -H 'Accept: */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Host: proxy1.test.com' \
  -H 'Referer: http://proxy1.test.com/' \
  -H 'cache-control: no-cache'

此时$host的值为proxy1.test.com

范例2:

curl -X GET \
  http://proxy2.test.com/ \
  -H 'Accept: */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Host: proxy2.test.com' \
  -H 'Referer: http://proxy1.test.com/' \
  -H 'cache-control: no-cache'

此时$proxy_host的值为app.example.com

范例2:

curl -X GET \
  http://proxy3.test.com/ \
  -H 'Accept: */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Host: proxy3.test.com' \
  -H 'Referer: http://proxy1.test.com/' \
  -H 'cache-control: no-cache'

此时$http_host的值为proxy3.test.com

header头中的Host对Nginx代理的影响

Nginx在做代理服务时,通常会启用多个server,请求会根据server name,进入相应的server配置执行流程,而Host就是Nginx进入不同server执行流程的依据,因此,在多server name的配置中,Host的正确至关重要,也要求我们在构建Http请求时,一定规范的使用Host这个header头。

在日常开发使用中经常会使用nginx进行反向代理或转发,因此记录一下:

location 匹配规则

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

模式含义
location = /uri= 表示精确匹配,只有完全匹配上才能生效
location ^~ /uri^~ 开头对URL路径进行前缀匹配,并且在正则之前
location ~ pattern开头表示区分大小写的正则匹配
location ~* pattern开头表示不区分大小写的正则匹配
location /uri不带任何修饰符,也表示前缀匹配,但是在正则匹配之后
location /通用匹配,任何未匹配到其它location的请求都会匹配到,相当于switch中的default

前缀匹配时,Nginx 不对 url 做编码,因此请求为 /static/20%/aa,可以被规则 ^~ /static/ /aa 匹配到(注意是空格)

多个 location 配置的情况下匹配顺序为(参考资料而来,还未实际验证,试试就知道了,不必拘泥,仅供参考):

  • 首先精确匹配 =
  • 其次前缀匹配 ^~
  • 其次是按文件中顺序的正则匹配
  • 然后匹配不带任何修饰的前缀匹配
  • 最后是交给 / 通用匹配
  • 当有匹配成功时候,停止匹配,按当前匹配规则处理请求

注意:前缀匹配,如果有包含关系时,按最大匹配原则进行匹配。比如在前缀匹配:location /dir01与 location /dir01/dir02,如有请求 http://localhost/dir01/dir02/file 将最终匹配到 location /dir01/dir02

例子,有如下匹配规则:

location = / {
   echo "规则A";
}
location = /login {
   echo "规则B";
}
location ^~ /static/ {
   echo "规则C";
}
location ^~ /static/files {
    echo "规则X";
}
location ~ \.(gif|jpg|png|js|css)$ {
   echo "规则D";
}
location ~* \.png$ {
   echo "规则E";
}
location /img {
    echo "规则Y";
}
location / {
   echo "规则F";
}

那么产生的效果如下:

  • 访问根目录 /,比如 http://localhost/ 将匹配 规则A
  • 访问 http://localhost/login 将匹配 规则Bhttp://localhost/register 则匹配 规则F
  • 访问 http://localhost/static/a.html 将匹配 规则C
  • 访问 http://localhost/static/files/a.exe 将匹配 规则X,虽然 规则C 也能匹配到,但因为最大匹配原则,最终选中了 规则X。你可以测试下,去掉规则 X ,则当前 URL 会匹配上 规则C
  • 访问 http://localhost/a.gifhttp://localhost/b.jpg 将匹配 规则D 和 规则 E ,但是 规则 D 顺序优先,规则 E 不起作用,而 http://localhost/static/c.png 则优先匹配到 规则 C
  • 访问 http://localhost/a.PNG 则匹配 规则 E ,而不会匹配 规则 D ,因为 规则 E 不区分大小写。
  • 访问 http://localhost/img/a.gif 会匹配上 规则D,虽然 规则Y 也可以匹配上,但是因为正则匹配优先,而忽略了 规则Y
  • 访问 http://localhost/img/a.tiff 会匹配上 规则Y

访问 http://localhost/category/id/1111 则最终匹配到规则 F ,因为以上规则都不匹配,这个时候应该是 Nginx 转发请求给后端应用服务器,比如 FastCGI(php),tomcat(jsp),Nginx 作为反向代理服务器存在。

Link: https://moonbingbing.gitbooks.io/openresty-best-practices/ngx/nginx_local_pcre.html

OpenResty基Nginx,把Web服务的整个生命周期和请求处理流程分为如下几个阶段:

处理阶段

Web服务的生命周期分为三个阶段:

  • initing : 服务启动,通常是读取配置文件,初始化内部数据结构;
  • running : 服务运行,接收客户端的请求,返回响应结果;
  • exiting : 服务停止,做一些必要的清理工作。

OpenResty关注的是initing和running两个阶段,并做了更细致的划分。

initing阶段在OpenResty里分为三个子阶段:

  • configuration : 读取配置文件,解析配置指令,设置运行参数;
  • master-initing : 配置文件解析完毕,master进程初始化公用数据;
  • worker-initing : worker进程的初始化。

在running阶段,OpenResty会按照如下流程来处理:

  • ssl : SSL / TLS 安全通信和验证
  • preread : 在正式处理之前“预读”数据,接收 HTTP 请求头
  • rewrite : 检查、改写 URI,实现跳转/重定向
  • access : 访问权限控制:
  • content : 产生响应内容
  • filter : 对 content 阶段产生的内容进行过滤加工处理
  • log : 请求处理完毕,记录日志,或者其他的收尾工作

关系图如下:

OpenResty处理阶段

执行程序

对应于上述处理阶段,OpenResty提供了一系列的”xxx_by_lua”的指令:

  • init_by_lua : master-initing阶段,初始化全局配置或模块
  • init_work_by_lua : worker-initing阶段, 初始化进程专用功能
  • ssl_session_fetch_by_lua : ssl阶段,读取session
  • ssl_certificate_by_lua : ssl阶段,在“握手”时设置安全证书
  • ssl_session_store_by_lua : ssl阶段,存储session
  • set_by_lua : rewrite阶段,改写Nginx变量
  • rewrite_by_lua : rewrite阶段,改写URI,实现跳转或重定向
  • access_by_lua : access阶段,访问控制或限速
  • content_by_lua : content阶段,产生响应内容
  • balancer_by_lua : content阶段,反向代理时选择后端服务器
  • header_filter_by_lua : filter阶段,加工处理响应头
  • body_filter_by_lua : filter阶段,加工处理响应体
  • log_by_lua : log阶段,记录日志或其他收尾工作

流程图

参考文献:

  • 罗剑锋 《OpenResty完全开发指南-构建百万级别并发的Web应用》 电子工业出版社 ISBN 978-7-121-34896-9

com.mysql.jdbc.Driver 是 mysql-connector-java 5中的, 即如果你的数据库是5.x及以下的使用该驱动
com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6中的,即如果你的数据库是6.0及以上版本的,使用该驱动。

mysql5的驱动pom范例:

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
			<scope>runtime</scope>
		</dependency>

mysql8的驱动pom范例:

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.16</version>
			<scope>runtime</scope>
		</dependency>

MySQL 5的application.properties 属性配置:

#mysql数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&useSSL=false
spring.datasource.username=user
spring.datasource.password=test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

MySQL 8的application.properties 属性配置:

#mysql数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF8&useSSL=false
spring.datasource.username=user
spring.datasource.password=test
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

在使用纯代码构建UI界面时,如果只是把NSViewController的View简单的Add到NSWindow中,则导致无法监听到action的。例如如下代码:

// mainwindow
let result = MainWindow(contentRect: AppConfig.windowRect, styleMask: .titled, backing: .buffered, defer: false)
result.styleMask.insert(.closable)
result.styleMask.insert(.miniaturizable)
result.title = NSLocalizedString("HomeTitle", comment: "")
result.titleVisibility = .visible
result.titlebarAppearsTransparent = false
result.delegate = result
result.center()
        
let viewController = MainViewController()
result.contentView?.addSubview(viewController.view)


// MainViewController 
... ...

slPasswordLength.target = self
slPasswordLength.action = #selector(onChangedPasswordLength(sender:))

... ...

@objc private func onChangedPasswordLength(sender: NSSlider) {
        tfPasswordLengthValue.stringValue = "\(sender.integerValue)"
        scStepper.intValue = sender.intValue
    }

错误的原因是在result.contentView?.addSubview(viewController.view)这一句,仅仅将view添加进去,正确的做法应该是将整个ViewController设置为MainWindow的contentViewController,如下:

let result = MainWindow(contentRect: AppConfig.windowRect, styleMask: .titled, backing: .buffered, defer: false)
result.styleMask.insert(.closable)
result.styleMask.insert(.miniaturizable)
result.title = NSLocalizedString("HomeTitle", comment: "")
result.titleVisibility = .visible
result.titlebarAppearsTransparent = false
result.delegate = result
result.center()
        
let viewController = MainViewController()
result.contentViewController = viewController

在开发Mac OS App的时候如果想使用自定义的字体,并且在发布的时候也带上自定义的字体库,则需要如下几个步骤:

  • 添加字体文件到Xcode的项目中
  • 修改Info.plist

1. 添加字体文件

将字体文件拖拽(添加)到项目的资源库中。范例如下图:

2. 修改Info.plist文件

新增Fonts provided by application及Application fonts resource path两项。

  • Fonts provided by application选Array类型,每个item后填上一个字体文件的路径,新增了多少个字体文件,就填写多少个item
  • Application fonts resource path选String类型,填上字体文件所在目录路径即可。

注意:Application fonts resource path是Mac OS App项目必填的,否则找不到字体文件,这点是与iOS项目不一样的,iOS项目只需要填写Fonts provided by application即可。范例如下图:

完成上述两个步骤,即可使用自定义字体了。

extension NSFont {
    class func mainBoldFont(size: CGFloat) -> NSFont {
        let font = NSFont(name: "FZCUJINLJW--GB1-0", size: size)
        return font ?? NSFont.systemFont(ofSize: size)
    }
    
    class func mainFont(size: CGFloat) -> NSFont {
        let font = NSFont(name: "FZXIJINLJW--GB1-0", size: size)
        return font ?? NSFont.systemFont(ofSize: size)
    }
}

Mac OS App的storyboard中无法直接使用自定义的字体,但是可以在代码中使用,这个问题我没找到原因,如果您找到了方法请告知我。谢谢!

PS: 查看所有可用字体代码片段

let manager = NSFontManager.shared
for name: String in manager.availableFonts {
    print("font name=====" + name)
}