1 Star 1 Fork 345

AnnOS / swoole-src

forked from swoole / swoole-src 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
swoole_http2_client_coro.cc 55.16 KB
一键复制 编辑 原始数据 按行查看 历史
twosee 提交于 2020-05-19 11:16 . Zero copy (#3327)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623
/*
+----------------------------------------------------------------------+
| Swoole |
+----------------------------------------------------------------------+
| This source file is subject to version 2.0 of the Apache license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.apache.org/licenses/LICENSE-2.0.html |
| If you did not receive a copy of the Apache2.0 license and are unable|
| to obtain it through the world-wide-web, please send a note to |
| license@swoole.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Tianfeng Han <mikan.tenny@gmail.com> |
+----------------------------------------------------------------------+
*/
#include "php_swoole_cxx.h"
#include "swoole_http.h"
#ifdef SW_USE_HTTP2
#include "http.h"
#include "http2.h"
#define HTTP2_CLIENT_HOST_HEADER_INDEX 3
#include <vector>
using namespace swoole;
using swoole::coroutine::Socket;
static zend_class_entry *swoole_http2_client_coro_ce;
static zend_object_handlers swoole_http2_client_coro_handlers;
static zend_class_entry *swoole_http2_client_coro_exception_ce;
static zend_object_handlers swoole_http2_client_coro_exception_handlers;
static zend_class_entry *swoole_http2_request_ce;
static zend_object_handlers swoole_http2_request_handlers;
static zend_class_entry *swoole_http2_response_ce;
static zend_object_handlers swoole_http2_response_handlers;
struct http2_client_stream
{
uint32_t stream_id;
uint8_t gzip;
uint8_t flags;
swString *buffer;
#ifdef SW_HAVE_ZLIB
z_stream gzip_stream;
swString *gzip_buffer;
#endif
zval zresponse;
// flow control
uint32_t remote_window_size;
uint32_t local_window_size;
};
class http2_client
{
public:
std::string host;
int port;
bool ssl;
double timeout = Socket::default_read_timeout;
Socket *client = nullptr;
nghttp2_hd_inflater *inflater = nullptr;
nghttp2_hd_deflater *deflater = nullptr;
uint32_t stream_id = 0; // the next send stream id
uint32_t last_stream_id = 0; // the last received stream id
swHttp2_settings local_settings = {};
swHttp2_settings remote_settings = {};
std::unordered_map<uint32_t, http2_client_stream*> streams;
/* safety zval */
zval _zobject;
zval *zobject;
http2_client(const char *_host, size_t _host_len, int _port, bool _ssl, zval *__zobject)
{
host = std::string(_host, _host_len);
port = _port;
ssl = _ssl;
_zobject = *__zobject;
zobject = &_zobject;
swHttp2_init_settings(&local_settings);
}
inline http2_client_stream* get_stream(uint32_t stream_id)
{
auto i = streams.find(stream_id);
if (i == streams.end())
{
return nullptr;
}
else
{
return i->second;
}
}
inline void update_error_properties(int code, const char *msg)
{
zend_update_property_long(swoole_http2_client_coro_ce, zobject, ZEND_STRL("errCode"), code);
zend_update_property_string(swoole_http2_client_coro_ce, zobject, ZEND_STRL("errMsg"), msg);
}
inline void io_error()
{
update_error_properties(client->errCode, client->errMsg);
}
inline void nghttp2_error(int code, const char *msg)
{
update_error_properties(code, cpp_string::format("%s with error: %s", msg, nghttp2_strerror(code)).c_str());
}
inline bool is_available()
{
if (sw_unlikely(!client))
{
swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION);
zend_update_property_long(swoole_http2_client_coro_ce, zobject, ZEND_STRL("errCode"), ECONNRESET);
zend_update_property_string(swoole_http2_client_coro_ce, zobject, ZEND_STRL("errMsg"), "client is not connected to server");
return false;
}
return true;
}
inline void apply_setting(zval *zset)
{
if (client && ZVAL_IS_ARRAY(zset))
{
php_swoole_client_set(client, zset);
}
}
inline bool recv_packet(double timeout)
{
if (sw_unlikely(client->recv_packet(timeout) <= 0))
{
io_error();
return false;
}
return true;
}
bool connect();
http2_client_stream* create_stream(uint32_t stream_id, bool pipeline);
void destroy_stream(http2_client_stream *stream);
inline bool delete_stream(uint32_t stream_id)
{
auto i = streams.find(stream_id);
if (i == streams.end())
{
return false;
}
destroy_stream(i->second);
streams.erase(i);
return true;
}
bool send_window_update(int stream_id, uint32_t size);
bool send_ping_frame();
bool send_data(uint32_t stream_id, const char *p, size_t len, int flag);
uint32_t send_request(zval *req);
bool write_data(uint32_t stream_id, zval *zdata, bool end);
bool send_goaway_frame(zend_long error_code, const char *debug_data, size_t debug_data_len);
enum swReturn_code parse_frame(zval *return_value, bool pipeline_read = false);
bool close();
~http2_client()
{
close();
}
private:
bool send_setting();
int parse_header(http2_client_stream *stream , int flags, char *in, size_t inlen);
inline bool send(const char *buf, size_t len)
{
if (sw_unlikely(client->send_all(buf, len) != (ssize_t )len))
{
io_error();
return false;
}
return true;
}
};
typedef struct
{
http2_client *h2c;
zend_object std;
} http2_client_coro_t;
static sw_inline http2_client_coro_t* php_swoole_http2_client_coro_fetch_object(zend_object *obj)
{
return (http2_client_coro_t *) ((char *) obj - swoole_http2_client_coro_handlers.offset);
}
static sw_inline http2_client* php_swoole_get_h2c(zval *zobject)
{
return php_swoole_http2_client_coro_fetch_object(Z_OBJ_P(zobject))->h2c;
}
static sw_inline void php_swoole_set_h2c(zval *zobject, http2_client *h2c)
{
php_swoole_http2_client_coro_fetch_object(Z_OBJ_P(zobject))->h2c = h2c;
}
static void php_swoole_http2_client_coro_free_object(zend_object *object)
{
http2_client_coro_t *request = php_swoole_http2_client_coro_fetch_object(object);
http2_client *h2c = request->h2c;
if (h2c)
{
delete h2c;
}
zend_object_std_dtor(&request->std);
}
static zend_object *php_swoole_http2_client_coro_create_object(zend_class_entry *ce)
{
http2_client_coro_t *request = (http2_client_coro_t *) zend_object_alloc(sizeof(http2_client_coro_t), ce);
zend_object_std_init(&request->std, ce);
object_properties_init(&request->std, ce);
request->std.handlers = &swoole_http2_client_coro_handlers;
return &request->std;
}
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_void, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_construct, 0, 0, 1)
ZEND_ARG_INFO(0, host)
ZEND_ARG_INFO(0, port)
ZEND_ARG_INFO(0, ssl)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_set, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, settings, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_stats, 0, 0, 0)
ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_isStreamExist, 0, 0, 1)
ZEND_ARG_INFO(0, stream_id)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_send, 0, 0, 1)
ZEND_ARG_INFO(0, request)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_write, 0, 0, 2)
ZEND_ARG_INFO(0, stream_id)
ZEND_ARG_INFO(0, data)
ZEND_ARG_INFO(0, end_stream)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_recv, 0, 0, 0)
ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http2_client_coro_goaway, 0, 0, 0)
ZEND_ARG_INFO(0, error_code)
ZEND_ARG_INFO(0, debug_data)
ZEND_END_ARG_INFO()
static PHP_METHOD(swoole_http2_client_coro, __construct);
static PHP_METHOD(swoole_http2_client_coro, __destruct);
static PHP_METHOD(swoole_http2_client_coro, set);
static PHP_METHOD(swoole_http2_client_coro, connect);
static PHP_METHOD(swoole_http2_client_coro, stats);
static PHP_METHOD(swoole_http2_client_coro, isStreamExist);
static PHP_METHOD(swoole_http2_client_coro, send);
static PHP_METHOD(swoole_http2_client_coro, write);
static PHP_METHOD(swoole_http2_client_coro, recv);
static PHP_METHOD(swoole_http2_client_coro, read);
static PHP_METHOD(swoole_http2_client_coro, ping);
static PHP_METHOD(swoole_http2_client_coro, goaway);
static PHP_METHOD(swoole_http2_client_coro, close);
static const zend_function_entry swoole_http2_client_methods[] =
{
PHP_ME(swoole_http2_client_coro, __construct, arginfo_swoole_http2_client_coro_construct, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, __destruct, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, set, arginfo_swoole_http2_client_coro_set, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, connect, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, stats, arginfo_swoole_http2_client_coro_stats, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, isStreamExist, arginfo_swoole_http2_client_coro_isStreamExist, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, send, arginfo_swoole_http2_client_coro_send, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, write, arginfo_swoole_http2_client_coro_write, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, recv, arginfo_swoole_http2_client_coro_recv, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, read, arginfo_swoole_http2_client_coro_recv, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, goaway, arginfo_swoole_http2_client_coro_goaway, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, ping, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http2_client_coro, close, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_FE_END
};
void php_swoole_http2_client_coro_minit(int module_number)
{
SW_INIT_CLASS_ENTRY(swoole_http2_client_coro, "Swoole\\Coroutine\\Http2\\Client", NULL, "Co\\Http2\\Client", swoole_http2_client_methods);
SW_SET_CLASS_SERIALIZABLE(swoole_http2_client_coro, zend_class_serialize_deny, zend_class_unserialize_deny);
SW_SET_CLASS_CLONEABLE(swoole_http2_client_coro, sw_zend_class_clone_deny);
SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_client_coro, sw_zend_class_unset_property_deny);
SW_SET_CLASS_CUSTOM_OBJECT(swoole_http2_client_coro, php_swoole_http2_client_coro_create_object, php_swoole_http2_client_coro_free_object, http2_client_coro_t, std);
SW_INIT_CLASS_ENTRY_EX(swoole_http2_client_coro_exception, "Swoole\\Coroutine\\Http2\\Client\\Exception", NULL, "Co\\Http2\\Client\\Exception", NULL, swoole_exception);
SW_INIT_CLASS_ENTRY(swoole_http2_request, "Swoole\\Http2\\Request", "swoole_http2_request", NULL, NULL);
SW_SET_CLASS_SERIALIZABLE(swoole_http2_request, zend_class_serialize_deny, zend_class_unserialize_deny);
SW_SET_CLASS_CLONEABLE(swoole_http2_request, sw_zend_class_clone_deny);
SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_request, sw_zend_class_unset_property_deny);
SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_http2_request);
SW_INIT_CLASS_ENTRY(swoole_http2_response, "Swoole\\Http2\\Response", "swoole_http2_response", NULL, NULL);
SW_SET_CLASS_SERIALIZABLE(swoole_http2_response, zend_class_serialize_deny, zend_class_unserialize_deny);
SW_SET_CLASS_CLONEABLE(swoole_http2_response, sw_zend_class_clone_deny);
SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_response, sw_zend_class_unset_property_deny);
SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_http2_response);
zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("errMsg"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC);
zend_declare_property_bool(swoole_http2_client_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("host"), ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_bool(swoole_http2_client_coro_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("path"), "/", ZEND_ACC_PUBLIC);
zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("method"), "GET", ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http2_request_ce, ZEND_STRL("headers"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http2_request_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC);
zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("data"), "", ZEND_ACC_PUBLIC);
zend_declare_property_bool(swoole_http2_request_ce, ZEND_STRL("pipeline"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("streamId"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("statusCode"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_bool(swoole_http2_response_ce, ZEND_STRL("pipeline"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("headers"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("set_cookie_headers"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http2_response_ce, ZEND_STRL("data"), ZEND_ACC_PUBLIC);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_DATA", SW_HTTP2_TYPE_DATA);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_HEADERS", SW_HTTP2_TYPE_HEADERS);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PRIORITY", SW_HTTP2_TYPE_PRIORITY);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_RST_STREAM", SW_HTTP2_TYPE_RST_STREAM);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_SETTINGS", SW_HTTP2_TYPE_SETTINGS);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PUSH_PROMISE", SW_HTTP2_TYPE_PUSH_PROMISE);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_PING", SW_HTTP2_TYPE_PING);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_GOAWAY", SW_HTTP2_TYPE_GOAWAY);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_WINDOW_UPDATE", SW_HTTP2_TYPE_WINDOW_UPDATE);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_TYPE_CONTINUATION", SW_HTTP2_TYPE_CONTINUATION);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_NO_ERROR", SW_HTTP2_ERROR_NO_ERROR);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_PROTOCOL_ERROR", SW_HTTP2_ERROR_PROTOCOL_ERROR);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_INTERNAL_ERROR", SW_HTTP2_ERROR_INTERNAL_ERROR);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_FLOW_CONTROL_ERROR", SW_HTTP2_ERROR_FLOW_CONTROL_ERROR);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_SETTINGS_TIMEOUT", SW_HTTP2_ERROR_SETTINGS_TIMEOUT);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_STREAM_CLOSED", SW_HTTP2_ERROR_STREAM_CLOSED);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_FRAME_SIZE_ERROR", SW_HTTP2_ERROR_FRAME_SIZE_ERROR);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_REFUSED_STREAM", SW_HTTP2_ERROR_REFUSED_STREAM);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_CANCEL", SW_HTTP2_ERROR_CANCEL);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_COMPRESSION_ERROR", SW_HTTP2_ERROR_COMPRESSION_ERROR);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_CONNECT_ERROR", SW_HTTP2_ERROR_CONNECT_ERROR);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_ENHANCE_YOUR_CALM", SW_HTTP2_ERROR_ENHANCE_YOUR_CALM);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_INADEQUATE_SECURITY", SW_HTTP2_ERROR_INADEQUATE_SECURITY);
}
bool http2_client::connect()
{
if (sw_unlikely(client != nullptr))
{
return false;
}
client = new Socket(SW_SOCK_TCP);
if (UNEXPECTED(client->get_fd() < 0))
{
php_swoole_sys_error(E_WARNING, "new Socket() failed");
zend_update_property_long(swoole_http2_client_coro_ce, zobject, ZEND_STRL("errCode"), errno);
zend_update_property_string(swoole_http2_client_coro_ce, zobject, ZEND_STRL("errMsg"), swoole_strerror(errno));
delete client;
client = nullptr;
return false;
}
client->set_zero_copy(true);
#ifdef SW_USE_OPENSSL
client->open_ssl = ssl;
#endif
client->http2 = 1;
client->open_length_check = 1;
client->protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE;
client->protocol.package_length_offset = 0;
client->protocol.package_body_offset = 0;
client->protocol.get_package_length = swHttp2_get_frame_length;
apply_setting(sw_zend_read_property(swoole_http2_client_coro_ce, zobject, ZEND_STRL("setting"), 0));
if (!client->connect(host, port))
{
io_error();
close();
return false;
}
stream_id = 1;
// [init]: we must set default value, server is not always send all the settings
swHttp2_init_settings(&remote_settings);
int ret = nghttp2_hd_inflate_new2(&inflater, php_nghttp2_mem());
if (ret != 0)
{
nghttp2_error(ret, "nghttp2_hd_inflate_new2() failed");
close();
return false;
}
ret = nghttp2_hd_deflate_new2(&deflater, local_settings.header_table_size, php_nghttp2_mem());
if (ret != 0)
{
nghttp2_error(ret, "nghttp2_hd_deflate_new2() failed");
close();
return false;
}
if (!send(ZEND_STRL(SW_HTTP2_PRI_STRING)))
{
close();
return false;
}
if (!send_setting())
{
close();
return false;
}
zend_update_property_bool(swoole_http2_client_coro_ce, zobject, ZEND_STRL("connected"), 1);
return true;
}
bool http2_client::close()
{
Socket *_client = client;
if (!_client)
{
return false;
}
zend_update_property_bool(swoole_http2_client_coro_ce, zobject, ZEND_STRL("connected"), 0);
if (!_client->has_bound())
{
auto i = streams.begin();
while (i != streams.end())
{
destroy_stream(i->second);
streams.erase(i++);
}
if (inflater)
{
nghttp2_hd_inflate_del(inflater);
inflater = NULL;
}
if (deflater)
{
nghttp2_hd_deflate_del(deflater);
deflater = NULL;
}
client = nullptr;
}
if (_client->close())
{
delete _client;
}
return true;
}
enum swReturn_code http2_client::parse_frame(zval *return_value, bool pipeline_read)
{
char *buf = client->get_read_buffer()->str;
uint8_t type = buf[3];
uint8_t flags = buf[4];
uint32_t stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff;
ssize_t length = swHttp2_get_length(buf);
buf += SW_HTTP2_FRAME_HEADER_SIZE;
char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE];
if (stream_id > last_stream_id)
{
last_stream_id = stream_id;
}
uint16_t id = 0;
uint32_t value = 0;
switch (type)
{
case SW_HTTP2_TYPE_SETTINGS:
{
if (flags & SW_HTTP2_FLAG_ACK)
{
swHttp2FrameTraceLog(recv, "ACK");
return SW_CONTINUE;
}
while (length > 0)
{
id = ntohs(*(uint16_t *) (buf));
value = ntohl(*(uint32_t *) (buf + sizeof(uint16_t)));
swHttp2FrameTraceLog(recv, "id=%d, value=%d", id, value);
switch (id)
{
case SW_HTTP2_SETTING_HEADER_TABLE_SIZE:
if (value != remote_settings.header_table_size)
{
remote_settings.header_table_size = value;
int ret = nghttp2_hd_deflate_change_table_size(deflater, value);
if (ret != 0)
{
nghttp2_error(ret, "nghttp2_hd_deflate_change_table_size() failed");
return SW_ERROR;
}
}
swTraceLog(SW_TRACE_HTTP2, "setting: header_compression_table_max=%u", value);
break;
case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
remote_settings.max_concurrent_streams = value;
swTraceLog(SW_TRACE_HTTP2, "setting: max_concurrent_streams=%u", value);
break;
case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE:
remote_settings.window_size = value;
swTraceLog(SW_TRACE_HTTP2, "setting: init_send_window=%u", value);
break;
case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE:
remote_settings.max_frame_size = value;
swTraceLog(SW_TRACE_HTTP2, "setting: max_frame_size=%u", value);
break;
case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
if (value != remote_settings.max_header_list_size)
{
remote_settings.max_header_list_size = value;
/*
int ret = nghttp2_hd_inflate_change_table_size(inflater, value);
if (ret != 0)
{
nghttp2_error(ret, "nghttp2_hd_inflate_change_table_size() failed");
return SW_ERROR;
}
*/
}
swTraceLog(SW_TRACE_HTTP2, "setting: max_header_list_size=%u", value);
break;
default:
// disable warning and ignore it because some websites are not following http2 protocol totally
// swWarn("unknown option[%d]: %d", id, value);
break;
}
buf += sizeof(id) + sizeof(value);
length -= sizeof(id) + sizeof(value);
}
swHttp2_set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 0, SW_HTTP2_FLAG_ACK, stream_id);
if (!send(frame, SW_HTTP2_FRAME_HEADER_SIZE))
{
return SW_ERROR;
}
return SW_CONTINUE;
}
case SW_HTTP2_TYPE_WINDOW_UPDATE:
{
value = ntohl(*(uint32_t *) buf);
swHttp2FrameTraceLog(recv, "window_size_increment=%d", value);
if (stream_id == 0)
{
remote_settings.window_size += value;
}
else
{
http2_client_stream *stream = get_stream(stream_id);
if (stream)
{
stream->remote_window_size += value;
}
}
return SW_CONTINUE;
}
case SW_HTTP2_TYPE_PING:
{
swHttp2FrameTraceLog(recv, "ping");
if (!(flags & SW_HTTP2_FLAG_ACK))
{
swHttp2_set_frame_header(frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_ACK, stream_id);
memcpy(frame + SW_HTTP2_FRAME_HEADER_SIZE, buf + SW_HTTP2_FRAME_HEADER_SIZE, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE);
if (!send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE))
{
return SW_ERROR;
}
}
return SW_CONTINUE;
}
case SW_HTTP2_TYPE_GOAWAY:
{
uint32_t server_last_stream_id = ntohl(*(uint32_t *) (buf));
buf += 4;
value = ntohl(*(uint32_t *) (buf));
buf += 4;
swHttp2FrameTraceLog(recv, "last_stream_id=%d, error_code=%d, opaque_data=[%.*s]", server_last_stream_id, value, (int) (length - SW_HTTP2_GOAWAY_SIZE), buf);
// update goaway error code and error msg
zend_update_property_long(swoole_http2_client_coro_ce, zobject, ZEND_STRL("errCode"), value);
zend_update_property_stringl(swoole_http2_client_coro_ce, zobject, ZEND_STRL("errMsg"), buf, length - SW_HTTP2_GOAWAY_SIZE);
zend_update_property_long(swoole_http2_client_coro_ce, zobject, ZEND_STRL("serverLastStreamId"), server_last_stream_id);
close();
return SW_CLOSE;
}
case SW_HTTP2_TYPE_RST_STREAM:
{
value = ntohl(*(uint32_t *) (buf));
swHttp2FrameTraceLog(recv, "error_code=%d", value);
// delete and free quietly
delete_stream(stream_id);
return SW_CONTINUE;
}
/**
* TODO not support push_promise
*/
case SW_HTTP2_TYPE_PUSH_PROMISE:
{
#ifdef SW_DEBUG
uint32_t promise_stream_id = ntohl(*(uint32_t *) (buf)) & 0x7fffffff;
swHttp2FrameTraceLog(recv, "promise_stream_id=%d", promise_stream_id);
#endif
// auto promise_stream = create_stream(promise_stream_id, false);
// RETVAL_ZVAL(promise_stream->response_object, 0, 0);
// return SW_READY;
return SW_CONTINUE;
}
default:
{
swHttp2FrameTraceLog(recv, "");
}
}
http2_client_stream *stream = get_stream(stream_id);
// The stream is not found or has closed
if (stream == NULL)
{
swNotice("http2 stream#%d belongs to an unknown type or it never registered", stream_id);
return SW_CONTINUE;
}
if (type == SW_HTTP2_TYPE_HEADERS)
{
parse_header(stream, flags, buf, length);
}
else if (type == SW_HTTP2_TYPE_DATA)
{
if (!(flags & SW_HTTP2_FLAG_END_STREAM))
{
stream->flags |= SW_HTTP2_STREAM_PIPELINE_RESPONSE;
}
if (length > 0)
{
if (!stream->buffer)
{
stream->buffer = swString_new(SW_HTTP2_DATA_BUFFER_SIZE);
}
#ifdef SW_HAVE_ZLIB
if (stream->gzip)
{
if (php_swoole_zlib_decompress(&stream->gzip_stream, stream->gzip_buffer, buf, length) == SW_ERR)
{
swWarn("decompress failed");
return SW_ERROR;
}
swString_append_ptr(stream->buffer, stream->gzip_buffer->str, stream->gzip_buffer->length);
}
else
#endif
{
swString_append_ptr(stream->buffer, buf, length);
}
// now we control the connection flow only (not stream)
// our window size is unlimited, so we don't worry about subtraction overflow
local_settings.window_size -= length;
stream->local_window_size -= length;
if (local_settings.window_size < (SW_HTTP2_MAX_WINDOW_SIZE / 4))
{
if (!send_window_update(0, SW_HTTP2_MAX_WINDOW_SIZE - local_settings.window_size))
{
return SW_ERROR;
}
local_settings.window_size = SW_HTTP2_MAX_WINDOW_SIZE;
}
if (stream->local_window_size < (SW_HTTP2_MAX_WINDOW_SIZE / 4))
{
if (!send_window_update(stream_id, SW_HTTP2_MAX_WINDOW_SIZE - stream->local_window_size))
{
return SW_ERROR;
}
stream->local_window_size = SW_HTTP2_MAX_WINDOW_SIZE;
}
}
}
bool end = (flags & SW_HTTP2_FLAG_END_STREAM) ||
type == SW_HTTP2_TYPE_RST_STREAM ||
type == SW_HTTP2_TYPE_GOAWAY;
pipeline_read = (pipeline_read && (stream->flags & SW_HTTP2_STREAM_PIPELINE_RESPONSE));
if (end || pipeline_read)
{
zval *zresponse = &stream->zresponse;
if (type == SW_HTTP2_TYPE_RST_STREAM)
{
zend_update_property_long(swoole_http2_response_ce, zresponse, ZEND_STRL("statusCode"), -3 /* HTTP_CLIENT_ESTATUS_SERVER_RESET */);
zend_update_property_long(swoole_http2_response_ce, zresponse, ZEND_STRL("errCode"), value);
}
if (stream->buffer && stream->buffer->length > 0)
{
zend_update_property_stringl(swoole_http2_response_ce, zresponse, ZEND_STRL("data"), stream->buffer->str, stream->buffer->length);
swString_clear(stream->buffer);
}
if (!end)
{
zend_update_property_bool(swoole_http2_response_ce, &stream->zresponse, ZEND_STRL("pipeline"), 1);
}
RETVAL_ZVAL(zresponse, end, 0);
if (!end)
{
// reinit response object for the following frames
object_init_ex(zresponse, swoole_http2_response_ce);
zend_update_property_long(swoole_http2_response_ce, &stream->zresponse, ZEND_STRL("streamId"), stream_id);
}
else
{
delete_stream(stream_id);
}
return SW_READY;
}
return SW_CONTINUE;
}
#ifdef SW_HAVE_ZLIB
int php_swoole_zlib_decompress(z_stream *stream, swString *buffer, char *body, int length)
{
int status = 0;
stream->avail_in = length;
stream->next_in = (Bytef *) body;
stream->total_in = 0;
stream->total_out = 0;
#if 0
printf(SW_START_LINE"\nstatus=%d\tavail_in=%ld,\tavail_out=%ld,\ttotal_in=%ld,\ttotal_out=%ld\n", status,
stream->avail_in, stream->avail_out, stream->total_in, stream->total_out);
#endif
swString_clear(buffer);
while (1)
{
stream->avail_out = buffer->size - buffer->length;
stream->next_out = (Bytef *) (buffer->str + buffer->length);
status = inflate(stream, Z_SYNC_FLUSH);
#if 0
printf("status=%d\tavail_in=%ld,\tavail_out=%ld,\ttotal_in=%ld,\ttotal_out=%ld,\tlength=%ld\n", status,
stream->avail_in, stream->avail_out, stream->total_in, stream->total_out, buffer->length);
#endif
if (status >= 0)
{
buffer->length = stream->total_out;
}
if (status == Z_STREAM_END)
{
return SW_OK;
}
else if (status == Z_OK)
{
if (buffer->length + 4096 >= buffer->size)
{
if (swString_extend(buffer, buffer->size * 2) < 0)
{
return SW_ERR;
}
}
if (stream->avail_in == 0)
{
return SW_OK;
}
}
else
{
return SW_ERR;
}
}
return SW_ERR;
}
#endif
static PHP_METHOD(swoole_http2_client_coro, __construct)
{
char *host;
size_t host_len;
zend_long port = 80;
zend_bool ssl = SW_FALSE;
ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 3)
Z_PARAM_STRING(host, host_len)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(port)
Z_PARAM_BOOL(ssl)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
if (host_len == 0)
{
zend_throw_exception(swoole_http2_client_coro_exception_ce, "host is empty", SW_ERROR_INVALID_PARAMS);
RETURN_FALSE;
}
http2_client *h2c = new http2_client(host, host_len, port, ssl, ZEND_THIS);
if (ssl)
{
#ifndef SW_USE_OPENSSL
zend_throw_exception_ex(
swoole_http2_client_coro_exception_ce,
EPROTONOSUPPORT, "you must configure with `--enable-openssl` to support ssl connection when compiling Swoole"
);
delete h2c;
RETURN_FALSE;
#endif
}
php_swoole_set_h2c(ZEND_THIS, h2c);
zend_update_property_stringl(swoole_http2_client_coro_ce, ZEND_THIS, ZEND_STRL("host"), host, host_len);
zend_update_property_long(swoole_http2_client_coro_ce, ZEND_THIS, ZEND_STRL("port"), port);
zend_update_property_bool(swoole_http2_client_coro_ce, ZEND_THIS, ZEND_STRL("ssl"), ssl);
}
static PHP_METHOD(swoole_http2_client_coro, set)
{
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
zval *zset;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(zset)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zval *zsetting = sw_zend_read_and_convert_property_array(swoole_http2_client_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0);
php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset));
h2c->apply_setting(zset);
RETURN_TRUE;
}
bool http2_client::send_window_update(int stream_id, uint32_t size)
{
char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE];
swTraceLog(SW_TRACE_HTTP2, "[" SW_ECHO_YELLOW "] stream_id=%d, size=%d", "WINDOW_UPDATE", stream_id, size);
*(uint32_t*) ((char *)frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(size);
swHttp2_set_frame_header(frame, SW_HTTP2_TYPE_WINDOW_UPDATE, SW_HTTP2_WINDOW_UPDATE_SIZE, 0, stream_id);
return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE);
}
bool http2_client::send_setting()
{
swHttp2_settings *settings = &local_settings;
uint16_t id = 0;
uint32_t value = 0;
char frame[SW_HTTP2_FRAME_HEADER_SIZE + 18];
memset(frame, 0, sizeof(frame));
swHttp2_set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 18, 0, 0);
char *p = frame + SW_HTTP2_FRAME_HEADER_SIZE;
/**
* HEADER_TABLE_SIZE
*/
id = htons(SW_HTTP2_SETTING_HEADER_TABLE_SIZE);
memcpy(p, &id, sizeof(id));
p += 2;
value = htonl(settings->header_table_size);
memcpy(p, &value, sizeof(value));
p += 4;
/**
* MAX_CONCURRENT_STREAMS
*/
id = htons(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
memcpy(p, &id, sizeof(id));
p += 2;
value = htonl(settings->max_concurrent_streams);
memcpy(p, &value, sizeof(value));
p += 4;
/**
* INIT_WINDOW_SIZE
*/
id = htons(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE);
memcpy(p, &id, sizeof(id));
p += 2;
value = htonl(settings->window_size);
memcpy(p, &value, sizeof(value));
p += 4;
swTraceLog(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN "]\t[length=%d]", swHttp2_get_type(SW_HTTP2_TYPE_SETTINGS), 18);
return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + 18);
}
void http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers);
int http2_client::parse_header(http2_client_stream *stream, int flags, char *in, size_t inlen)
{
zval *zresponse = &stream->zresponse;
if (flags & SW_HTTP2_FLAG_PRIORITY)
{
// int stream_deps = ntohl(*(int *) (in));
// uint8_t weight = in[4];
in += 5;
inlen -= 5;
}
zval *zheaders = sw_zend_read_and_convert_property_array(swoole_http2_response_ce, zresponse, ZEND_STRL("headers"), 0);
zval *zcookies = sw_zend_read_and_convert_property_array(swoole_http2_response_ce, zresponse, ZEND_STRL("cookies"), 0);
zval *zset_cookie_headers = sw_zend_read_and_convert_property_array(swoole_http2_response_ce, zresponse, ZEND_STRL("set_cookie_headers"), 0);
ssize_t rv;
while (true)
{
nghttp2_nv nv;
int inflate_flags = 0;
rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1);
if (rv < 0)
{
nghttp2_error(rv, "nghttp2_hd_inflate_hd failed");
return SW_ERR;
}
in += (size_t) rv;
inlen -= (size_t) rv;
swTraceLog(
SW_TRACE_HTTP2, "[" SW_ECHO_GREEN "] %.*s[%d]: %.*s[%d]", "HEADER",
(int) nv.namelen, nv.name, nv.namelen, (int) nv.valuelen, nv.value, nv.valuelen
);
if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)
{
if (nv.name[0] == ':')
{
if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "status"))
{
zend_update_property_long(swoole_http2_response_ce, zresponse, ZEND_STRL("statusCode"), atoi((char *) nv.value));
goto _check_end;
}
}
#ifdef SW_HAVE_ZLIB
else if (
SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-encoding") &&
SW_STRCASECT((char *) nv.value, nv.valuelen, "gzip")
)
{
/**
* init zlib stream
*/
stream->gzip = 1;
memset(&stream->gzip_stream, 0, sizeof(stream->gzip_stream));
stream->gzip_buffer = swString_new(8192);
stream->gzip_stream.zalloc = php_zlib_alloc;
stream->gzip_stream.zfree = php_zlib_free;
/**
* zlib decode
*/
if (Z_OK != inflateInit2(&stream->gzip_stream, MAX_WBITS + 16))
{
swWarn("inflateInit2() failed");
return SW_ERR;
}
}
#endif
else if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "set-cookie"))
{
http_parse_set_cookies((char *) nv.value, nv.valuelen, zcookies, zset_cookie_headers);
}
add_assoc_stringl_ex(zheaders, (char *) nv.name, nv.namelen, (char *) nv.value, nv.valuelen);
}
_check_end:
if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL)
{
nghttp2_hd_inflate_end_headers(inflater);
break;
}
if (inlen == 0)
{
break;
}
}
return SW_OK;
}
static ssize_t http2_client_build_header(zval *zobject, zval *zrequest, char *buffer)
{
http2_client *h2c = php_swoole_get_h2c(zobject);
zval *zmethod = sw_zend_read_property(swoole_http2_request_ce, zrequest, ZEND_STRL("method"), 0);
zval *zpath = sw_zend_read_property(swoole_http2_request_ce, zrequest, ZEND_STRL("path"), 0);
zval *zheaders = sw_zend_read_property(swoole_http2_request_ce, zrequest, ZEND_STRL("headers"), 0);
zval *zcookies = sw_zend_read_property(swoole_http2_request_ce, zrequest, ZEND_STRL("cookies"), 0);
http2::headers headers(8 + php_swoole_array_length_safe(zheaders) + php_swoole_array_length_safe(zcookies));
bool find_host = 0;
if (Z_TYPE_P(zmethod) != IS_STRING || Z_STRLEN_P(zmethod) == 0)
{
headers.add(ZEND_STRL(":method"), ZEND_STRL("GET"));
}
else
{
headers.add(ZEND_STRL(":method"), Z_STRVAL_P(zmethod), Z_STRLEN_P(zmethod));
}
if (Z_TYPE_P(zpath) != IS_STRING || Z_STRLEN_P(zpath) == 0)
{
headers.add(ZEND_STRL(":path"), "/", 1);
}
else
{
headers.add(ZEND_STRL(":path"), Z_STRVAL_P(zpath), Z_STRLEN_P(zpath));
}
if (h2c->ssl)
{
headers.add(ZEND_STRL(":scheme"), ZEND_STRL("https"));
}
else
{
headers.add(ZEND_STRL(":scheme"), ZEND_STRL("http"));
}
// Host
headers.reserve_one();
if (ZVAL_IS_ARRAY(zheaders))
{
zend_string *key;
zval *zvalue;
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zheaders), key, zvalue)
{
if (UNEXPECTED(!key || *ZSTR_VAL(key) == ':' || ZVAL_IS_NULL(zvalue)))
{
continue;
}
zend::string str_value(zvalue);
if (SW_STRCASEEQ(ZSTR_VAL(key), ZSTR_LEN(key), "host"))
{
headers.add(HTTP2_CLIENT_HOST_HEADER_INDEX, ZEND_STRL(":authority"), str_value.val(), str_value.len());
find_host = true;
}
else
{
headers.add(ZSTR_VAL(key), ZSTR_LEN(key), str_value.val(), str_value.len());
}
}
ZEND_HASH_FOREACH_END();
}
if (!find_host)
{
const std::string *host;
std::string _host;
#ifndef SW_USE_OPENSSL
if (h2c->port != 80)
#else
if (!h2c->ssl ? h2c->port != 80 : h2c->port != 443)
#endif
{
_host = cpp_string::format("%s:%d", h2c->host.c_str(), h2c->port);
host = &_host;
} else {
host = &h2c->host;
}
headers.add(HTTP2_CLIENT_HOST_HEADER_INDEX,ZEND_STRL(":authority"), host->c_str(), host->length());
}
// http cookies
if (ZVAL_IS_ARRAY(zcookies))
{
zend_string *key;
zval *zvalue;
char *encoded_value;
int encoded_value_len;
swString *buffer = SwooleTG.buffer_stack;
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zcookies), key, zvalue)
{
if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue)))
{
continue;
}
zend::string str_value(zvalue);
swString_clear(buffer);
swString_append_ptr(buffer, ZSTR_VAL(key), ZSTR_LEN(key));
swString_append_ptr(buffer, "=", 1);
encoded_value = php_swoole_url_encode(str_value.val(), str_value.len(), &encoded_value_len);
if (encoded_value)
{
swString_append_ptr(buffer, encoded_value, encoded_value_len);
efree(encoded_value);
headers.add(ZEND_STRL("cookie"), buffer->str, buffer->length);
}
}
ZEND_HASH_FOREACH_END();
}
size_t buflen = nghttp2_hd_deflate_bound(h2c->deflater, headers.get(), headers.len());
/*
if (buflen > h2c->remote_settings.max_header_list_size)
{
php_swoole_error(E_WARNING, "header cannot bigger than remote max_header_list_size %u", h2c->remote_settings.max_header_list_size);
return -1;
}
*/
ssize_t rv = nghttp2_hd_deflate_hd(h2c->deflater, (uchar *) buffer, buflen, headers.get(), headers.len());
if (rv < 0)
{
h2c->nghttp2_error(rv, "nghttp2_hd_deflate_hd() failed");
return -1;
}
return rv;
}
void http2_client::destroy_stream(http2_client_stream *stream)
{
if (stream->buffer)
{
swString_free(stream->buffer);
}
#ifdef SW_HAVE_ZLIB
if (stream->gzip)
{
inflateEnd(&stream->gzip_stream);
swString_free(stream->gzip_buffer);
}
#endif
zval_ptr_dtor(&stream->zresponse);
efree(stream);
}
http2_client_stream* http2_client::create_stream(uint32_t stream_id, bool pipeline)
{
// malloc
http2_client_stream *stream = (http2_client_stream *) ecalloc(1, sizeof(http2_client_stream));
// init
stream->stream_id = stream_id;
stream->flags = pipeline ? SW_HTTP2_STREAM_PIPELINE_REQUEST : SW_HTTP2_STREAM_NORMAL;
stream->remote_window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE;
stream->local_window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE;
streams.emplace(stream_id, stream);
// create response object
object_init_ex(&stream->zresponse, swoole_http2_response_ce);
zend_update_property_long(swoole_http2_response_ce, &stream->zresponse, ZEND_STRL("streamId"), stream_id);
return stream;
}
bool http2_client::send_ping_frame()
{
char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE];
swHttp2_set_frame_header(frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_NONE, 0);
return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE);
}
bool http2_client::send_data(uint32_t stream_id, const char *p, size_t len, int flag)
{
uint8_t send_flag;
uint32_t send_len;
char header[SW_HTTP2_FRAME_HEADER_SIZE];
while (len > 0)
{
if (len > remote_settings.max_frame_size)
{
send_len = remote_settings.max_frame_size;
send_flag = 0;
}
else
{
send_len = len;
send_flag = flag;
}
swHttp2_set_frame_header(header, SW_HTTP2_TYPE_DATA, send_len, send_flag, stream_id);
if (!send(header, SW_HTTP2_FRAME_HEADER_SIZE))
{
return false;
}
if (!send(p, send_len))
{
return false;
}
len -= send_len;
p += send_len;
}
return true;
}
uint32_t http2_client::send_request(zval *req)
{
zval *zheaders = sw_zend_read_and_convert_property_array(swoole_http2_request_ce, req, ZEND_STRL("headers"), 0);
zval *zdata = sw_zend_read_property(swoole_http2_request_ce, req, ZEND_STRL("data"), 0);
zval *zpipeline = sw_zend_read_property(swoole_http2_request_ce, req, ZEND_STRL("pipeline"), 0);
bool is_data_empty = Z_TYPE_P(zdata) == IS_STRING ? Z_STRLEN_P(zdata) == 0 : !zval_is_true(zdata);
if (ZVAL_IS_ARRAY(zdata))
{
add_assoc_stringl_ex(zheaders, ZEND_STRL("content-type"), (char *) ZEND_STRL("application/x-www-form-urlencoded"));
}
/**
* send headers
*/
char* buffer = SwooleTG.buffer_stack->str;
ssize_t bytes = http2_client_build_header(zobject, req, buffer + SW_HTTP2_FRAME_HEADER_SIZE);
if (bytes <= 0)
{
return 0;
}
auto stream = create_stream(stream_id, Z_BVAL_P(zpipeline));
if (is_data_empty)
{
if (stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST)
{
swHttp2_set_frame_header(buffer, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS, stream->stream_id);
}
else
{
swHttp2_set_frame_header(buffer, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_STREAM | SW_HTTP2_FLAG_END_HEADERS, stream->stream_id);
}
}
else
{
swHttp2_set_frame_header(buffer, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS, stream->stream_id);
}
swTraceLog(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN ", STREAM#%d] length=%zd", swHttp2_get_type(SW_HTTP2_TYPE_HEADERS), stream->stream_id, bytes);
if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE + bytes))
{
return 0;
}
/**
* send body
*/
if (!is_data_empty)
{
char *p;
size_t len;
smart_str formstr_s = {};
zend::string str_zpost_data;
int flag = (stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST) ? 0 : SW_HTTP2_FLAG_END_STREAM;
if (ZVAL_IS_ARRAY(zdata))
{
p = php_swoole_http_build_query(zdata, &len, &formstr_s);
if (p == NULL)
{
php_swoole_error(E_WARNING, "http_build_query failed");
return 0;
}
}
else
{
str_zpost_data = zdata;
p = str_zpost_data.val();
len = str_zpost_data.len();
}
swTraceLog(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN ", END, STREAM#%d] length=%zu", swHttp2_get_type(SW_HTTP2_TYPE_DATA), stream->stream_id, len);
if (!send_data(stream->stream_id, p, len, flag))
{
return 0;
}
if (formstr_s.s)
{
smart_str_free(&formstr_s);
}
}
stream_id += 2;
return stream->stream_id;
}
bool http2_client::write_data(uint32_t stream_id, zval *zdata, bool end)
{
char buffer[SW_HTTP2_FRAME_HEADER_SIZE];
http2_client_stream *stream = get_stream(stream_id);
int flag = end ? SW_HTTP2_FLAG_END_STREAM : 0;
if (stream == NULL || !(stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST) || (stream->flags & SW_HTTP2_STREAM_REQUEST_END))
{
update_error_properties(EINVAL, cpp_string::format("unable to found active pipeline stream#%u", stream_id).c_str());
return false;
}
if (ZVAL_IS_ARRAY(zdata))
{
size_t len;
smart_str formstr_s = {};
char *formstr = php_swoole_http_build_query(zdata, &len, &formstr_s);
if (formstr == NULL)
{
php_swoole_error(E_WARNING, "http_build_query failed");
return false;
}
swHttp2_set_frame_header(buffer, SW_HTTP2_TYPE_DATA, len, flag, stream_id);
swTraceLog(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN ",%s STREAM#%d] length=%zu", swHttp2_get_type(SW_HTTP2_TYPE_DATA), end ? " END," : "", stream_id, len);
if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE) || !send(formstr, len))
{
smart_str_free(&formstr_s);
return false;
}
smart_str_free(&formstr_s);
}
else
{
zend::string data(zdata);
swHttp2_set_frame_header(buffer, SW_HTTP2_TYPE_DATA, data.len(), flag, stream_id);
swTraceLog(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN ",%s STREAM#%d] length=%zu", swHttp2_get_type(SW_HTTP2_TYPE_DATA), end ? " END," : "", stream_id, data.len());
if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE) || !send(data.val(), data.len()))
{
return false;
}
}
if (end)
{
stream->flags |= SW_HTTP2_STREAM_REQUEST_END;
}
return true;
}
bool http2_client::send_goaway_frame(zend_long error_code, const char *debug_data, size_t debug_data_len)
{
size_t length = SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE + debug_data_len;
char *frame = (char *) ecalloc(1, length);
bool ret;
swHttp2_set_frame_header(frame, SW_HTTP2_TYPE_GOAWAY, SW_HTTP2_GOAWAY_SIZE + debug_data_len, error_code, 0);
*(uint32_t*) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(last_stream_id);
*(uint32_t*) (frame + SW_HTTP2_FRAME_HEADER_SIZE + 4) = htonl(error_code);
if (debug_data_len > 0)
{
memcpy(frame + SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE, debug_data, debug_data_len);
}
swTraceLog(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN "] Send: last-sid=%d, error-code=%d", swHttp2_get_type(SW_HTTP2_TYPE_GOAWAY), last_stream_id, error_code);
ret = send(frame, length);
efree(frame);
return ret;
}
static PHP_METHOD(swoole_http2_client_coro, send)
{
zval *request;
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
if (!h2c->is_available())
{
RETURN_FALSE;
}
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &request) == FAILURE)
{
RETURN_FALSE;
}
if (Z_TYPE_P(request) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(request), swoole_http2_request_ce))
{
zend_throw_exception_ex(swoole_http2_client_coro_exception_ce, SW_ERROR_INVALID_PARAMS, "Object is not a instanceof %s", ZSTR_VAL(swoole_http2_request_ce->name));
RETURN_FALSE;
}
uint32_t stream_id = h2c->send_request(request);
if (stream_id == 0)
{
RETURN_FALSE;
}
else
{
RETURN_LONG(stream_id);
}
}
static void php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAMETERS, bool pipeline_read)
{
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
double timeout = 0;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_DOUBLE(timeout)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
while (true)
{
if (!h2c->is_available())
{
RETURN_FALSE;
}
if (!h2c->recv_packet(timeout))
{
RETURN_FALSE;
}
enum swReturn_code ret = h2c->parse_frame(return_value, pipeline_read);
if (ret == SW_CONTINUE)
{
continue;
}
else if (ret == SW_READY)
{
break;
}
else
{
RETURN_FALSE;
}
}
}
static PHP_METHOD(swoole_http2_client_coro, recv)
{
php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, false);
}
static PHP_METHOD(swoole_http2_client_coro, __destruct) { }
static PHP_METHOD(swoole_http2_client_coro, close)
{
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
RETURN_BOOL(h2c->close());
}
static PHP_METHOD(swoole_http2_client_coro, connect)
{
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
RETURN_BOOL(h2c->connect());
}
static sw_inline void http2_settings_to_array(swHttp2_settings *settings, zval* zarray)
{
array_init(zarray);
add_assoc_long_ex(zarray, ZEND_STRL("header_table_size"), settings->header_table_size);
add_assoc_long_ex(zarray, ZEND_STRL("window_size"), settings->window_size);
add_assoc_long_ex(zarray, ZEND_STRL("max_concurrent_streams"), settings->max_concurrent_streams);
add_assoc_long_ex(zarray, ZEND_STRL("max_frame_size"), settings->max_frame_size);
add_assoc_long_ex(zarray, ZEND_STRL("max_header_list_size"), settings->max_header_list_size);
}
static PHP_METHOD(swoole_http2_client_coro, stats)
{
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
zval _zarray, *zarray = &_zarray;
swString key = {};
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &key.str, &key.length) == FAILURE)
{
RETURN_FALSE;
}
if (key.length > 0)
{
if (SW_STREQ(key.str, key.length, "current_stream_id"))
{
RETURN_LONG(h2c->stream_id);
}
else if (SW_STREQ(key.str, key.length, "last_stream_id"))
{
RETURN_LONG(h2c->last_stream_id);
}
else if (SW_STREQ(key.str, key.length, "local_settings"))
{
http2_settings_to_array(&h2c->local_settings, zarray);
RETURN_ZVAL(zarray, 0, 0);
}
else if (SW_STREQ(key.str, key.length, "remote_settings"))
{
http2_settings_to_array(&h2c->remote_settings, zarray);
RETURN_ZVAL(zarray, 0, 0);
}
else if (SW_STREQ(key.str, key.length, "active_stream_num"))
{
RETURN_LONG(h2c->streams.size());
}
}
else
{
array_init(return_value);
add_assoc_long_ex(return_value, ZEND_STRL("current_stream_id"), h2c->stream_id);
add_assoc_long_ex(return_value, ZEND_STRL("last_stream_id"), h2c->last_stream_id);
http2_settings_to_array(&h2c->local_settings, zarray);
add_assoc_zval_ex(return_value, ZEND_STRL("local_settings"), zarray);
http2_settings_to_array(&h2c->remote_settings, zarray);
add_assoc_zval_ex(return_value, ZEND_STRL("remote_settings"), zarray);
add_assoc_long_ex(return_value, ZEND_STRL("active_stream_num"), h2c->streams.size());
}
}
static PHP_METHOD(swoole_http2_client_coro, isStreamExist)
{
zend_long stream_id = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &stream_id) == FAILURE)
{
RETURN_FALSE;
}
if (stream_id < 0)
{
RETURN_FALSE;
}
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
if (!h2c->client)
{
RETURN_FALSE;
}
else if (stream_id == 0)
{
RETURN_TRUE;
}
http2_client_stream *stream = h2c->get_stream(stream_id);
RETURN_BOOL(stream ? 1 : 0);
}
static PHP_METHOD(swoole_http2_client_coro, write)
{
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
if (!h2c->is_available())
{
RETURN_FALSE;
}
zend_long stream_id;
zval *data;
zend_bool end = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz|b", &stream_id, &data, &end) == FAILURE)
{
RETURN_FALSE;
}
RETURN_BOOL(h2c->write_data(stream_id, data, end));
}
static PHP_METHOD(swoole_http2_client_coro, read)
{
php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, true);
}
static PHP_METHOD(swoole_http2_client_coro, ping)
{
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
if (!h2c->is_available())
{
RETURN_FALSE;
}
RETURN_BOOL(h2c->send_ping_frame());
}
/**
* +-+-------------------------------------------------------------+
* |R| Last-Stream-ID (31) |
* +-+-------------------------------------------------------------+
* | Error Code (32) |
* +---------------------------------------------------------------+
* | Additional Debug Data (*) |
* +---------------------------------------------------------------+
*/
static PHP_METHOD(swoole_http2_client_coro, goaway)
{
http2_client *h2c = php_swoole_get_h2c(ZEND_THIS);
zend_long error_code = SW_HTTP2_ERROR_NO_ERROR;
char* debug_data = NULL;
size_t debug_data_len = 0;
if (!h2c->is_available())
{
RETURN_FALSE;
}
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ls", &error_code, &debug_data, &debug_data_len) == FAILURE)
{
RETURN_FALSE;
}
RETURN_BOOL(h2c->send_goaway_frame(error_code, debug_data, debug_data_len));
}
#endif
C
1
https://gitee.com/AnnOS/swoole.git
git@gitee.com:AnnOS/swoole.git
AnnOS
swoole
swoole-src
master

搜索帮助