Swoole 源码分析之 Http Server 模块

首发原文链接:Swoole 源码分析之 Http Server 模块
大家好,我是码农先森。

Http 模块的注册初始化

这次我们分析的就是 Swoole 官网的这段代码,看似简单,实则不简单。

Swoole 源码文件 swoole_http_server.c 中有这样一个函数 php_swoole_http_server_minit
这个函数是专门用来注册及初始化 Http Server 模块的,如果不预先注册,那么在 PHP 编程 中无法使用的。

// swoole-src/ext-src/swoole_http_server.c:172
void php_swoole_http_server_minit(int module_number) {
    // 定义 Swoole\Http\Server 为 PHP 中的类名
    // 并且 swoole_http_serve 继承了 swoole_server 即可以使用 `swoole_server` 的所有方法
    SW_INIT_CLASS_ENTRY_EX(swoole_http_server, "Swoole\\Http\\Server", nullptr, nullptr, swoole_server);
    // 这里设置为不可序列化,也就是说这个类能被序列化
    SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_server);
    // 这里设置为不可克隆,也就是说这个类的对象不能被复制
    SW_SET_CLASS_CLONEABLE(swoole_http_server, sw_zend_class_clone_deny);
    // 这里设置为不可删除属性,也就是这个类的属性不能被删除
    SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_server, sw_zend_class_unset_property_deny);
}

从上面的这段初始化代码可以看出, swoole_http_server 继承了 swoole_server。因此,可以使用 swoole_server 的所有方法。

$http = new Swoole\Http\Server('127.0.0.1', 9501);
$http->on('start', function ($server) {});
$http->on('request', function ($request, $response) {});

