lua-webclient.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. /****************************************************************************
  2. Copyright (c) 2014 dpull.com
  3. http://www.dpull.com
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. ****************************************************************************/
  20. #define LUA_LIB
  21. #include <stdlib.h>
  22. #include <assert.h>
  23. #include <stdbool.h>
  24. #include <string.h>
  25. #include <curl/curl.h>
  26. #include "lua.h"
  27. #include "lualib.h"
  28. #include "lauxlib.h"
  29. #define IP_LENGTH 16
  30. #define MAX(a, b) (((a) > (b)) ? (a) : (b))
  31. #define LUA_WEB_CLIENT_MT ("com.dpull.lib.WebClientMT")
  32. #define ENABLE_FOLLOWLOCATION 1
  33. struct webclient
  34. {
  35. CURLM* curlm;
  36. CURL* encoding_curl;
  37. };
  38. struct webrequest
  39. {
  40. CURL* curl;
  41. struct curl_slist* header;
  42. char error[CURL_ERROR_SIZE];
  43. char* content;
  44. size_t content_length;
  45. size_t content_maxlength;
  46. bool content_realloc_failed;
  47. };
  48. static int webclient_create(lua_State* l)
  49. {
  50. curl_version_info_data* data = curl_version_info(CURLVERSION_NOW);
  51. if (data->version_num < 0x070f04)
  52. return luaL_error(l, "requires 7.15.4 or higher curl, current version is %s", data->version);
  53. curl_global_init(CURL_GLOBAL_ALL);
  54. CURLM* curlm = curl_multi_init();
  55. if (!curlm) {
  56. curl_global_cleanup();
  57. return luaL_error(l, "webclient create failed");
  58. }
  59. struct webclient* webclient = (struct webclient*)lua_newuserdata(l, sizeof(*webclient));
  60. webclient->curlm = curlm;
  61. webclient->encoding_curl = NULL;
  62. luaL_getmetatable(l, LUA_WEB_CLIENT_MT);
  63. lua_setmetatable(l, -2);
  64. return 1;
  65. }
  66. static int webclient_destory(lua_State* l)
  67. {
  68. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  69. if (!webclient)
  70. return luaL_argerror(l, 1, "parameter self invalid");
  71. if (webclient->encoding_curl) {
  72. curl_easy_cleanup(webclient->encoding_curl);
  73. webclient->encoding_curl = NULL;
  74. }
  75. curl_multi_cleanup(webclient->curlm);
  76. webclient->curlm = NULL;
  77. curl_global_cleanup();
  78. return 0;
  79. }
  80. static CURL* webclient_realquery(struct webclient* webclient)
  81. {
  82. while (true)
  83. {
  84. int msgs_in_queue;
  85. CURLMsg* curlmsg = curl_multi_info_read(webclient->curlm, &msgs_in_queue);
  86. if (!curlmsg)
  87. return NULL;
  88. if (curlmsg->msg != CURLMSG_DONE)
  89. continue;
  90. return curlmsg->easy_handle;
  91. }
  92. }
  93. static int webclient_query(lua_State* l)
  94. {
  95. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  96. if (!webclient)
  97. return luaL_argerror(l, 1, "parameter self invalid");
  98. CURL* handle = webclient_realquery(webclient);
  99. if (handle) {
  100. lua_pushlightuserdata(l, handle);
  101. return 1;
  102. }
  103. int running_handles;
  104. CURLMcode euRetCode = curl_multi_perform(webclient->curlm, &running_handles);
  105. if (euRetCode != CURLM_OK && euRetCode != CURLM_CALL_MULTI_PERFORM) {
  106. return luaL_error(l, "webclient query failed");
  107. }
  108. handle = webclient_realquery(webclient);
  109. if (handle) {
  110. lua_pushlightuserdata(l, handle);
  111. return 1;
  112. }
  113. return 0;
  114. }
  115. static size_t write_callback(char* buffer, size_t block_size, size_t count, void* arg)
  116. {
  117. struct webrequest* webrequest = (struct webrequest*)arg;
  118. assert(webrequest);
  119. size_t length = block_size * count;
  120. if (webrequest->content_realloc_failed)
  121. return length;
  122. if (webrequest->content_length + length > webrequest->content_maxlength) {
  123. webrequest->content_maxlength = MAX(webrequest->content_maxlength, webrequest->content_length + length);
  124. webrequest->content_maxlength = MAX(webrequest->content_maxlength, 512);
  125. webrequest->content_maxlength = 2 * webrequest->content_maxlength;
  126. void* new_content = (char*)realloc(webrequest->content, webrequest->content_maxlength);
  127. if (!new_content) {
  128. webrequest->content_realloc_failed = true;
  129. return length;
  130. }
  131. webrequest->content = new_content;
  132. }
  133. memcpy(webrequest->content + webrequest->content_length, buffer, length);
  134. webrequest->content_length += length;
  135. return length;
  136. }
  137. static struct webrequest* webclient_realrequest(struct webclient* webclient, const char* url, const char* postdata, size_t postdatalen, long connect_timeout_ms)
  138. {
  139. struct webrequest* webrequest = (struct webrequest*)malloc(sizeof(*webrequest));
  140. memset(webrequest, 0, sizeof(*webrequest));
  141. CURL* handle = curl_easy_init();
  142. if (!handle)
  143. goto failed;
  144. curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1);
  145. curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, false);
  146. curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, false);
  147. curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, ENABLE_FOLLOWLOCATION);
  148. curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
  149. curl_easy_setopt(handle, CURLOPT_WRITEDATA, webrequest);
  150. curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, webrequest->error);
  151. curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT_MS, connect_timeout_ms);
  152. curl_easy_setopt(handle, CURLOPT_URL, url);
  153. if (postdata) {
  154. curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, (long)postdatalen);
  155. curl_easy_setopt(handle, CURLOPT_POSTFIELDS, postdata);
  156. }
  157. if (curl_multi_add_handle(webclient->curlm, handle) == CURLM_OK) {
  158. webrequest->curl = handle;
  159. return webrequest;
  160. }
  161. failed:
  162. if (handle) {
  163. curl_easy_cleanup(handle);
  164. handle = NULL;
  165. }
  166. free(webrequest);
  167. return NULL;
  168. }
  169. static int webclient_request(lua_State* l)
  170. {
  171. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  172. if (!webclient)
  173. return luaL_argerror(l, 1, "parameter self invalid");
  174. const char* url = lua_tostring(l, 2);
  175. if (!url)
  176. return luaL_argerror(l, 2, "parameter url invalid");
  177. const char* postdata = NULL;
  178. size_t postdatalen = 0;
  179. long connect_timeout_ms = 5000;
  180. int top = lua_gettop(l);
  181. if (top > 2 && lua_isstring(l, 3))
  182. postdata = lua_tolstring(l, 3, &postdatalen);
  183. if (top > 3 && lua_isnumber(l, 4)) {
  184. connect_timeout_ms = lua_tointeger(l, 4);
  185. if (connect_timeout_ms < 0)
  186. return luaL_argerror(l, 4, "parameter connect_timeout_ms invalid");
  187. }
  188. struct webrequest* webrequest = webclient_realrequest(webclient, url, postdata, postdatalen, connect_timeout_ms);
  189. if (!webrequest)
  190. return 0;
  191. lua_pushlightuserdata(l, webrequest);
  192. lua_pushlightuserdata(l, webrequest->curl);
  193. return 2;
  194. }
  195. static int webclient_removerequest(lua_State* l)
  196. {
  197. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  198. if (!webclient)
  199. return luaL_argerror(l, 1, "parameter self invalid");
  200. struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2);
  201. if (!webrequest)
  202. return luaL_argerror(l, 2, "parameter index invalid");
  203. curl_multi_remove_handle(webclient->curlm, webrequest->curl);
  204. curl_easy_cleanup(webrequest->curl);
  205. curl_slist_free_all(webrequest->header);
  206. if (webrequest->content)
  207. free(webrequest->content);
  208. free(webrequest);
  209. return 0;
  210. }
  211. static int webclient_getrespond(lua_State* l)
  212. {
  213. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  214. if (!webclient)
  215. return luaL_argerror(l, 1, "parameter self invalid");
  216. struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2);
  217. if (!webrequest)
  218. return luaL_argerror(l, 2, "parameter index invalid");
  219. if (webrequest->content_realloc_failed) {
  220. strncpy(webrequest->error, "not enough memory.", sizeof(webrequest->error));
  221. }
  222. if (webrequest->error[0] == '\0') {
  223. lua_pushlstring(l, webrequest->content, webrequest->content_length);
  224. return 1;
  225. }
  226. lua_pushlstring(l, webrequest->content, webrequest->content_length);
  227. lua_pushstring(l, webrequest->error);
  228. return 2;
  229. }
  230. static int webclient_getinfo(lua_State* l)
  231. {
  232. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  233. if (!webclient)
  234. return luaL_argerror(l, 1, "parameter self invalid");
  235. struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2);
  236. if (!webrequest)
  237. return luaL_argerror(l, 2, "parameter index invalid");
  238. lua_newtable(l);
  239. char* ip = NULL;
  240. if (curl_easy_getinfo(webrequest->curl, CURLINFO_PRIMARY_IP, &ip) == CURLE_OK) {
  241. lua_pushstring(l, "ip");
  242. lua_pushstring(l, ip);
  243. lua_settable(l, -3);
  244. }
  245. long port = 0;
  246. if (curl_easy_getinfo(webrequest->curl, CURLINFO_LOCAL_PORT, &port) == CURLE_OK) {
  247. lua_pushstring(l, "port");
  248. lua_pushinteger(l, port);
  249. lua_settable(l, -3);
  250. }
  251. double content_length = 0;
  252. if (curl_easy_getinfo(webrequest->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length) == CURLE_OK) {
  253. lua_pushstring(l, "content_length");
  254. lua_pushnumber(l, content_length);
  255. lua_settable(l, -3);
  256. }
  257. long response_code = 0;
  258. if (curl_easy_getinfo(webrequest->curl, CURLINFO_RESPONSE_CODE, &response_code) == CURLE_OK) {
  259. lua_pushstring(l, "response_code");
  260. lua_pushinteger(l, response_code);
  261. lua_settable(l, -3);
  262. }
  263. if (webrequest->content_realloc_failed) {
  264. lua_pushstring(l, "content_save_failed");
  265. lua_pushboolean(l, webrequest->content_realloc_failed);
  266. lua_settable(l, -3);
  267. }
  268. return 1;
  269. }
  270. static int webclient_sethttpheader(lua_State* l)
  271. {
  272. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  273. if (!webclient)
  274. return luaL_argerror(l, 1, "parameter self invalid");
  275. struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2);
  276. if (!webrequest)
  277. return luaL_argerror(l, 2, "parameter index invalid");
  278. int top = lua_gettop(l);
  279. for (int i = 3; i <= top; ++i) {
  280. const char* str = lua_tostring(l, i);
  281. webrequest->header = curl_slist_append(webrequest->header, str);
  282. }
  283. if (webrequest->header) {
  284. curl_easy_setopt(webrequest->curl, CURLOPT_HTTPHEADER, webrequest->header);
  285. }
  286. return 0;
  287. }
  288. static int webclient_debug(lua_State* l)
  289. {
  290. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  291. if (!webclient)
  292. return luaL_argerror(l, 1, "parameter self invalid");
  293. struct webrequest* webrequest = (struct webrequest*)lua_touserdata(l, 2);
  294. if (!webrequest)
  295. return luaL_argerror(l, 2, "parameter index invalid");
  296. int enable = lua_toboolean(l, 3);
  297. curl_easy_setopt(webrequest->curl, CURLOPT_VERBOSE, enable ? 1L : 0L);
  298. return 0;
  299. }
  300. static int url_encoding(lua_State* l)
  301. {
  302. struct webclient* webclient = (struct webclient*)luaL_checkudata(l, 1, LUA_WEB_CLIENT_MT);
  303. if (!webclient)
  304. return luaL_argerror(l, 1, "parameter self invalid");
  305. if (!webclient->encoding_curl)
  306. webclient->encoding_curl = curl_easy_init();
  307. size_t length = 0;
  308. const char* str = lua_tolstring(l, 2, &length);
  309. char* ret = curl_easy_escape(webclient->encoding_curl, str, (int)length);
  310. if (!ret) {
  311. lua_pushlstring(l, str, length);
  312. return 1;
  313. }
  314. lua_pushstring(l, ret);
  315. curl_free(ret);
  316. return 1;
  317. }
  318. static int get_curl_version(lua_State* l)
  319. {
  320. const char* ver = curl_version();
  321. lua_pushstring(l, ver);
  322. return 1;
  323. }
  324. luaL_Reg webclient_createfuns[] = {
  325. { "create", webclient_create },
  326. { "curl_version", get_curl_version },
  327. { NULL, NULL }
  328. };
  329. luaL_Reg webclient_funs[] = {
  330. { "__gc", webclient_destory },
  331. { "query", webclient_query },
  332. { "request", webclient_request },
  333. { "remove_request", webclient_removerequest },
  334. { "get_respond", webclient_getrespond },
  335. { "get_info", webclient_getinfo },
  336. { "set_httpheader", webclient_sethttpheader },
  337. { "debug", webclient_debug },
  338. { "url_encoding", url_encoding },
  339. { NULL, NULL }
  340. };
  341. LUAMOD_API int luaopen_extlib_webclient(lua_State * L)
  342. {
  343. luaL_checkversion(L);
  344. if (luaL_newmetatable(L, LUA_WEB_CLIENT_MT))
  345. {
  346. lua_pushvalue(L, -1);
  347. lua_setfield(L, -2, "__index");
  348. luaL_setfuncs(L, webclient_funs, 0);
  349. lua_pop(L, 1);
  350. }
  351. luaL_newlib(L, webclient_createfuns);
  352. return 1;
  353. }