惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园_首页
AWS News Blog
AWS News Blog
阮一峰的网络日志
阮一峰的网络日志
Vercel News
Vercel News
C
Cybersecurity and Infrastructure Security Agency CISA
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Tailwind CSS Blog
PCI Perspectives
PCI Perspectives
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 叶小钗
IT之家
IT之家
V2EX - 技术
V2EX - 技术
Cloudbric
Cloudbric
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
宝玉的分享
宝玉的分享
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Schneier on Security
博客园 - 【当耐特】
G
Google Developers Blog
S
Security @ Cisco Blogs
N
Netflix TechBlog - Medium
T
Tenable Blog
C
Check Point Blog
The Cloudflare Blog
J
Java Code Geeks
The Register - Security
The Register - Security
Google Online Security Blog
Google Online Security Blog
Security Latest
Security Latest
T
Tor Project blog
T
The Blog of Author Tim Ferriss
S
Security Affairs
S
Securelist
P
Privacy & Cybersecurity Law Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
P
Privacy International News Feed
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
A
About on SuperTechFans
G
GRAHAM CLULEY
Jina AI
Jina AI
Spread Privacy
Spread Privacy

文章列表 - 她和她的猫

使用小鹤双拼一个月 折腾 Hugo PaperMod 主题 去长鹿旅游休博园 1Password 自动化登录堡垒机 深入 Hyperf:Inject 注解是如何工作的? 译|理解容器镜像层 使用 Rust 实现高性能预写日志(Write Ahead Log) 译|使用 Linux 命名空间隔离系统 深入 Hyperf:HTTP 服务是如何处理请求的? Linux 文件权限学习笔记 译|了解 Bitcask:基于日志结构的 KV 存储引擎 还原 SM2 压缩公钥的几种方法 深入 Hyperf:HTTP 服务启动时发生了什么? 一次 Hyperf 注解失效问题分析 将博客迁移到又拍云 译|PHP 7 新的 Hashtable 实现 排查 ES 查询问题:深入了解 json_encode() 函数 浅析 Redlock 分布式锁实现原理 Yar 源码阅读笔记:客户端的并行调用 Yar 源码阅读笔记:客户端的同步调用 Yar 源码阅读笔记:数据传输模块 Yar 源码阅读笔记:消息编码模块 Yar 源码阅读笔记:RPC 通信协议 Yar 源码阅读笔记:开篇 PHP 8 新特性介绍 基于 OpenSSL 实现国密 SM4 加解密 PHP 源码阅读笔记:编译与调试 PHP 什么是惊群问题 大端模式和小端模式 基于 GitHub Actions 定时推送网址到百度站长平台 聊聊五种 I/O 模型 Workerman 源码分析:文件上传 浅入浅出 HTTP 协议 PHP 网络编程:构建 MySQL 蜜罐获取攻击者微信 ID 使用 Workerman 接入 Bilibili 直播弹幕协议 使用 GitHub Actions 自动部署 Hexo 什么是二进制安全 【转载】PHP 程序员进阶之路 优化 Workerman 检查主进程是否存活的逻辑 基于 Redis 实现分布式锁 《PHP 实现 Base64 编码/解码》笔记 PHP 多进程下载必应壁纸 基于 Redis 实现延迟队列 【转载】PHP 实现 Base64 编码/解码 使用 MySQL FIELD() 函数自定义排序规则 Laravel 的 Facades 实现原理 C语言单链表实现约瑟夫环 数据结构学习笔记:顺序栈和链栈(C语言) 使用 QueryList + Redis 下载壁纸 在 Laravel 登录/注册中使用 mews/captcha 扩展包 使用 mews/captcha 扩展包为 Laravel 应用添加图片验证码 给 YOURLS 短网址系统编写插件《批量生成短网址》 给 YOURLS 短网址系统编写插件《Hello World!》 PHP 7.1 使用 json_encode 函数造成浮点类型数据出现精度问题 Laravel 授权策略(Policy)的基本使用 Ubuntu 系统下 WiFi 频繁掉线解决方法 Laravel 应用部署到 Nginx 服务器上的第一个坑 Ubuntu 16.04 LST 安装 Redis 和 Composer PHP 采集逐浪小说章节列表 Nginx 环境 SSL 强制 HTTP 301 跳转到 HTTPS 【工作&生活】第一周 ServiceStack.Redis 与多线程 Count the number of Duplicates C# Base64 加密及解密 ASP.NET MVC 使用动态类型传递数据 Django 使用 order_by() 对数据进行排序操作 ASP.NET + jQuery + AJAX 实现用户登录 ASP.NET 从客户端中检测到有潜在危险的 Request.Form 值 ASP.NET 使用 FormData 实现 AJAX 上传图片 Python 使用 BeautifulSoup 抓取网页 贪心算法之字符串的完美度 一言不和就造轮子之 CookieHelper ASP.NET 上传图片 DBHelper 类 C# 捕获屏幕源码 用 JavaScript 做一些有趣的事情 第一篇文章 好久不见。 如何防止非法调用 PHP 文件 清明小记。 PHP 生成随机字符串 Z-Blog PHP 程序做 301 跳转 PHP 处理数组常用的几个函数 Sublime Text 3 快捷键总结 PHP 使用 file_get_contents() 函数实现采集网页 使用 C# 编写学生信息管理系统 终于到了。 zblog报错: magic_quotes_gpc 和 safe_mode 世事无常。 如何使用 PHP 上传文件 安卓手机编写和运行 PHP 程序的软件 使用 AJAX 刷新、验证 PHP 图片验证码 PHP 生成图片验证码 PHP 之 session 的使用方法 PHP 之 cookie 的使用方法 PHP 实现分页原理详解 Thinkphp 框架中系统常量在 Js 文件中不解析的解决方法
Yar 源码阅读笔记:RPC 服务端
她和她的猫 · 2022-01-18 · via 文章列表 - 她和她的猫