这里的 new Swoole\Http\Server('127.0.0.1', 9501)$http->on('start', function ($server) {}$http->on('request', function ($request, $response) {});
在实际的调用中都是在 swoole_server 完成的,因此这里不会再过多的介绍了。可以看我之前的文章 Swoole 源码分析之 TCP Server 模块 这里都介绍了关于 构造函数、回调函数的 实现方式。

下面我们会着重介绍 $http->start() 这个函数,针对 swoole_http_server 做了一些特殊的实现。

$http->start() 的实现

话不多说,先上一张整体的实现图。$http->start() 这个方法的实现,在 Swoole 源码中最直接对应的就是 static PHP_METHOD(swoole_server, start) 这个函数。

那么刚刚说过,针对 swoole_http_server 做了一些特殊的实现。那么在哪里做的特殊处理呢?
我们来分析这个方法 on_before_start(),它是在真正的 start 服务启动之前做了一些预先工作。

// swoole-src/ext-src/swoole_server.cc:779
void ServerObject::on_before_start() {
   ...
    bool find_http_port = false;
    // 检查是否是 redis 服务
    if (is_redis_server()) {
        ...
        serv->onReceive = php_swoole_redis_server_onReceive;
    // 检查是否是 http 服务
    } else if (is_http_server()) {
        // 检查是否是 websocket 服务
        if (is_websocket_server()) {
            if (!isset_callback(primary_port, SW_SERVER_CB_onMessage)) {
                php_swoole_fatal_error(E_ERROR, "require onMessage callback");
                return;
            }
        } else if (!isset_callback(primary_port, SW_SERVER_CB_onRequest)) {
            php_swoole_fatal_error(E_ERROR, "require onRequest callback");
            return;
        }
        ....
        primary_port->open_http_protocol = 1;
        primary_port->open_http2_protocol = !!(protocol_flag & SW_HTTP2_PROTOCOL);
        primary_port->open_websocket_protocol = !!(protocol_flag & SW_WEBSOCKET_PROTOCOL);
        find_http_port = true;
        // 设置 Swoole Server 真正的 onReceive 回调是 php_swoole_http_server_onReceive
        serv->onReceive = php_swoole_http_server_onReceive;
    } else {
        ...
        // 否则,就是默认回调到 Swoole Server onReceive 的方法 php_swoole_server_onReceive
        serv->onReceive = php_swoole_server_onReceive;
    }
    ...
    if (find_http_port) {
        serv->onReceive = php_swoole_http_server_onReceive;
    }
    ...
}

on_before_start 这个方法中,可以看到不仅是对 http_server 做了处理,针对 redis_server 也是如此。
我们最开始提到的 $http->on('request', function ($request, $response) {}); 其中针对被实现的方法是在 php_swoole_http_server_onReceive 中。
接下来,我们揭开 php_swoole_http_server_onReceive 函数的神秘面纱。

php_swoole_http_server_onReceive 函数的实现

这个函数里面会对 http serverwebsocket server 进行分别的处理,即回调函数的设置。
并且,最后会真正的执行到 用户自定义对回调函数的 实现。

// swoole-src/ext-src/swoole_http_server.cc:51
int php_swoole_http_server_onReceive(Server *serv, RecvData *req) {
    // 获取到对于的连接对象
    Connection *conn = serv->get_connection_verify_no_ssl(session_id);
    ...
    // 如果是 websocket 连接,则进行对应的处理
    if (conn->websocket_status == WebSocket::STATUS_ACTIVE) {
        return swoole_websocket_onMessage(serv, req);
    }

    // 如果是 http2 连接,则进行对应的处理
    if (conn->http2_stream) {
        return swoole_http2_server_onReceive(serv, conn, req);
    }

    ...

    // 开始注册对应的回调函数
    do {
        zend_fcall_info_cache *fci_cache = nullptr;
        // 如果是 websocket 连接,则这是对应的回调函数 SW_SERVER_CB_onHandshake
        if (conn->websocket_status == WebSocket::STATUS_CONNECTION) {
            fci_cache = php_swoole_server_get_fci_cache(serv, server_fd, SW_SERVER_CB_onHandshake);
            if (fci_cache == nullptr) {
                swoole_websocket_onHandshake(serv, port, ctx);
                goto _dtor_and_return;
            } else {
                conn->websocket_status = WebSocket::STATUS_HANDSHAKE;
                ctx->upgrade = 1;
            }
        // 否则是 Http 连接,则这是对应的回调函数 SW_SERVER_CB_onRequest
        } else {
            fci_cache = php_swoole_server_get_fci_cache(serv, server_fd, SW_SERVER_CB_onRequest);
            if (fci_cache == nullptr) {
                swoole_websocket_onRequest(ctx);
                goto _dtor_and_return;
            }
        }
        ctx->private_data_2 = fci_cache;
        if (ctx->onBeforeRequest && !ctx->onBeforeRequest(ctx)) {
            return SW_OK;
        }
        // 对 request 请求进行回调处理
        http_server_process_request(serv, fci_cache, ctx);
    } while (0);

    ...

    return SW_OK;
}

// swoole-src/ext-src/swoole_http_server.cc:51
static void http_server_process_request(Server *serv, zend_fcall_info_cache *fci_cache, HttpContext *ctx) {
    zval args[2];
    // request 回调函数中的 request 参数
    args[0] = *ctx->request.zobject;
    // request 回调函数中的 response 参数
    args[1] = *ctx->response.zobject;
    // 执行真正的调用,这里将会直接执行 用户自定义对回调函数的 实现
    if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) {
       ...
    }
}

其中 fci_cache 这个变量代表的就是 function ($request, $response) {} 这个函数,args[0] 代表的是 $requestargs[1] 代表的是 $response
对于 Http Server 模块来说,最重要的就是这个回调方法了,因为所有的业务逻辑都是在这里进行实现的。

总结

想要了解到 Http Server 的全貌,其实只要把那张整体的实现图看懂就足以了。但是,如果想要有足够的深度,那么就还需要深入 Swoole 的源代码中,就着源码自行分析一遍。同时,也希望这一次的分析,能够给大家带来对 Swoole 更多的一些了解。并不要求要深刻的掌握,因为,很多的事情都不可能一蹴而就。从自己的实力出发,勿忘初心。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2
CodingHePing

最新5.x版本?

2个月前 评论
yxhsea (楼主) 2个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!