目錄
Http\Server
文件上传upload_tmp_dir
文件压缩http_compression
压缩级别http_compression_level
静态根目录document_root
静态处理 enable_static_handler
静态处理器路径static_handler_locations
设置代理
Http\Request->$header
首頁 php框架 Swoole Swoole與HTTP

Swoole與HTTP

Mar 08, 2021 am 10:27 AM
http swoole

Swoole與HTTP

目標

  • 以了解swoole的http_server的使用
  • 了解swoole的tcp服務開發
  • 實際專案中問題如黏包處理、代理熱更新、使用者驗證等。
  • swoole與現有框架結合

#推薦(免費):swoole

風格

  • 偏基礎重程式碼

環境

  • #PHP版本:
  • Swoole版本:https:// github.com/swoole/swoole-src
  • zphp開發框架:https://github.com/shenzhe/zphp

HTTP Server

  • 靜態檔案處理
  • 動態請求與框架結合
# 查看SWOOLE版本
$ php -r 'echo SWOOLE_VERSION;'
4.3.1
登入後複製

#基礎概念

HTTP封包

關於HTTP請求訊息的組成結構

Swoole與HTTP

#HTTP請求訊息結構

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=domety
登入後複製

關於HTTP回應訊息的組成結構

Swoole與HTTP

HTTP回應封包結構

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: close
登入後複製

建立HTTP伺服器

Swoole在1.7.7版本後內建HTTP伺服器,可建立一個非同步非阻塞多進程的HTTP伺服器。 Swoole的HTTP伺服器對HTTP協定支援的並不完整,建議僅作為應用伺服器,並在前端增加Nginx作為代理。

因為Swoole是在CLI命令列中執行的,在傳統的NGINX FastCGI模式下很多rootshell是無法執行的,而使用Swoole伺服器就能很好的控制rsyncgitsvn等。

使用Swoole的API,建構HTTP伺服器需要4個步驟

  1. 建立Server物件
  2. 設定執行階段參數
  3. 註冊事件回呼函數
  4. 啟動伺服器
# 创建应用
$ 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
登入後複製

使用注意

  • #echovar_dumpprint_r的內容是在伺服器中輸出的
  • 瀏覽器中輸出需要使用$rp->end(string $contents)end()方法只能呼叫一次。
  • 如果需要多次先客戶端發送訊息可使用$rp->write(string $content)方法
  • 完整的HTTP協定請求會被解析並封裝在swoole_http_request物件中
  • 所有的HTTP協定回應會透過swoole_http_response物件進行封裝並傳送

HTTP伺服器的本質

由於swoole_http_server是基於swoole_server的,所以swoole_server下的方法在swoole_http_server中都可以使用,只是swoole_http_server只能被客戶端喚起。簡單來說,swoole_http_server是基於swoole_server加上HTTP協議,再加上requestresponse類庫去實現請求資料和取得資料。與PHP-FPM不同的是,Web伺服器收到請求後會傳遞給Swoole的HTTP伺服器,直接回傳請求。

Swoole與HTTP

swoole_http_server

Http\Server

Swoole\HTTP\Server繼承自Server,是一個HTTP伺服器實現,支援同步與有非同步兩種模式。無論是同步模式還是非同步模式,HTTP伺服器都可以維持大量的TCP客戶端連接,同步與非同步僅僅提現在對請求的處理方式。

  • 同步模式

同步模式等同於Nginx PHP-FPM/Apache,需要設定大量Worker工作進程來完成並發請求處理,Worker工作進程可以使用同步阻塞IO,程式設計方式與普通的PHP的Web程式完全一致。與PHP-FPM/Apache不同的是,客戶端連線並不會獨佔進程,伺服器仍可應付大量並發連線。

  • 非同步模式

非同步模式下整個HTTP伺服器是非同步非阻塞的,伺服器可以應答大規模的並發連接和並發請求,程式設計方式需要完全使用非同步API,如MySQL、Redis、HTTP客戶端、file_get_contentssleep等阻塞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);
登入後複製

POST解析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);
登入後複製

解析Cookiehttp_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支持gzipbr(需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_rootenable_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_handletrue后,底层收到HTTP请求会像判断document_root路径下是否存在目标文件,若存在则会直接发送文件给客户端,不再触发onRequest回调。

  1. 设置服务器运行时环境
$ vim server.php
登入後複製
$configs = [];
$configs["enable_static_handler"] =  true;
$configs["document_root"] = "/test";
$server->set($configs);
登入後複製
  1. 设置Nginx反向代理配置

例如:设置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客户端请求的相关信息,包括GETPOSTCOOKIEHeader等,请求对象$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\Request->$header

HTTP请求的头部信息,类型为数组,所有的键名均为小写。

$host = $request->header["host"];
$accept = $request->header["accept"];
登入後複製

Http\Request->$server

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\Request->$get

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\Request->$post

HTTP请求携带POST参数,格式为键值对的关联数组,POSTHeader加起来的尺寸不得超过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\Request->$cookie

HTTP请求携带的COOKIE信息,格式为键值对的关联数组。

Http\Request->$files

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_filemove_uploaded_file函数。当HTTP请求对象$request对象销毁时,会自动删除上传的临时文件。

Http\Request->rawContent()

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();
登入後複製

Http\Request->getData()

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对象。

Http\Response->header

function Http\Response->header(
  string $key,
  string $value,
  bool $ucworods = true
)
登入後複製

header方法用于设置HTTP响应的Header头信息,如果设置失败返回false,设置成功则无返回值。

  • string $key 表示HTTP头的Key
  • string $value 表示HTTP头的Value
  • bool $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;
};
登入後複製