前言

在前面的文章中介绍了 Yar 客户端以及相关模块的实现,弄清楚了客户端的远程调用是如何发送出去的、发送的内容是什么、以及如何处理响应结果。

今天我们就来看看 Yar 服务端是如何处理客户端请求的。

服务端的实现

在开篇的服务端示例中可以看到,Yar_Server 有两个方法:构造函数和 handle 方法。

下面是 Yar_Server 的方法和属性的定义。

// source:yar_server.c

zend_function_entry yar_server_methods[] = {
    PHP_ME(yar_server, __construct, arginfo_service___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR|ZEND_ACC_FINAL)
    PHP_ME(yar_server, handle, arginfo_service_void, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

YAR_STARTUP_FUNCTION(service) {
    zend_class_entry ce;

    INIT_CLASS_ENTRY(ce, "Yar_Server", yar_server_methods);
    yar_server_ce = zend_register_internal_class(&ce);
    zend_declare_property_null(yar_server_ce, ZEND_STRL("_executor"), ZEND_ACC_PROTECTED);

    return SUCCESS;
}

下面来看看这两个方法的实现。

构造函数

构造函数的作用是将需要对外提供服务的对象保存到 Yar_Server 的 _executor 中。处理客户端的请求时,就会执行该对象中被调用的方法。

// source:yar_server.c

PHP_METHOD(yar_server, __construct) {
    zval *obj;

    if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "o", &obj) == FAILURE) {
        return;
    }

    // 保存对象到 _executor 中
    zend_update_property(yar_server_ce, getThis(), "_executor", sizeof("_executor")-1, obj);
}

处理请求

在 handle 方法中,通过判断请求方式执行不同的逻辑。

  • 非 POST 请求:例如 GET 请求,输出 RPC 服务的 API 信息。
  • POST 请求:处理客户端的远程调用,执行被调用的方法,返回响应结果。
// sourceyar_server.c

