1 Star 0 Fork 345

wukong / swoole-src

forked from swoole / swoole-src 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
swoole_http_client_coro.cc 62.77 KB
一键复制 编辑 原始数据 按行查看 历史
韩天峰 提交于 2019-01-18 18:27 . remove SW_MAKE_STD_ZVAL
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900
/*
+----------------------------------------------------------------------+
| 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: Fang <coooold@live.com> |
| Author: Tianfeng Han <mikan.tenny@gmail.com> |
| Author: Yuanyi Zhi <syyuanyizhi@163.com> |
+----------------------------------------------------------------------+
*/
#include "php_swoole_cxx.h"
#include "swoole_http_client.h"
#include "swoole_coroutine.h"
#include "coroutine_c_api.h"
using namespace swoole;
extern swString *http_client_buffer;
extern bool php_swoole_client_coro_socket_free(Socket *cli);
static int http_parser_on_header_field(swoole_http_parser *parser, const char *at, size_t length);
static int http_parser_on_header_value(swoole_http_parser *parser, const char *at, size_t length);
static int http_parser_on_headers_complete(swoole_http_parser *parser);
static int http_parser_on_body(swoole_http_parser *parser, const char *at, size_t length);
static int http_parser_on_message_complete(swoole_http_parser *parser);
static const swoole_http_parser_settings http_parser_settings =
{
NULL,
NULL,
NULL,
NULL,
NULL,
http_parser_on_header_field,
http_parser_on_header_value,
http_parser_on_headers_complete,
http_parser_on_body,
http_parser_on_message_complete
};
class http_client
{
private:
Socket* socket = nullptr;
swSocket_type socket_type = SW_SOCK_TCP;
public:
/* states */
http_client_state state = HTTP_CLIENT_STATE_WAIT;
bool wait = false;
bool defer = false;
/* request info */
std::string host = "127.0.0.1";
uint16_t port = 80;
#ifdef SW_USE_OPENSSL
uint8_t ssl = false;
#endif
double connect_timeout = PHPCoroutine::socket_connect_timeout;
double timeout = PHPCoroutine::socket_timeout;
int8_t method = SW_HTTP_GET; // method
std::string uri;
/* response parse */
swoole_http_parser parser = {0};
char *tmp_header_field_name = nullptr;
int tmp_header_field_name_len = 0;
swString *body = nullptr;
#ifdef SW_HAVE_ZLIB
z_stream gzip_stream = {0};
swString *gzip_buffer = nullptr;
swString *_gzip_buffer = nullptr;
#endif
/* options */
uint8_t reconnect_interval = 1;
uint8_t reconnected_count = 0;
bool keep_alive = true; // enable default
bool websocket = false; // if upgrade successfully
bool gzip = false; // enable gzip
bool chunked = false; // Transfer-Encoding: chunked
bool completed = false; // response parse over
bool websocket_mask = false; // enable websocket mask
bool is_download = false; // save http response to file
int download_file_fd = 0;
bool has_upload_files = false;
/* safety zval */
zval _zobject;
zval *zobject = &_zobject;
http_client(zval* zobject, std::string host, zend_long port = 80, zend_bool ssl = false);
private:
#ifdef SW_HAVE_ZLIB
void init_gzip();
#endif
bool connect();
bool keep_liveness();
bool send();
void reset();
public:
#ifdef SW_HAVE_ZLIB
bool init_compression(http_compress_method method);
bool uncompress_response();
#endif
void check_bind();
void set(zval *zset);
bool exec(std::string uri);
bool recv(double timeout = 0);
void recv(zval *zframe, double timeout = 0);
bool upgrade(std::string uri);
bool push(zval *zdata, zend_long opcode = WEBSOCKET_OPCODE_TEXT, bool _fin = true);
bool close();
~http_client();
};
static zend_class_entry swoole_http_client_coro_ce;
zend_class_entry *swoole_http_client_coro_ce_ptr;
static zend_object_handlers swoole_http_client_coro_handlers;
typedef struct
{
http_client* phc;
zend_object std;
} http_client_coro;
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_void, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_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_http_client_coro_set, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, settings, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_setDefer, 0, 0, 0)
ZEND_ARG_INFO(0, defer)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_setMethod, 0, 0, 1)
ZEND_ARG_INFO(0, method)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_setHeaders, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, headers, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_setCookies, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, cookies, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_setData, 0, 0, 1)
ZEND_ARG_INFO(0, data)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_addFile, 0, 0, 2)
ZEND_ARG_INFO(0, path)
ZEND_ARG_INFO(0, name)
ZEND_ARG_INFO(0, type)
ZEND_ARG_INFO(0, filename)
ZEND_ARG_INFO(0, offset)
ZEND_ARG_INFO(0, length)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_addData, 0, 0, 2)
ZEND_ARG_INFO(0, path)
ZEND_ARG_INFO(0, name)
ZEND_ARG_INFO(0, type)
ZEND_ARG_INFO(0, filename)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_execute, 0, 0, 1)
ZEND_ARG_INFO(0, path)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_get, 0, 0, 1)
ZEND_ARG_INFO(0, path)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_post, 0, 0, 2)
ZEND_ARG_INFO(0, path)
ZEND_ARG_INFO(0, data)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_download, 0, 0, 2)
ZEND_ARG_INFO(0, path)
ZEND_ARG_INFO(0, file)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_upgrade, 0, 0, 1)
ZEND_ARG_INFO(0, path)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_push, 0, 0, 1)
ZEND_ARG_INFO(0, data)
ZEND_ARG_INFO(0, opcode)
ZEND_ARG_INFO(0, finish)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_http_client_coro_recv, 0, 0, 0)
ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()
static PHP_METHOD(swoole_http_client_coro, __construct);
static PHP_METHOD(swoole_http_client_coro, __destruct);
static PHP_METHOD(swoole_http_client_coro, set);
static PHP_METHOD(swoole_http_client_coro, getDefer);
static PHP_METHOD(swoole_http_client_coro, setDefer);
static PHP_METHOD(swoole_http_client_coro, setMethod);
static PHP_METHOD(swoole_http_client_coro, setHeaders);
static PHP_METHOD(swoole_http_client_coro, setCookies);
static PHP_METHOD(swoole_http_client_coro, setData);
static PHP_METHOD(swoole_http_client_coro, addFile);
static PHP_METHOD(swoole_http_client_coro, addData);
static PHP_METHOD(swoole_http_client_coro, execute);
static PHP_METHOD(swoole_http_client_coro, get);
static PHP_METHOD(swoole_http_client_coro, post);
static PHP_METHOD(swoole_http_client_coro, download);
static PHP_METHOD(swoole_http_client_coro, upgrade);
static PHP_METHOD(swoole_http_client_coro, push);
static PHP_METHOD(swoole_http_client_coro, recv);
static PHP_METHOD(swoole_http_client_coro, close);
static const zend_function_entry swoole_http_client_coro_methods[] =
{
PHP_ME(swoole_http_client_coro, __construct, arginfo_swoole_http_client_coro_coro_construct, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, __destruct, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, set, arginfo_swoole_http_client_coro_set, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, getDefer, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, setDefer, arginfo_swoole_http_client_coro_setDefer, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, setMethod, arginfo_swoole_http_client_coro_setMethod, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, setHeaders, arginfo_swoole_http_client_coro_setHeaders, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, setCookies, arginfo_swoole_http_client_coro_setCookies, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, setData, arginfo_swoole_http_client_coro_setData, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, execute, arginfo_swoole_http_client_coro_execute, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, get, arginfo_swoole_http_client_coro_get, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, post, arginfo_swoole_http_client_coro_post, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, download, arginfo_swoole_http_client_coro_download, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, upgrade, arginfo_swoole_http_client_coro_upgrade, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, addFile, arginfo_swoole_http_client_coro_addFile, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, addData, arginfo_swoole_http_client_coro_addData, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, recv, arginfo_swoole_http_client_coro_recv, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, push, arginfo_swoole_http_client_coro_push, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, close, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static int http_parser_on_header_field(swoole_http_parser *parser, const char *at, size_t length)
{
http_client* http = (http_client*) parser->data;
http->tmp_header_field_name = (char *) at;
http->tmp_header_field_name_len = length;
return 0;
}
static int http_parser_on_header_value(swoole_http_parser *parser, const char *at, size_t length)
{
http_client* http = (http_client*) parser->data;
zval* zobject = (zval*) http->zobject;
zval *zheaders = sw_zend_read_property_array(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("headers"), 1);
char *header_name = zend_str_tolower_dup(http->tmp_header_field_name, http->tmp_header_field_name_len);
int ret = 0;
add_assoc_stringl_ex(zheaders, header_name, http->tmp_header_field_name_len, (char *) at, length);
if (http->parser.status_code == SW_HTTP_SWITCHING_PROTOCOLS && strcmp(header_name, "upgrade") == 0 && strncasecmp(at, "websocket", length) == 0)
{
http->websocket = true;
}
else if (strcmp(header_name, "set-cookie") == 0)
{
zval *zcookies = sw_zend_read_property_array(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("cookies"), 1);
zval *zset_cookie_headers = sw_zend_read_property_array(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("set_cookie_headers"), 1);
ret = http_parse_set_cookies(at, length, zcookies, zset_cookie_headers);
}
#ifdef SW_HAVE_ZLIB
else if (strcmp(header_name, "content-encoding") == 0 && strncasecmp(at, "gzip", length) == 0)
{
ret = http->init_compression(HTTP_COMPRESS_GZIP) ? 0 : -1;
}
else if (strcasecmp(header_name, "content-encoding") == 0 && strncasecmp(at, "deflate", length) == 0)
{
ret = http->init_compression(HTTP_COMPRESS_DEFLATE) ? 0 : -1;
}
#endif
else if (strcasecmp(header_name, "transfer-encoding") == 0 && strncasecmp(at, "chunked", length) == 0)
{
http->chunked = true;
}
efree(header_name);
return ret;
}
static int http_parser_on_headers_complete(swoole_http_parser *parser)
{
http_client* http = (http_client*) parser->data;
//no content-length
if (http->chunked == 0 && parser->content_length == -1)
{
enum flags { F_CONNECTION_CLOSE = 1 << 2 };
http->state = HTTP_CLIENT_STATE_WAIT_CLOSE;
parser->flags |= F_CONNECTION_CLOSE;
}
if (http->method == SW_HTTP_HEAD || parser->status_code == SW_HTTP_NO_CONTENT)
{
return 1;
}
return 0;
}
static int http_parser_on_body(swoole_http_parser *parser, const char *at, size_t length)
{
http_client* http = (http_client*) parser->data;
if (swString_append_ptr(http->body, at, length) < 0)
{
return -1;
}
if (http->is_download)
{
#ifdef SW_HAVE_ZLIB
if (http->gzip)
{
if (!http->uncompress_response())
{
return -1;
}
if (swoole_coroutine_write(http->download_file_fd, (const void *) SW_STRINGL(http->gzip_buffer)) < 0)
{
return -1;
}
}
else
#endif
{
if (swoole_coroutine_write(http->download_file_fd, (const void *) SW_STRINGL(http->body)) < 0)
{
return -1;
}
}
swString_clear(http->body);
}
return 0;
}
static int http_parser_on_message_complete(swoole_http_parser *parser)
{
http_client* http = (http_client*) parser->data;
zval* zobject = (zval*) http->zobject;
if (parser->upgrade && !http->websocket)
{
// not support, continue.
parser->upgrade = 0;
return 0;
}
#ifdef SW_HAVE_ZLIB
if (http->gzip && http->body->length > 0)
{
if (!http->uncompress_response())
{
return 0;
}
zend_update_property_stringl(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("body"), SW_STRINGL(http->gzip_buffer));
}
else
#endif
{
zend_update_property_stringl(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("body"), SW_STRINGL(http->body));
}
http->completed = 1;
//http status code
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), http->parser.status_code);
if (parser->upgrade)
{
// return 1 will finish the parser and means yes we support it.
return 1;
}
else
{
return 0;
}
}
http_client::http_client(zval* zobject, std::string host, zend_long port, zend_bool ssl)
{
// check host
if (host.length() == 0)
{
swoole_php_fatal_error(E_WARNING, "host is empty.");
return;
}
if (host.compare(0, 6, "unix:/", 0, 6) == 0)
{
host = host.substr(sizeof("unix:") - 1);
host.erase(0, host.find_first_not_of('/') - 1);
socket_type = SW_SOCK_UNIX_STREAM;
}
else if (host.find(':') != std::string::npos)
{
socket_type = SW_SOCK_TCP6;
}
else
{
socket_type = SW_SOCK_TCP;
}
this->host = host;
this->port = port;
#ifdef SW_USE_OPENSSL
this->ssl = ssl;
#else
if (ssl)
{
swoole_php_fatal_error(E_ERROR, "Need to use `--enable-openssl` to support ssl when compiling swoole.");
return;
}
#endif
// init http response parser
swoole_http_parser_init(&parser, PHP_HTTP_RESPONSE);
parser.data = this;
_zobject = *zobject;
// TODO: zend_read_property cache here (strong type properties)
}
#ifdef SW_HAVE_ZLIB
void http_client::init_gzip()
{
gzip = true;
memset(&gzip_stream, 0, sizeof(gzip_stream));
if (is_download)
{
if (!_gzip_buffer)
{
_gzip_buffer = swString_new(SW_BUFFER_SIZE_STD);
}
gzip_buffer = _gzip_buffer;
}
else
{
gzip_buffer = swoole_zlib_buffer;
}
gzip_stream.zalloc = php_zlib_alloc;
gzip_stream.zfree = php_zlib_free;
}
bool http_client::init_compression(http_compress_method method)
{
switch(method)
{
case HTTP_COMPRESS_DEFLATE:
init_gzip();
if (Z_OK != inflateInit(&gzip_stream))
{
swWarn("inflateInit() failed.");
return false;
}
break;
case HTTP_COMPRESS_GZIP:
init_gzip();
if (Z_OK != inflateInit2(&gzip_stream, MAX_WBITS + 16))
{
swWarn("inflateInit2() failed.");
return false;
}
break;
default:
assert(0);
}
return true;
}
bool http_client::uncompress_response()
{
int status = 0;
swString_clear(gzip_buffer);
gzip_stream.avail_in = body->length;
gzip_stream.next_in = (Bytef *) body->str;
gzip_stream.total_in = 0;
gzip_stream.total_out = 0;
while (1)
{
gzip_stream.avail_out = gzip_buffer->size - gzip_buffer->length;
gzip_stream.next_out = (Bytef *) (gzip_buffer->str + gzip_buffer->length);
status = inflate(&gzip_stream, Z_SYNC_FLUSH);
if (status >= 0)
{
gzip_buffer->length = gzip_stream.total_out;
}
if (status == Z_STREAM_END)
{
return true;
}
else if (status == Z_OK)
{
if (gzip_buffer->length + 4096 >= gzip_buffer->size)
{
if (swString_extend(gzip_buffer, gzip_buffer->size * 2) < 0)
{
break;
}
}
if (gzip_stream.avail_in == 0)
{
return true;
}
}
else
{
break;
}
}
swWarn("http_response_uncompress failed.");
return false;
}
#endif
void http_client::check_bind()
{
if (socket)
{
PHPCoroutine::check_bind("http client", socket->has_bound());
}
}
void http_client::set(zval *zset = nullptr)
{
zval *ztmp;
HashTable *vht;
zval *zsettings = sw_zend_read_property_array(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("setting"), 1);
if (zset)
{
SW_ASSERT(ZVAL_IS_ARRAY(zset));
php_array_merge(Z_ARRVAL_P(zsettings), Z_ARRVAL_P(zset));
// will be set immediately
vht = Z_ARRVAL_P(zset);
if (php_swoole_array_get_value(vht, "timeout", ztmp))
{
// backward compatibility
timeout = connect_timeout = zval_get_double(ztmp);
if (socket)
{
socket->set_timeout(timeout);
}
}
if (php_swoole_array_get_value(vht, "connect_timeout", ztmp))
{
connect_timeout = zval_get_double(ztmp);
}
if (php_swoole_array_get_value(vht, "reconnect", ztmp))
{
reconnect_interval = (uint8_t) MIN(zval_get_long(ztmp), UINT8_MAX);
}
if (php_swoole_array_get_value(vht, "defer", ztmp))
{
defer = zval_is_true(ztmp);
}
if (php_swoole_array_get_value(vht, "keep_alive", ztmp))
{
keep_alive = zval_is_true(ztmp);
}
if (php_swoole_array_get_value(vht, "websocket_mask", ztmp))
{
websocket_mask = zval_is_true(ztmp);
}
}
if (socket)
{
sw_coro_socket_set(socket, zset ? zset : zsettings);
}
}
bool http_client::connect()
{
if (!socket)
{
socket = new Socket(socket_type);
if (unlikely(socket->socket == nullptr))
{
swoole_php_fatal_error(E_WARNING, "new Socket() failed. Error: %s [%d]", strerror(errno), errno);
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), errno);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), strerror(errno));
delete socket;
socket = nullptr;
return false;
}
#ifdef SW_USE_OPENSSL
socket->open_ssl = ssl;
#endif
// check settings
set();
// connect
socket->set_timeout(connect_timeout);
if (!socket->connect(host, port))
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_CONNECT_FAILED);
close();
return false;
}
else
{
reconnected_count = 0;
socket->set_timeout(timeout);
zend_update_property_bool(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("connected"), 1);
}
if (!body)
{
body = swString_new(SW_HTTP_RESPONSE_INIT_SIZE);
if (body == NULL)
{
swoole_php_fatal_error(E_ERROR, "[1] swString_new(%d) failed.", SW_HTTP_RESPONSE_INIT_SIZE);
return false;
}
}
}
return true;
}
bool http_client::keep_liveness()
{
if (!socket || !socket->check_liveness())
{
if (socket)
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SERVER_RESET);
close();
}
for (; reconnected_count < reconnect_interval; reconnected_count++)
{
if (connect())
{
return true;
}
}
return false;
}
return true;
}
bool http_client::send()
{
zval *value = NULL;
char *method;
uint32_t header_flag = 0x0;
zval *zmethod, *zheaders, *zbody, *zupload_files, *zcookies, *z_download_file;
check_bind();
if (uri.length() == 0)
{
swoole_php_fatal_error(E_WARNING, "path is empty.");
return false;
}
// when new request, clear all properties about the last response
{
zval *zattr;
zattr = sw_zend_read_property(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("headers"), 0);
if (Z_TYPE_P(zattr) == IS_ARRAY)
{
zend_hash_clean(Z_ARRVAL_P(zattr));
}
zattr = sw_zend_read_property(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("set_cookie_headers"), 0);
if (Z_TYPE_P(zattr) == IS_ARRAY)
{
zend_hash_clean(Z_ARRVAL_P(zattr));
}
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("body"), "");
}
if (!keep_liveness())
{
return false;
}
else
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), 0);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), "");
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), 0);
}
//clear errno
SwooleG.error = 0;
//clear buffer
swString_clear(http_client_buffer);
// clear body
swString_clear(body);
zmethod = sw_zend_read_property_not_null(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("requestMethod"), 1);
zheaders = sw_zend_read_property(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("requestHeaders"), 1);
zbody = sw_zend_read_property_not_null(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("requestBody"), 1);
zupload_files = sw_zend_read_property(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("uploadFiles"), 1);
zcookies = sw_zend_read_property(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("cookies"), 1);
z_download_file = sw_zend_read_property_not_null(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("downloadFile"), 1);
// ============ download ============
if (z_download_file)
{
zend::string str_download_file(z_download_file);
char *download_file_name = str_download_file.val();
zval *z_download_offset = sw_zend_read_property_not_null(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("downloadOffset"), 1);
off_t download_offset = 0;
if (z_download_offset)
{
download_offset = (off_t) Z_LVAL_P(z_download_offset);
}
int fd = ::open(download_file_name, O_CREAT | O_WRONLY, 0664);
if (fd < 0)
{
swSysError("open(%s, O_CREAT | O_WRONLY) failed.", download_file_name);
return SW_ERR;
}
if (download_offset == 0)
{
if (ftruncate(fd, 0) < 0)
{
swSysError("ftruncate(%s) failed.", download_file_name);
::close(fd);
return SW_ERR;
}
}
else
{
if (lseek(fd, download_offset, SEEK_SET) < 0)
{
swSysError("fseek(%s, %jd) failed.", download_file_name, (intmax_t) download_offset);
::close(fd);
return SW_ERR;
}
}
is_download = 1;
download_file_fd = fd;
}
// ============ method ============
zend::string str_method;
if (zmethod)
{
str_method = zmethod;
method = str_method.val();
}
else
{
method = (char *) (zbody ? "POST" : "GET");
}
this->method = swHttp_get_method(method, strlen(method) + 1);
swString_append_ptr(http_client_buffer, method, strlen(method));
swString_append_ptr(http_client_buffer, ZEND_STRL(" "));
// ============ proxy ============
#ifdef SW_USE_OPENSSL
if (socket->http_proxy && !socket->open_ssl)
#else
if (socket->http_proxy)
#endif
{
zend::string str_host;
const static char *pre = "http://";
char *_host = (char *) host.c_str();
size_t _host_len = host.length();
if (zheaders && Z_TYPE_P(zheaders) == IS_ARRAY)
{
if ((value = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("Host"))))
{
str_host = value;
_host = str_host.val();
_host_len = str_host.len();
}
}
size_t proxy_uri_len = uri.length() + _host_len + strlen(pre) + 10;
char *proxy_uri = (char*) emalloc(proxy_uri_len);
proxy_uri_len = sw_snprintf(proxy_uri, proxy_uri_len, "%s%s:%u%s", pre, _host, port, uri.c_str());
uri = std::string(proxy_uri, proxy_uri_len);
efree(proxy_uri);
}
// ============ uri ============
swString_append_ptr(http_client_buffer, uri.c_str(), uri.length());
swString_append_ptr(http_client_buffer, ZEND_STRL(" HTTP/1.1\r\n"));
// ============ headers ============
char *key;
uint32_t keylen;
int keytype;
if (zheaders && Z_TYPE_P(zheaders) == IS_ARRAY)
{
// As much as possible to ensure that Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
if ((value = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("Host"))))
{
http_client_swString_append_headers(http_client_buffer, ZEND_STRL("Host"), Z_STRVAL_P(value), Z_STRLEN_P(value));
}
else
{
http_client_swString_append_headers(http_client_buffer, ZEND_STRL("Host"), host.c_str(), host.length());
}
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zheaders), key, keylen, keytype, value)
if (HASH_KEY_IS_STRING != keytype)
{
continue;
}
zend::string _value(value);
if ((_value.len() == 0) || (strncasecmp(key, ZEND_STRL("Host")) == 0))
{
continue;
}
if (strncasecmp(key, ZEND_STRL("Content-Length")) == 0)
{
header_flag |= HTTP_HEADER_CONTENT_LENGTH;
//ignore custom Content-Length value
continue;
}
else if (strncasecmp(key, ZEND_STRL("Connection")) == 0)
{
header_flag |= HTTP_HEADER_CONNECTION;
}
else if (strncasecmp(key, ZEND_STRL("Accept-Encoding")) == 0)
{
header_flag |= HTTP_HEADER_ACCEPT_ENCODING;
}
http_client_swString_append_headers(http_client_buffer, key, keylen, _value.val(), _value.len());
SW_HASHTABLE_FOREACH_END();
}
else
{
http_client_swString_append_headers(http_client_buffer, ZEND_STRL("Host"), host.c_str(), host.length());
}
if (!(header_flag & HTTP_HEADER_CONNECTION))
{
if (keep_alive)
{
http_client_swString_append_headers(http_client_buffer, ZEND_STRL("Connection"), ZEND_STRL("keep-alive"));
}
else
{
http_client_swString_append_headers(http_client_buffer, ZEND_STRL("Connection"), ZEND_STRL("closed"));
}
}
#ifdef SW_HAVE_ZLIB
if (!(header_flag & HTTP_HEADER_ACCEPT_ENCODING))
{
http_client_swString_append_headers(http_client_buffer, ZEND_STRL("Accept-Encoding"), ZEND_STRL("gzip"));
}
#endif
// ============ cookies ============
if (zcookies && Z_TYPE_P(zcookies) == IS_ARRAY)
{
swString_append_ptr(http_client_buffer, ZEND_STRL("Cookie: "));
int n_cookie = php_swoole_array_length(zcookies);
int i = 0;
char *encoded_value;
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zcookies), key, keylen, keytype, value)
i++;
if (HASH_KEY_IS_STRING != keytype)
{
continue;
}
zend::string _value(value);
if (_value.len() == 0)
{
continue;
}
swString_append_ptr(http_client_buffer, key, keylen);
swString_append_ptr(http_client_buffer, "=", 1);
int encoded_value_len;
encoded_value = sw_php_url_encode(_value.val(), _value.len(), &encoded_value_len);
if (encoded_value)
{
swString_append_ptr(http_client_buffer, encoded_value, encoded_value_len);
efree(encoded_value);
}
if (i < n_cookie)
{
swString_append_ptr(http_client_buffer, "; ", 2);
}
SW_HASHTABLE_FOREACH_END();
swString_append_ptr(http_client_buffer, ZEND_STRL("\r\n"));
}
// ============ multipart/form-data ============
if (ZVAL_IS_ARRAY(zupload_files))
{
char header_buf[2048];
char boundary_str[39];
int n;
has_upload_files = php_swoole_array_length(zupload_files) > 0;
// ============ content-type ============
memcpy(boundary_str, SW_HTTP_CLIENT_BOUNDARY_PREKEY, sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY) - 1);
swoole_random_string(
boundary_str + sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY) - 1,
sizeof(boundary_str) - sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY)
);
n = sw_snprintf(
header_buf,
sizeof(header_buf), "Content-Type: multipart/form-data; boundary=%*s\r\n",
(int)(sizeof(boundary_str) - 1), boundary_str
);
swString_append_ptr(http_client_buffer, header_buf, n);
// ============ content-length ============
size_t content_length = 0;
// calculate length before encode array
if (zbody && Z_TYPE_P(zbody) == IS_ARRAY)
{
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zbody), key, keylen, keytype, value)
if (HASH_KEY_IS_STRING != keytype)
{
continue;
}
zend::string _value(value);
//strlen("%.*")*2 = 6
//header + body + CRLF
content_length += (sizeof(SW_HTTP_FORM_DATA_FORMAT_STRING) - 7) + (sizeof(boundary_str) - 1) + keylen + _value.len() + 2;
SW_HASHTABLE_FOREACH_END();
}
zval *zname;
zval *ztype;
zval *zsize = NULL;
zval *zpath = NULL;
zval *zcontent = NULL;
zval *zfilename;
zval *zoffset;
// calculate length of files
{
//upload files
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zupload_files), key, keylen, keytype, value)
if (!(zname = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("name"))))
{
continue;
}
if (!(zfilename = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("filename"))))
{
continue;
}
if (!(zsize = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("size"))))
{
continue;
}
if (!(ztype = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("type"))))
{
continue;
}
//strlen("%.*")*4 = 12
//header + body + CRLF
content_length += (sizeof(SW_HTTP_FORM_DATA_FORMAT_FILE) - 13) + (sizeof(boundary_str) - 1)
+ Z_STRLEN_P(zname) + Z_STRLEN_P(zfilename) + Z_STRLEN_P(ztype) + Z_LVAL_P(zsize) + 2;
SW_HASHTABLE_FOREACH_END();
}
http_client_append_content_length(http_client_buffer, content_length + sizeof(boundary_str) - 1 + 6);
// ============ form-data body ============
if (zbody && Z_TYPE_P(zbody) == IS_ARRAY)
{
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zbody), key, keylen, keytype, value)
if (HASH_KEY_IS_STRING != keytype)
{
continue;
}
zend::string _value(value);
n = sw_snprintf(
header_buf, sizeof(header_buf),
SW_HTTP_FORM_DATA_FORMAT_STRING, (int)(sizeof(boundary_str) - 1),
boundary_str, keylen, key
);
swString_append_ptr(http_client_buffer, header_buf, n);
swString_append_ptr(http_client_buffer, _value.val(), _value.len());
swString_append_ptr(http_client_buffer, ZEND_STRL("\r\n"));
SW_HASHTABLE_FOREACH_END();
}
if (socket->send(http_client_buffer->str, http_client_buffer->length) < 0)
{
goto _send_fail;
}
{
//upload files
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zupload_files), key, keylen, keytype, value)
if (!(zname = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("name"))))
{
continue;
}
if (!(zfilename = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("filename"))))
{
continue;
}
/**
* from disk file
*/
if (!(zcontent = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("content"))))
{
//file path
if (!(zpath = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("path"))))
{
continue;
}
//file offset
if (!(zoffset = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("offset"))))
{
continue;
}
zcontent = NULL;
}
else
{
zpath = NULL;
zoffset = NULL;
}
if (!(zsize = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("size"))))
{
continue;
}
if (!(ztype = zend_hash_str_find(Z_ARRVAL_P(value), ZEND_STRL("type"))))
{
continue;
}
/**
* part header
*/
n = sw_snprintf(
header_buf, sizeof(header_buf), SW_HTTP_FORM_DATA_FORMAT_FILE,
(int) (sizeof(boundary_str) - 1), boundary_str,
(int) Z_STRLEN_P(zname), Z_STRVAL_P(zname),
(int) Z_STRLEN_P(zfilename), Z_STRVAL_P(zfilename),
(int) Z_STRLEN_P(ztype), Z_STRVAL_P(ztype)
);
/**
* from memory
*/
if (zcontent)
{
swString_clear(http_client_buffer);
swString_append_ptr(http_client_buffer, header_buf, n);
swString_append_ptr(http_client_buffer, Z_STRVAL_P(zcontent), Z_STRLEN_P(zcontent));
swString_append_ptr(http_client_buffer, "\r\n", 2);
if (socket->send(http_client_buffer->str, http_client_buffer->length) < 0)
{
goto _send_fail;
}
}
/**
* from disk file
*/
else
{
if (socket->send(header_buf, n) < 0)
{
goto _send_fail;
}
if (!socket->sendfile(Z_STRVAL_P(zpath), Z_LVAL_P(zoffset), Z_LVAL_P(zsize)))
{
goto _send_fail;
}
if (socket->send("\r\n", 2) < 0)
{
goto _send_fail;
}
}
SW_HASHTABLE_FOREACH_END();
}
n = sw_snprintf(header_buf, sizeof(header_buf), "--%*s--\r\n", (int)(sizeof(boundary_str) - 1), boundary_str);
if (socket->send(header_buf, n) < 0)
{
goto _send_fail;
}
}
// ============ x-www-form-urlencoded or raw ============
else if (zbody)
{
if (Z_TYPE_P(zbody) == IS_ARRAY)
{
size_t len;
http_client_swString_append_headers(http_client_buffer, ZEND_STRL("Content-Type"), ZEND_STRL("application/x-www-form-urlencoded"));
if (php_swoole_array_length(zbody) > 0)
{
smart_str formstr_s = { 0 };
char *formstr = sw_http_build_query(zbody, &len, &formstr_s);
if (formstr == NULL)
{
swoole_php_error(E_WARNING, "http_build_query failed.");
return SW_ERR;
}
http_client_append_content_length(http_client_buffer, len);
swString_append_ptr(http_client_buffer, formstr, len);
smart_str_free(&formstr_s);
}
else
{
http_client_append_content_length(http_client_buffer, 0);
}
}
else
{
char *body;
size_t body_length = php_swoole_get_send_data(zbody, &body);
http_client_append_content_length(http_client_buffer, body_length);
swString_append_ptr(http_client_buffer, body, body_length);
}
}
// ============ no body ============
else
{
if (header_flag & HTTP_HEADER_CONTENT_LENGTH)
{
http_client_append_content_length(http_client_buffer, 0);
}
else
{
swString_append_ptr(http_client_buffer, ZEND_STRL("\r\n"));
}
}
swTraceLog(
SW_TRACE_HTTP_CLIENT,
"to [%s:%u%s] by fd#%d in cid#%ld with [%zu] bytes: <<EOF\n%.*s\nEOF",
host.c_str(), port, uri.c_str(), socket->get_fd(), Coroutine::get_current_cid(),
http_client_buffer->length, (int) http_client_buffer->length, http_client_buffer->str
);
if (socket->send(http_client_buffer->str, http_client_buffer->length) < 0)
{
_send_fail:
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), SwooleG.error = errno);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), strerror(SwooleG.error));
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SERVER_RESET);
close();
return false;
}
wait = true;
return true;
}
bool http_client::exec(std::string uri)
{
this->uri = uri;
// bzero when make a new reqeust
reconnected_count = 0;
if (defer)
{
return send();
}
else
{
return send() && recv();
}
}
bool http_client::recv(double timeout)
{
long parsed_n = 0;
ssize_t total_bytes = 0, retval = 0;
swString *buffer;
check_bind();
if (!wait)
{
return false;
}
if (!socket || !socket->is_connect())
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), SwooleG.error = SW_ERROR_CLIENT_NO_CONNECTION);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), "connection is not available.");
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SERVER_RESET);
return false;
}
buffer = socket->get_read_buffer();
socket->set_timer(Socket::TIMER_LV_GLOBAL, timeout);
while (completed == 0)
{
retval = socket->recv(buffer->str, buffer->size);
if (retval > 0)
{
total_bytes += retval;
parsed_n = swoole_http_parser_execute(&parser, &http_parser_settings, buffer->str, retval);
swTraceLog(SW_TRACE_HTTP_CLIENT, "parsed_n=%ld, retval=%ld, total_bytes=%ld, completed=%d.", parsed_n, retval, total_bytes, completed);
if (unlikely(parser.state == s_dead && !completed))
{
socket->set_err(EPROTO);
goto _error;
}
if (parsed_n >= 0)
{
continue;
}
}
else if (retval == 0 && state == HTTP_CLIENT_STATE_WAIT_CLOSE)
{
http_parser_on_message_complete(&parser);
break;
}
else
{
_error:
// IO error
socket->del_timer(Socket::TIMER_LV_GLOBAL);
if (retval == 0)
{
// TODO: move to Client::recv
socket->set_err(ECONNRESET);
}
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), socket->errMsg);
if (socket->errCode == ETIMEDOUT)
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT);
}
else
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SERVER_RESET);
}
close();
return false;
}
}
socket->del_timer(Socket::TIMER_LV_GLOBAL);
/**
* TODO: Sec-WebSocket-Accept check
*/
if (websocket)
{
state = HTTP_CLIENT_STATE_UPGRADE;
socket->open_length_check = 1;
socket->protocol.get_package_length = swWebSocket_get_package_length;
socket->protocol.package_length_size = SW_WEBSOCKET_HEADER_LEN;
/**
* websocket message queue
*/
if (retval > parsed_n + 3)
{
buffer->length = retval - parsed_n - 1;
memmove(buffer->str, buffer->str + parsed_n + 1, buffer->length);
}
}
// handler keep alive
if (!keep_alive && state != HTTP_CLIENT_STATE_WAIT_CLOSE && !websocket)
{
close();
}
else
{
reset();
}
return true;
}
void http_client::recv(zval *zframe, double timeout)
{
check_bind();
SW_ASSERT(websocket);
ZVAL_FALSE(zframe);
if (!socket || !socket->is_connect())
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), SwooleG.error = SW_ERROR_CLIENT_NO_CONNECTION);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), "connection is not available.");
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SERVER_RESET);
return;
}
socket->set_timer(Socket::TIMER_LV_GLOBAL, timeout);
ssize_t retval = socket->recv_packet();
socket->del_timer(Socket::TIMER_LV_GLOBAL);
if (retval <= 0)
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SERVER_RESET);
if (socket->errCode != ETIMEDOUT)
{
close();
}
}
else
{
swString msg;
msg.length = retval;
msg.str = socket->get_read_buffer()->str;
php_swoole_websocket_frame_unpack(&msg, zframe);
}
}
bool http_client::upgrade(std::string uri)
{
defer = false;
if (!websocket)
{
char buf[SW_WEBSOCKET_KEY_LENGTH + 1];
zval *zheaders = sw_zend_read_property_array(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("requestHeaders"), 1);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("requestMethod"), "GET");
http_client_create_token(SW_WEBSOCKET_KEY_LENGTH, buf);
add_assoc_string(zheaders, "Connection", (char* )"Upgrade");
add_assoc_string(zheaders, "Upgrade", (char* ) "websocket");
add_assoc_string(zheaders, "Sec-WebSocket-Version", (char*)SW_WEBSOCKET_VERSION);
add_assoc_str_ex(zheaders, ZEND_STRL("Sec-WebSocket-Key"), php_base64_encode((const unsigned char *) buf, SW_WEBSOCKET_KEY_LENGTH));
exec(uri);
}
return websocket;
}
bool http_client::push(zval *zdata, zend_long opcode, bool fin)
{
check_bind();
if (!websocket)
{
swoole_php_fatal_error(E_WARNING, "websocket handshake failed, cannot push data.");
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), SwooleG.error = SW_ERROR_WEBSOCKET_HANDSHAKE_FAILED);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), "websocket handshake failed, cannot push data.");
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_CONNECT_FAILED);
return false;
}
if (!socket || !socket->is_connect())
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), SwooleG.error = SW_ERROR_CLIENT_NO_CONNECTION);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), "connection is not available.");
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SERVER_RESET);
return false;
}
swString_clear(http_client_buffer);
if (php_swoole_websocket_frame_pack(http_client_buffer, zdata, opcode, fin, websocket_mask) < 0)
{
return false;
}
if (socket->send(http_client_buffer->str, http_client_buffer->length) < 0)
{
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errCode"), SwooleG.error = socket->errCode);
zend_update_property_string(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("errMsg"), strerror(SwooleG.error));
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SERVER_RESET);
close();
return false;
}
else
{
return true;
}
}
void http_client::reset()
{
wait = false;
state = HTTP_CLIENT_STATE_READY;
completed = false;
// clear
#ifdef SW_HAVE_ZLIB
if (gzip)
{
inflateEnd(&gzip_stream);
gzip = false;
}
#endif
if (has_upload_files)
{
zend_update_property_null(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("uploadFiles"));
}
if (is_download)
{
::close(download_file_fd);
is_download = false;
download_file_fd = 0;
zend_update_property_null(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("downloadFile"));
zend_update_property_long(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("downloadOffset"), 0);
}
}
bool http_client::close()
{
Socket *socket = this->socket;
if (socket)
{
zend_update_property_bool(swoole_http_client_coro_ce_ptr, zobject, ZEND_STRL("connected"), 0);
if (!socket->has_bound())
{
// reset some request period states
reset();
// reset the properties that depend on the connection
this->websocket = false;
this->socket = nullptr;
}
return php_swoole_client_coro_socket_free(socket);
}
return false;
}
http_client::~http_client()
{
close();
if (body)
{
swString_free(body);
}
#ifdef SW_HAVE_ZLIB
if (_gzip_buffer)
{
swString_free(_gzip_buffer);
_gzip_buffer = nullptr;
}
#endif
}
static sw_inline http_client_coro* swoole_http_client_coro_fetch_object(zend_object *obj)
{
return (http_client_coro *) ((char *) obj - swoole_http_client_coro_handlers.offset);
}
static sw_inline http_client * swoole_get_phc(zval *zobject)
{
http_client *phc = swoole_http_client_coro_fetch_object(Z_OBJ_P(zobject))->phc;
if (UNEXPECTED(!phc))
{
swoole_php_fatal_error(E_ERROR, "you must call Http Client constructor first.");
}
return phc;
}
static void swoole_http_client_coro_free_object(zend_object *object)
{
http_client_coro *hcc_t = swoole_http_client_coro_fetch_object(object);
if (hcc_t->phc)
{
hcc_t->phc->close();
delete hcc_t->phc;
hcc_t->phc = nullptr;
}
zend_object_std_dtor(&hcc_t->std);
}
static zend_object *swoole_http_client_coro_create_object(zend_class_entry *ce)
{
http_client_coro *hcc_t = (http_client_coro *) ecalloc(1, sizeof(http_client_coro) + zend_object_properties_size(ce));
zend_object_std_init(&hcc_t->std, ce);
object_properties_init(&hcc_t->std, ce);
hcc_t->std.handlers = &swoole_http_client_coro_handlers;
return &hcc_t->std;
}
void swoole_http_client_coro_init(int module_number)
{
SWOOLE_INIT_CLASS_ENTRY(swoole_http_client_coro, "Swoole\\Coroutine\\Http\\Client", NULL, "Co\\Http\\Client", swoole_http_client_coro_methods);
SWOOLE_SET_CLASS_SERIALIZABLE(swoole_http_client_coro, zend_class_serialize_deny, zend_class_unserialize_deny);
SWOOLE_SET_CLASS_CLONEABLE(swoole_http_client_coro, zend_class_clone_deny);
SWOOLE_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_client_coro, zend_class_unset_property_deny);
SWOOLE_SET_CLASS_CUSTOM_OBJECT(swoole_http_client_coro, swoole_http_client_coro_create_object, swoole_http_client_coro_free_object, http_client_coro, std);
// client status
zend_declare_property_long(swoole_http_client_coro_ce_ptr, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_string(swoole_http_client_coro_ce_ptr, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC);
zend_declare_property_bool(swoole_http_client_coro_ce_ptr, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC);
// client info
zend_declare_property_string(swoole_http_client_coro_ce_ptr, ZEND_STRL("host"), "", ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http_client_coro_ce_ptr, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_bool(swoole_http_client_coro_ce_ptr, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("setting"), ZEND_ACC_PUBLIC);
// request properties
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("requestMethod"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("requestHeaders"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("requestBody"), ZEND_ACC_PUBLIC);
// always set by API (make it private?)
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("uploadFiles"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("downloadFile"), ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http_client_coro_ce_ptr, ZEND_STRL("downloadOffset"), 0, ZEND_ACC_PUBLIC);
// response properties
zend_declare_property_long(swoole_http_client_coro_ce_ptr, ZEND_STRL("statusCode"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("headers"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("set_cookie_headers"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce_ptr, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC);
zend_declare_property_string(swoole_http_client_coro_ce_ptr, ZEND_STRL("body"), "", ZEND_ACC_PUBLIC);
SWOOLE_DEFINE_NS(HTTP_CLIENT_ESTATUS_CONNECT_FAILED);
SWOOLE_DEFINE_NS(HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT);
SWOOLE_DEFINE_NS(HTTP_CLIENT_ESTATUS_SERVER_RESET);
}
static PHP_METHOD(swoole_http_client_coro, __construct)
{
http_client_coro *hcc_t = swoole_http_client_coro_fetch_object(Z_OBJ_P(getThis()));
char *host;
size_t host_len;
zend_long port = 80;
zend_bool ssl = 0;
ZEND_PARSE_PARAMETERS_START(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);
php_swoole_check_reactor();
zend_update_property_stringl(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("host"), host, host_len);
zend_update_property_long(swoole_http_client_coro_ce_ptr,getThis(), ZEND_STRL("port"), port);
zend_update_property_bool(swoole_http_client_coro_ce_ptr,getThis(), ZEND_STRL("ssl"), ssl);
hcc_t->phc = new http_client(getThis(), std::string(host, host_len), port, ssl);
}
static PHP_METHOD(swoole_http_client_coro, __destruct) { }
static PHP_METHOD(swoole_http_client_coro, set)
{
zval *zset;
http_client* phc = swoole_get_phc(getThis());
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(zset)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
phc->set(zset);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, getDefer)
{
http_client *phc = swoole_get_phc(getThis());
RETURN_BOOL(phc->defer);
}
static PHP_METHOD(swoole_http_client_coro, setDefer)
{
http_client *phc = swoole_get_phc(getThis());
zend_bool defer = 1;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_BOOL(defer)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
phc->defer = defer;
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, setMethod)
{
zval *method;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(method)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("requestMethod"), method);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, setHeaders)
{
zval *headers;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(headers)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("requestHeaders"), headers);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, setCookies)
{
zval *cookies;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(cookies)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("cookies"), cookies);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, setData)
{
char *data;
size_t data_len;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(data, data_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property_stringl(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("requestBody"), data, data_len);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, addFile)
{
char *path;
size_t l_path;
char *name;
size_t l_name;
char *type = NULL;
size_t l_type = 0;
char *filename = NULL;
size_t l_filename = 0;
zend_long offset = 0;
zend_long length = 0;
ZEND_PARSE_PARAMETERS_START(2, 6)
Z_PARAM_STRING(path, l_path)
Z_PARAM_STRING(name, l_name)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(type, l_type)
Z_PARAM_STRING(filename, l_filename)
Z_PARAM_LONG(offset)
Z_PARAM_LONG(length)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
if (offset < 0)
{
offset = 0;
}
if (length < 0)
{
length = 0;
}
struct stat file_stat;
if (stat(path, &file_stat) < 0)
{
swoole_php_sys_error(E_WARNING, "stat(%s) failed.", path);
RETURN_FALSE;
}
if (file_stat.st_size == 0)
{
swoole_php_sys_error(E_WARNING, "cannot send empty file[%s].", filename);
RETURN_FALSE;
}
if (file_stat.st_size <= offset)
{
swoole_php_error(E_WARNING, "parameter $offset[" ZEND_LONG_FMT "] exceeds the file size.", offset);
RETURN_FALSE;
}
if (length > file_stat.st_size - offset)
{
swoole_php_sys_error(E_WARNING, "parameter $length[" ZEND_LONG_FMT "] exceeds the file size.", length);
RETURN_FALSE;
}
if (length == 0)
{
length = file_stat.st_size - offset;
}
if (l_type == 0)
{
type = (char *) swoole_get_mime_type(path);
l_type = strlen(type);
}
if (l_filename == 0)
{
char *dot = strrchr(path, '/');
if (dot == NULL)
{
filename = path;
l_filename = l_path;
}
else
{
filename = dot + 1;
l_filename = strlen(filename);
}
}
zval *zupload_files = sw_zend_read_property_array(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("uploadFiles"), 1);
zval zupload_file;
array_init(&zupload_file);
add_assoc_stringl_ex(&zupload_file, ZEND_STRL("path"), path, l_path);
add_assoc_stringl_ex(&zupload_file, ZEND_STRL("name"), name, l_name);
add_assoc_stringl_ex(&zupload_file, ZEND_STRL("filename"), filename, l_filename);
add_assoc_stringl_ex(&zupload_file, ZEND_STRL("type"), type, l_type);
add_assoc_long(&zupload_file, "size", length);
add_assoc_long(&zupload_file, "offset", offset);
add_next_index_zval(zupload_files, &zupload_file);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, addData)
{
char *data;
size_t l_data;
char *name;
size_t l_name;
char *type = NULL;
size_t l_type = 0;
char *filename = NULL;
size_t l_filename = 0;
ZEND_PARSE_PARAMETERS_START(2, 4)
Z_PARAM_STRING(data, l_data)
Z_PARAM_STRING(name, l_name)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(type, l_type)
Z_PARAM_STRING(filename, l_filename)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
if (l_type == 0)
{
type = (char *) "application/octet-stream";
l_type = strlen(type);
}
if (l_filename == 0)
{
filename = name;
l_filename = l_name;
}
zval *zupload_files = sw_zend_read_property_array(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("uploadFiles"), 1);
zval zupload_file;
array_init(&zupload_file);
add_assoc_stringl_ex(&zupload_file, ZEND_STRL("content"), data, l_data);
add_assoc_stringl_ex(&zupload_file, ZEND_STRL("name"), name, l_name);
add_assoc_stringl_ex(&zupload_file, ZEND_STRL("filename"), filename, l_filename);
add_assoc_stringl_ex(&zupload_file, ZEND_STRL("type"), type, l_type);
add_assoc_long(&zupload_file, "size", l_data);
add_next_index_zval(zupload_files, &zupload_file);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, execute)
{
http_client* phc = swoole_get_phc(getThis());
char *uri = NULL;
size_t uri_len = 0;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(uri, uri_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
RETURN_BOOL(phc->exec(std::string(uri, uri_len)));
}
static PHP_METHOD(swoole_http_client_coro, get)
{
http_client* phc = swoole_get_phc(getThis());
char *uri = NULL;
size_t uri_len = 0;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(uri, uri_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property_string(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("requestMethod"), "GET");
RETURN_BOOL(phc->exec(std::string(uri, uri_len)));
}
static PHP_METHOD(swoole_http_client_coro, post)
{
http_client* phc = swoole_get_phc(getThis());
char *uri = NULL;
size_t uri_len = 0;
zval *post_data;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STRING(uri, uri_len)
Z_PARAM_ZVAL(post_data)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property_string(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("requestMethod"), "POST");
zend_update_property(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("requestBody"), post_data);
RETURN_BOOL(phc->exec(std::string(uri, uri_len)));
}
static PHP_METHOD(swoole_http_client_coro, download)
{
http_client* phc = swoole_get_phc(getThis());
char *uri;
size_t uri_len;
zval *download_file;
zend_long offset = 0;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STRING(uri, uri_len)
Z_PARAM_ZVAL(download_file)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(offset)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("downloadFile"), download_file);
zend_update_property_long(swoole_http_client_coro_ce_ptr, getThis(), ZEND_STRL("downloadOffset"), offset);
RETURN_BOOL(phc->exec(std::string(uri, uri_len)));
}
static PHP_METHOD(swoole_http_client_coro, upgrade)
{
http_client* phc = swoole_get_phc(getThis());
char *uri = NULL;
size_t uri_len = 0;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(uri, uri_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
RETURN_BOOL(phc->upgrade(std::string(uri, uri_len)));
}
static PHP_METHOD(swoole_http_client_coro, push)
{
http_client* phc = swoole_get_phc(getThis());
zval *zdata;
zend_long opcode = WEBSOCKET_OPCODE_TEXT;
zend_bool fin = 1;
ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_ZVAL(zdata)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(opcode)
Z_PARAM_BOOL(fin)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
RETURN_BOOL(phc->push(zdata, opcode, fin));
}
static PHP_METHOD(swoole_http_client_coro, recv)
{
http_client *phc = swoole_get_phc(getThis());
double timeout = phc->timeout;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_DOUBLE(timeout)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
if (phc->websocket)
{
phc->recv(return_value, timeout);
return;
}
else
{
RETURN_BOOL(phc->recv(timeout));
}
}
static PHP_METHOD(swoole_http_client_coro, close)
{
http_client* phc = swoole_get_phc(getThis());
RETURN_BOOL(phc->close());
}
C
1
https://gitee.com/scaly/swoole.git
git@gitee.com:scaly/swoole.git
scaly
swoole
swoole-src
master

搜索帮助