Http\Response->cookie

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。

Http\Response->status

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表示永久跳转。

Http\Response->redirect

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();
登入後複製

Http\Response->write

write方法用于启用HTTP的chunk分段以向浏览器发送相应的内容,使用write分段发送数据后end方法将不再接收任何参数,调用end方法后会发送一个长度为0的分段chunk表示数据传输完毕。

bool Http\Response->write(string $data)
登入後複製

参数$data表示要发送的数据内容,最大长度不得超过2MB,受buffer_output_size配置项控制。

Http\Response->sendfile

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压缩。

Http\Response->end

end方法用于发送HTTP响应体,并结束请求处理。

function Http\Response->end(string $html);
登入後複製

end方法只能调用一次,如果需要分多次向客户端发送数据下需使用write方法,send操作后将会向客户端浏览器发送HTML内容。如果客户端开启了KeepAlive连接会保持,服务器会等待下一次请求。如果没有开启KeepAlive服务器将会切断连接。

Http\Response->detach

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不接受onConnectonReceive回调设置,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)

性能优化

使用swoole_http_server服務後,若發現服務的請求耗時監控毛邊十分嚴重,介面耗時波動較大的情況,可以觀察下服務的回應套件response的大小,若響應包超過1~2M甚至更大,則可判斷是由於包太多而且很大導致服務響應波動較大。

為什麼反應包惠會導致對應的時間波動呢?主要有兩個方面的影響,第一是響應包太大導致Swoole之間進程通訊更加耗時並佔用更多資源。第二是回應包太大導致Swoole的Reactor線程發包更加耗時。

以上是Swoole與HTTP的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1670
14
CakePHP 教程
1428
52
Laravel 教程
1329
25
PHP教程
1276
29
C# 教程
1256
24
swoole協程如何在laravel使用 swoole協程如何在laravel使用 Apr 09, 2024 pm 06:48 PM

Laravel 中使用 Swoole 協程可以並發處理大量請求,優點包括:同時處理:允許同時處理多個請求。高效能:基於 Linux epoll 事件機制,高效處理請求。低資源消耗:所需伺服器資源更少。易於整合:與 Laravel 框架無縫集成,使用簡單。

瞭解網頁重定向的常見應用場景並了解HTTP301狀態碼 瞭解網頁重定向的常見應用場景並了解HTTP301狀態碼 Feb 18, 2024 pm 08:41 PM

掌握HTTP301狀態碼的意思:網頁重定向的常見應用場景隨著網路的快速發展,人們對網頁互動的要求也越來越高。在網頁設計領域,網頁重定向是一種常見且重要的技術,透過HTTP301狀態碼來實現。本文將探討HTTP301狀態碼的意義以及在網頁重新導向中的常見應用場景。 HTTP301狀態碼是指永久重新導向(PermanentRedirect)。當伺服器接收到客戶端發

HTTP 200 OK:了解成功回應的意義與用途 HTTP 200 OK:了解成功回應的意義與用途 Dec 26, 2023 am 10:25 AM

HTTP狀態碼200:探索成功回應的意義與用途HTTP狀態碼是用來表示伺服器回應狀態的數字代碼。其中,狀態碼200表示請求已成功被伺服器處理。本文將探討HTTP狀態碼200的具體意義與用途。首先,讓我們來了解HTTP狀態碼的分類。狀態碼分為五個類別,分別是1xx、2xx、3xx、4xx和5xx。其中,2xx表示成功的回應。而200是2xx中最常見的狀態碼

swoole和workerman哪個好 swoole和workerman哪個好 Apr 09, 2024 pm 07:00 PM

Swoole 和 Workerman 都是高效能 PHP 伺服器框架。 Swoole 以其非同步處理、出色的效能和可擴展性而聞名,適用於需要處理大量並發請求和高吞吐量的專案。 Workerman 提供了非同步和同步模式的靈活性,具有直覺的 API,更適合易用性和處理較低並發量的專案。

swoole和java哪個表現好 swoole和java哪個表現好 Apr 09, 2024 pm 07:03 PM

效能比較:吞吐量:Swoole 以協程機制,吞吐量更高。延遲:Swoole 的協程上下文切換開銷更低,延遲更小。記憶體消耗:Swoole 的協程佔用記憶體較少。易用性:Swoole 提供更易於使用的並發程式設計 API。

swoole框架怎麼重啟服務 swoole框架怎麼重啟服務 Apr 09, 2024 pm 06:15 PM

若要重新啟動 Swoole 服務,請依照下列步驟操作:檢查服務狀態並取得 PID。使用 "kill -15 PID" 停止服務。使用啟動服務的相同命令重新啟動服務。

swoole_process 怎麼讓使用者切換 swoole_process 怎麼讓使用者切換 Apr 09, 2024 pm 06:21 PM

Swoole Process 中可讓使用者切換,具體操作步驟為:建立進程;設定進程使用者;啟動進程。

http請求415錯誤解決方法 http請求415錯誤解決方法 Nov 14, 2023 am 10:49 AM

解決方法:1、檢查請求頭中的Content-Type;2、檢查請求體中的資料格式;3、使用適當的編碼格式;4、使用適當的請求方法;5、檢查伺服器端的支援。

See all articles