PHP_METHOD(yar_server, handle)
{
    // ...

    const char *method;
    zval *executor, rv;

    // 获取被调用的对象
    executor = zend_read_property(yar_server_ce, getThis(), ZEND_STRL("_executor"), 0, &rv);
    if (IS_OBJECT != Z_TYPE_P(executor)) {
        php_error_docref(NULL, E_WARNING, "executor is not a valid object");
        RETURN_FALSE;
    }

    // 判断请求方式,非 POST 请求则输出 API 信息
    method = SG(request_info).request_method;
    if (!method || strncasecmp(method, "POST", 4)) {
        // 检查是否开启了 expose_info 
        if (YAR_G(expose_info)) {
            php_yar_server_info(executor);
            RETURN_TRUE;
        } else {
            zend_throw_exception(yar_server_exception_ce, "server info is not allowed to access", YAR_ERR_FORBIDDEN);
            return;
        }
    }
    
    // 处理客户端的远程调用
    php_yar_server_handle(executor);

    RETURN_TRUE;
}

php_yar_server_info 函数的作用是输出 API 信息页面的 HTML 及被调用对象中所有 public 方法的信息。

// source:yar_server.c

static void php_yar_server_info(zval *obj) {
    char buf[1024];
    zend_class_entry *ce = Z_OBJCE_P(obj);

    // 输出 header 部分
    snprintf(buf, sizeof(buf), HTML_MARKUP_HEADER, ZSTR_VAL(ce->name));
    PHPWRITE(buf, strlen(buf));

    // 输出 css 和 javascript 部分
    PHPWRITE(HTML_MARKUP_CSS, sizeof(HTML_MARKUP_CSS) - 1);
    PHPWRITE(HTML_MARKUP_SCRIPT, sizeof(HTML_MARKUP_SCRIPT) - 1);

    // 输出网页标题
    snprintf(buf, sizeof(buf), HTML_MARKUP_TITLE, ZSTR_VAL(ce->name));
    PHPWRITE(buf, strlen(buf));

    // 读取并输出被调用对象的方法列表
    zend_hash_apply_with_argument(&ce->function_table, (apply_func_arg_t)php_yar_print_info, (void *)(ce));

    // 输出 footer 部分
    PHPWRITE(HTML_MARKUP_FOOTER, sizeof(HTML_MARKUP_FOOTER) - 1);
}

static int php_yar_print_info(zval *ptr, void *argument) /* {{{ */ {
    zend_function *f = Z_FUNC_P(ptr);

    // 判断是否为 public 方法
    if (f->common.fn_flags & ZEND_ACC_PUBLIC 
        && f->common.function_name && *(ZSTR_VAL(f->common.function_name)) != '_') {
        char *prototype = NULL;
        // 获取函数原型
        if ((prototype = php_yar_get_function_declaration(f))) {
            char *buf, *doc_comment = NULL;
            if (f->type == ZEND_USER_FUNCTION) {
                if (f->op_array.doc_comment != NULL) {
                    // 读取函数的 doc 注释
                    doc_comment = (char *)ZSTR_VAL(f->op_array.doc_comment);
                }
            }
            // 输出函数原型及注释
            spprintf(&buf, 0, HTML_MARKUP_ENTRY, prototype, doc_comment? doc_comment : "");
            efree(prototype);
            PHPWRITE(buf, strlen(buf));
            efree(buf);
        }
    }

    return ZEND_HASH_APPLY_KEEP;
}

下面继续看看 php_yar_server_handle 函数,该函数的主要逻辑如下:

  • 读取 HTTP 请求中 body 的内容。
  • 调用 php_yar_protocol_parse 函数解析读取到的内容。
  • 调用 php_yar_packager_unpack 函数对 payload 进行解码。
  • 调用 php_yar_request_unpack 函数解析请求数据。
  • 执行被调用的方法。
  • 返回响应结果。
// source:yar_server.c

