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