前言

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

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

服务端的实现

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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 中。处理客户端的请求时,就会执行该对象中被调用的方法。

1
2
3
4
5
6
7
8
9
10
11
12
// 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 请求:处理客户端的远程调用,执行被调用的方法,返回响应结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 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 方法的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 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 函数解析请求数据。
  • 执行被调用的方法。
  • 返回响应结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 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 服务端是如何处理客户端请求的,主要是三个步骤:解析请求数据、执行被调用方法、返回响应结果。结合之前的文章就可以知道在一次远程调用的过程中,客户端、服务端都分别做了些什么。