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