1 /*
2 +----------------------------------------------------------------------+
3 | Swoole |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 2.0 of the Apache license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | http://www.apache.org/licenses/LICENSE-2.0.html |
9 | If you did not receive a copy of the Apache2.0 license and are unable|
10 | to obtain it through the world-wide-web, please send a note to |
11 | license@swoole.com so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Tianfeng Han <mikan.tenny@gmail.com> |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "php_swoole_cxx.h"
18 #include "php_swoole_http.h"
19
20 #include "swoole_string.h"
21 #include "swoole_protocol.h"
22 #include "swoole_socket.h"
23 #include "swoole_util.h"
24
25 #ifdef SW_USE_HTTP2
26
27 #include "swoole_http2.h"
28
29 #define HTTP2_CLIENT_HOST_HEADER_INDEX 3
30
31 using namespace swoole;
32 using swoole::coroutine::Socket;
33
34 namespace Http2 = swoole::http2;
35
36 static zend_class_entry *swoole_http2_client_coro_ce;
37 static zend_object_handlers swoole_http2_client_coro_handlers;
38
39 static zend_class_entry *swoole_http2_client_coro_exception_ce;
40 static zend_object_handlers swoole_http2_client_coro_exception_handlers;
41
42 static zend_class_entry *swoole_http2_request_ce;
43 static zend_object_handlers swoole_http2_request_handlers;
44
45 static zend_class_entry *swoole_http2_response_ce;
46 static zend_object_handlers swoole_http2_response_handlers;
47
48 namespace swoole {
49 namespace coroutine {
50 namespace http2 {
51
52 struct Stream {
53 uint32_t stream_id;
54 uint8_t gzip;
55 uint8_t flags;
56 String *buffer;
57 #ifdef SW_HAVE_ZLIB
58 z_stream gzip_stream;
59 String *gzip_buffer;
60 #endif
61 zval zresponse;
62
63 // flow control
64 uint32_t remote_window_size;
65 uint32_t local_window_size;
66 };
67
68 class Client {
69 public:
70 std::string host;
71 int port;
72 bool open_ssl;
73 double timeout = network::Socket::default_read_timeout;
74
75 Socket *client = nullptr;
76
77 nghttp2_hd_inflater *inflater = nullptr;
78 nghttp2_hd_deflater *deflater = nullptr;
79
80 uint32_t stream_id = 0; // the next send stream id
81 uint32_t last_stream_id = 0; // the last received stream id
82
83 Http2::Settings local_settings = {};
84 Http2::Settings remote_settings = {};
85
86 std::unordered_map<uint32_t, Stream *> streams;
87
88 /* safety zval */
89 zval _zobject;
90 zval *zobject;
91
Client(const char * _host,size_t _host_len,int _port,bool _ssl,zval * __zobject)92 Client(const char *_host, size_t _host_len, int _port, bool _ssl, zval *__zobject) {
93 host = std::string(_host, _host_len);
94 port = _port;
95 open_ssl = _ssl;
96 _zobject = *__zobject;
97 zobject = &_zobject;
98 Http2::init_settings(&local_settings);
99 }
100
get_stream(uint32_t stream_id)101 inline Stream *get_stream(uint32_t stream_id) {
102 auto i = streams.find(stream_id);
103 if (i == streams.end()) {
104 return nullptr;
105 } else {
106 return i->second;
107 }
108 }
109
110 ssize_t build_header(zval *zobject, zval *zrequest, char *buffer);
111
update_error_properties(int code,const char * msg)112 inline void update_error_properties(int code, const char *msg) {
113 zend_update_property_long(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), code);
114 zend_update_property_string(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), msg);
115 }
116
io_error()117 inline void io_error() {
118 update_error_properties(client->errCode, client->errMsg);
119 }
120
nghttp2_error(int code,const char * msg)121 inline void nghttp2_error(int code, const char *msg) {
122 update_error_properties(code, std_string::format("%s with error: %s", msg, nghttp2_strerror(code)).c_str());
123 }
124
is_available()125 inline bool is_available() {
126 if (sw_unlikely(!client || !client->is_connected())) {
127 swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION);
128 zend_update_property_long(
129 swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), SW_ERROR_CLIENT_NO_CONNECTION);
130 zend_update_property_string(swoole_http2_client_coro_ce,
131 SW_Z8_OBJ_P(zobject),
132 ZEND_STRL("errMsg"),
133 "client is not connected to server");
134 return false;
135 }
136 return true;
137 }
138
apply_setting(zval * zset)139 inline void apply_setting(zval *zset) {
140 if (client && ZVAL_IS_ARRAY(zset)) {
141 php_swoole_client_set(client, zset);
142 }
143 }
144
recv_packet(double timeout)145 inline bool recv_packet(double timeout) {
146 if (sw_unlikely(client->recv_packet(timeout) <= 0)) {
147 io_error();
148 return false;
149 }
150 return true;
151 }
152
153 bool connect();
154 Stream *create_stream(uint32_t stream_id, uint8_t flags);
155 void destroy_stream(Stream *stream);
156
delete_stream(uint32_t stream_id)157 inline bool delete_stream(uint32_t stream_id) {
158 auto i = streams.find(stream_id);
159 if (i == streams.end()) {
160 return false;
161 }
162
163 destroy_stream(i->second);
164 streams.erase(i);
165
166 return true;
167 }
168
169 bool send_window_update(int stream_id, uint32_t size);
170 bool send_ping_frame();
171 bool send_data(uint32_t stream_id, const char *p, size_t len, int flag);
172 uint32_t send_request(zval *zrequest);
173 bool write_data(uint32_t stream_id, zval *zdata, bool end);
174 bool send_goaway_frame(zend_long error_code, const char *debug_data, size_t debug_data_len);
175 ReturnCode parse_frame(zval *return_value, bool pipeline_read = false);
176 bool close();
177
~Client()178 ~Client() {
179 close();
180 }
181
182 private:
183 bool send_setting();
184 int parse_header(Stream *stream, int flags, char *in, size_t inlen);
185
send(const char * buf,size_t len)186 inline bool send(const char *buf, size_t len) {
187 if (sw_unlikely(client->send_all(buf, len) != (ssize_t) len)) {
188 io_error();
189 return false;
190 }
191 return true;
192 }
193 };
194
195 } // namespace http2
196 } // namespace coroutine
197 } // namespace swoole
198
199 using swoole::coroutine::http2::Client;
200 using swoole::coroutine::http2::Stream;
201 using swoole::http2::HeaderSet;
202
203 struct Http2ClientObject {
204 Client *h2c;
205 zend_object std;
206 };
207
php_swoole_http2_client_coro_fetch_object(zend_object * obj)208 static sw_inline Http2ClientObject *php_swoole_http2_client_coro_fetch_object(zend_object *obj) {
209 return (Http2ClientObject *) ((char *) obj - swoole_http2_client_coro_handlers.offset);
210 }
211
php_swoole_get_h2c(zval * zobject)212 static sw_inline Client *php_swoole_get_h2c(zval *zobject) {
213 return php_swoole_http2_client_coro_fetch_object(Z_OBJ_P(zobject))->h2c;
214 }
215
php_swoole_set_h2c(zval * zobject,Client * h2c)216 static sw_inline void php_swoole_set_h2c(zval *zobject, Client *h2c) {
217 php_swoole_http2_client_coro_fetch_object(Z_OBJ_P(zobject))->h2c = h2c;
218 }
219
php_swoole_http2_client_coro_free_object(zend_object * object)220 static void php_swoole_http2_client_coro_free_object(zend_object *object) {
221 Http2ClientObject *request = php_swoole_http2_client_coro_fetch_object(object);
222 Client *h2c = request->h2c;
223
224 if (h2c) {
225 delete h2c;
226 }
227 zend_object_std_dtor(&request->std);
228 }
229
php_swoole_http2_client_coro_create_object(zend_class_entry * ce)230 static zend_object *php_swoole_http2_client_coro_create_object(zend_class_entry *ce) {
231 Http2ClientObject *request = (Http2ClientObject *) zend_object_alloc(sizeof(Http2ClientObject), ce);
232 zend_object_std_init(&request->std, ce);
233 object_properties_init(&request->std, ce);
234 request->std.handlers = &swoole_http2_client_coro_handlers;
235 return &request->std;
236 }
237
238 SW_EXTERN_C_BEGIN
239 static PHP_METHOD(swoole_http2_client_coro, __construct);
240 static PHP_METHOD(swoole_http2_client_coro, __destruct);
241 static PHP_METHOD(swoole_http2_client_coro, set);
242 static PHP_METHOD(swoole_http2_client_coro, connect);
243 static PHP_METHOD(swoole_http2_client_coro, stats);
244 static PHP_METHOD(swoole_http2_client_coro, isStreamExist);
245 static PHP_METHOD(swoole_http2_client_coro, send);
246 static PHP_METHOD(swoole_http2_client_coro, write);
247 static PHP_METHOD(swoole_http2_client_coro, recv);
248 static PHP_METHOD(swoole_http2_client_coro, read);
249 static PHP_METHOD(swoole_http2_client_coro, ping);
250 static PHP_METHOD(swoole_http2_client_coro, goaway);
251 static PHP_METHOD(swoole_http2_client_coro, close);
252 SW_EXTERN_C_END
253
254 // clang-format off
255 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_void, 0, 0, 0)
256 ZEND_END_ARG_INFO()
257
258 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_construct, 0, 0, 1)
259 ZEND_ARG_INFO(0, host)
260 ZEND_ARG_INFO(0, port)
261 ZEND_ARG_INFO(0, open_ssl)
262 ZEND_END_ARG_INFO()
263
264 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_set, 0, 0, 1)
265 ZEND_ARG_ARRAY_INFO(0, settings, 0)
266 ZEND_END_ARG_INFO()
267
268 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_stats, 0, 0, 0)
269 ZEND_ARG_INFO(0, key)
270 ZEND_END_ARG_INFO()
271
272 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_isStreamExist, 0, 0, 1)
273 ZEND_ARG_INFO(0, stream_id)
274 ZEND_END_ARG_INFO()
275
276 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_send, 0, 0, 1)
277 ZEND_ARG_INFO(0, request)
278 ZEND_END_ARG_INFO()
279
280 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_write, 0, 0, 2)
281 ZEND_ARG_INFO(0, stream_id)
282 ZEND_ARG_INFO(0, data)
283 ZEND_ARG_INFO(0, end_stream)
284 ZEND_END_ARG_INFO()
285
286 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_recv, 0, 0, 0)
287 ZEND_ARG_INFO(0, timeout)
288 ZEND_END_ARG_INFO()
289
290 ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_goaway, 0, 0, 0)
291 ZEND_ARG_INFO(0, error_code)
292 ZEND_ARG_INFO(0, debug_data)
293 ZEND_END_ARG_INFO()
294
295 static const zend_function_entry swoole_http2_client_methods[] =
296 {
297 PHP_ME(swoole_http2_client_coro, __construct, arginfo_swoole_http2_client_coro_construct, ZEND_ACC_PUBLIC)
298 PHP_ME(swoole_http2_client_coro, __destruct, arginfo_swoole_void, ZEND_ACC_PUBLIC)
299 PHP_ME(swoole_http2_client_coro, set, arginfo_swoole_http2_client_coro_set, ZEND_ACC_PUBLIC)
300 PHP_ME(swoole_http2_client_coro, connect, arginfo_swoole_void, ZEND_ACC_PUBLIC)
301 PHP_ME(swoole_http2_client_coro, stats, arginfo_swoole_http2_client_coro_stats, ZEND_ACC_PUBLIC)
302 PHP_ME(swoole_http2_client_coro, isStreamExist, arginfo_swoole_http2_client_coro_isStreamExist, ZEND_ACC_PUBLIC)
303 PHP_ME(swoole_http2_client_coro, send, arginfo_swoole_http2_client_coro_send, ZEND_ACC_PUBLIC)
304 PHP_ME(swoole_http2_client_coro, write, arginfo_swoole_http2_client_coro_write, ZEND_ACC_PUBLIC)
305 PHP_ME(swoole_http2_client_coro, recv, arginfo_swoole_http2_client_coro_recv, ZEND_ACC_PUBLIC)
306 PHP_ME(swoole_http2_client_coro, read, arginfo_swoole_http2_client_coro_recv, ZEND_ACC_PUBLIC)
307 PHP_ME(swoole_http2_client_coro, goaway, arginfo_swoole_http2_client_coro_goaway, ZEND_ACC_PUBLIC)
308 PHP_ME(swoole_http2_client_coro, ping, arginfo_swoole_void, ZEND_ACC_PUBLIC)
309 PHP_ME(swoole_http2_client_coro, close, arginfo_swoole_void, ZEND_ACC_PUBLIC)
310 PHP_FE_END
311 };
312 // clang-format on
313
php_swoole_http2_client_coro_minit(int module_number)314 void php_swoole_http2_client_coro_minit(int module_number) {
315 SW_INIT_CLASS_ENTRY(swoole_http2_client_coro,
316 "Swoole\\Coroutine\\Http2\\Client",
317 nullptr,
318 "Co\\Http2\\Client",
319 swoole_http2_client_methods);
320 SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http2_client_coro);
321 SW_SET_CLASS_CLONEABLE(swoole_http2_client_coro, sw_zend_class_clone_deny);
322 SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_client_coro, sw_zend_class_unset_property_deny);
323 SW_SET_CLASS_CUSTOM_OBJECT(swoole_http2_client_coro,
324 php_swoole_http2_client_coro_create_object,
325 php_swoole_http2_client_coro_free_object,
326 Http2ClientObject,
327 std);
328
329 SW_INIT_CLASS_ENTRY_EX(swoole_http2_client_coro_exception,
330 "Swoole\\Coroutine\\Http2\\Client\\Exception",
331 nullptr,
332 "Co\\Http2\\Client\\Exception",
333 nullptr,
334 swoole_exception);
335
336 SW_INIT_CLASS_ENTRY(swoole_http2_request, "Swoole\\Http2\\Request", "swoole_http2_request", nullptr, nullptr);
337 SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http2_request);
338 SW_SET_CLASS_CLONEABLE(swoole_http2_request, sw_zend_class_clone_deny);
339 SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_request, sw_zend_class_unset_property_deny);
340 SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_http2_request);
341
342 SW_INIT_CLASS_ENTRY(swoole_http2_response, "Swoole\\Http2\\Response", "swoole_http2_response", nullptr, nullptr);
343 SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http2_response);
344 SW_SET_CLASS_CLONEABLE(swoole_http2_response, sw_zend_class_clone_deny);
345 SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_response, sw_zend_class_unset_property_deny);
346 SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_http2_response);
347
348 zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC);
349 zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("errMsg"), 0, ZEND_ACC_PUBLIC);
350 zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC);
351 zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC);
352 zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC);
353 zend_declare_property_bool(swoole_http2_client_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC);
354 zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("host"), ZEND_ACC_PUBLIC);
355 zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC);
356 zend_declare_property_bool(swoole_http2_client_coro_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC);
357
358 zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("path"), "/", ZEND_ACC_PUBLIC);
359 zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("method"), "GET", ZEND_ACC_PUBLIC);
360 zend_declare_property_null(swoole_http2_request_ce, ZEND_STRL("headers"), ZEND_ACC_PUBLIC);
361 zend_declare_property_null(swoole_http2_request_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC);
362 zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("data"), "", ZEND_ACC_PUBLIC);
363 zend_declare_property_bool(swoole_http2_request_ce, ZEND_STRL("pipeline"), 0, ZEND_ACC_PUBLIC);
364
365 zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("streamId"), 0, ZEND_ACC_PUBLIC);
366 zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC);
367 zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("statusCode"), 0, ZEND_ACC_PUBLIC);
368 zend_declare_property_bool(swoole_http2_response_ce, ZEND_STRL("pipeline"), 0, ZEND_ACC_PUBLIC);
369 zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("headers"), ZEND_ACC_PUBLIC);
370 zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("set_cookie_headers"), ZEND_ACC_PUBLIC);
371 zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC);
372 zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("data"), ZEND_ACC_PUBLIC);
373
374 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_DATA", SW_HTTP2_TYPE_DATA);
375 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_HEADERS", SW_HTTP2_TYPE_HEADERS);
376 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PRIORITY", SW_HTTP2_TYPE_PRIORITY);
377 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_RST_STREAM", SW_HTTP2_TYPE_RST_STREAM);
378 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_SETTINGS", SW_HTTP2_TYPE_SETTINGS);
379 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PUSH_PROMISE", SW_HTTP2_TYPE_PUSH_PROMISE);
380 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PING", SW_HTTP2_TYPE_PING);
381 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_GOAWAY", SW_HTTP2_TYPE_GOAWAY);
382 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_WINDOW_UPDATE", SW_HTTP2_TYPE_WINDOW_UPDATE);
383 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_CONTINUATION", SW_HTTP2_TYPE_CONTINUATION);
384
385 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_NO_ERROR", SW_HTTP2_ERROR_NO_ERROR);
386 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_PROTOCOL_ERROR", SW_HTTP2_ERROR_PROTOCOL_ERROR);
387 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_INTERNAL_ERROR", SW_HTTP2_ERROR_INTERNAL_ERROR);
388 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_FLOW_CONTROL_ERROR", SW_HTTP2_ERROR_FLOW_CONTROL_ERROR);
389 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_SETTINGS_TIMEOUT", SW_HTTP2_ERROR_SETTINGS_TIMEOUT);
390 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_STREAM_CLOSED", SW_HTTP2_ERROR_STREAM_CLOSED);
391 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_FRAME_SIZE_ERROR", SW_HTTP2_ERROR_FRAME_SIZE_ERROR);
392 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_REFUSED_STREAM", SW_HTTP2_ERROR_REFUSED_STREAM);
393 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_CANCEL", SW_HTTP2_ERROR_CANCEL);
394 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_COMPRESSION_ERROR", SW_HTTP2_ERROR_COMPRESSION_ERROR);
395 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_CONNECT_ERROR", SW_HTTP2_ERROR_CONNECT_ERROR);
396 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_ENHANCE_YOUR_CALM", SW_HTTP2_ERROR_ENHANCE_YOUR_CALM);
397 SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_INADEQUATE_SECURITY", SW_HTTP2_ERROR_INADEQUATE_SECURITY);
398 }
399
connect()400 bool Client::connect() {
401 if (sw_unlikely(client != nullptr)) {
402 return false;
403 }
404
405 client = new Socket(network::Socket::convert_to_type(host));
406 if (UNEXPECTED(client->get_fd() < 0)) {
407 php_swoole_sys_error(E_WARNING, "new Socket() failed");
408 zend_update_property_long(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), errno);
409 zend_update_property_string(
410 swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), swoole_strerror(errno));
411 delete client;
412 client = nullptr;
413 return false;
414 }
415 client->set_zero_copy(true);
416 #ifdef SW_USE_OPENSSL
417 if (open_ssl) {
418 client->enable_ssl_encrypt();
419 }
420 #endif
421 client->http2 = 1;
422 client->open_length_check = 1;
423 client->protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE;
424 client->protocol.package_length_offset = 0;
425 client->protocol.package_body_offset = 0;
426 client->protocol.get_package_length = Http2::get_frame_length;
427
428 apply_setting(
429 sw_zend_read_property_ex(swoole_http2_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0));
430
431 if (!client->connect(host, port)) {
432 io_error();
433 close();
434 return false;
435 }
436
437 stream_id = 1;
438 // [init]: we must set default value, server is not always send all the settings
439 Http2::init_settings(&remote_settings);
440
441 int ret = nghttp2_hd_inflate_new2(&inflater, php_nghttp2_mem());
442 if (ret != 0) {
443 nghttp2_error(ret, "nghttp2_hd_inflate_new2() failed");
444 close();
445 return false;
446 }
447 ret = nghttp2_hd_deflate_new2(&deflater, local_settings.header_table_size, php_nghttp2_mem());
448 if (ret != 0) {
449 nghttp2_error(ret, "nghttp2_hd_deflate_new2() failed");
450 close();
451 return false;
452 }
453
454 if (!send(ZEND_STRL(SW_HTTP2_PRI_STRING))) {
455 close();
456 return false;
457 }
458
459 if (!send_setting()) {
460 close();
461 return false;
462 }
463
464 zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 1);
465
466 return true;
467 }
468
close()469 bool Client::close() {
470 Socket *_client = client;
471 if (!_client) {
472 return false;
473 }
474 zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0);
475 if (!_client->has_bound()) {
476 auto i = streams.begin();
477 while (i != streams.end()) {
478 destroy_stream(i->second);
479 streams.erase(i++);
480 }
481 if (inflater) {
482 nghttp2_hd_inflate_del(inflater);
483 inflater = nullptr;
484 }
485 if (deflater) {
486 nghttp2_hd_deflate_del(deflater);
487 deflater = nullptr;
488 }
489 client = nullptr;
490 }
491 if (_client->close()) {
492 delete _client;
493 }
494 return true;
495 }
496
parse_frame(zval * return_value,bool pipeline_read)497 ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) {
498 char *buf = client->get_read_buffer()->str;
499 uint8_t type = buf[3];
500 uint8_t flags = buf[4];
501 uint32_t stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff;
502 ssize_t length = Http2::get_length(buf);
503 buf += SW_HTTP2_FRAME_HEADER_SIZE;
504
505 char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE];
506
507 if (stream_id > last_stream_id) {
508 last_stream_id = stream_id;
509 }
510
511 uint16_t id = 0;
512 uint32_t value = 0;
513
514 switch (type) {
515 case SW_HTTP2_TYPE_SETTINGS: {
516 if (flags & SW_HTTP2_FLAG_ACK) {
517 swoole_http2_frame_trace_log(recv, "ACK");
518 return SW_CONTINUE;
519 }
520
521 while (length > 0) {
522 id = ntohs(*(uint16_t *) (buf));
523 value = ntohl(*(uint32_t *) (buf + sizeof(uint16_t)));
524 swoole_http2_frame_trace_log(recv, "id=%d, value=%d", id, value);
525 switch (id) {
526 case SW_HTTP2_SETTING_HEADER_TABLE_SIZE:
527 if (value != remote_settings.header_table_size) {
528 remote_settings.header_table_size = value;
529 int ret = nghttp2_hd_deflate_change_table_size(deflater, value);
530 if (ret != 0) {
531 nghttp2_error(ret, "nghttp2_hd_deflate_change_table_size() failed");
532 return SW_ERROR;
533 }
534 }
535 swoole_trace_log(SW_TRACE_HTTP2, "setting: header_compression_table_max=%u", value);
536 break;
537 case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
538 remote_settings.max_concurrent_streams = value;
539 swoole_trace_log(SW_TRACE_HTTP2, "setting: max_concurrent_streams=%u", value);
540 break;
541 case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE:
542 remote_settings.window_size = value;
543 swoole_trace_log(SW_TRACE_HTTP2, "setting: init_send_window=%u", value);
544 break;
545 case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE:
546 remote_settings.max_frame_size = value;
547 swoole_trace_log(SW_TRACE_HTTP2, "setting: max_frame_size=%u", value);
548 break;
549 case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
550 if (value != remote_settings.max_header_list_size) {
551 remote_settings.max_header_list_size = value;
552 /*
553 int ret = nghttp2_hd_inflate_change_table_size(inflater, value);
554 if (ret != 0)
555 {
556 nghttp2_error(ret, "nghttp2_hd_inflate_change_table_size() failed");
557 return SW_ERROR;
558 }
559 */
560 }
561 swoole_trace_log(SW_TRACE_HTTP2, "setting: max_header_list_size=%u", value);
562 break;
563 default:
564 // disable warning and ignore it because some websites are not following http2 protocol totally
565 // swoole_warning("unknown option[%d]: %d", id, value);
566 break;
567 }
568 buf += sizeof(id) + sizeof(value);
569 length -= sizeof(id) + sizeof(value);
570 }
571
572 Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 0, SW_HTTP2_FLAG_ACK, stream_id);
573 if (!send(frame, SW_HTTP2_FRAME_HEADER_SIZE)) {
574 return SW_ERROR;
575 }
576 return SW_CONTINUE;
577 }
578 case SW_HTTP2_TYPE_WINDOW_UPDATE: {
579 value = ntohl(*(uint32_t *) buf);
580 swoole_http2_frame_trace_log(recv, "window_size_increment=%d", value);
581 if (stream_id == 0) {
582 remote_settings.window_size += value;
583 } else {
584 Stream *stream = get_stream(stream_id);
585 if (stream) {
586 stream->remote_window_size += value;
587 }
588 }
589 return SW_CONTINUE;
590 }
591 case SW_HTTP2_TYPE_PING: {
592 swoole_http2_frame_trace_log(recv, "ping");
593 if (!(flags & SW_HTTP2_FLAG_ACK)) {
594 Http2::set_frame_header(
595 frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_ACK, stream_id);
596 memcpy(
597 frame + SW_HTTP2_FRAME_HEADER_SIZE, buf + SW_HTTP2_FRAME_HEADER_SIZE, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE);
598 if (!send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE)) {
599 return SW_ERROR;
600 }
601 }
602 return SW_CONTINUE;
603 }
604 case SW_HTTP2_TYPE_GOAWAY: {
605 uint32_t server_last_stream_id = ntohl(*(uint32_t *) (buf));
606 buf += 4;
607 value = ntohl(*(uint32_t *) (buf));
608 buf += 4;
609 swoole_http2_frame_trace_log(recv,
610 "last_stream_id=%d, error_code=%d, opaque_data=[%.*s]",
611 server_last_stream_id,
612 value,
613 (int) (length - SW_HTTP2_GOAWAY_SIZE),
614 buf);
615
616 // update goaway error code and error msg
617 zend_update_property_long(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), value);
618 zend_update_property_stringl(
619 swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), buf, length - SW_HTTP2_GOAWAY_SIZE);
620 zend_update_property_long(
621 swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("serverLastStreamId"), server_last_stream_id);
622 close();
623 return SW_CLOSE;
624 }
625 case SW_HTTP2_TYPE_RST_STREAM: {
626 value = ntohl(*(uint32_t *) (buf));
627 swoole_http2_frame_trace_log(recv, "error_code=%d", value);
628
629 // delete and free quietly
630 delete_stream(stream_id);
631
632 return SW_CONTINUE;
633 }
634 /**
635 * TODO not support push_promise
636 */
637 case SW_HTTP2_TYPE_PUSH_PROMISE: {
638 #ifdef SW_DEBUG
639 uint32_t promise_stream_id = ntohl(*(uint32_t *) (buf)) & 0x7fffffff;
640 swoole_http2_frame_trace_log(recv, "promise_stream_id=%d", promise_stream_id);
641 #endif
642 // auto promise_stream = create_stream(promise_stream_id, false);
643 // RETVAL_ZVAL(promise_stream->response_object, 0, 0);
644 // return SW_READY;
645 return SW_CONTINUE;
646 }
647 default: {
648 swoole_http2_frame_trace_log(recv, "");
649 }
650 }
651
652 Stream *stream = get_stream(stream_id);
653 // The stream is not found or has closed
654 if (stream == nullptr) {
655 swoole_notice("http2 stream#%d belongs to an unknown type or it never registered", stream_id);
656 return SW_CONTINUE;
657 }
658 if (type == SW_HTTP2_TYPE_HEADERS) {
659 parse_header(stream, flags, buf, length);
660 } else if (type == SW_HTTP2_TYPE_DATA) {
661 if (!(flags & SW_HTTP2_FLAG_END_STREAM)) {
662 stream->flags |= SW_HTTP2_STREAM_PIPELINE_RESPONSE;
663 }
664 if (length > 0) {
665 if (!stream->buffer) {
666 stream->buffer = make_string(SW_HTTP2_DATA_BUFFER_SIZE);
667 }
668 #ifdef SW_HAVE_ZLIB
669 if (stream->gzip) {
670 if (php_swoole_zlib_decompress(&stream->gzip_stream, stream->gzip_buffer, buf, length) == SW_ERR) {
671 swoole_warning("decompress failed");
672 return SW_ERROR;
673 }
674 stream->buffer->append(stream->gzip_buffer->str, stream->gzip_buffer->length);
675 } else
676 #endif
677 {
678 stream->buffer->append(buf, length);
679 }
680
681 // now we control the connection flow only (not stream)
682 // our window size is unlimited, so we don't worry about subtraction overflow
683 local_settings.window_size -= length;
684 stream->local_window_size -= length;
685 if (local_settings.window_size < (SW_HTTP2_MAX_WINDOW_SIZE / 4)) {
686 if (!send_window_update(0, SW_HTTP2_MAX_WINDOW_SIZE - local_settings.window_size)) {
687 return SW_ERROR;
688 }
689 local_settings.window_size = SW_HTTP2_MAX_WINDOW_SIZE;
690 }
691 if (stream->local_window_size < (SW_HTTP2_MAX_WINDOW_SIZE / 4)) {
692 if (!send_window_update(stream_id, SW_HTTP2_MAX_WINDOW_SIZE - stream->local_window_size)) {
693 return SW_ERROR;
694 }
695 stream->local_window_size = SW_HTTP2_MAX_WINDOW_SIZE;
696 }
697 }
698 }
699
700 bool end = (flags & SW_HTTP2_FLAG_END_STREAM) || type == SW_HTTP2_TYPE_RST_STREAM || type == SW_HTTP2_TYPE_GOAWAY;
701 pipeline_read = ((pipeline_read || (stream->flags & SW_HTTP2_STREAM_USE_PIPELINE_READ)) &&
702 (stream->flags & SW_HTTP2_STREAM_PIPELINE_RESPONSE));
703 if (end || pipeline_read) {
704 zval *zresponse = &stream->zresponse;
705 if (type == SW_HTTP2_TYPE_RST_STREAM) {
706 zend_update_property_long(swoole_http2_response_ce,
707 SW_Z8_OBJ_P(zresponse),
708 ZEND_STRL("statusCode"),
709 -3 /* HTTP_CLIENT_ESTATUS_SERVER_RESET */);
710 zend_update_property_long(swoole_http2_response_ce, SW_Z8_OBJ_P(zresponse), ZEND_STRL("errCode"), value);
711 }
712 if (stream->buffer && stream->buffer->length > 0) {
713 zend_update_property_stringl(swoole_http2_response_ce,
714 SW_Z8_OBJ_P(zresponse),
715 ZEND_STRL("data"),
716 stream->buffer->str,
717 stream->buffer->length);
718 stream->buffer->clear();
719 }
720 if (!end) {
721 zend_update_property_bool(
722 swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("pipeline"), 1);
723 }
724 RETVAL_ZVAL(zresponse, end, 0);
725 if (!end) {
726 // reinit response object for the following frames
727 object_init_ex(zresponse, swoole_http2_response_ce);
728 zend_update_property_long(
729 swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("streamId"), stream_id);
730 } else {
731 delete_stream(stream_id);
732 }
733
734 return SW_READY;
735 }
736
737 return SW_CONTINUE;
738 }
739
740 #ifdef SW_HAVE_ZLIB
php_swoole_zlib_decompress(z_stream * stream,String * buffer,char * body,int length)741 int php_swoole_zlib_decompress(z_stream *stream, String *buffer, char *body, int length) {
742 int status = 0;
743
744 stream->avail_in = length;
745 stream->next_in = (Bytef *) body;
746 stream->total_in = 0;
747 stream->total_out = 0;
748
749 #if 0
750 printf(SW_START_LINE"\nstatus=%d\tavail_in=%ld,\tavail_out=%ld,\ttotal_in=%ld,\ttotal_out=%ld\n", status,
751 stream->avail_in, stream->avail_out, stream->total_in, stream->total_out);
752 #endif
753
754 buffer->clear();
755
756 while (1) {
757 stream->avail_out = buffer->size - buffer->length;
758 stream->next_out = (Bytef *) (buffer->str + buffer->length);
759
760 status = inflate(stream, Z_SYNC_FLUSH);
761
762 #if 0
763 printf("status=%d\tavail_in=%ld,\tavail_out=%ld,\ttotal_in=%ld,\ttotal_out=%ld,\tlength=%ld\n", status,
764 stream->avail_in, stream->avail_out, stream->total_in, stream->total_out, buffer->length);
765 #endif
766 if (status >= 0) {
767 buffer->length = stream->total_out;
768 }
769 if (status == Z_STREAM_END) {
770 return SW_OK;
771 } else if (status == Z_OK) {
772 if (buffer->length + 4096 >= buffer->size) {
773 if (!buffer->extend()) {
774 return SW_ERR;
775 }
776 }
777 if (stream->avail_in == 0) {
778 return SW_OK;
779 }
780 } else {
781 return SW_ERR;
782 }
783 }
784 return SW_ERR;
785 }
786 #endif
787
PHP_METHOD(swoole_http2_client_coro,__construct)788 static PHP_METHOD(swoole_http2_client_coro, __construct) {
789 char *host;
790 size_t host_len;
791 zend_long port = 80;
792 zend_bool ssl = false;
793
794 ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 3)
795 Z_PARAM_STRING(host, host_len)
796 Z_PARAM_OPTIONAL
797 Z_PARAM_LONG(port)
798 Z_PARAM_BOOL(ssl)
799 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
800
801 if (host_len == 0) {
802 zend_throw_exception(swoole_http2_client_coro_exception_ce, "host is empty", SW_ERROR_INVALID_PARAMS);
803 RETURN_FALSE;
804 }
805
806 Client *h2c = new Client(host, host_len, port, ssl, ZEND_THIS);
807 if (ssl) {
808 #ifndef SW_USE_OPENSSL
809 zend_throw_exception_ex(
810 swoole_http2_client_coro_exception_ce,
811 EPROTONOSUPPORT,
812 "you must configure with `--enable-openssl` to support ssl connection when compiling Swoole");
813 delete h2c;
814 RETURN_FALSE;
815 #endif
816 }
817
818 php_swoole_set_h2c(ZEND_THIS, h2c);
819
820 zend_update_property_stringl(
821 swoole_http2_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("host"), host, host_len);
822 zend_update_property_long(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("port"), port);
823 zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("ssl"), ssl);
824 }
825
PHP_METHOD(swoole_http2_client_coro,set)826 static PHP_METHOD(swoole_http2_client_coro, set) {
827 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
828 zval *zset;
829
830 ZEND_PARSE_PARAMETERS_START(1, 1)
831 Z_PARAM_ARRAY(zset)
832 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
833
834 zval *zsetting =
835 sw_zend_read_and_convert_property_array(swoole_http2_client_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0);
836 php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset));
837
838 h2c->apply_setting(zset);
839
840 RETURN_TRUE;
841 }
842
send_window_update(int stream_id,uint32_t size)843 bool Client::send_window_update(int stream_id, uint32_t size) {
844 char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE];
845 swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_YELLOW "] stream_id=%d, size=%d", "WINDOW_UPDATE", stream_id, size);
846 *(uint32_t *) ((char *) frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(size);
847 Http2::set_frame_header(frame, SW_HTTP2_TYPE_WINDOW_UPDATE, SW_HTTP2_WINDOW_UPDATE_SIZE, 0, stream_id);
848 return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE);
849 }
850
send_setting()851 bool Client::send_setting() {
852 Http2::Settings *settings = &local_settings;
853 uint16_t id = 0;
854 uint32_t value = 0;
855
856 char frame[SW_HTTP2_FRAME_HEADER_SIZE + 18];
857 memset(frame, 0, sizeof(frame));
858 Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 18, 0, 0);
859
860 char *p = frame + SW_HTTP2_FRAME_HEADER_SIZE;
861 /**
862 * HEADER_TABLE_SIZE
863 */
864 id = htons(SW_HTTP2_SETTING_HEADER_TABLE_SIZE);
865 memcpy(p, &id, sizeof(id));
866 p += 2;
867 value = htonl(settings->header_table_size);
868 memcpy(p, &value, sizeof(value));
869 p += 4;
870 /**
871 * MAX_CONCURRENT_STREAMS
872 */
873 id = htons(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
874 memcpy(p, &id, sizeof(id));
875 p += 2;
876 value = htonl(settings->max_concurrent_streams);
877 memcpy(p, &value, sizeof(value));
878 p += 4;
879 /**
880 * INIT_WINDOW_SIZE
881 */
882 id = htons(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE);
883 memcpy(p, &id, sizeof(id));
884 p += 2;
885 value = htonl(settings->window_size);
886 memcpy(p, &value, sizeof(value));
887 p += 4;
888
889 swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN "]\t[length=%d]", Http2::get_type(SW_HTTP2_TYPE_SETTINGS), 18);
890 return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + 18);
891 }
892
893 void http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers);
894
parse_header(Stream * stream,int flags,char * in,size_t inlen)895 int Client::parse_header(Stream *stream, int flags, char *in, size_t inlen) {
896 zval *zresponse = &stream->zresponse;
897
898 if (flags & SW_HTTP2_FLAG_PRIORITY) {
899 // int stream_deps = ntohl(*(int *) (in));
900 // uint8_t weight = in[4];
901 in += 5;
902 inlen -= 5;
903 }
904
905 zval *zheaders =
906 sw_zend_read_and_convert_property_array(swoole_http2_response_ce, zresponse, ZEND_STRL("headers"), 0);
907 zval *zcookies =
908 sw_zend_read_and_convert_property_array(swoole_http2_response_ce, zresponse, ZEND_STRL("cookies"), 0);
909 zval *zset_cookie_headers = sw_zend_read_and_convert_property_array(
910 swoole_http2_response_ce, zresponse, ZEND_STRL("set_cookie_headers"), 0);
911
912 int inflate_flags = 0;
913 ssize_t rv;
914
915 do {
916 nghttp2_nv nv;
917
918 rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1);
919 if (rv < 0) {
920 nghttp2_error(rv, "nghttp2_hd_inflate_hd failed");
921 return SW_ERR;
922 }
923
924 in += (size_t) rv;
925 inlen -= (size_t) rv;
926
927 swoole_trace_log(SW_TRACE_HTTP2,
928 "[" SW_ECHO_GREEN "] %.*s[%lu]: %.*s[%lu]",
929 "HEADER",
930 (int) nv.namelen,
931 nv.name,
932 nv.namelen,
933 (int) nv.valuelen,
934 nv.value,
935 nv.valuelen);
936
937 if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
938 if (nv.name[0] == ':') {
939 if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "status")) {
940 zend_update_property_long(swoole_http2_response_ce,
941 SW_Z8_OBJ_P(zresponse),
942 ZEND_STRL("statusCode"),
943 atoi((char *) nv.value));
944 }
945 } else {
946 #ifdef SW_HAVE_ZLIB
947 if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-encoding") &&
948 SW_STRCASECT((char *) nv.value, nv.valuelen, "gzip")) {
949 /**
950 * init zlib stream
951 */
952 stream->gzip = 1;
953 memset(&stream->gzip_stream, 0, sizeof(stream->gzip_stream));
954 stream->gzip_buffer = make_string(8192);
955 stream->gzip_stream.zalloc = php_zlib_alloc;
956 stream->gzip_stream.zfree = php_zlib_free;
957 /**
958 * zlib decode
959 */
960 if (Z_OK != inflateInit2(&stream->gzip_stream, MAX_WBITS + 16)) {
961 swoole_warning("inflateInit2() failed");
962 return SW_ERR;
963 }
964 } else
965 #endif
966 if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "set-cookie")) {
967 http_parse_set_cookies((char *) nv.value, nv.valuelen, zcookies, zset_cookie_headers);
968 }
969 add_assoc_stringl_ex(zheaders, (char *) nv.name, nv.namelen, (char *) nv.value, nv.valuelen);
970 }
971 }
972 } while ([=] {
973 if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
974 nghttp2_hd_inflate_end_headers(inflater);
975 return false;
976 }
977 return inlen != 0;
978 }());
979
980 return SW_OK;
981 }
982
build_header(zval * zobject,zval * zrequest,char * buffer)983 ssize_t Client::build_header(zval *zobject, zval *zrequest, char *buffer) {
984 Client *h2c = php_swoole_get_h2c(zobject);
985 zval *zmethod = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_METHOD), 0);
986 zval *zpath = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_PATH), 0);
987 zval *zheaders = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADERS), 0);
988 zval *zcookies = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIES), 0);
989 HeaderSet headers(8 + php_swoole_array_length_safe(zheaders) + php_swoole_array_length_safe(zcookies));
990 bool find_host = 0;
991
992 if (Z_TYPE_P(zmethod) != IS_STRING || Z_STRLEN_P(zmethod) == 0) {
993 headers.add(ZEND_STRL(":method"), ZEND_STRL("GET"));
994 } else {
995 headers.add(ZEND_STRL(":method"), Z_STRVAL_P(zmethod), Z_STRLEN_P(zmethod));
996 }
997 if (Z_TYPE_P(zpath) != IS_STRING || Z_STRLEN_P(zpath) == 0) {
998 headers.add(ZEND_STRL(":path"), "/", 1);
999 } else {
1000 headers.add(ZEND_STRL(":path"), Z_STRVAL_P(zpath), Z_STRLEN_P(zpath));
1001 }
1002 if (h2c->open_ssl) {
1003 headers.add(ZEND_STRL(":scheme"), ZEND_STRL("https"));
1004 } else {
1005 headers.add(ZEND_STRL(":scheme"), ZEND_STRL("http"));
1006 }
1007 // Host
1008 headers.reserve_one();
1009
1010 if (ZVAL_IS_ARRAY(zheaders)) {
1011 zend_string *key;
1012 zval *zvalue;
1013
1014 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zheaders), key, zvalue) {
1015 if (UNEXPECTED(!key || *ZSTR_VAL(key) == ':' || ZVAL_IS_NULL(zvalue))) {
1016 continue;
1017 }
1018 zend::String str_value(zvalue);
1019 if (SW_STRCASEEQ(ZSTR_VAL(key), ZSTR_LEN(key), "host")) {
1020 headers.add(HTTP2_CLIENT_HOST_HEADER_INDEX, ZEND_STRL(":authority"), str_value.val(), str_value.len());
1021 find_host = true;
1022 } else {
1023 headers.add(ZSTR_VAL(key), ZSTR_LEN(key), str_value.val(), str_value.len());
1024 }
1025 }
1026 ZEND_HASH_FOREACH_END();
1027 }
1028 if (!find_host) {
1029 const std::string *host;
1030 std::string _host;
1031 #ifndef SW_USE_OPENSSL
1032 if (h2c->port != 80)
1033 #else
1034 if (!h2c->open_ssl ? h2c->port != 80 : h2c->port != 443)
1035 #endif
1036 {
1037 _host = std_string::format("%s:%d", h2c->host.c_str(), h2c->port);
1038 host = &_host;
1039 } else {
1040 host = &h2c->host;
1041 }
1042 headers.add(HTTP2_CLIENT_HOST_HEADER_INDEX, ZEND_STRL(":authority"), host->c_str(), host->length());
1043 }
1044 // http cookies
1045 if (ZVAL_IS_ARRAY(zcookies)) {
1046 zend_string *key;
1047 zval *zvalue;
1048 char *encoded_value;
1049 int encoded_value_len;
1050 String *buffer = sw_tg_buffer();
1051
1052 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zcookies), key, zvalue) {
1053 if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) {
1054 continue;
1055 }
1056 zend::String str_value(zvalue);
1057 buffer->clear();
1058 buffer->append(ZSTR_VAL(key), ZSTR_LEN(key));
1059 buffer->append("=", 1);
1060 encoded_value = php_swoole_url_encode(str_value.val(), str_value.len(), &encoded_value_len);
1061 if (encoded_value) {
1062 buffer->append(encoded_value, encoded_value_len);
1063 efree(encoded_value);
1064 headers.add(ZEND_STRL("cookie"), buffer->str, buffer->length);
1065 }
1066 }
1067 ZEND_HASH_FOREACH_END();
1068 }
1069
1070 size_t buflen = nghttp2_hd_deflate_bound(h2c->deflater, headers.get(), headers.len());
1071 // if (buflen > h2c->remote_settings.max_header_list_size) {
1072 // php_swoole_error(E_WARNING, "header cannot bigger than remote max_header_list_size %u",
1073 // h2c->remote_settings.max_header_list_size);
1074 // return -1;
1075 // }
1076 ssize_t rv = nghttp2_hd_deflate_hd(h2c->deflater, (uchar *) buffer, buflen, headers.get(), headers.len());
1077 if (rv < 0) {
1078 h2c->nghttp2_error(rv, "nghttp2_hd_deflate_hd() failed");
1079 return -1;
1080 }
1081 return rv;
1082 }
1083
destroy_stream(Stream * stream)1084 void Client::destroy_stream(Stream *stream) {
1085 if (stream->buffer) {
1086 delete (stream->buffer);
1087 }
1088 #ifdef SW_HAVE_ZLIB
1089 if (stream->gzip) {
1090 inflateEnd(&stream->gzip_stream);
1091 delete (stream->gzip_buffer);
1092 }
1093 #endif
1094 zval_ptr_dtor(&stream->zresponse);
1095 efree(stream);
1096 }
1097
create_stream(uint32_t stream_id,uint8_t flags)1098 Stream *Client::create_stream(uint32_t stream_id, uint8_t flags) {
1099 // malloc
1100 Stream *stream = (Stream *) ecalloc(1, sizeof(Stream));
1101 // init
1102 stream->stream_id = stream_id;
1103 stream->flags = flags;
1104 stream->remote_window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE;
1105 stream->local_window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE;
1106 streams.emplace(stream_id, stream);
1107 // create response object
1108 object_init_ex(&stream->zresponse, swoole_http2_response_ce);
1109 zend_update_property_long(
1110 swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("streamId"), stream_id);
1111
1112 return stream;
1113 }
1114
send_ping_frame()1115 bool Client::send_ping_frame() {
1116 char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE];
1117 Http2::set_frame_header(frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_NONE, 0);
1118 return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE);
1119 }
1120
send_data(uint32_t stream_id,const char * p,size_t len,int flag)1121 bool Client::send_data(uint32_t stream_id, const char *p, size_t len, int flag) {
1122 uint8_t send_flag;
1123 uint32_t send_len;
1124 char header[SW_HTTP2_FRAME_HEADER_SIZE];
1125 while (len > 0) {
1126 if (len > local_settings.max_frame_size) {
1127 send_len = local_settings.max_frame_size;
1128 send_flag = 0;
1129 } else {
1130 send_len = len;
1131 send_flag = flag;
1132 }
1133 Http2::set_frame_header(header, SW_HTTP2_TYPE_DATA, send_len, send_flag, stream_id);
1134 if (!send(header, SW_HTTP2_FRAME_HEADER_SIZE)) {
1135 return false;
1136 }
1137 if (!send(p, send_len)) {
1138 return false;
1139 }
1140 len -= send_len;
1141 p += send_len;
1142 }
1143 return true;
1144 }
1145
send_request(zval * zrequest)1146 uint32_t Client::send_request(zval *zrequest) {
1147 zval *zheaders =
1148 sw_zend_read_and_convert_property_array(swoole_http2_request_ce, zrequest, ZEND_STRL("headers"), 0);
1149 zval *zdata = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_DATA), 0);
1150 zval *zpipeline =
1151 sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_PIPELINE), 0);
1152 zval ztmp, *zuse_pipeline_read = zend_read_property_ex(
1153 Z_OBJCE_P(zrequest), SW_Z8_OBJ_P(zrequest), SW_ZSTR_KNOWN(SW_ZEND_STR_USE_PIPELINE_READ), 1, &ztmp);
1154 bool is_data_empty = Z_TYPE_P(zdata) == IS_STRING ? Z_STRLEN_P(zdata) == 0 : !zval_is_true(zdata);
1155
1156 if (ZVAL_IS_ARRAY(zdata)) {
1157 add_assoc_stringl_ex(
1158 zheaders, ZEND_STRL("content-type"), (char *) ZEND_STRL("application/x-www-form-urlencoded"));
1159 }
1160
1161 /**
1162 * send headers
1163 */
1164 char *buffer = sw_tg_buffer()->str;
1165 ssize_t bytes = build_header(zobject, zrequest, buffer + SW_HTTP2_FRAME_HEADER_SIZE);
1166
1167 if (bytes <= 0) {
1168 return 0;
1169 }
1170
1171 uint8_t flags = 0;
1172 if (zval_is_true(zpipeline)) {
1173 flags |= SW_HTTP2_STREAM_PIPELINE_REQUEST;
1174 }
1175 if (zval_is_true(zuse_pipeline_read)) {
1176 flags |= SW_HTTP2_STREAM_USE_PIPELINE_READ;
1177 }
1178
1179 auto stream = create_stream(stream_id, flags);
1180
1181 flags = SW_HTTP2_FLAG_END_HEADERS;
1182
1183 if (is_data_empty && !(stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST)) {
1184 flags |= SW_HTTP2_FLAG_END_STREAM;
1185 }
1186
1187 Http2::set_frame_header(buffer, SW_HTTP2_TYPE_HEADERS, bytes, flags, stream->stream_id);
1188
1189 swoole_trace_log(SW_TRACE_HTTP2,
1190 "[" SW_ECHO_GREEN ", STREAM#%d] length=%zd",
1191 Http2::get_type(SW_HTTP2_TYPE_HEADERS),
1192 stream->stream_id,
1193 bytes);
1194 if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE + bytes)) {
1195 return 0;
1196 }
1197
1198 /**
1199 * send body
1200 */
1201 if (!is_data_empty) {
1202 char *p;
1203 size_t len;
1204 smart_str formstr_s = {};
1205 zend::String str_zpost_data;
1206
1207 int flag = (stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST) ? 0 : SW_HTTP2_FLAG_END_STREAM;
1208 if (ZVAL_IS_ARRAY(zdata)) {
1209 p = php_swoole_http_build_query(zdata, &len, &formstr_s);
1210 if (p == nullptr) {
1211 php_swoole_error(E_WARNING, "http_build_query failed");
1212 return 0;
1213 }
1214 } else {
1215 str_zpost_data = zdata;
1216 p = str_zpost_data.val();
1217 len = str_zpost_data.len();
1218 }
1219
1220 swoole_trace_log(SW_TRACE_HTTP2,
1221 "[" SW_ECHO_GREEN ", END, STREAM#%d] length=%zu",
1222 Http2::get_type(SW_HTTP2_TYPE_DATA),
1223 stream->stream_id,
1224 len);
1225
1226 if (!send_data(stream->stream_id, p, len, flag)) {
1227 return 0;
1228 }
1229
1230 if (formstr_s.s) {
1231 smart_str_free(&formstr_s);
1232 }
1233 }
1234
1235 stream_id += 2;
1236
1237 return stream->stream_id;
1238 }
1239
write_data(uint32_t stream_id,zval * zdata,bool end)1240 bool Client::write_data(uint32_t stream_id, zval *zdata, bool end) {
1241 char buffer[SW_HTTP2_FRAME_HEADER_SIZE];
1242 Stream *stream = get_stream(stream_id);
1243 int flag = end ? SW_HTTP2_FLAG_END_STREAM : 0;
1244
1245 if (stream == nullptr || !(stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST) ||
1246 (stream->flags & SW_HTTP2_STREAM_REQUEST_END)) {
1247 update_error_properties(EINVAL,
1248 std_string::format("unable to found active pipeline stream#%u", stream_id).c_str());
1249 return false;
1250 }
1251
1252 if (ZVAL_IS_ARRAY(zdata)) {
1253 size_t len;
1254 smart_str formstr_s = {};
1255 char *formstr = php_swoole_http_build_query(zdata, &len, &formstr_s);
1256 if (formstr == nullptr) {
1257 php_swoole_error(E_WARNING, "http_build_query failed");
1258 return false;
1259 }
1260 Http2::set_frame_header(buffer, SW_HTTP2_TYPE_DATA, len, flag, stream_id);
1261 swoole_trace_log(SW_TRACE_HTTP2,
1262 "[" SW_ECHO_GREEN ",%s STREAM#%d] length=%zu",
1263 Http2::get_type(SW_HTTP2_TYPE_DATA),
1264 end ? " END," : "",
1265 stream_id,
1266 len);
1267 if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE) || !send(formstr, len)) {
1268 smart_str_free(&formstr_s);
1269 return false;
1270 }
1271 smart_str_free(&formstr_s);
1272 } else {
1273 zend::String data(zdata);
1274 Http2::set_frame_header(buffer, SW_HTTP2_TYPE_DATA, data.len(), flag, stream_id);
1275 swoole_trace_log(SW_TRACE_HTTP2,
1276 "[" SW_ECHO_GREEN ",%s STREAM#%d] length=%zu",
1277 Http2::get_type(SW_HTTP2_TYPE_DATA),
1278 end ? " END," : "",
1279 stream_id,
1280 data.len());
1281 if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE) || !send(data.val(), data.len())) {
1282 return false;
1283 }
1284 }
1285
1286 if (end) {
1287 stream->flags |= SW_HTTP2_STREAM_REQUEST_END;
1288 }
1289
1290 return true;
1291 }
1292
send_goaway_frame(zend_long error_code,const char * debug_data,size_t debug_data_len)1293 bool Client::send_goaway_frame(zend_long error_code, const char *debug_data, size_t debug_data_len) {
1294 size_t length = SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE + debug_data_len;
1295 char *frame = (char *) ecalloc(1, length);
1296 bool ret;
1297 Http2::set_frame_header(frame, SW_HTTP2_TYPE_GOAWAY, SW_HTTP2_GOAWAY_SIZE + debug_data_len, error_code, 0);
1298 *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(last_stream_id);
1299 *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE + 4) = htonl(error_code);
1300 if (debug_data_len > 0) {
1301 memcpy(frame + SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE, debug_data, debug_data_len);
1302 }
1303 swoole_trace_log(SW_TRACE_HTTP2,
1304 "[" SW_ECHO_GREEN "] Send: last-sid=%u, error-code=%ld",
1305 Http2::get_type(SW_HTTP2_TYPE_GOAWAY),
1306 last_stream_id,
1307 error_code);
1308 ret = send(frame, length);
1309 efree(frame);
1310 return ret;
1311 }
1312
PHP_METHOD(swoole_http2_client_coro,send)1313 static PHP_METHOD(swoole_http2_client_coro, send) {
1314 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1315
1316 if (!h2c->is_available()) {
1317 RETURN_FALSE;
1318 }
1319
1320 zval *zrequest;
1321
1322 ZEND_PARSE_PARAMETERS_START(1, 1)
1323 Z_PARAM_OBJECT_OF_CLASS(zrequest, swoole_http2_request_ce)
1324 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1325
1326 uint32_t stream_id = h2c->send_request(zrequest);
1327 if (stream_id == 0) {
1328 RETURN_FALSE;
1329 } else {
1330 RETURN_LONG(stream_id);
1331 }
1332 }
1333
php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAMETERS,bool pipeline_read)1334 static void php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAMETERS, bool pipeline_read) {
1335 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1336
1337 double timeout = 0;
1338
1339 ZEND_PARSE_PARAMETERS_START(0, 1)
1340 Z_PARAM_OPTIONAL
1341 Z_PARAM_DOUBLE(timeout)
1342 ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
1343
1344 while (true) {
1345 if (!h2c->is_available()) {
1346 RETURN_FALSE;
1347 }
1348 if (!h2c->recv_packet(timeout)) {
1349 RETURN_FALSE;
1350 }
1351 ReturnCode ret = h2c->parse_frame(return_value, pipeline_read);
1352 if (ret == SW_CONTINUE) {
1353 continue;
1354 } else if (ret == SW_READY) {
1355 break;
1356 } else {
1357 RETURN_FALSE;
1358 }
1359 }
1360 }
1361
PHP_METHOD(swoole_http2_client_coro,recv)1362 static PHP_METHOD(swoole_http2_client_coro, recv) {
1363 php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
1364 }
1365
PHP_METHOD(swoole_http2_client_coro,__destruct)1366 static PHP_METHOD(swoole_http2_client_coro, __destruct) {}
1367
PHP_METHOD(swoole_http2_client_coro,close)1368 static PHP_METHOD(swoole_http2_client_coro, close) {
1369 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1370 RETURN_BOOL(h2c->close());
1371 }
1372
PHP_METHOD(swoole_http2_client_coro,connect)1373 static PHP_METHOD(swoole_http2_client_coro, connect) {
1374 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1375 RETURN_BOOL(h2c->connect());
1376 }
1377
http2_settings_to_array(Http2::Settings * settings,zval * zarray)1378 static sw_inline void http2_settings_to_array(Http2::Settings *settings, zval *zarray) {
1379 array_init(zarray);
1380 add_assoc_long_ex(zarray, ZEND_STRL("header_table_size"), settings->header_table_size);
1381 add_assoc_long_ex(zarray, ZEND_STRL("window_size"), settings->window_size);
1382 add_assoc_long_ex(zarray, ZEND_STRL("max_concurrent_streams"), settings->max_concurrent_streams);
1383 add_assoc_long_ex(zarray, ZEND_STRL("max_frame_size"), settings->max_frame_size);
1384 add_assoc_long_ex(zarray, ZEND_STRL("max_header_list_size"), settings->max_header_list_size);
1385 }
1386
PHP_METHOD(swoole_http2_client_coro,stats)1387 static PHP_METHOD(swoole_http2_client_coro, stats) {
1388 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1389 zval _zarray, *zarray = &_zarray;
1390 String key = {};
1391 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &key.str, &key.length) == FAILURE) {
1392 RETURN_FALSE;
1393 }
1394 if (key.length > 0) {
1395 if (SW_STREQ(key.str, key.length, "current_stream_id")) {
1396 RETURN_LONG(h2c->stream_id);
1397 } else if (SW_STREQ(key.str, key.length, "last_stream_id")) {
1398 RETURN_LONG(h2c->last_stream_id);
1399 } else if (SW_STREQ(key.str, key.length, "local_settings")) {
1400 http2_settings_to_array(&h2c->local_settings, zarray);
1401 RETURN_ZVAL(zarray, 0, 0);
1402 } else if (SW_STREQ(key.str, key.length, "remote_settings")) {
1403 http2_settings_to_array(&h2c->remote_settings, zarray);
1404 RETURN_ZVAL(zarray, 0, 0);
1405 } else if (SW_STREQ(key.str, key.length, "active_stream_num")) {
1406 RETURN_LONG(h2c->streams.size());
1407 }
1408 } else {
1409 array_init(return_value);
1410 add_assoc_long_ex(return_value, ZEND_STRL("current_stream_id"), h2c->stream_id);
1411 add_assoc_long_ex(return_value, ZEND_STRL("last_stream_id"), h2c->last_stream_id);
1412 http2_settings_to_array(&h2c->local_settings, zarray);
1413 add_assoc_zval_ex(return_value, ZEND_STRL("local_settings"), zarray);
1414 http2_settings_to_array(&h2c->remote_settings, zarray);
1415 add_assoc_zval_ex(return_value, ZEND_STRL("remote_settings"), zarray);
1416 add_assoc_long_ex(return_value, ZEND_STRL("active_stream_num"), h2c->streams.size());
1417 }
1418 }
1419
PHP_METHOD(swoole_http2_client_coro,isStreamExist)1420 static PHP_METHOD(swoole_http2_client_coro, isStreamExist) {
1421 zend_long stream_id = 0;
1422 if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &stream_id) == FAILURE) {
1423 RETURN_FALSE;
1424 }
1425 if (stream_id < 0) {
1426 RETURN_FALSE;
1427 }
1428
1429 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1430 if (!h2c->client) {
1431 RETURN_FALSE;
1432 } else if (stream_id == 0) {
1433 RETURN_TRUE;
1434 }
1435 Stream *stream = h2c->get_stream(stream_id);
1436 RETURN_BOOL(stream ? 1 : 0);
1437 }
1438
PHP_METHOD(swoole_http2_client_coro,write)1439 static PHP_METHOD(swoole_http2_client_coro, write) {
1440 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1441
1442 if (!h2c->is_available()) {
1443 RETURN_FALSE;
1444 }
1445
1446 zend_long stream_id;
1447 zval *data;
1448 zend_bool end = 0;
1449 if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz|b", &stream_id, &data, &end) == FAILURE) {
1450 RETURN_FALSE;
1451 }
1452 RETURN_BOOL(h2c->write_data(stream_id, data, end));
1453 }
1454
PHP_METHOD(swoole_http2_client_coro,read)1455 static PHP_METHOD(swoole_http2_client_coro, read) {
1456 php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
1457 }
1458
PHP_METHOD(swoole_http2_client_coro,ping)1459 static PHP_METHOD(swoole_http2_client_coro, ping) {
1460 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1461
1462 if (!h2c->is_available()) {
1463 RETURN_FALSE;
1464 }
1465
1466 RETURN_BOOL(h2c->send_ping_frame());
1467 }
1468
1469 /**
1470 * +-+-------------------------------------------------------------+
1471 * |R| Last-Stream-ID (31) |
1472 * +-+-------------------------------------------------------------+
1473 * | Error Code (32) |
1474 * +---------------------------------------------------------------+
1475 * | Additional Debug Data (*) |
1476 * +---------------------------------------------------------------+
1477 */
PHP_METHOD(swoole_http2_client_coro,goaway)1478 static PHP_METHOD(swoole_http2_client_coro, goaway) {
1479 Client *h2c = php_swoole_get_h2c(ZEND_THIS);
1480 zend_long error_code = SW_HTTP2_ERROR_NO_ERROR;
1481 char *debug_data = nullptr;
1482 size_t debug_data_len = 0;
1483
1484 if (!h2c->is_available()) {
1485 RETURN_FALSE;
1486 }
1487
1488 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ls", &error_code, &debug_data, &debug_data_len) == FAILURE) {
1489 RETURN_FALSE;
1490 }
1491
1492 RETURN_BOOL(h2c->send_goaway_frame(error_code, debug_data, debug_data_len));
1493 }
1494
1495 #endif
1496