static void php_yar_server_handle(zval *obj) /* {{{ */ {
    // ...

    // 读取 HTTP 请求 body 中的内容
    while (!php_stream_eof(s)) {
        len += php_stream_read(s, buf + len, sizeof(buf) - len);
        if (len == sizeof(buf) || raw_data.s) {
            smart_str_appendl(&raw_data, buf, len);
            len = 0;
        }
    }

    // 获取 body 中的内容及长度
    if (len) {
        payload = buf;
        payload_len = len;
    } else if (raw_data.s) {
        smart_str_0(&raw_data);
        payload = ZSTR_VAL(raw_data.s);
        payload_len = ZSTR_LEN(raw_data.s);
    } else {
        php_yar_error(response, YAR_ERR_PACKAGER, "empty request body");
        DEBUG_S("0: empty request '%s'");
        goto response_no_output;
    }

    // 解析 body 中的内容
    if (!(header = php_yar_protocol_parse(payload))) {
        php_yar_error(response, YAR_ERR_PACKAGER, "malformed request header '%.10s'", payload);
        DEBUG_S("0: malformed request '%s'", payload);
        goto response_no_output;
    }

    // 跳过 Yar 协议的头部信息
    payload += sizeof(yar_header_t);
    payload_len -= sizeof(yar_header_t);

    // 解码 payload 部分
    if (!(post_data = php_yar_packager_unpack(payload, payload_len, &err_msg, &ret))) {
        php_yar_error(response, YAR_ERR_PACKAGER, err_msg);
        efree(err_msg);
        goto response_no_output;
    }

    pkg_name = payload;

    // 解析请求数据
    request = php_yar_request_unpack(post_data);

    if (php_output_start_user(NULL, 0, PHP_OUTPUT_HANDLER_STDFLAGS) == FAILURE) {
        php_yar_error(response, YAR_ERR_OUTPUT, "start output buffer failed");
        goto response_no_output;
    }

    // 检查被调用方法是否存在
    if (!zend_hash_exists(&ce->function_table, method)) {
        zend_string_release(method);
        php_yar_error(response, YAR_ERR_REQUEST, "call to undefined api %s::%s()", ce->name, ZSTR_VAL(request->method));
        goto response;
    }

    zend_try {

        // 初始化被调用方法的参数
        func_params_ht = Z_ARRVAL(request->parameters);
        count = zend_hash_num_elements(func_params_ht);

        if (count) {
            int i = 0;
            func_params = safe_emalloc(sizeof(zval), count, 0);

            ZEND_HASH_FOREACH_VAL(func_params_ht, tmp_zval) {
                ZVAL_COPY(&func_params[i++], tmp_zval);
            } ZEND_HASH_FOREACH_END();
        } else {
            func_params = NULL;
        }

        ZVAL_STR(&func, request->method);

        // 执行被调用的方法
        if (FAILURE == call_user_function(NULL, obj, &func, &retval, count, func_params)) {
            if (count) {
                int i = 0;
                for (; i < count; i++) {
                    zval_ptr_dtor(&func_params[i]);
                }
                efree(func_params);
            }
            php_yar_error(response, YAR_ERR_REQUEST, "call to api %s::%s() failed", ce->name, request->method);
            goto response;
        }
        
        // 释放参数
        if (count) {
            int i = 0;
            for (; i < count; i++) {
                zval_ptr_dtor(&func_params[i]);
            }
            efree(func_params);
        }
    } zend_catch {
        bailout = 1;
    } zend_end_try();

    // 如果发生异常则响应异常信息
    if (EG(exception)) {
        zend_object *exception = EG(exception);
        php_yar_response_set_exception(response, exception);
        EG(exception) = NULL; /* exception may have __destruct will be called */
        OBJ_RELEASE(exception);
        zend_clear_exception();
    }

// 正常响应
response:
   
    // 获取输出到缓冲区的内容
    if (php_output_get_contents(&output) == FAILURE) {
        php_output_end();
        php_yar_error(response, YAR_ERR_OUTPUT, "unable to get ob content");
        goto response_no_output;
    }
    
    // 将缓冲区的内容保存到 response 的 out 中
    php_yar_response_alter_body(response, Z_STR(output), YAR_RESPONSE_REPLACE);

// 无输出响应
response_no_output:
    // 组装并输出响应内容
    php_yar_server_response(request, response, pkg_name);
    if (request) {
        // 释放请求对象
        php_yar_request_destroy(request);
    }

    // 释放响应对象
    php_yar_response_destroy(response);

    if (bailout) {
        zend_bailout();
    }

    return;
}

总结

在这篇文章中,介绍了 Yar 服务端是如何处理客户端请求的,主要是三个步骤:解析请求数据、执行被调用方法、返回响应结果。结合之前的文章就可以知道在一次远程调用的过程中,客户端、服务端都分别做了些什么。