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

推荐订阅源

Hacker News - Newest:
Hacker News - Newest: "LLM"
Webroot Blog
Webroot Blog
S
Security @ Cisco Blogs
H
Heimdal Security Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
N
News and Events Feed by Topic
H
Hacker News: Front Page
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
SecWiki News
SecWiki News
N
News | PayPal Newsroom
T
Tor Project blog
W
WeLiveSecurity
A
Arctic Wolf
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
月光博客
月光博客
AWS News Blog
AWS News Blog
D
Docker
C
CERT Recently Published Vulnerability Notes
MyScale Blog
MyScale Blog
Google Online Security Blog
Google Online Security Blog
大猫的无限游戏
大猫的无限游戏
T
The Blog of Author Tim Ferriss
I
InfoQ
人人都是产品经理
人人都是产品经理
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
Hacker News: Ask HN
Hacker News: Ask HN
Blog — PlanetScale
Blog — PlanetScale
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
Stack Overflow Blog
Stack Overflow Blog
Recorded Future
Recorded Future
罗磊的独立博客
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
The Exploit Database - CXSecurity.com
D
DataBreaches.Net
S
Security Affairs
WordPress大学
WordPress大学
T
Threatpost
Microsoft Security Blog
Microsoft Security Blog
V
Vulnerabilities – Threatpost
The Hacker News
The Hacker News
S
SegmentFault 最新的问题
B
Blog RSS Feed
Project Zero
Project Zero
P
Proofpoint News Feed

文章列表 - 她和她的猫

使用小鹤双拼一个月 折腾 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 服务端是如何处理客户端请求的,主要是三个步骤:解析请求数据、执行被调用方法、返回响应结果。结合之前的文章就可以知道在一次远程调用的过程中,客户端、服务端都分别做了些什么。