Home > Article > PHP Framework > Let's take a look at Swoole HTTP
Objective
Style
Environment
HTTP Server
# 查看SWOOLE版本 $ php -r 'echo SWOOLE_VERSION;' 4.3.1
Recommended (free ): swoole
Basic concepts
HTTP message
About HTTP requests The structure of the message
POST /search HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-silverlight, application/x-shockwave-flash, */* Referer: http://www.google.cn/ Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld) Host: www.google.cn Connection: Keep-Alive Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g; NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwVh7pGaRUb0RnHcJU37y- FxlRugatx63JLv7CWMD6UB_O_r hl=zh-CN&source=hp&q=dometyAbout the composition structure of HTTP response message
##HTTP response message structure
HTTP/1.1 200 OK Date: Mon, 23 May 2005 22:38:34 GMT Content-Type: text/html; charset=UTF-8 Content-Encoding: UTF-8 Content-Length: 138 Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux) ETag: "3f80f-1b6-3e1cb03b" Accept-Ranges: bytes Connection: closeCreate HTTP server
Swoole has a built-in HTTP server after version 1.7.7, you can create one Asynchronous non-blocking multi-process HTTP server. Swoole's HTTP server does not fully support the HTTP protocol. It is recommended to only serve as an application server and add Nginx as a proxy on the front end.
Because Swoole is executed in the CLI command line, many
rootshell
cannot be executed in the traditional NGINX FastCGI mode, but using the Swoole server Can control rsync
, git
, svn
, etc. very well. Using Swoole's API, it takes 4 steps to build an HTTP server
# 创建应用 $ mkdir test && cd test # 创建并编辑服务器文件 $ vim server.php
<?php //创建HTTP服务器对象 $host = "0.0.0.0"; $port = 9501; $server = new swoole_http_server($host, $port); var_dump($server); //设置服务器运行参数 $configs = []; $configs["worker_num"] = 2;//设置Worker工作进程数量 $configs["daemonize"] = 0;//设置是否已后台守护进程运行 $server->set($configs); //注册监听客户端HTTP请求回调事件 $server->on("request", function(swoole_http_request $request, swoole_http_response $response) use($server){ var_dump($request); var_dump($response); //获取客户端文件描述符 $fd = $request->fd; if(!empty($fd)){ //获取连接信息 $clientinfo = $server->getClientInfo($fd); var_dump($clientinfo); //获取收包时间 var_dump($clientinfo["last_time"]); } //响应客户端HTTP请求 $response->write("success");//向客户端写入响应数据 $response->end();//浏览器中输出结果 }); //启动服务器 $server->start();
# 使用PHP-CLI运行服务器脚本 $ php server.php # 使用CURL向HTTP服务器发送请求测试 $ curl 127.0.0.1:9501
var_dump
、print_r# The content of ## is output in the server.
To output in the browser, you need to use the
$rp->end(string $contents) method. Can only be called once.
If you need to send messages to the client multiple times, you can use
$rp->write(string $content)The complete HTTP protocol request will be parsed and Encapsulated in the
swoole_http_requestAll HTTP protocol responses will be encapsulated and sent through the
swoole_http_response
The essence of the HTTP serverswoole_http_server
is based onswoole_server, the methods under
swoole_server can be used in
swoole_http_server, just
swoole_http_server can only be invoked by the client. Simply put,
swoole_http_server is based on
swoole_server plus
HTTP protocol, plus
request and
response classes Library to implement requesting data and obtaining data. Different from PHP-FPM, after the web server receives the request, it will be passed to Swoole's
HTTP server and return the request directly.
Http \Server
Swoole\HTTP\ServerSynchronous mode
, which requires setting up a large number of Worker processes. To complete concurrent request processing, the Worker process can use synchronous blocking IO, and the programming method is exactly the same as an ordinary PHP Web program. Unlike PHP-FPM/Apache
, the client connection does not monopolize the process, and the server can still handle a large number of concurrent connections. <ul><li>异步模式</li></ul>
<p>异步模式下整个HTTP服务器是异步非阻塞的,服务器可以应答大规模的并发连接和并发请求,编程方式需要完全使用异步API,如MySQL、Redis、HTTP客户端、<code>file_get_contents
、sleep
等阻塞IO操作必须切换为异步方式,如异步Client、Event、Timer等API。
查看HTTP服务器实例对象
var_dump($server);
object(Swoole\Connection\Iterator)#2 (0) { ["host"]=>string(7) "0.0.0.0" ["port"]=>int(9501) ["type"]=>int(1) ["mode"]=>int(2) ["ports"]=> array(1) { [0]=> object(Swoole\Server\Port)#3 (16) { ["onConnect":"Swoole\Server\Port":private]=>NULL ["onReceive":"Swoole\Server\Port":private]=>NULL ["onClose":"Swoole\Server\Port":private]=>NULL ["onPacket":"Swoole\Server\Port":private]=>NULL ["onBufferFull":"Swoole\Server\Port":private]=>NULL ["onBufferEmpty":"Swoole\Server\Port":private]=>NULL ["onRequest":"Swoole\Server\Port":private]=>NULL ["onHandShake":"Swoole\Server\Port":private]=>NULL ["onOpen":"Swoole\Server\Port":private]=>NULL ["onMessage":"Swoole\Server\Port":private]=>NULL ["host"]=>string(7) "0.0.0.0" ["port"]=>int(9501) ["type"]=>int(1) ["sock"]=>int(4) ["setting"]=>NULL ["connections"]=>object(Swoole\Connection\Iterator)#4 (0) { } } } ["master_pid"]=>int(0) ["manager_pid"]=>int(0) ["worker_id"]=>int(-1) ["taskworker"]=>bool(false) ["worker_pid"]=>int(0) ["onRequest":"Swoole\Http\Server":private]=>NULL ["onHandshake":"Swoole\Http\Server":private]=>NULL }
upload_tmp_dir
HTTP服务器支持大文件上传,但由于Swoole底层的限制,文件内容是存放在内存中的,因此如果并发上传大量文件可能会导致内存占用量过大的问题。
可以修改upload_tmp_dir
选项用于配置上传文件的临时目录,需要注意是目录文件夹的名称最大长度不得超过220个字节。
$configs = []; $configs["upload_tmp_dir"] = "/data/uploads/"; $server->set($configs);
http_parse_post
可通过修改http_parse_post
配置项用来设置表单POST提交后是否解析,若设置为true
则表示会自动将Content-Type
内容类型为x-www-urlencode
的请求包体解析到 POST 数组,若设置为false
则表示将会关闭 POST解析。
$configs = []; $configs["http_parse_post"] = true; $server->set($configs);
POST尺寸 package_max_length
默认情况下,表单上传或POST
提交2MB的数据的限制,可通过修改package_max_length
选项调整POST尺寸大小。
$configs = []; $configs["package_max_length"] = 2*1024; $server->set($configs);
http_parse_cookie
通过修改http_parse_cookie
配置项可以开启或关闭Cookie解析,关闭后将会在Header头信息学中保留未经处理的原始Cookies信息。
$configs = []; $configs["http_parse_cookie"] = true; $server->set($configs);
http_compression
http_compression
适用于Swoole4.1.0+版本,用于启用或关闭对HTTP信息的压缩,默认为开启状态。
由于http-chunk
不支持分段独立压缩,因此默认已强制关闭了压缩功能。
$configs = []; $configs["http_compression"] = false; $server->set($configs);
目前HTTP支持gzip
、br
(需google brotli
库支持)、deflate
三种压缩格式,Swoole底层会根据客户端浏览器传入的Accept-Encoding
头信息自动选择压缩方式。
http_compression_level
http_compression_level
选项用于配置压缩的级别,压缩级别越高压缩后体积越小,同时也会越占用CPU。
$configs = []; $configs["http_compression_level"] = 1; $server->set($configs);
document_root
document_root
选项适用于Swoole1.9.17+版本,用于配置静态文件的根目录,该功能由于较为简易不推荐在公网环境下直接使用,常于enable_static_handler
选项配合使用。
如果设置document_root
和enable_static_handler = true
后,Swoole底层收到HTTP请求时会先判断document_root
的路径下是否存在某静态文件,如果存在会直接发送内容给客户端,并不再调用onRequest
函数。
这里需要注意的时,在使用静态文件处理特性时,应当将动态PHP代码于静态文件进行隔离,静态文件应存放到特定的目录下。
$configs = []; $configs["document_root"] = "/app"; $server->set($configs);
enable_static_handler
enable_static_handler
选项用于开启或关闭静态文件请求处理功能,常配合document_root
选项使用。
$configs = []; $configs["enable_static_handler"] = true; $server->set($configs);
static_handler_locations
static_handler_location
选项适用于Swoole4.4.0+版本,用于设置静态处理器的路径,类型为数组,默认不启用。
静态处理器类似于Nginx的location
指令,可以指定一个或多个路径为静态路径。只有URL在指定路径下才会启用静态问而建处理器,否则会视为动态请求。location
选项必须以/
开头并支持多级路径,如/app/images
。
当启用static_handler_locations
选项后,如果请求对应的文件不存在,将直接会返回404错误。
$configs = []; $configs["static_handler_locations"] = ["/static", "/public/assets"]; $server->set($configs);
由于swoole_http_server
对HTTP协议支持的并不完整,建议仅仅作为应用服务器,并在前端增加Nginx作为反向代理。
操作前需要修改服务器的运行参数,设置enable_static_handle
为true
后,底层收到HTTP请求会像判断document_root
路径下是否存在目标文件,若存在则会直接发送文件给客户端,不再触发onRequest
回调。
$ vim server.php
$configs = []; $configs["enable_static_handler"] = true; $configs["document_root"] = "/test"; $server->set($configs);
例如:设置Nginx反向代理127.0.0.1:9501
$ vim /usr/local/nginx/conf/nginx.conf
http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; upstream swoole{ server 127.0.0.1:9501; keepalive 4; } server { listen 80; server_name www.swoole.com; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://swoole; proxy_set_header Connection ""; proxy_http_version 1.1; root html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
Nginx+Swoole的组合中Nginx反向代理的配置
server { root /data/wwwroot/; server_name local.swoole.com; location / { proxy_http_version 1.1; proxy_set_header Connection "keep-alive"; proxy_set_header X-Real-IP $remote_addr; if (!-e $request_filename) { proxy_pass http://127.0.0.1:9501; } } }
swoole_http_request
请求对象保存了HTTP客户端请求的相关信息,包括GET
、POST
、COOKIE
、Header
等,请求对象$request
销毁时会自动删除上传的临时文件,不要使用&
符号引用$request
请求对象。
var_dump($request); object(Swoole\Http\Request)#6 (10) { ["fd"]=>int(1) ["streamId"]=>int(0) ["header"]=>array(3) { ["host"]=>string(14) "127.0.0.1:9501" ["user-agent"]=>string(11) "curl/7.52.1" ["accept"]=>string(3) "*/*" } ["server"]=>array(10) { ["request_method"]=>string(3) "GET" ["request_uri"]=>string(1) "/" ["path_info"]=>string(1) "/" ["request_time"]=>int(1561689532) ["request_time_float"]=>float(1561689533.0563) ["server_port"]=>int(9501) ["remote_port"]=>int(51188) ["remote_addr"]=>string(9) "127.0.0.1" ["master_time"]=>int(1561689532) ["server_protocol"]=>string(8) "HTTP/1.1" } ["request"]=>NULL ["cookie"]=>NULL ["get"]=>NULL ["files"]=>NULL ["post"]=>NULL ["tmpfiles"]=>NULL }
HTTP请求的头部信息,类型为数组,所有的键名均为小写。
$host = $request->header["host"]; $accept = $request->header["accept"];
HTTP请求相关的服务器信息,相当于PHP的$_SERVER
全局数组,包含了HTTP请求的方法、URL路径、客户端IP等信息。服务器信息为关联数组,数组中的键名全部小写,并且与PHP的$_SERVER
数组保持一致。
$request_method = $request->server["request_method"]; $request_time = $request->server["request_time"]; $request_uri = $request->server["request_uri"];
请求路径
当Google的Chrome浏览器访问服务器是会产生两次请求,这是因为Chrome会自动请求一次favicon.ico
文件,所以服务器会收到两个HTTP请求,通过打印$request->server["request_uri"]
可以查看到请求URL路径。如果需要屏蔽掉对favicon.ico
的请求,可采用以下方式。
$uri = $request->server["request_uri"]; if($uri == "/favicon.icon") { $respoonse->status(404); $response->end(); }
收包时间
request_time
请求时间是在Worker工作进程中设置的,在SWOOLE_PROCESS
多进程模式下存在dispatch
分发的过程,因此可能会与实际收包时间存在偏差,尤其当请求量超过服务器处理能力时,有可能滞后于实际收包时间。
可通过Server->getClientInfo()
方法获取last_time
以获取 准确的收包时间。
//获取客户端文件描述符 $fd = $request->fd; if(!empty($fd)){ //获取连接信息 $clientinfo = $server->getClientInfo($fd); var_dump($clientinfo); //获取收包时间 var_dump($clientinfo["last_time"]); }
客户端信息
Server->getClientInfo()
用于获取连接的客户端信息
bool|array Server->getClientInfo(int $fd, int $extraData, bool $ignoreError = false)
int $fd
表示客户端连接文件描述符int $extraData
表示扩展信息是保留参数目前无任何效果bool $ignoreError
表示是否忽略错误,若设置为true
表示即使连接关闭也会返回连接信息。如果传入的$fd
客户端连接文件描述符存在则返回一个数组,若不存在或已关闭则返回false
。
array(10) { ["server_port"]=>int(9501) ["server_fd"]=>int(4) ["socket_fd"]=>int(12) ["socket_type"]=>int(1) ["remote_port"]=>int(51194) ["remote_ip"]=>string(9) "127.0.0.1" ["reactor_id"]=>int(0) ["connect_time"]=>int(1561690606) ["last_time"]=>int(1561690606) ["close_errno"]=>int(0) }
HTTP请求的GET
参数,相当于PHP中的$_GET
,格式为键值对的关联数组。为防止HASH
攻击,GET
参数最大不允许超过128个。
$get = $request->get;//获取HTTP请求的所有GET参数
HTTP的GET请求只有一个HTTP Header头,Swowole底层使用固定大小的内存缓冲区为8K,而且不可修改。如果请求不是正确的HTTP请求,将会出现错误,底层会抛出错误。
WARN swReactorThead_onReceive_http_request: http header is too long.
HTTP请求携带POST
参数,格式为键值对的关联数组,POST
与Header
加起来的尺寸不得超过package_max_length
的设置,否则会认为是恶意请求,另外POST
参数的个数不得超过128个。
$post = $request->post;
由于POST文件上传时最大尺寸收到package_max_length
配置项目的限制,默认为2MB,可以调用swoole_server->set
传入新值修改尺寸。
由于Swoole底层是全内存的,因此如果设置过大可能会导致大量并发请求,将服务器资源耗尽。
设置计算方法:最大内存占用 = 最大并发请求数量 * package_max_length
当使用CURL发送POST请求时服务器端会超时
CURL在发送较大的POST请求时会首先发送一个100-continue
的请求,当收到服务器的回应才会发送实际的POST数据。然后swoole_http_server
并不支持100-continue
,因此会导致CURL请求超时。解决的办法时关闭CURL的100-continue。
$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1);//设置为POST方式 curl_setopt($ch, CURLOPT_HTTPHEADER, ["Exception:"]); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
HTTP请求携带的COOKIE
信息,格式为键值对的关联数组。
HTTP请求携带的文件上传信息,类型为以form
表单名称为key
键名的二维数组,与PHP原生的$_FILES
相同,最大文件尺寸不得超过package_max_length
中设置的值,不要使用Swoole\Http\Server
处理大文件上传。
$files = $request->files; var_dump($files);
array(5) { [name] => facepalm.jpg [type] => image/jpeg [tmp_name] => /tmp/swoole.upfile.n3FmFr [error] => 0 [size] => 15476 }
name
表示浏览器上传时传入的文件名称type
表示浏览器上传时的MIME类型tmp_name
表示浏览器上传的临时文件,文件名默认以/tmp/swoole.upfile
开头。size
表示上传文件的尺寸Swoole1.9.10+版本支持is_uploaded_file
和move_uploaded_file
函数。当HTTP请求对象$request
对象销毁时,会自动删除上传的临时文件。
rawContent
表示获取原始的POST
包体,用于非application/x-www-form-urlencode
格式的HTTP的POST
请求。等同于原生PHP的fopen("php://input")
,有时服务器不需要解析HTTP的POST请求参数。
Swoole1.7.18+版本增加了http_parse_post
配置用于关闭或开启POST
数据解析。
string HTTP\Request->rawContent();
getData()
方法用于获取完整的HTTP请求报文,包括 Http Header
和`HTTP Body消息体。
function swoole_http_request_getData() : string
getData
需要Swoole1.10.3或Swoole2.1.2或更高的版本。
响应对象
swoole_http_response
响应对象是进程隔离的,不能跨越进程或对象。如果是当前进程中,想使用fd
文件描述符保存response
响应对象、存储上下文,可使用PHP全局数组变量来保存。
swoole_http_response
响应对象,通过调用此对象的方法实现HTTP响应的发送,当响应对象销毁时,如果没有调用end
发送HTTP响应,底层会自动执行end
方法。不要使用&
符号引用$response
对象。
object(Swoole\Http\Response)#7 (4) { ["fd"]=>int(1) ["header"]=>NULL ["cookie"]=>NULL ["trailer"]=>NULL }
HTTP服务器Response响应对象,通过调过此对象的方法,实现HTTP响应发送。当Response对象销毁时,如果未调用则直接调用end
方法,不要使用&
符号引用$response
对象。
function Http\Response->header( string $key, string $value, bool $ucworods = true )
header
方法用于设置HTTP响应的Header头信息,如果设置失败返回false
,设置成功则无返回值。
string $key
表示HTTP头的Keystring $value
表示HTTP头的Valuebool $ucwords
表示是否需要对Key进行HTTP约定格式化,默认true
会自动格式化。$response->header("Content-Type", "image/jpeg", true);
跨域处理
$origin = $request->header['origin']; // Access-Control-Allow-Origin 不能使用 *,这样修改是不支持php版本低于7.0的。 // $response->header('Access-Control-Allow-Origin', '*'); $response->header('Access-Control-Allow-Origin', $origin); $response->header('Access-Control-Allow-Methods', 'OPTIONS'); $response->header('Access-Control-Allow-Headers', 'x-requested-with,session_id,Content-Type,token,Origin'); $response->header('Access-Control-Max-Age', '86400'); $response->header('Access-Control-Allow-Credentials', 'true'); if ($request->server['request_method'] == 'OPTIONS') { $response->status(200); $response->end(); return; };
cookie
方法用来设置HTTP响应的Cookie信息,方法参数与原生PHP的setcookie
函数完全一致。
function Http\Response->cookie( string $key, string $value = "", int $expire = 0, string $path = "/", string $domain = "", bool $secure = false, bool $httponly = false )
Cookie设置必须在end
方法之前方才生效,Swoole底层自动会对$value
进行urlencode
编码处理,同时允许设置多个相同的$key
的Cookie。
swoole_http_response->status( int $http_status_code )
status
方法用于发送HTTP状态码,$http_status_code
必须是合法的HTTP状态码,如2xx、3xx、4xx、5xx等,若不是在会报错,另外status
方法也必须在$response->end()
之前执行方才生效。
string $url
表示跳转的新地址会作为HTTP Header头中的Location
选项进行发送int $http_code
表示状态码,默认为302临时跳转,传入301表示永久跳转。redirect
方法适用于Swoole2.2.0+版本,用于发送HTTP跳转,调用后会自动执行end
方法并发送结束响应。
function Http\Response->redirect( string $url, int $http_code = 302 )
例如
$server = new swoole_http_server("0.0.0.0", 9501, SWOOLE_BASE); $server->on("request", function(swoole_http_request $request, swoole_http_response $response){ $url = "http://www.baidu.com"; $response->redirect($url, 301); }); $server->start();
write
方法用于启用HTTP的chunk
分段以向浏览器发送相应的内容,使用write
分段发送数据后end
方法将不再接收任何参数,调用end
方法后会发送一个长度为0的分段chunk
表示数据传输完毕。
bool Http\Response->write(string $data)
参数$data
表示要发送的数据内容,最大长度不得超过2MB,受buffer_output_size
配置项控制。
sendfile
用于发送文件到浏览器
function Http\Response->sendfile( string $filename, int $offset = 0, int $length = 0 )
string $filename
表示要发送的文件名称,文件不存在或没有访问权限则会发送失败。int $offset
表示上传文件的偏移量,可以指定从文件在中间部分开始传输数据,用于断点续传,适用于Swoole1.9.11+。int $length
表示发送数据的尺寸,默认为整个文件的尺寸,适用于Swoole1.9.11+。$response->header("Content-Type", "image/jpeg"); $filepath = $request->server["request_uri"]; $filename = __DIR__.$filepath; $response->sendfile($filename);
由于Swoole底层无法推断要发送文件的媒体类型MIME
格式,因此需要应用程序指定Content-Type
。调用sendfile
前不得使用write
方法发送HTTP数据段Chunk
,调用sendfile
后Swoole底层会自动执行end
方法,另外sendfile
不支持gzip
压缩。
end
方法用于发送HTTP响应体,并结束请求处理。
function Http\Response->end(string $html);
end
方法只能调用一次,如果需要分多次向客户端发送数据下需使用write
方法,send
操作后将会向客户端浏览器发送HTML内容。如果客户端开启了KeepAlive
连接会保持,服务器会等待下一次请求。如果没有开启KeepAlive
服务器将会切断连接。
detach
表示分离响应对应,调用后$response
对象销毁时将不会自动执行end
方法,一般detach
会与Http\Response::create
以及Server::send
配合使用,适用于Swoole2.2.0+版本。
function Http\Response->detach():bool
detach
方法操作后,若客户端已经完成响应则会返回true
,否则返回false
。
detach
应用于跨进程响应
在某些情况下需要在Task
任务进程中对客户端发出响应,此时可以利用detach
方法使$response
对象独立,如此一来在Task
任务进程中就可以重新构建$response
对象以发起HTTP请求响应。
<?php //创建HTTP服务器对象 $host = "0.0.0.0"; $port = 9501; $server = new swoole_http_server($host, $port); //设置服务器运行参数 $configs = []; $configs["worker_num"] = 1;//设置Worker工作进程数量 $configs["task_worker_num"] = 1;//设置Task任务进程数量 $configs["daemonize"] = 0;//设置是否已后台守护进程运行 $server->set($configs); //注册客户端请求处理回调函数 $server->on("request", function(swoole_http_request $request, swoole_http_response $response) use($server){ //分离响应对象 $response->detach(); //在Task任务进程中对客户端发出响应 $fd = strval($response->fd); $server->task($fd); }); //注册异步任务处理回调函数 $server->on("task", function(swoole_http_server $server, $worker_id, $data){ //创建响应对象 $response = swoole_http_response::create($data); //向客户端发送响应 $html = "in task"; $response->end($html); }); //注册Task异步任务执行完毕回调函数 $server->on("finish", function(){ echo "[finish] task".PHP_EOL; }); //启动服务器 $server->start();
detach
方法应用于发送任意内容
在某些特殊场景下,需要对客户端发送特殊的响应内容,Http\Response
对象自带的end
方法无法满足需求,可以使用detach
方法分离响应对象,然后自行组包并使用Server::send
方法发送数据。
<?php //创建HTTP服务器对象 $host = "0.0.0.0"; $port = 9501; $server = new swoole_http_server($host, $port); //设置服务器运行参数 $configs = []; $configs["worker_num"] = 2;//设置Worker工作进程数量 $configs["daemonize"] = 0;//设置是否已后台守护进程运行 $server->set($configs); //注册监听客户端HTTP请求回调事件 $server->on("request", function(swoole_http_request $request, swoole_http_response $response) use($server){ //分离响应对象 $response->detach(); //自行组包并使用Server::send方法发送数据 $fd = $response->fd; $message = "HTTP/1.1 200 OK\r\n"; $message .= "Server: server\r\n"; $message .= "\r\n"; $message .= "Hello World\n"; $server->send($fd, $message); }); //启动服务器 $server->start();
Http\Response::create
create
静态方法用于构造新的Http\Response
响应对象,使用前必须调用detach
方法将旧有$response
对象分离,否则 可能会造成同一个请求发送两次响应内容。
function Http\Response::createE(int $fd) : Http\Response
create
静态方法的参数$fd
表示需要绑定连接的文件描述符,调用Http\Response
对象的end
方法和write
方法时会向此连接发送数据。如果调用成功则返回一个新的Http\Response
对象,否则失败返回false
,适用于Swoole2.2.0+版本。
注册事件回调函数
Http\Server
注册事件回调函数于Http\Server->on
相同,不同之处在于HTTP\Server->on
不接受onConnect
和onReceive
回调设置,Http\Server->on
会额外接受一种新的事务类型onRequest
。
onRequest 事件
onRequest
事件适用于Swoole1.7.7+版本,当服务器收到一个完整的HTTP请求后会调用onRequest
函数。
$server->on("request", function(swoole_http_request $request, swoole_http_response $response) use($server){ $html = "success"; $response->end($html); });
onRequest
回调函数共有两个参数
swoole_http_requst $request
HTTP请求信息对象,包含了Header/GET/POST/Cookie等信息。swoole_http_response $response
HTTP响应信息对象,支持Cookie/Header/Status等HTTP操作。在onRequest
回调函数返回时会销毁$request
和$response
对象,如果未执行$response->end()
操作,Swoole底层会自动执行一次$response->end("")
。
$request
和$response
对象在传递给其它函数时,是不需要添加&
取地址的引用符号的,传递后引用计数会增加,当onRequest
退出时并不会被销毁。
案例
$ vim http_server.php
<?php $addr = "0.0.0.0"; $port = 9501; $svr = new swoole_http_server($addr, $port); $svr->on("request", function(swoole_http_request $rq, swoole_http_response $rp){ //处理动态请求 $path_info = $rq->server["path_info"]; $file = __DIR__.$path_info; echo "\nfile:{$file}"; if(is_file($file) && file_exists($file)){ $ext = pathinfo($path_info, PATHINFO_EXTENSION); echo "\next:{$ext}"; if($ext == "php"){ ob_start(); include($file); $contents = ob_get_contents(); ob_end_clean(); }else{ $contents = file_get_contents($file); } echo "\ncontents:{$contents}"; $rp->end($contents); }else{ $rp->status(404); $rp->end("404 not found"); } }); $svr->start();
# 创建静态文件 $ vim index.html index.html # 测试静态文件 $ curl 127.0.0.1:9501/index.html # 观察http_server输出 file:/home/jc/projects/swoole/chat/index.html ext:html contents:index.html # 测试动态文件 $ vim index.php <?php echo "index.php"; #观察http_server日志输出 file:/home/jc/projects/swoole/chat/index.php ext:php contents:index.php
获取动态请求的参数
$ vim http_server.php
<?php $addr = "0.0.0.0"; $port = 9501; $svr = new swoole_http_server($addr, $port); $svr->on("request", function(swoole_http_request $rq, swoole_http_response $rp){ //获取请求参数 $params = $rq->get; echo "\nparams:".json_encode($params); //处理动态请求 $path_info = $rq->server["path_info"]; $file = __DIR__.$path_info; echo "\nfile:{$file}"; if(is_file($file) && file_exists($file)){ $ext = pathinfo($path_info, PATHINFO_EXTENSION); echo "\next:{$ext}"; if($ext == "php"){ ob_start(); include($file); $contents = ob_get_contents(); ob_end_clean(); }else{ $contents = file_get_contents($file); } echo "\ncontents:{$contents}"; $rp->end($contents); }else{ $rp->status(404); $rp->end("404 not found"); } }); $svr->start();
测试带参数的请求
$ curl 127.0.0.1:9501?k=v
观察请求参数的输出
params:{"k":"v"} file:/home/jc/projects/swoole/chat/index.html ext:html contents:index.html
静态文件处理
$ vim mimes.php
<?php return [ "jpg"=>"image/jpeg", "jpeg"=>"image/jpeg", "bmp"=>"image/bmp", "ico"=>"image/x-icon", "gif"=>"image/gif", "png"=>"image/png", "css"=>"text/css", "html"=>"text/html", "xml"=>"text/xml", "bin"=>"application/octet-stream", "js"=>"application/javascript", "tar"=>"application/x-tar", "ppt"=>"application/vnd.ms-powerpoint", "pdf"=>"application/pdf", "swf"=>"application/x-shockwave-flash", "zip"=>"application/x-zip-compressed" ];
$ vim http_server.php
<?php //创建HTTP服务器 $addr = "0.0.0.0"; $port = 9501; $srv = new swoole_http_server($addr, $port); //设置HTTP服务器参数 $cfg = []; $cfg["worker_num"] = 4;//设置工作进程数量 $cfg["daemonize"] = 0;//守护进程化,程序转入后台。 $srv->set($cfg); //处理请求 $srv->on("request", function(swoole_http_request $rq, swoole_http_response $rp) use($srv){ //获取请求文件信息与文件后缀 $path_info = $rq->server["path_info"]; $ext = pathinfo($path_info, PATHINFO_EXTENSION); //文件是否存在 $file = __DIR__.$path_info; if(!is_file($file) || !file_exists($file)){ $rp->status(404); $rp->end("404 NOT FOUND"); } //处理静态请求 if($ext != "php"){ //设置响应头信息的内容内容 $mimes = include("mimes.php"); $rp->header("Content-Type", $mimes[$ext]); //获取静态文件内容 $contents = file_get_contents($file); //返回内容 $rp->end($contents); } }); //启动服务 $srv->start();
发送请求,浏览器访问127.0.0.1:9501/test.jpeg
,查看图片。
面向对象
$ vim http_server.php
<?php class HttpServer { public static function run($host, $port, $options=[]) { $srv = new swoole_http_server($host, $port); if(!empty($options)){ $srv->set($options); } $srv->on("request", function(swoole_http_request $rq, swoole_http_response $rp) use($srv){ $rp->end("test"); $srv->close($rq->fd); }); $srv->start(); } } HttpServer::run("127.0.0.1", 9501, ["worker_num"=>2, "daemonize"=>0]);
压力测试
使用Apache Bench工具进行压力测试可以发现,swoole_http_server
远超过PHP-FPM、Golang自带的HTTP服务器、Node.js自带的HTTP服务器,性能接近Nginx的静态文件处理。
Swoole的http server与PHP-FPM的性能对比
安装Apache的压测工作ab
$ sudo apt install apache2-util
使用100个客户端跑1000次,平均每个客户端10个请求。
$ ab -c 100 -n 1000 127.0.0.1:9501/index.php Concurrency Level: 100 Time taken for tests: 0.480 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 156000 bytes HTML transferred: 9000 bytes Requests per second: 2084.98 [#/sec] (mean) Time per request: 47.962 [ms] (mean) Time per request: 0.480 [ms] (mean, across all concurrent requests) Transfer rate: 317.63 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 3.0 0 12 Processing: 4 44 10.0 45 57 Waiting: 4 44 10.1 45 57 Total: 16 45 7.8 45 57 Percentage of the requests served within a certain time (ms) 50% 45 66% 49 75% 51 80% 52 90% 54 95% 55 98% 55 99% 56 100% 57 (longest request)
观察可以发现QPS可以达到 Requests per second: 2084.98 [#/sec] (mean)
。
HTTP SERVER 配置选项
swoole_server::set()
用于设置swoole_server
运行时的各项参数化。
$cfg = []; // 处理请求的进程数量 $cfg["worker_num"] = 4; // 守护进程化 $cfg["daemonize"] = 1; // 设置工作进程的最大任务数量 $cfg["max_request"] = 0; $cfg["backlog"] = 128; $cfg["max_request"] = 50; $cfg["dispatch_mode"] = 1; $srv->set($cfg);
配置HTTP SERVER参数后测试并发
$ vim http_server.php
<?php //创建HTTP服务器 $addr = "0.0.0.0"; $port = 9501; $srv = new swoole_http_server($addr, $port); //设置HTTP服务器参数 $cfg = []; $cfg["worker_num"] = 4;//设置工作进程数量 $cfg["daemonize"] = 1;//守护进程化,程序转入后台。 $srv->set($cfg); $srv->on("request", function(swoole_http_request $rq, swoole_http_response $rp){ //获取请求参数 $params = $rq->get; echo "\nparams:".json_encode($params); //处理动态请求 $path_info = $rq->server["path_info"]; $file = __DIR__.$path_info; echo "\nfile:{$file}"; if(is_file($file) && file_exists($file)){ $ext = pathinfo($path_info, PATHINFO_EXTENSION); echo "\next:{$ext}"; if($ext == "php"){ ob_start(); include($file); $contents = ob_get_contents(); ob_end_clean(); }else{ $contents = file_get_contents($file); } echo "\ncontents:{$contents}"; $rp->end($contents); }else{ $rp->status(404); $rp->end("404 not found"); } }); //启动服务 $srv->start();
查看进程
$ ps -ef|grep http_server.php root 16224 1207 0 22:41 ? 00:00:00 php http_server.php root 16225 16224 0 22:41 ? 00:00:00 php http_server.php root 16227 16225 0 22:41 ? 00:00:00 php http_server.php root 16228 16225 0 22:41 ? 00:00:00 php http_server.php root 16229 16225 0 22:41 ? 00:00:00 php http_server.php root 16230 16225 0 22:41 ? 00:00:00 php http_server.php root 16233 2456 0 22:42 pts/0 00:00:00 grep --color=auto http_server.php
查看后台守护进程
$ ps axuf|grep http_server.php root 16622 0.0 0.0 21536 1044 pts/0 S+ 22:46 0:00 | | \_ grep --color=auto http_server.php root 16224 0.0 0.3 269036 8104 ? Ssl 22:41 0:00 \_ php http_server.php root 16225 0.0 0.3 196756 8440 ? S 22:41 0:00 \_ php http_server.php root 16227 0.0 0.6 195212 14524 ? S 22:41 0:00 \_ php http_server.php root 16228 0.0 0.6 195212 14524 ? S 22:41 0:00 \_ php http_server.php root 16229 0.0 0.6 195212 14524 ? S 22:41 0:00 \_ php http_server.php root 16230 0.0 0.6 195212 14524 ? S 22:41 0:00 \_ php http_server.php $ ps auxf|grep http_server.php|wc -l 7
杀死后台进程
# 强杀后台进程 $ kill -9 $(ps aux|grep swoole|grep -v grep|awk '{print $2}') $ kill -9 16224 $ kill -9 16225 $ kill -9 16227 $ kill -9 16228 $ kill -9 16229 $ kill -9 16230 # 重启后台进程 $ kill -10 $(ps aux|grep http_server|grep -v grep|awk '{print $2}')
压测
$ ab -c 100 -n 1000 127.0.0.1:9501/index.php Server Software: swoole-http-server Server Hostname: 127.0.0.1 Server Port: 9501 Document Path: /index.php Document Length: 9 bytes Concurrency Level: 100 Time taken for tests: 0.226 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 156000 bytes HTML transferred: 9000 bytes Requests per second: 4417.72 [#/sec] (mean) Time per request: 22.636 [ms] (mean) Time per request: 0.226 [ms] (mean, across all concurrent requests) Transfer rate: 673.01 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 2.8 0 11 Processing: 4 21 7.2 20 49 Waiting: 1 21 7.2 20 49 Total: 5 22 7.6 20 56 Percentage of the requests served within a certain time (ms) 50% 20 66% 23 75% 25 80% 26 90% 30 95% 38 98% 45 99% 53 100% 56 (longest request)
观察可以发现QPC为Requests per second: 4417.72 [#/sec] (mean)
。
性能优化
After using the swoole_http_server
service, if you find that the request time monitoring glitches of the service are very serious and the interface time consumption fluctuates greatly, you can observe the response packet response
of the service. Size, if the response packet exceeds 1~2M or even larger, it can be judged that the service response fluctuates greatly because there are too many and large packets.
Why does responding to the package benefits lead to corresponding time fluctuations? There are two main impacts. The first is that the response packet is too large, which makes process communication between Swoole more time-consuming and takes up more resources. The second is that the response packet is too large, making it more time-consuming for Swoole's Reactor thread to send packets.
The above is the detailed content of Let's take a look at Swoole HTTP. For more information, please follow other related articles on the PHP Chinese website!