• 技术文章 >php框架 >Swoole

    一起看看Swoole HTTP

    coldplay.xixicoldplay.xixi2021-04-09 19:24:36转载549

    目标

    风格

    环境

    HTTP Server

    # 查看SWOOLE版本
    $ php -r 'echo SWOOLE_VERSION;'
    4.3.1

    推荐(免费):swoole

    基础概念

    HTTP报文

    关于HTTP请求报文的组成结构




    4933701-88e24d3fc76fa536.png

    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响应报文的组成结构




    4933701-7401bcececa9efae.png

    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

    使用注意

    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服务器,直接返回请求。




    4933701-986cef92ece579b3.png

    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)

    如果传入的$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
    }

    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,设置成功则无返回值。

    $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()之前执行方才生效。

    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
    )
    $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回调函数共有两个参数

    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中文网其它相关文章!

    声明:本文转载于:CSDN,如有侵犯,请联系admin@php.cn删除
    专题推荐:Swoole HTTP
    上一篇:介绍swoole http_server 适配 thinkphp 5.1 下一篇:详解swoole框架快速入门
    大前端线上培训班

    相关文章推荐

    • 浅析Swoole server• 讲解swoole HTTP服务器中异步MySQL• 介绍EasySwoole安装使用• 介绍swoole异步群发模板消息• 介绍swoole http_server 适配 thinkphp 5.1

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网