1 Star 0 Fork 345

行者漫步 / swoole-src

forked from swoole / swoole-src 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
swoole_http_client_coro.cc 79.13 KB
一键复制 编辑 原始数据 按行查看 历史
韩天峰 提交于 2020-09-02 19:46 . Optimize header file name (#3618)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178
/*
+----------------------------------------------------------------------+
| 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> |
| Author: Twosee <twose@qq.com> |
| Author: Fang <coooold@live.com> |
| Author: Yuanyi Zhi <syyuanyizhi@163.com> |
+----------------------------------------------------------------------+
*/
#include "php_swoole_cxx.h"
#include "php_swoole_http.h"
#include "swoole_util.h"
#include "swoole_websocket.h"
SW_EXTERN_C_BEGIN
#include "thirdparty/swoole_http_parser.h"
#include "ext/standard/basic_functions.h"
#include "ext/standard/php_http.h"
#include "ext/standard/base64.h"
#ifdef SW_HAVE_ZLIB
#include <zlib.h>
#endif
SW_EXTERN_C_END
#include "swoole_mime_type.h"
#include "swoole_base64.h"
#ifdef SW_HAVE_BROTLI
#include <brotli/decode.h>
#endif
using namespace swoole;
using swoole::coroutine::Socket;
enum http_client_error_status_code {
HTTP_CLIENT_ESTATUS_CONNECT_FAILED = -1,
HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT = -2,
HTTP_CLIENT_ESTATUS_SERVER_RESET = -3,
HTTP_CLIENT_ESTATUS_SEND_FAILED = -4,
};
extern void 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);
// clang-format off
static const swoole_http_parser_settings http_parser_settings =
{
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
http_parser_on_header_field,
http_parser_on_header_value,
http_parser_on_headers_complete,
http_parser_on_body,
http_parser_on_message_complete
};
// clang-format on
namespace swoole { namespace coroutine {
class HttpClient {
public:
/* request info */
std::string host = "127.0.0.1";
uint16_t port = 80;
/* must bind address first */
std::string bind_address;
int bind_port = 0;
#ifdef SW_USE_OPENSSL
uint8_t ssl = false;
#endif
double connect_timeout = network::Socket::default_connect_timeout;
bool defer = false;
bool lowercase_header = true;
int8_t method = SW_HTTP_GET;
std::string path;
std::string basic_auth;
/* for response parser */
char *tmp_header_field_name = nullptr;
int tmp_header_field_name_len = 0;
String *body = nullptr;
#ifdef SW_HAVE_COMPRESSION
enum http_compress_method compress_method = HTTP_COMPRESS_NONE;
bool compression_error = false;
#endif
/* options */
uint8_t reconnect_interval = 1;
uint8_t reconnected_count = 0;
bool keep_alive = true; // enable by default
bool websocket = false; // if upgrade successfully
bool chunked = false; // Transfer-Encoding: chunked
bool websocket_mask = true; // enable websocket mask
#ifdef SW_HAVE_ZLIB
bool websocket_compression = false; // allow to compress websocket messages
#endif
int download_file_fd = -1; // save http response to file
zend::String download_file_name; // unlink the file on error
zend_long download_offset = 0;
bool has_upload_files = false;
/* safety zval */
zval _zobject;
zval *zobject = &_zobject;
String *tmp_write_buffer = nullptr;
HttpClient(zval *zobject, std::string host, zend_long port = 80, zend_bool ssl = false);
private:
#ifdef SW_HAVE_ZLIB
bool gzip_stream_active = false;
z_stream gzip_stream;
#endif
#ifdef SW_HAVE_BROTLI
BrotliDecoderState *brotli_decoder_state = nullptr;
#endif
bool bind(std::string address, int port = 0);
bool connect();
bool keep_liveness();
bool send();
void reset();
static inline void add_headers(String *buf, const char *key, size_t key_len, const char *data, size_t data_len) {
buf->append(key, key_len);
buf->append(ZEND_STRL(": "));
buf->append(data, data_len);
buf->append(ZEND_STRL("\r\n"));
}
static inline void add_content_length(String *buf, int length) {
char content_length_str[32];
int n = snprintf(SW_STRS(content_length_str), "Content-Length: %d\r\n\r\n", length);
buf->append(content_length_str, n);
}
static inline void create_token(int length, char *buf) {
char characters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"§$%&/()=[]{}";
int i;
assert(length < 1024);
for (i = 0; i < length; i++) {
buf[i] = characters[rand() % (sizeof(characters) - 1)];
}
buf[length] = '\0';
}
public:
#ifdef SW_HAVE_COMPRESSION
bool decompress_response(const char *in, size_t in_len);
#endif
void apply_setting(zval *zset, const bool check_all = true);
void set_basic_auth(const std::string &username, const std::string &password);
bool exec(std::string path);
bool recv(double timeout = 0);
void recv(zval *zframe, double timeout = 0);
bool recv_http_response(double timeout = 0);
bool upgrade(std::string path);
bool push(zval *zdata, zend_long opcode = WEBSOCKET_OPCODE_TEXT, uint8_t flags = SW_WEBSOCKET_FLAG_FIN);
bool close(const bool should_be_reset = true);
void get_header_out(zval *return_value) {
swString *buffer = nullptr;
if (socket == nullptr) {
if (tmp_write_buffer) {
buffer = tmp_write_buffer;
}
} else {
buffer = socket->get_write_buffer();
}
if (buffer == nullptr) {
RETURN_FALSE;
}
off_t offset = swoole_strnpos(buffer->str, buffer->length, ZEND_STRL("\r\n\r\n"));
if (offset <= 0) {
RETURN_FALSE;
}
RETURN_STRINGL(buffer->str, offset);
}
void getsockname(zval *return_value) {
swSocketAddress sa;
if (!socket || !socket->getsockname(&sa)) {
ZVAL_FALSE(return_value);
return;
}
array_init(return_value);
add_assoc_string(return_value, "address", (char *) sa.get_ip());
add_assoc_long(return_value, "port", sa.get_port());
}
void getpeername(zval *return_value) {
swSocketAddress sa;
if (!socket || !socket->getpeername(&sa)) {
ZVAL_FALSE(return_value);
return;
}
array_init(return_value);
add_assoc_string(return_value, "address", (char *) sa.get_ip());
add_assoc_long(return_value, "port", sa.get_port());
}
#ifdef SW_USE_OPENSSL
void getpeercert(zval *return_value) {
auto cert = socket->ssl_get_peer_cert();
if (cert.empty()) {
ZVAL_FALSE(return_value);
return;
} else {
ZVAL_STRINGL(return_value, cert.c_str(), cert.length());
}
}
#endif
~HttpClient();
private:
Socket *socket = nullptr;
swSocket_type socket_type = SW_SOCK_TCP;
swoole_http_parser parser = {};
bool wait = false;
};
}}
static zend_class_entry *swoole_http_client_coro_ce;
static zend_object_handlers swoole_http_client_coro_handlers;
static zend_class_entry *swoole_http_client_coro_exception_ce;
static zend_object_handlers swoole_http_client_coro_exception_handlers;
using swoole::coroutine::HttpClient;
struct HttpClientObject {
HttpClient *phc;
zend_object std;
};
SW_EXTERN_C_BEGIN
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, setBasicAuth);
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, getsockname);
static PHP_METHOD(swoole_http_client_coro, getpeername);
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, getBody);
static PHP_METHOD(swoole_http_client_coro, getHeaders);
static PHP_METHOD(swoole_http_client_coro, getCookies);
static PHP_METHOD(swoole_http_client_coro, getStatusCode);
static PHP_METHOD(swoole_http_client_coro, getHeaderOut);
#ifdef SW_USE_OPENSSL
static PHP_METHOD(swoole_http_client_coro, getPeerCert);
#endif
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);
SW_EXTERN_C_END
// clang-format off
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_setBasicAuth, 0, 0, 2)
ZEND_ARG_INFO(0, username)
ZEND_ARG_INFO(0, password)
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, flags)
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 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, setBasicAuth, arginfo_swoole_http_client_coro_setBasicAuth, 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, 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, execute, arginfo_swoole_http_client_coro_execute, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, getpeername, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, getsockname, arginfo_swoole_void, 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, getBody, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, getHeaders, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, getCookies, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, getStatusCode, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, getHeaderOut, arginfo_swoole_void, ZEND_ACC_PUBLIC)
#ifdef SW_USE_OPENSSL
PHP_ME(swoole_http_client_coro, getPeerCert, arginfo_swoole_void, ZEND_ACC_PUBLIC)
#endif
PHP_ME(swoole_http_client_coro, upgrade, arginfo_swoole_http_client_coro_upgrade, 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, recv, arginfo_swoole_http_client_coro_recv, ZEND_ACC_PUBLIC)
PHP_ME(swoole_http_client_coro, close, arginfo_swoole_void, ZEND_ACC_PUBLIC)
PHP_FE_END
};
// clang-format on
void http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers) {
const char *p, *eof = at + length;
size_t key_len = 0, value_len = 0;
zval zvalue;
// key
p = (char *) memchr(at, '=', length);
if (p) {
key_len = p - at;
p++; // point to value
} else {
p = at; // key is empty
}
// value
eof = (char *) memchr(p, ';', at + length - p);
if (!eof) {
eof = at + length;
}
value_len = eof - p;
if (value_len != 0) {
ZVAL_STRINGL(&zvalue, p, value_len);
Z_STRLEN(zvalue) = php_url_decode(Z_STRVAL(zvalue), value_len);
} else {
ZVAL_EMPTY_STRING(&zvalue);
}
if (key_len == 0) {
add_next_index_zval(zcookies, &zvalue);
} else {
add_assoc_zval_ex(zcookies, at, key_len, &zvalue);
}
// set_cookie_headers
add_next_index_stringl(zset_cookie_headers, (char *) at, length);
}
static int http_parser_on_header_field(swoole_http_parser *parser, const char *at, size_t length) {
HttpClient *http = (HttpClient *) 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) {
HttpClient *http = (HttpClient *) parser->data;
zval *zobject = (zval *) http->zobject;
zval *zheaders =
sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("headers"), 0);
char *header_name = http->tmp_header_field_name;
size_t header_len = http->tmp_header_field_name_len;
if (http->lowercase_header) {
header_name = zend_str_tolower_dup(header_name, header_len);
}
add_assoc_stringl_ex(zheaders, header_name, header_len, (char *) at, length);
if (parser->status_code == SW_HTTP_SWITCHING_PROTOCOLS && SW_STREQ(header_name, header_len, "upgrade")) {
if (SW_STRCASEEQ(at, length, "websocket")) {
http->websocket = true;
}
/* TODO: protocol error? */
}
#ifdef SW_HAVE_ZLIB
else if (http->websocket && http->websocket_compression &&
SW_STREQ(header_name, header_len, "sec-websocket-extensions")) {
if (SW_STRCASECT(at, length, "permessage-deflate") && SW_STRCASECT(at, length, "client_no_context_takeover") &&
SW_STRCASECT(at, length, "server_no_context_takeover")) {
http->websocket_compression = true;
}
}
#endif
else if (SW_STREQ(header_name, header_len, "set-cookie")) {
zval *zcookies =
sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("cookies"), 0);
zval *zset_cookie_headers = sw_zend_read_and_convert_property_array(
swoole_http_client_coro_ce, zobject, ZEND_STRL("set_cookie_headers"), 0);
http_parse_set_cookies(at, length, zcookies, zset_cookie_headers);
}
#ifdef SW_HAVE_COMPRESSION
else if (SW_STREQ(header_name, header_len, "content-encoding")) {
if (0) {
}
#ifdef SW_HAVE_BROTLI
else if (SW_STRCASECT(at, length, "br")) {
http->compress_method = HTTP_COMPRESS_BR;
}
#endif
#ifdef SW_HAVE_ZLIB
else if (SW_STRCASECT(at, length, "gzip")) {
http->compress_method = HTTP_COMPRESS_GZIP;
} else if (SW_STRCASECT(at, length, "deflate")) {
http->compress_method = HTTP_COMPRESS_DEFLATE;
}
#endif
}
#endif
else if (SW_STREQ(header_name, header_len, "transfer-encoding") && SW_STRCASECT(at, length, "chunked")) {
http->chunked = true;
}
if (http->lowercase_header) {
efree(header_name);
}
return 0;
}
static int http_parser_on_headers_complete(swoole_http_parser *parser) {
HttpClient *http = (HttpClient *) parser->data;
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) {
HttpClient *http = (HttpClient *) parser->data;
#ifdef SW_HAVE_COMPRESSION
if (!http->compression_error && http->compress_method != HTTP_COMPRESS_NONE) {
if (!http->decompress_response(at, length)) {
http->compression_error = true;
goto _append_raw;
}
} else
#endif
{
#ifdef SW_HAVE_COMPRESSION
_append_raw:
#endif
if (http->body->append(at, length) < 0) {
return -1;
}
}
if (http->download_file_name.get() && http->body->length > 0) {
if (http->download_file_fd < 0) {
char *download_file_name = http->download_file_name.val();
int fd = ::open(download_file_name, O_CREAT | O_WRONLY, 0664);
if (fd < 0) {
swSysWarn("open(%s, O_CREAT | O_WRONLY) failed", download_file_name);
return false;
}
if (http->download_offset == 0) {
if (::ftruncate(fd, 0) < 0) {
swSysWarn("ftruncate(%s) failed", download_file_name);
::close(fd);
return false;
}
} else {
if (::lseek(fd, http->download_offset, SEEK_SET) < 0) {
swSysWarn("fseek(%s, %jd) failed", download_file_name, (intmax_t) http->download_offset);
::close(fd);
return false;
}
}
http->download_file_fd = fd;
}
if (swoole_coroutine_write(http->download_file_fd, SW_STRINGL(http->body)) != (ssize_t) http->body->length) {
return -1;
}
swString_clear(http->body);
}
return 0;
}
static int http_parser_on_message_complete(swoole_http_parser *parser) {
HttpClient *http = (HttpClient *) parser->data;
zval *zobject = (zval *) http->zobject;
if (parser->upgrade && !http->websocket) {
// not support, continue.
parser->upgrade = 0;
return 0;
}
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("statusCode"), parser->status_code);
if (http->download_file_fd <= 0) {
zend_update_property_stringl(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("body"), SW_STRINGL(http->body));
} else {
http->download_file_name.release();
}
if (parser->upgrade) {
// return 1 will finish the parser and means yes we support it.
return 1;
} else {
return 0;
}
}
HttpClient::HttpClient(zval *zobject, std::string host, zend_long port, zend_bool ssl) {
this->socket_type = Socket::convert_to_type(host);
this->host = host;
this->port = port;
#ifdef SW_USE_OPENSSL
this->ssl = ssl;
#endif
_zobject = *zobject;
// TODO: zend_read_property cache here (strong type properties)
}
#ifdef SW_HAVE_COMPRESSION
bool HttpClient::decompress_response(const char *in, size_t in_len) {
if (in_len == 0) {
return false;
}
size_t reserved_body_length = body->length;
switch (compress_method) {
#ifdef SW_HAVE_ZLIB
case HTTP_COMPRESS_GZIP:
case HTTP_COMPRESS_DEFLATE: {
int status;
int encoding = compress_method == HTTP_COMPRESS_GZIP ? SW_ZLIB_ENCODING_GZIP : SW_ZLIB_ENCODING_DEFLATE;
bool first_decompress = !gzip_stream_active;
size_t total_out;
if (!gzip_stream_active) {
_retry:
memset(&gzip_stream, 0, sizeof(gzip_stream));
gzip_stream.zalloc = php_zlib_alloc;
gzip_stream.zfree = php_zlib_free;
// gzip_stream.total_out = 0;
status = inflateInit2(&gzip_stream, encoding);
if (status != Z_OK) {
swWarn("inflateInit2() failed by %s", zError(status));
return false;
}
gzip_stream_active = true;
}
gzip_stream.next_in = (Bytef *) in;
gzip_stream.avail_in = in_len;
gzip_stream.total_in = 0;
while (1) {
total_out = gzip_stream.total_out;
gzip_stream.avail_out = body->size - body->length;
gzip_stream.next_out = (Bytef *) (body->str + body->length);
SW_ASSERT(body->length <= body->size);
status = inflate(&gzip_stream, Z_SYNC_FLUSH);
if (status >= 0) {
body->length += (gzip_stream.total_out - total_out);
if (body->length + (SW_BUFFER_SIZE_STD / 2) >= body->size) {
if (!body->extend()) {
status = Z_MEM_ERROR;
break;
}
}
}
if (status == Z_STREAM_END || (status == Z_OK && gzip_stream.avail_in == 0)) {
return true;
}
if (status != Z_OK) {
break;
}
}
if (status == Z_DATA_ERROR && first_decompress) {
first_decompress = false;
inflateEnd(&gzip_stream);
encoding = SW_ZLIB_ENCODING_RAW;
body->length = reserved_body_length;
goto _retry;
}
swWarn("HttpClient::decompress_response failed by %s", zError(status));
body->length = reserved_body_length;
return false;
}
#endif
#ifdef SW_HAVE_BROTLI
case HTTP_COMPRESS_BR: {
if (!brotli_decoder_state) {
brotli_decoder_state = BrotliDecoderCreateInstance(php_brotli_alloc, php_brotli_free, nullptr);
if (!brotli_decoder_state) {
swWarn("BrotliDecoderCreateInstance() failed");
return false;
}
}
const char *next_in = in;
size_t available_in = in_len;
while (1) {
size_t available_out = body->size - body->length, reserved_available_out = available_out;
char *next_out = body->str + body->length;
size_t total_out;
BrotliDecoderResult result;
SW_ASSERT(body->length <= body->size);
result = BrotliDecoderDecompressStream(brotli_decoder_state,
&available_in,
(const uint8_t **) &next_in,
&available_out,
(uint8_t **) &next_out,
&total_out);
body->length += reserved_available_out - available_out;
if (result == BROTLI_DECODER_RESULT_SUCCESS || result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
return true;
} else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
if (!body->extend()) {
swWarn("BrotliDecoderDecompressStream() failed, no memory is available");
break;
}
} else {
swWarn("BrotliDecoderDecompressStream() failed, %s",
BrotliDecoderErrorString(BrotliDecoderGetErrorCode(brotli_decoder_state)));
break;
}
}
body->length = reserved_body_length;
return false;
}
#endif
default:
break;
}
swWarn("HttpClient::decompress_response unknown compress method [%d]", compress_method);
return false;
}
#endif
void HttpClient::apply_setting(zval *zset, const bool check_all) {
if (!ZVAL_IS_ARRAY(zset) || php_swoole_array_length(zset) == 0) {
return;
}
if (check_all) {
zval *ztmp;
HashTable *vht = Z_ARRVAL_P(zset);
if (php_swoole_array_get_value(vht, "connect_timeout", ztmp) ||
php_swoole_array_get_value(vht, "timeout", ztmp) /* backward compatibility */) {
connect_timeout = zval_get_double(ztmp);
}
if (php_swoole_array_get_value(vht, "reconnect", ztmp)) {
reconnect_interval = (uint8_t) SW_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, "lowercase_header", ztmp)) {
lowercase_header = 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 (php_swoole_array_get_value(vht, "bind_address", ztmp)) {
zend::String tmp = ztmp;
bind_address = tmp.to_std_string();
}
if (php_swoole_array_get_value(vht, "bind_port", ztmp)) {
bind_port = zval_get_long(ztmp);
bind_port = bind_port > 0 ? bind_port : 0;
}
#ifdef SW_HAVE_ZLIB
if (php_swoole_array_get_value(vht, "websocket_compression", ztmp)) {
websocket_compression = zval_is_true(ztmp);
}
#endif
}
if (socket) {
php_swoole_client_set(socket, zset);
#ifdef SW_USE_OPENSSL
if (socket->http_proxy && !socket->open_ssl)
#else
if (socket->http_proxy)
#endif
{
socket->http_proxy->dont_handshake = 1;
}
if (!bind_address.empty()) {
if (!bind(bind_address, bind_port)) {
return;
}
}
}
}
void HttpClient::set_basic_auth(const std::string &username, const std::string &password) {
std::string input = username + ":" + password;
size_t output_size = sizeof("Basic ") + BASE64_ENCODE_OUT_SIZE(input.size());
char *output = (char *) emalloc(output_size);
if (sw_likely(output)) {
size_t output_len = sprintf(output, "Basic ");
output_len += swBase64_encode((const unsigned char *) input.c_str(), input.size(), output + output_len);
basic_auth = std::string((const char *) output, output_len);
efree(output);
}
}
bool HttpClient::bind(std::string address, int port) {
return socket->bind(address, port);
}
bool HttpClient::connect() {
if (!socket) {
if (!body) {
body = swString_new(SW_HTTP_RESPONSE_INIT_SIZE);
if (!body) {
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), ENOMEM);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), swoole_strerror(ENOMEM));
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_CONNECT_FAILED);
return false;
}
}
php_swoole_check_reactor();
socket = new Socket(socket_type);
if (UNEXPECTED(socket->get_fd() < 0)) {
php_swoole_sys_error(E_WARNING, "new Socket() failed");
zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), errno);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), swoole_strerror(errno));
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_CONNECT_FAILED);
delete socket;
socket = nullptr;
return false;
}
#ifdef SW_USE_OPENSSL
socket->open_ssl = ssl;
#endif
// apply settings
apply_setting(
sw_zend_read_property_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0),
false);
// socket->set_buffer_allocator(&SWOOLE_G(zend_string_allocator));
// connect
socket->set_timeout(connect_timeout, SW_TIMEOUT_CONNECT);
if (!socket->connect(host, port)) {
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_CONNECT_FAILED);
close();
return false;
}
reconnected_count = 0;
zend_update_property_bool(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 1);
}
return true;
}
bool HttpClient::keep_liveness() {
if (!socket || !socket->check_liveness()) {
if (socket) {
/* in progress */
socket->check_bound_co(SW_EVENT_RDWR);
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_SERVER_RESET);
close(false);
}
for (; reconnected_count < reconnect_interval; reconnected_count++) {
if (connect()) {
return true;
}
}
return false;
}
return true;
}
bool HttpClient::send() {
zval *zvalue = nullptr;
uint32_t header_flag = 0x0;
zval *zmethod, *zheaders, *zbody, *zupload_files, *zcookies, *z_download_file;
if (path.length() == 0) {
php_swoole_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_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADERS), 0);
if (ZVAL_IS_ARRAY(zattr)) {
zend_hash_clean(Z_ARRVAL_P(zattr));
}
zattr = sw_zend_read_property_ex(
swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SET_COOKIE_HEADERS), 0);
if (ZVAL_IS_ARRAY(zattr)) {
zend_hash_clean(Z_ARRVAL_P(zattr));
}
zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("body"), "");
}
if (!keep_liveness()) {
return false;
} else {
zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), 0);
zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "");
zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("statusCode"), 0);
}
/* another coroutine is connecting */
socket->check_bound_co(SW_EVENT_WRITE);
// clear errno
swoole_set_last_error(0);
// alloc buffer
swString *buffer = socket->get_write_buffer();
swString_clear(buffer);
// clear body
swString_clear(body);
zmethod = sw_zend_read_property_not_null_ex(
swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_METHOD), 0);
zheaders =
sw_zend_read_property_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_HEADERS), 0);
zbody = sw_zend_read_property_not_null_ex(
swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_BODY), 0);
zupload_files =
sw_zend_read_property_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_UPLOAD_FILES), 0);
zcookies = sw_zend_read_property_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIES), 0);
z_download_file = sw_zend_read_property_not_null_ex(
swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_DOWNLOAD_FILE), 0);
// ============ host ============
zend::String str_host;
if ((ZVAL_IS_ARRAY(zheaders)) && ((zvalue = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("Host"))) ||
(zvalue = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("host"))))) {
str_host = zvalue;
}
// ============ download ============
if (z_download_file) {
download_file_name = z_download_file;
download_offset = zval_get_long(sw_zend_read_property_ex(
swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_DOWNLOAD_OFFSET), 0));
}
// ============ method ============
{
zend::String str_method;
const char *method;
size_t method_len;
if (zmethod) {
str_method = zmethod;
method = str_method.val();
method_len = str_method.len();
} else {
method = zbody ? "POST" : "GET";
method_len = strlen(method);
}
this->method = swHttp_get_method(method, method_len);
buffer->append(method, method_len);
buffer->append(ZEND_STRL(" "));
}
// ============ path & proxy ============
#ifdef SW_USE_OPENSSL
if (socket->http_proxy && !socket->open_ssl)
#else
if (socket->http_proxy)
#endif
{
const static char *pre = "http://";
char *_host = (char *) host.c_str();
size_t _host_len = host.length();
if (str_host.get()) {
_host = str_host.val();
_host_len = str_host.len();
}
size_t proxy_uri_len = path.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, path.c_str());
buffer->append(proxy_uri, proxy_uri_len);
efree(proxy_uri);
} else {
buffer->append(path.c_str(), path.length());
}
// ============ protocol ============
buffer->append(ZEND_STRL(" HTTP/1.1\r\n"));
// ============ headers ============
char *key;
uint32_t keylen;
int keytype;
// As much as possible to ensure that Host is the first header.
// See: http://tools.ietf.org/html/rfc7230#section-5.4
if (str_host.get()) {
add_headers(buffer, ZEND_STRL("Host"), str_host.val(), str_host.len());
} else {
// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.23
const std::string *_host;
std::string __host;
#ifndef SW_USE_OPENSSL
if (port != 80)
#else
if (!ssl ? port != 80 : port != 443)
#endif
{
__host = std_string::format("%s:%u", host.c_str(), port);
_host = &__host;
} else {
_host = &host;
}
add_headers(buffer, ZEND_STRL("Host"), _host->c_str(), _host->length());
}
if (ZVAL_IS_ARRAY(zheaders)) {
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zheaders), key, keylen, keytype, zvalue) {
if (UNEXPECTED(HASH_KEY_IS_STRING != keytype || ZVAL_IS_NULL(zvalue))) {
continue;
}
if (SW_STRCASEEQ(key, keylen, "Host")) {
continue;
}
if (SW_STRCASEEQ(key, keylen, "Content-Length")) {
header_flag |= HTTP_HEADER_CONTENT_LENGTH;
// ignore custom Content-Length value
continue;
} else if (SW_STRCASEEQ(key, keylen, "Connection")) {
header_flag |= HTTP_HEADER_CONNECTION;
} else if (SW_STRCASEEQ(key, keylen, "Accept-Encoding")) {
header_flag |= HTTP_HEADER_ACCEPT_ENCODING;
}
zend::String str_value(zvalue);
add_headers(buffer, key, keylen, str_value.val(), str_value.len());
}
SW_HASHTABLE_FOREACH_END();
}
if (!basic_auth.empty()) {
add_headers(buffer, ZEND_STRL("Authorization"), basic_auth.c_str(), basic_auth.size());
}
if (!(header_flag & HTTP_HEADER_CONNECTION)) {
if (keep_alive) {
add_headers(buffer, ZEND_STRL("Connection"), ZEND_STRL("keep-alive"));
} else {
add_headers(buffer, ZEND_STRL("Connection"), ZEND_STRL("closed"));
}
}
#ifdef SW_HAVE_COMPRESSION
if (!(header_flag & HTTP_HEADER_ACCEPT_ENCODING)) {
add_headers(buffer,
ZEND_STRL("Accept-Encoding"),
#if defined(SW_HAVE_ZLIB) && defined(SW_HAVE_BROTLI)
ZEND_STRL("gzip, deflate, br")
#else
#ifdef SW_HAVE_ZLIB
ZEND_STRL("gzip, deflate")
#else
#ifdef SW_HAVE_BROTLI
ZEND_STRL("br")
#endif
#endif
#endif
);
}
#endif
// ============ cookies ============
if (ZVAL_IS_ARRAY(zcookies)) {
buffer->append(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, zvalue) {
i++;
if (HASH_KEY_IS_STRING != keytype) {
continue;
}
zend::String str_value(zvalue);
if (str_value.len() == 0) {
continue;
}
buffer->append(key, keylen);
buffer->append("=", 1);
int encoded_value_len;
encoded_value = php_swoole_url_encode(str_value.val(), str_value.len(), &encoded_value_len);
if (encoded_value) {
buffer->append(encoded_value, encoded_value_len);
efree(encoded_value);
}
if (i < n_cookie) {
buffer->append("; ", 2);
}
}
SW_HASHTABLE_FOREACH_END();
buffer->append(ZEND_STRL("\r\n"));
}
// ============ multipart/form-data ============
if ((has_upload_files = (php_swoole_array_length_safe(zupload_files) > 0))) {
char header_buf[2048];
char boundary_str[SW_HTTP_CLIENT_BOUNDARY_TOTAL_SIZE];
int n;
// ============ 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);
buffer->append(header_buf, n);
// ============ content-length ============
size_t content_length = 0;
// calculate length before encode array
if (zbody && ZVAL_IS_ARRAY(zbody)) {
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zbody), key, keylen, keytype, zvalue)
if (UNEXPECTED(HASH_KEY_IS_STRING != keytype || ZVAL_IS_NULL(zvalue))) {
continue;
}
zend::String str_value(zvalue);
// strlen("%.*s")*2 = 8
// header + body + CRLF(2)
content_length += (sizeof(SW_HTTP_FORM_RAW_DATA_FMT) - SW_HTTP_FORM_RAW_DATA_FMT_LEN - 1) +
(sizeof(boundary_str) - 1) + keylen + str_value.len() + 2;
SW_HASHTABLE_FOREACH_END();
}
zval *zname;
zval *ztype;
zval *zsize = nullptr;
zval *zpath = nullptr;
zval *zcontent = nullptr;
zval *zfilename;
zval *zoffset;
// calculate length of files
{
// upload files
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zupload_files), key, keylen, keytype, zvalue) {
HashTable *ht = Z_ARRVAL_P(zvalue);
if (!(zname = zend_hash_str_find(ht, ZEND_STRL("name")))) {
continue;
}
if (!(zfilename = zend_hash_str_find(ht, ZEND_STRL("filename")))) {
continue;
}
if (!(zsize = zend_hash_str_find(ht, ZEND_STRL("size")))) {
continue;
}
if (!(ztype = zend_hash_str_find(ht, ZEND_STRL("type")))) {
continue;
}
// strlen("%.*s")*4 = 16
// header + body + CRLF(2)
content_length += (sizeof(SW_HTTP_FORM_FILE_DATA_FMT) - SW_HTTP_FORM_FILE_DATA_FMT_LEN - 1) +
(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();
}
add_content_length(buffer, content_length + sizeof(boundary_str) - 1 + 6);
// ============ form-data body ============
if (zbody && ZVAL_IS_ARRAY(zbody)) {
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zbody), key, keylen, keytype, zvalue) {
if (UNEXPECTED(HASH_KEY_IS_STRING != keytype || ZVAL_IS_NULL(zvalue))) {
continue;
}
zend::String str_value(zvalue);
n = sw_snprintf(header_buf,
sizeof(header_buf),
SW_HTTP_FORM_RAW_DATA_FMT,
(int) (sizeof(boundary_str) - 1),
boundary_str,
keylen,
key);
buffer->append(header_buf, n);
buffer->append(str_value.val(), str_value.len());
buffer->append(ZEND_STRL("\r\n"));
}
SW_HASHTABLE_FOREACH_END();
}
if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) {
goto _send_fail;
}
{
// upload files
SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zupload_files), key, keylen, keytype, zvalue) {
if (!(zname = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("name")))) {
continue;
}
if (!(zfilename = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("filename")))) {
continue;
}
/**
* from disk file
*/
if (!(zcontent = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("content")))) {
// file path
if (!(zpath = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("path")))) {
continue;
}
// file offset
if (!(zoffset = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("offset")))) {
continue;
}
zcontent = nullptr;
} else {
zpath = nullptr;
zoffset = nullptr;
}
if (!(zsize = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("size")))) {
continue;
}
if (!(ztype = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("type")))) {
continue;
}
/**
* part header
*/
n = sw_snprintf(header_buf,
sizeof(header_buf),
SW_HTTP_FORM_FILE_DATA_FMT,
(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(buffer);
buffer->append(header_buf, n);
buffer->append(Z_STRVAL_P(zcontent), Z_STRLEN_P(zcontent));
buffer->append("\r\n", 2);
if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) {
goto _send_fail;
}
}
/**
* from disk file
*/
else {
if (socket->send_all(header_buf, n) != n) {
goto _send_fail;
}
if (!socket->sendfile(Z_STRVAL_P(zpath), Z_LVAL_P(zoffset), Z_LVAL_P(zsize))) {
goto _send_fail;
}
if (socket->send_all("\r\n", 2) != 2) {
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_all(header_buf, n) != n) {
goto _send_fail;
}
wait = true;
return true;
}
// ============ x-www-form-urlencoded or raw ============
else if (zbody) {
if (ZVAL_IS_ARRAY(zbody)) {
size_t len;
add_headers(buffer, ZEND_STRL("Content-Type"), ZEND_STRL("application/x-www-form-urlencoded"));
if (php_swoole_array_length(zbody) > 0) {
smart_str formstr_s = {};
char *formstr = php_swoole_http_build_query(zbody, &len, &formstr_s);
if (formstr == nullptr) {
php_swoole_error(E_WARNING, "http_build_query failed");
return false;
}
add_content_length(buffer, len);
buffer->append(formstr, len);
smart_str_free(&formstr_s);
} else {
add_content_length(buffer, 0);
}
} else {
char *body;
size_t body_length = php_swoole_get_send_data(zbody, &body);
add_content_length(buffer, body_length);
buffer->append(body, body_length);
}
}
// ============ no body ============
else {
if (header_flag & HTTP_HEADER_CONTENT_LENGTH) {
add_content_length(buffer, 0);
} else {
buffer->append(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,
path.c_str(),
socket->get_fd(),
Coroutine::get_current_cid(),
buffer->length,
(int) buffer->length,
buffer->str);
if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) {
_send_fail:
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("statusCode"), HTTP_CLIENT_ESTATUS_SEND_FAILED);
close();
return false;
}
wait = true;
return true;
}
bool HttpClient::exec(std::string path) {
this->path = path;
// bzero when make a new reqeust
reconnected_count = 0;
if (defer) {
return send();
} else {
return send() && recv();
}
}
bool HttpClient::recv(double timeout) {
if (!wait) {
return false;
}
if (!socket || !socket->is_connect()) {
swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION);
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error());
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "connection is not available");
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_SERVER_RESET);
return false;
}
if (!recv_http_response(timeout)) {
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(
swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
socket->errCode == ETIMEDOUT ? HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT : HTTP_CLIENT_ESTATUS_SERVER_RESET);
close();
return false;
}
/**
* TODO: Sec-WebSocket-Accept check
*/
if (websocket) {
socket->open_length_check = 1;
socket->protocol.package_length_size = SW_WEBSOCKET_HEADER_LEN;
socket->protocol.package_length_offset = 0;
socket->protocol.package_body_offset = 0;
socket->protocol.get_package_length = swWebSocket_get_package_length;
}
// handler keep alive
if (!keep_alive && !websocket) {
close();
} else {
reset();
}
return true;
}
void HttpClient::recv(zval *zframe, double timeout) {
SW_ASSERT(websocket);
ZVAL_FALSE(zframe);
if (!socket || !socket->is_connect()) {
swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION);
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error());
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "connection is not available");
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_SERVER_RESET);
return;
}
ssize_t retval = socket->recv_packet(timeout);
if (retval <= 0) {
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(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;
#ifdef SW_HAVE_ZLIB
php_swoole_websocket_frame_unpack_ex(&msg, zframe, websocket_compression);
#else
php_swoole_websocket_frame_unpack(&msg, zframe);
#endif
zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("fd"), socket->get_fd());
}
}
bool HttpClient::recv_http_response(double timeout) {
ssize_t retval = 0;
size_t total_bytes = 0, parsed_n = 0;
swString *buffer = socket->get_read_buffer();
// re-init http response parser
swoole_http_parser_init(&parser, PHP_HTTP_RESPONSE);
parser.data = this;
if (timeout == 0) {
timeout = socket->get_timeout(SW_TIMEOUT_READ);
}
Socket::timeout_controller tc(socket, timeout, SW_TIMEOUT_READ);
while (true) {
if (sw_unlikely(tc.has_timedout(SW_TIMEOUT_READ))) {
return false;
}
retval = socket->recv(buffer->str, buffer->size);
if (sw_unlikely(retval <= 0)) {
if (retval == 0) {
socket->set_err(ECONNRESET);
if (total_bytes > 0 && !swoole_http_should_keep_alive(&parser)) {
http_parser_on_message_complete(&parser);
return true;
}
}
return false;
}
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,
parser.state == s_start_res);
if (parser.state == s_start_res) {
// handle redundant data (websocket packet)
if (parser.upgrade && (size_t) retval > parsed_n + SW_WEBSOCKET_HEADER_LEN) {
buffer->length = retval;
buffer->offset = parsed_n;
buffer->reduce(parsed_n);
}
return true;
}
if (sw_unlikely(parser.state == s_dead)) {
socket->set_err(EPROTO);
return false;
}
}
}
bool HttpClient::upgrade(std::string path) {
defer = false;
if (!websocket) {
char buf[SW_WEBSOCKET_KEY_LENGTH + 1];
zval *zheaders = sw_zend_read_and_convert_property_array(
swoole_http_client_coro_ce, zobject, ZEND_STRL("requestHeaders"), 0);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("requestMethod"), "GET");
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));
#ifdef SW_HAVE_ZLIB
if (websocket_compression) {
add_assoc_string(zheaders, "Sec-Websocket-Extensions", (char *) SW_WEBSOCKET_EXTENSION_DEFLATE);
}
#endif
exec(path);
}
return websocket;
}
bool HttpClient::push(zval *zdata, zend_long opcode, uint8_t flags) {
if (!websocket) {
swoole_set_last_error(SW_ERROR_WEBSOCKET_HANDSHAKE_FAILED);
php_swoole_fatal_error(E_WARNING, "websocket handshake failed, cannot push data");
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error());
zend_update_property_string(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("errMsg"),
"websocket handshake failed, cannot push data");
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_CONNECT_FAILED);
return false;
}
if (!socket || !socket->is_connect()) {
swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION);
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error());
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "connection is not available");
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_SERVER_RESET);
return false;
}
swString *buffer = socket->get_write_buffer();
swString_clear(buffer);
if (php_swoole_websocket_frame_is_object(zdata)) {
if (php_swoole_websocket_frame_object_pack(buffer, zdata, websocket_mask, websocket_compression) < 0) {
return false;
}
} else {
if (php_swoole_websocket_frame_pack(buffer, zdata, opcode, flags, websocket_mask, websocket_compression) < 0) {
return false;
}
}
if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) {
zend_update_property_long(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode);
zend_update_property_string(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg);
zend_update_property_long(swoole_http_client_coro_ce,
SW_Z8_OBJ_P(zobject),
ZEND_STRL("statusCode"),
HTTP_CLIENT_ESTATUS_SERVER_RESET);
close();
return false;
} else {
return true;
}
}
void HttpClient::reset() {
wait = false;
#ifdef SW_HAVE_COMPRESSION
compress_method = HTTP_COMPRESS_NONE;
compression_error = false;
#endif
#ifdef SW_HAVE_ZLIB
if (gzip_stream_active) {
inflateEnd(&gzip_stream);
gzip_stream_active = false;
}
#endif
#ifdef SW_HAVE_BROTLI
if (brotli_decoder_state) {
BrotliDecoderDestroyInstance(brotli_decoder_state);
brotli_decoder_state = nullptr;
}
#endif
if (has_upload_files) {
zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("uploadFiles"));
}
if (download_file_fd >= 0) {
::close(download_file_fd);
download_file_fd = -1;
download_file_name.release();
download_offset = 0;
zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("downloadFile"));
zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("downloadOffset"), 0);
}
}
bool HttpClient::close(const bool should_be_reset) {
Socket *_socket = socket;
if (_socket) {
zend_update_property_bool(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0);
if (!_socket->has_bound()) {
if (should_be_reset) {
reset();
}
// reset the properties that depend on the connection
websocket = false;
#ifdef SW_HAVE_ZLIB
websocket_compression = false;
#endif
if (tmp_write_buffer) {
delete tmp_write_buffer;
}
tmp_write_buffer = socket->pop_write_buffer();
socket = nullptr;
}
php_swoole_client_coro_socket_free(_socket);
return true;
}
return false;
}
HttpClient::~HttpClient() {
close();
if (body) {
delete body;
}
if (tmp_write_buffer) {
delete tmp_write_buffer;
}
}
static sw_inline HttpClientObject *php_swoole_http_client_coro_fetch_object(zend_object *obj) {
return (HttpClientObject *) ((char *) obj - swoole_http_client_coro_handlers.offset);
}
static sw_inline HttpClient *php_swoole_get_phc(zval *zobject) {
HttpClient *phc = php_swoole_http_client_coro_fetch_object(Z_OBJ_P(zobject))->phc;
if (UNEXPECTED(!phc)) {
php_swoole_fatal_error(E_ERROR, "you must call Http Client constructor first");
}
return phc;
}
static void php_swoole_http_client_coro_free_object(zend_object *object) {
HttpClientObject *hcc = php_swoole_http_client_coro_fetch_object(object);
if (hcc->phc) {
delete hcc->phc;
hcc->phc = nullptr;
}
zend_object_std_dtor(&hcc->std);
}
static zend_object *php_swoole_http_client_coro_create_object(zend_class_entry *ce) {
HttpClientObject *hcc = (HttpClientObject *) zend_object_alloc(sizeof(HttpClientObject ), ce);
zend_object_std_init(&hcc->std, ce);
object_properties_init(&hcc->std, ce);
hcc->std.handlers = &swoole_http_client_coro_handlers;
return &hcc->std;
}
void php_swoole_http_client_coro_minit(int module_number) {
SW_INIT_CLASS_ENTRY(swoole_http_client_coro,
"Swoole\\Coroutine\\Http\\Client",
nullptr,
"Co\\Http\\Client",
swoole_http_client_coro_methods);
SW_SET_CLASS_SERIALIZABLE(swoole_http_client_coro, zend_class_serialize_deny, zend_class_unserialize_deny);
SW_SET_CLASS_CLONEABLE(swoole_http_client_coro, sw_zend_class_clone_deny);
SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_client_coro, sw_zend_class_unset_property_deny);
SW_SET_CLASS_CUSTOM_OBJECT(swoole_http_client_coro,
php_swoole_http_client_coro_create_object,
php_swoole_http_client_coro_free_object,
HttpClientObject ,
std);
// client status
zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_string(swoole_http_client_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC);
zend_declare_property_bool(swoole_http_client_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC);
// client info
zend_declare_property_string(swoole_http_client_coro_ce, ZEND_STRL("host"), "", ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_bool(swoole_http_client_coro_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC);
// request properties
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("requestMethod"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("requestHeaders"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("requestBody"), ZEND_ACC_PUBLIC);
// always set by API (make it private?)
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("uploadFiles"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("downloadFile"), ZEND_ACC_PUBLIC);
zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("downloadOffset"), 0, ZEND_ACC_PUBLIC);
// response properties
zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("statusCode"), 0, ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("headers"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("set_cookie_headers"), ZEND_ACC_PUBLIC);
zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC);
zend_declare_property_string(swoole_http_client_coro_ce, ZEND_STRL("body"), "", ZEND_ACC_PUBLIC);
SW_INIT_CLASS_ENTRY_EX(swoole_http_client_coro_exception,
"Swoole\\Coroutine\\Http\\Client\\Exception",
nullptr,
"Co\\Http\\Client\\Exception",
nullptr,
swoole_exception);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED", HTTP_CLIENT_ESTATUS_CONNECT_FAILED);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT", HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT);
SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET", HTTP_CLIENT_ESTATUS_SERVER_RESET);
#ifdef SW_HAVE_COMPRESSION
swoole_zlib_buffer = swString_new(SW_HTTP_RESPONSE_INIT_SIZE);
if (!swoole_zlib_buffer) {
php_swoole_fatal_error(E_ERROR, "[2] swString_new(%d) failed", SW_HTTP_RESPONSE_INIT_SIZE);
}
#endif
}
static PHP_METHOD(swoole_http_client_coro, __construct) {
HttpClientObject *hcc = php_swoole_http_client_coro_fetch_object(Z_OBJ_P(ZEND_THIS));
char *host;
size_t host_len;
zend_long port = 80;
zend_bool ssl = 0;
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);
zend_update_property_stringl(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("host"), host, host_len);
zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("port"), port);
zend_update_property_bool(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("ssl"), ssl);
// check host
if (host_len == 0) {
zend_throw_exception_ex(swoole_http_client_coro_exception_ce, EINVAL, "host is empty");
RETURN_FALSE;
}
// check ssl
#ifndef SW_USE_OPENSSL
if (ssl) {
zend_throw_exception_ex(
swoole_http_client_coro_exception_ce,
EPROTONOSUPPORT,
"you must configure with `--enable-openssl` to support ssl connection when compiling Swoole");
RETURN_FALSE;
}
#endif
hcc->phc = new HttpClient(ZEND_THIS, std::string(host, host_len), port, ssl);
}
static PHP_METHOD(swoole_http_client_coro, __destruct) {}
static PHP_METHOD(swoole_http_client_coro, set) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
zval *zset;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(zset)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
if (php_swoole_array_length(zset) == 0) {
RETURN_FALSE;
} else {
zval *zsettings =
sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0);
php_array_merge(Z_ARRVAL_P(zsettings), Z_ARRVAL_P(zset));
phc->apply_setting(zset);
RETURN_TRUE;
}
}
static PHP_METHOD(swoole_http_client_coro, getDefer) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
RETURN_BOOL(phc->defer);
}
static PHP_METHOD(swoole_http_client_coro, setDefer) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
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) {
char *method;
size_t method_length;
// Notice: maybe string or array
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(method, method_length)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property_stringl(
swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestMethod"), method, method_length);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, setHeaders) {
zval *headers;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_EX(headers, 0, 1)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestHeaders"), headers);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, setBasicAuth) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
char *username, *password;
size_t username_len, password_len;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STRING(username, username_len)
Z_PARAM_STRING(password, password_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
phc->set_basic_auth(std::string(username, username_len), std::string(password, password_len));
}
static PHP_METHOD(swoole_http_client_coro, setCookies) {
zval *cookies;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_EX(cookies, 0, 1)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("cookies"), cookies);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, setData) {
zval *zdata;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(zdata)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestBody"), zdata);
RETURN_TRUE;
}
static PHP_METHOD(swoole_http_client_coro, addFile) {
char *path;
size_t l_path;
char *name;
size_t l_name;
char *type = nullptr;
size_t l_type = 0;
char *filename = nullptr;
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) {
php_swoole_sys_error(E_WARNING, "stat(%s) failed", path);
RETURN_FALSE;
}
if (file_stat.st_size == 0) {
php_swoole_sys_error(E_WARNING, "cannot send empty file[%s]", filename);
RETURN_FALSE;
}
if (file_stat.st_size <= offset) {
php_swoole_error(E_WARNING, "parameter $offset[" ZEND_LONG_FMT "] exceeds the file size", offset);
RETURN_FALSE;
}
if (length > file_stat.st_size - offset) {
php_swoole_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::mime_type::get(path).c_str();
l_type = strlen(type);
}
if (l_filename == 0) {
char *dot = strrchr(path, '/');
if (dot == nullptr) {
filename = path;
l_filename = l_path;
} else {
filename = dot + 1;
l_filename = strlen(filename);
}
}
zval *zupload_files =
sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, ZEND_THIS, ZEND_STRL("uploadFiles"), 0);
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);
RETURN_BOOL(add_next_index_zval(zupload_files, &zupload_file) == SUCCESS);
}
static PHP_METHOD(swoole_http_client_coro, addData) {
char *data;
size_t l_data;
char *name;
size_t l_name;
char *type = nullptr;
size_t l_type = 0;
char *filename = nullptr;
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_and_convert_property_array(swoole_http_client_coro_ce, ZEND_THIS, ZEND_STRL("uploadFiles"), 0);
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);
RETURN_BOOL(add_next_index_zval(zupload_files, &zupload_file) == SUCCESS);
}
static PHP_METHOD(swoole_http_client_coro, execute) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
char *path = nullptr;
size_t path_len = 0;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(path, path_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
RETURN_BOOL(phc->exec(std::string(path, path_len)));
}
static PHP_METHOD(swoole_http_client_coro, get) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
char *path = nullptr;
size_t path_len = 0;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(path, path_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestMethod"), "GET");
RETURN_BOOL(phc->exec(std::string(path, path_len)));
}
static PHP_METHOD(swoole_http_client_coro, post) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
char *path = nullptr;
size_t path_len = 0;
zval *post_data;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_STRING(path, path_len)
Z_PARAM_ZVAL(post_data)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestMethod"), "POST");
zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("requestBody"), post_data);
RETURN_BOOL(phc->exec(std::string(path, path_len)));
}
static PHP_METHOD(swoole_http_client_coro, download) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
char *path;
size_t path_len;
zval *download_file;
zend_long offset = 0;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STRING(path, path_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, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("downloadFile"), download_file);
zend_update_property_long(swoole_http_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("downloadOffset"), offset);
RETURN_BOOL(phc->exec(std::string(path, path_len)));
}
static PHP_METHOD(swoole_http_client_coro, upgrade) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
char *path = nullptr;
size_t path_len = 0;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_STRING(path, path_len)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
RETURN_BOOL(phc->upgrade(std::string(path, path_len)));
}
static PHP_METHOD(swoole_http_client_coro, push) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
zval *zdata;
zend_long opcode = WEBSOCKET_OPCODE_TEXT;
zval *zflags = nullptr;
zend_long flags = SW_WEBSOCKET_FLAG_FIN;
ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_ZVAL(zdata)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(opcode)
Z_PARAM_ZVAL_EX(zflags, 1, 0)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
if (zflags != nullptr) {
flags = zval_get_long(zflags);
}
RETURN_BOOL(phc->push(zdata, opcode, flags & SW_WEBSOCKET_FLAGS_ALL));
}
static PHP_METHOD(swoole_http_client_coro, recv) {
HttpClient *phc = php_swoole_get_phc(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);
if (phc->websocket) {
phc->recv(return_value, timeout);
return;
} else {
RETURN_BOOL(phc->recv(timeout));
}
}
static PHP_METHOD(swoole_http_client_coro, close) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
RETURN_BOOL(phc->close());
}
static PHP_METHOD(swoole_http_client_coro, getBody) {
SW_RETURN_PROPERTY("body");
}
static PHP_METHOD(swoole_http_client_coro, getHeaders) {
SW_RETURN_PROPERTY("headers");
}
static PHP_METHOD(swoole_http_client_coro, getCookies) {
SW_RETURN_PROPERTY("cookies");
}
static PHP_METHOD(swoole_http_client_coro, getStatusCode) {
SW_RETURN_PROPERTY("statusCode");
}
static PHP_METHOD(swoole_http_client_coro, getHeaderOut) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
phc->get_header_out(return_value);
}
static PHP_METHOD(swoole_http_client_coro, getsockname) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
phc->getsockname(return_value);
}
static PHP_METHOD(swoole_http_client_coro, getpeername) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
phc->getpeername(return_value);
}
#ifdef SW_USE_OPENSSL
static PHP_METHOD(swoole_http_client_coro, getPeerCert) {
HttpClient *phc = php_swoole_get_phc(ZEND_THIS);
phc->getpeercert(return_value);
}
#endif
C
1
https://gitee.com/lampsdx/swoole.git
git@gitee.com:lampsdx/swoole.git
lampsdx
swoole
swoole-src
master

搜索帮助