##PHP_FPM
关于FPM
参考文章:Nginx+Php-fpm 运行原理详解
正向代理与反向代理:
vpn对于“我们”来说,是可以感知到的(我们连接vpn)
vpn对于”google服务器”来说,是不可感知的(google只知道有http请求过来)。
对于人来说可以感知到,但服务器感知不到的服务器,我们叫他正向代理服务器。反向代理:通过反向代理实现负载均衡
此代理服务器,对于“我们”来说是不可感知的(我们只能感知到访问的是百度的服务器,不知道中间还有代理服务器来做负载均衡)。
此代理服务器,对于”server1 server2 server3”是可感知的(代理服务器负载均衡路由到不同的server)
对于人来说不可感知,但对于服务器来说是可以感知的,我们叫他反向代理服务器Nginx是什么
Nginx (“engine x”) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。
cgi、fast-cgi协议
cgi的历史
早期的webserver只处理html等静态文件,但是随着技术的发展,出现了像php等动态语言。
webserver处理不了了,怎么办呢?那就交给php解释器来处理吧!
交给php解释器处理很好,但是,php解释器如何与webserver进行通信呢?
==为了解决不同的语言解释器(如php、python解释器)与webserver的通信,于是出现了cgi协议。==只要你按照cgi协议去编写程序,就能实现语言解释器与webwerver的通信。如php-cgi程序。fast-cgi的改进
有了cgi协议,解决了php解释器与webserver通信的问题,webserver终于可以处理动态语言了。但是,webserver每收到一个请求,都会去fork一个cgi进程,请求结束再kill掉这个进程。这样有10000个请求,就需要fork、kill php-cgi进程10000次。
有没有发现很浪费资源?
于是,出现了cgi的改良版本,fast-cgi。fast-cgi每次处理完请求后,不会kill掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求。这样每次就不用重新fork一个进程了,大大提高了效率。
php-fpm是什么
php-fpm即php-Fastcgi Process Manager.
php-fpm是 FastCGI 的实现,并提供了进程管理的功能。
进程包含 master 进程和 worker 进程两种进程。
master 进程只有一个,负责监听端口,接收来自 Web Server 的请求,而 worker 进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方。
www.example.com/index.php | | nginx | | 加载nginx的fast-cgi模块 | | fast-cgi对根据fast-cgi协议请求包进行封装,然后将封装好的包发给php-fpm | | php-fpm 据fast-cgi协议将TCP流解析成真正的数据,调用php文件 | | php-fpm处理完请求,返回给nginx | | nginx将结果通过http返回给浏览器
攻击原理
基本原理就是模仿nginx的fast-cgi,直接与php-fpm进行通信。在通信之前,我们首先需要了解一下其通信包的构成。
Fastcgi协议由多个record组成,record也有header和body一说,但是和HTTP头不同,record的头固定8个字节,body是由头中的contentLength指定,其结构如下:
typedef struct { /* Header */ unsigned char version; // 版本 unsigned char type; // 请求包的类型 unsigned char requestIdB1; // 请求包id高8位 unsigned char requestIdB0; // 请求包id低8位 unsigned char contentLengthB1; // body长度高8位 unsigned char contentLengthB0; // body长度低8位 unsigned char paddingLength; // 结尾填充长度 unsigned char reserved; // 保留字节 /* Body */ unsigned char contentData[contentLength]; unsigned char paddingData[paddingLength]; } FCGI_Record;
可以看出一个请求头为8个字节。其参数解释如下: - version:用来表示版本信息,如果是web服务器给php-fpm发送的消息,请求头中只需要将其置0就可以 - type:此字段用来说明每次所发送消息的类型,其具体值可以为如下:
-requestId:占俩个字节,一个唯一的标志id,以避免同时处理多个请求时的影响。 - contentLength:占2个字节,表示body的长度。语言端解析了fastcgi头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是body体。 - paddingLength:填充长度的值,为了提高处理消息的能力,我们的每个消息大小都必须为8的倍数,此长度标示,我们在消息的尾部填充的长度 - reserved:保留字段
fastcgi客户端脚本分析
协议的内容大致了解了,接下来就是写代码,封装一下请求包。已经有前辈做了这件事情–>https://github.com/wuyunfeng/Python-FastCGI-Client,我们来尝试分析一下代码。
#!/usr/bin/python import socket import random
class FastCGIClient:
“””A Fast-CGI Client for Python”””
# 版本号,不重要
__FCGI_VERSION = 1
# FastCGI服务器角色及其设置
__FCGI_ROLE_RESPONDER = 1
__FCGI_ROLE_AUTHORIZER = 2
__FCGI_ROLE_FILTER = 3
# # type 记录类型1-11
__FCGI_TYPE_BEGIN = 1
__FCGI_TYPE_ABORT = 2
__FCGI_TYPE_END = 3
__FCGI_TYPE_PARAMS = 4
__FCGI_TYPE_STDIN = 5
__FCGI_TYPE_STDOUT = 6
__FCGI_TYPE_STDERR = 7
__FCGI_TYPE_DATA = 8
__FCGI_TYPE_GETVALUES = 9
__FCGI_TYPE_GETVALUES_RESULT = 10
__FCGI_TYPE_UNKOWNTYPE = 11
# 头部长度,默认为8
__FCGI_HEADER_SIZE = 8
# 自定义请求状态
FCGI_STATE_SEND = 1
FCGI_STATE_ERROR = 2
FCGI_STATE_SUCCESS = 3
def init(self, host, port, timeout, keepalive):
self.host = host
self.port = port
self.timeout = timeout
if keepalive:
self.keepalive = 1
else:
self.keepalive = 0
self.sock = None
self.requests = dict()
def __connect(self):
# 此函数创建了一个socket,并且去连接(self.host, self.port)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.sock.connect((self.host, int(self.port)))
except socket.error as msg:
self.sock.close()
self.sock = None
print(repr(msg))
return False
return True
def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
# 此函数根据fcgi_type对content进行封装
length = len(content)
return chr(FastCGIClient.__FCGI_VERSION)
+ chr(fcgi_type)
+ chr((requestid >> 8) & 0xFF)
+ chr(requestid & 0xFF)
+ chr((length >> 8) & 0xFF)
+ chr(length & 0xFF)
+ chr(0)
+ chr(0)
+ content
def __encodeNameValueParams(self, name, value):
# 此函数对body进行编码
nLen = len(str(name))
vLen = len(str(value))
record = ‘’
if nLen < 128:
record += chr(nLen)
else:
record += chr((nLen >> 24) | 0x80)
+ chr((nLen >> 16) & 0xFF)
+ chr((nLen >> 8) & 0xFF)
+ chr(nLen & 0xFF)
if vLen < 128:
record += chr(vLen)
else:
record += chr((vLen >> 24) | 0x80)
+ chr((vLen >> 16) & 0xFF)
+ chr((vLen >> 8) & 0xFF)
+ chr(vLen & 0xFF)
return record + str(name) + str(value)
def __decodeFastCGIHeader(self, stream):
# 此函数对header进行解码
# 被用于__decodeFastCGIRecord函数的一部分
header = dict()
header[‘version’] = ord(stream[0])
header[‘type’] = ord(stream[1])
header[‘requestId’] = (ord(stream[2]) << 8) + ord(stream[3])
header[‘contentLength’] = (ord(stream[4]) << 8) + ord(stream[5])
header[‘paddingLength’] = ord(stream[6])
header[‘reserved’] = ord(stream[7])
return header
def __decodeFastCGIRecord(self):
# 此函数对record进行解码
header = self.sock.recv(int(FastCGIClient.__FCGI_HEADER_SIZE))
if not header:
return False
else:
record = self.__decodeFastCGIHeader(header)
record[‘content’] = ‘’
if ‘contentLength’ in record.keys():
contentLength = int(record[‘contentLength’])
buffer = self.sock.recv(contentLength)
while contentLength and buffer:
contentLength -= len(buffer)
record[‘content’] += buffer
if ‘paddingLength’ in record.keys():
skiped = self.sock.recv(int(record[‘paddingLength’]))
return record
def request(self, nameValuePairs={}, post=’’):
if not self.__connect():
print(‘connect failure! please check your fasctcgi-server !!’)
return
# 区分多段Record.requestId作为同一次请求的标志
requestId = random.randint(1, (1 << 16) - 1)
self.requests[requestId] = dict()
request = “”
# 构造header
beginFCGIRecordContent = chr(0)
+ chr(FastCGIClient.__FCGI_ROLE_RESPONDER)
+ chr(self.keepalive)
+ chr(0) * 5
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
beginFCGIRecordContent, requestId)
# 构造body
paramsRecord = ‘’
if nameValuePairs:
for (name, value) in nameValuePairs.iteritems():
# paramsRecord = self.__encodeNameValueParams(name, value)
# request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
paramsRecord += self.__encodeNameValueParams(name, value)
if paramsRecord:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, ‘’, requestId)
if post:
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, post, requestId)
request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, ‘’, requestId)
# 发送fast-cgi格式的包
self.sock.send(request)
self.requests[requestId][‘state’] = FastCGIClient.FCGI_STATE_SEND
self.requests[requestId][‘response’] = ‘’
# 接受返回包
return self.__waitForResponse(requestId)
def __waitForResponse(self, requestId):
# 接受返回包
while True:
response = self.__decodeFastCGIRecord()
if not response:
break
if response[‘type’] == FastCGIClient.__FCGI_TYPE_STDOUT
or response[‘type’] == FastCGIClient.__FCGI_TYPE_STDERR:
if response[‘type’] == FastCGIClient.__FCGI_TYPE_STDERR:
self.requests[‘state’] = FastCGIClient.FCGI_STATE_ERROR
if requestId == int(response[‘requestId’]):
self.requests[requestId][‘response’] += response[‘content’]
if response[‘type’] == FastCGIClient.FCGI_STATE_SUCCESS:
self.requests[requestId]
return self.requests[requestId][‘response’]
def repr(self):
return “fastcgi connect host:{} port:{}”.format(self.host, self.port)
```