| /* | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| #include "ts_lua_util.h" | |
| #include "ts_lua_io.h" | |
| #include "ts_lua_fetch.h" | |
| #define TS_LUA_EVENT_FETCH_OVER 20010 | |
| #define TS_LUA_FETCH_CLIENT_ADDRPORT "127.0.0.1:33333" | |
| #define TS_LUA_FETCH_CLIENT_ADDRPORT_LEN 15 | |
| #define TS_LUA_FETCH_USER_AGENT "TS Fetcher/1.0" | |
| static int ts_lua_fetch(lua_State *L); | |
| static int ts_lua_fetch_multi(lua_State *L); | |
| static int ts_lua_fetch_handler(TSCont contp, TSEvent event, void *edata); | |
| static int ts_lua_fetch_multi_cleanup(ts_lua_async_item *ai); | |
| static int ts_lua_fetch_multi_handler(TSCont contp, TSEvent event, void *edata); | |
| static int ts_lua_fetch_one_item(lua_State *L, const char *url, size_t url_len, ts_lua_fetch_info *fi); | |
| static inline void ts_lua_destroy_fetch_multi_info(ts_lua_fetch_multi_info *fmi); | |
| void | |
| ts_lua_inject_fetch_api(lua_State *L) | |
| { | |
| /* ts.fetch() */ | |
| lua_pushcfunction(L, ts_lua_fetch); | |
| lua_setfield(L, -2, "fetch"); | |
| /* ts.fetch_multi() */ | |
| lua_pushcfunction(L, ts_lua_fetch_multi); | |
| lua_setfield(L, -2, "fetch_multi"); | |
| } | |
| static int | |
| ts_lua_fetch(lua_State *L) | |
| { | |
| int sz; | |
| size_t n; | |
| const char *url; | |
| size_t url_len; | |
| TSCont contp; | |
| ts_lua_cont_info *ci; | |
| ts_lua_async_item *ai; | |
| ts_lua_fetch_info *fi; | |
| ts_lua_fetch_multi_info *fmi; | |
| ci = ts_lua_get_cont_info(L); | |
| if (ci == nullptr) { | |
| TSError("[ts_lua][%s] no cont info found", __FUNCTION__); | |
| TSReleaseAssert(!"Unexpected fetch of cont info"); | |
| return 0; | |
| } | |
| n = lua_gettop(L); | |
| if (n < 1) { | |
| return luaL_error(L, "'ts.fetch' requires parameter"); | |
| } | |
| /* url */ | |
| if (!lua_isstring(L, 1)) { | |
| return luaL_error(L, "'ts.fetch' first param is not string"); | |
| } | |
| url = luaL_checklstring(L, 1, &url_len); | |
| /* replicate misc table */ | |
| if (n >= 2) { | |
| lua_pushvalue(L, 2); | |
| } else { | |
| lua_pushnil(L); | |
| } | |
| contp = TSContCreate(ts_lua_fetch_multi_handler, ci->mutex); | |
| sz = sizeof(ts_lua_fetch_multi_info) + 1 * sizeof(ts_lua_fetch_info); | |
| fmi = (ts_lua_fetch_multi_info *)TSmalloc(sz); | |
| memset(fmi, 0, sz); | |
| fmi->total = 1; | |
| fmi->contp = contp; | |
| fi = &fmi->fiv[0]; | |
| fi->fmi = fmi; | |
| fi->buffer = TSIOBufferCreate(); | |
| fi->reader = TSIOBufferReaderAlloc(fi->buffer); | |
| ts_lua_fetch_one_item(L, url, url_len, fi); | |
| // pop the replicated misc table | |
| lua_pop(L, 1); | |
| ai = ts_lua_async_create_item(contp, ts_lua_fetch_multi_cleanup, fmi, ci); | |
| TSContDataSet(contp, ai); | |
| return lua_yield(L, 0); | |
| ; | |
| } | |
| static int | |
| ts_lua_fetch_multi(lua_State *L) | |
| { | |
| int type, sz; | |
| size_t i, n; | |
| const char *url; | |
| size_t url_len; | |
| TSCont contp; | |
| ts_lua_cont_info *ci; | |
| ts_lua_async_item *ai; | |
| ts_lua_fetch_info *fi; | |
| ts_lua_fetch_multi_info *fmi; | |
| ci = ts_lua_get_cont_info(L); | |
| if (ci == nullptr) { | |
| TSError("[ts_lua][%s] no cont info found", __FUNCTION__); | |
| TSReleaseAssert(!"Unexpected fetch of cont info"); | |
| return 0; | |
| } | |
| if (lua_gettop(L) < 1) { | |
| return luaL_error(L, "'ts.fetch_mutli' requires one parameter"); | |
| } | |
| type = lua_type(L, 1); | |
| if (type != LUA_TTABLE) { | |
| return luaL_error(L, "'ts.fetch_mutli' requires table as parameter"); | |
| } | |
| // main continuation handler | |
| contp = TSContCreate(ts_lua_fetch_multi_handler, ci->mutex); | |
| // Iterate the table | |
| n = lua_objlen(L, 1); | |
| sz = sizeof(ts_lua_fetch_multi_info) + n * sizeof(ts_lua_fetch_info); | |
| fmi = (ts_lua_fetch_multi_info *)TSmalloc(sz); | |
| memset(fmi, 0, sz); | |
| fmi->total = n; | |
| fmi->contp = contp; | |
| fmi->multi = 1; | |
| for (i = 0; i < n; i++) { | |
| /* push fetch item */ | |
| lua_pushinteger(L, i + 1); | |
| lua_gettable(L, -2); | |
| if (lua_objlen(L, -1) < 1) { | |
| ts_lua_destroy_fetch_multi_info(fmi); | |
| TSContDestroy(contp); | |
| return luaL_error(L, "'ts.fetch_mutli' got empty table item"); | |
| } | |
| /* push url */ | |
| lua_pushnumber(L, 1); | |
| lua_gettable(L, -2); | |
| if (!lua_isstring(L, -1)) { | |
| ts_lua_destroy_fetch_multi_info(fmi); | |
| TSContDestroy(contp); | |
| return luaL_error(L, "'ts.fetch_mutli' got invalid table item: url illegal"); | |
| } | |
| url = luaL_checklstring(L, -1, &url_len); | |
| /* push misc table */ | |
| lua_pushinteger(L, 2); | |
| lua_gettable(L, -3); | |
| fi = &fmi->fiv[i]; | |
| fi->fmi = fmi; | |
| fi->buffer = TSIOBufferCreate(); | |
| fi->reader = TSIOBufferReaderAlloc(fi->buffer); | |
| ts_lua_fetch_one_item(L, url, url_len, fi); | |
| lua_pop(L, 3); // misc table, url, fetch item | |
| } | |
| ai = ts_lua_async_create_item(contp, ts_lua_fetch_multi_cleanup, (void *)fmi, ci); | |
| TSContDataSet(contp, ai); | |
| return lua_yield(L, 0); | |
| ; | |
| } | |
| static int | |
| ts_lua_fetch_one_item(lua_State *L, const char *url, size_t url_len, ts_lua_fetch_info *fi) | |
| { | |
| TSCont contp; | |
| int tb, flags, host_len, n; | |
| int cl, ht, ua; | |
| const char *method, *key, *value, *body, *opt; | |
| const char *addr, *ptr, *host; | |
| size_t method_len, key_len, value_len, body_len; | |
| size_t addr_len, opt_len, i, left; | |
| char c; | |
| struct sockaddr_storage clientaddr; | |
| char buf[32]; | |
| tb = lua_istable(L, -1); | |
| /* method */ | |
| if (tb) { | |
| lua_pushlstring(L, "method", sizeof("method") - 1); | |
| lua_gettable(L, -2); | |
| if (lua_isstring(L, -1)) { | |
| method = luaL_checklstring(L, -1, &method_len); | |
| } else { | |
| method = "GET"; | |
| method_len = sizeof("GET") - 1; | |
| } | |
| lua_pop(L, 1); | |
| } else { | |
| method = "GET"; | |
| method_len = sizeof("GET") - 1; | |
| } | |
| /* body */ | |
| body = nullptr; | |
| body_len = 0; | |
| if (tb) { | |
| lua_pushlstring(L, "body", sizeof("body") - 1); | |
| lua_gettable(L, -2); | |
| if (lua_isstring(L, -1)) { | |
| body = luaL_checklstring(L, -1, &body_len); | |
| } | |
| lua_pop(L, 1); | |
| } | |
| /* cliaddr */ | |
| memset(&clientaddr, 0, sizeof(clientaddr)); | |
| if (tb) { | |
| lua_pushlstring(L, "cliaddr", sizeof("cliaddr") - 1); | |
| lua_gettable(L, -2); | |
| if (lua_isstring(L, -1)) { | |
| addr = luaL_checklstring(L, -1, &addr_len); | |
| if (TS_ERROR == TSIpStringToAddr(addr, addr_len, reinterpret_cast<struct sockaddr *>(&clientaddr))) { | |
| TSError("[ts_lua][%s] Client ip parse failed! Using default.", TS_LUA_DEBUG_TAG); | |
| if (TS_ERROR == TSIpStringToAddr(TS_LUA_FETCH_CLIENT_ADDRPORT, TS_LUA_FETCH_CLIENT_ADDRPORT_LEN, | |
| reinterpret_cast<struct sockaddr *>(&clientaddr))) { | |
| TSError("[ts_lua][%s] Default client ip parse failed!", TS_LUA_DEBUG_TAG); | |
| return 0; | |
| } | |
| } | |
| } else { | |
| if (TS_ERROR == TSIpStringToAddr(TS_LUA_FETCH_CLIENT_ADDRPORT, TS_LUA_FETCH_CLIENT_ADDRPORT_LEN, | |
| reinterpret_cast<struct sockaddr *>(&clientaddr))) { | |
| TSError("[ts_lua][%s] Default client ip parse failed!", TS_LUA_DEBUG_TAG); | |
| return 0; | |
| } | |
| } | |
| lua_pop(L, 1); | |
| } else { | |
| if (TS_ERROR == TSIpStringToAddr(TS_LUA_FETCH_CLIENT_ADDRPORT, TS_LUA_FETCH_CLIENT_ADDRPORT_LEN, | |
| reinterpret_cast<struct sockaddr *>(&clientaddr))) { | |
| TSError("[ts_lua][%s] Default client ip parse failed!", TS_LUA_DEBUG_TAG); | |
| return 0; | |
| } | |
| } | |
| /* option */ | |
| flags = TS_FETCH_FLAGS_DECHUNK; // dechunk the body default | |
| if (tb) { | |
| lua_pushlstring(L, "option", sizeof("option") - 1); | |
| lua_gettable(L, -2); | |
| if (lua_isstring(L, -1)) { | |
| opt = luaL_checklstring(L, -1, &opt_len); | |
| for (i = 0; i < opt_len; i++) { | |
| c = opt[i]; | |
| switch (c) { | |
| case 'c': | |
| flags &= (~TS_FETCH_FLAGS_DECHUNK); | |
| break; | |
| case 's': | |
| flags |= (TS_FETCH_FLAGS_SKIP_REMAP); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| lua_pop(L, 1); | |
| } | |
| contp = TSContCreate(ts_lua_fetch_handler, TSContMutexGet(fi->fmi->contp)); // reuse parent cont's mutex | |
| TSContDataSet(contp, fi); | |
| fi->contp = contp; | |
| fi->fch = TSFetchCreate(contp, method, url, "HTTP/1.1", reinterpret_cast<struct sockaddr *>(&clientaddr), flags); | |
| /* header */ | |
| cl = ht = ua = 0; | |
| if (tb) { | |
| lua_pushlstring(L, "header", sizeof("header") - 1); | |
| lua_gettable(L, -2); | |
| if (lua_istable(L, -1)) { | |
| // iterate the header table | |
| lua_pushnil(L); | |
| while (lua_next(L, -2)) { | |
| lua_pushvalue(L, -2); | |
| key = luaL_checklstring(L, -1, &key_len); | |
| value = luaL_checklstring(L, -2, &value_len); | |
| if ((int)key_len == TS_MIME_LEN_CONTENT_LENGTH && | |
| !strncasecmp(TS_MIME_FIELD_CONTENT_LENGTH, key, key_len)) { // Content-Length | |
| cl = 1; | |
| } else if ((int)key_len == TS_MIME_LEN_HOST && !strncasecmp(TS_MIME_FIELD_HOST, key, key_len)) { // Host | |
| ht = 1; | |
| } else if ((int)key_len == TS_MIME_LEN_USER_AGENT && !strncasecmp(TS_MIME_FIELD_USER_AGENT, key, key_len)) { // User-Agent | |
| ua = 1; | |
| } | |
| TSFetchHeaderAdd(fi->fch, key, key_len, value, value_len); | |
| lua_pop(L, 2); | |
| } | |
| } | |
| lua_pop(L, 1); | |
| } | |
| /* Host */ | |
| if (ht == 0) { | |
| ptr = static_cast<char const *>(memchr(url, ':', url_len)); | |
| if (ptr) { | |
| host = ptr + 3; | |
| left = url_len - (host - url); | |
| ptr = static_cast<char const *>(memchr(host, '/', left)); | |
| if (ptr) { | |
| host_len = ptr - host; | |
| } else { | |
| host_len = left; | |
| } | |
| TSFetchHeaderAdd(fi->fch, TS_MIME_FIELD_HOST, TS_MIME_LEN_HOST, host, host_len); | |
| } | |
| } | |
| /* User-Agent */ | |
| if (ua == 0) { | |
| TSFetchHeaderAdd(fi->fch, TS_MIME_FIELD_USER_AGENT, TS_MIME_LEN_USER_AGENT, TS_LUA_FETCH_USER_AGENT, | |
| sizeof(TS_LUA_FETCH_USER_AGENT) - 1); | |
| } | |
| if (body_len > 0 && cl == 0) { // add Content-Length header | |
| n = snprintf(buf, sizeof(buf), "%zu", body_len); | |
| TSFetchHeaderAdd(fi->fch, TS_MIME_FIELD_CONTENT_LENGTH, TS_MIME_LEN_CONTENT_LENGTH, buf, n); | |
| } | |
| TSFetchLaunch(fi->fch); | |
| if (body_len > 0) { | |
| TSFetchWriteData(fi->fch, body, body_len); | |
| } | |
| return 0; | |
| } | |
| static int | |
| ts_lua_fetch_handler(TSCont contp, TSEvent ev, void *edata ATS_UNUSED) | |
| { | |
| int event; | |
| char *from; | |
| int64_t n, wavail; | |
| TSIOBufferBlock blk; | |
| ts_lua_fetch_info *fi; | |
| ts_lua_fetch_multi_info *fmi; | |
| event = (int)ev; | |
| fi = static_cast<decltype(fi)>(TSContDataGet(contp)); | |
| fmi = fi->fmi; | |
| switch (event) { | |
| case TS_FETCH_EVENT_EXT_HEAD_READY: | |
| case TS_FETCH_EVENT_EXT_HEAD_DONE: | |
| break; | |
| case TS_FETCH_EVENT_EXT_BODY_READY: | |
| case TS_FETCH_EVENT_EXT_BODY_DONE: | |
| do { | |
| blk = TSIOBufferStart(fi->buffer); | |
| from = TSIOBufferBlockWriteStart(blk, &wavail); | |
| n = TSFetchReadData(fi->fch, from, wavail); | |
| TSIOBufferProduce(fi->buffer, n); | |
| } while (n == wavail); | |
| if (event == TS_FETCH_EVENT_EXT_BODY_DONE) { // fetch over | |
| fi->over = 1; | |
| } | |
| break; | |
| default: | |
| fi->failed = 1; | |
| break; | |
| } | |
| if (fmi && (fi->over || fi->failed)) { | |
| TSContCall(fmi->contp, TSEvent(TS_LUA_EVENT_FETCH_OVER), fi); // error exist | |
| ts_lua_destroy_fetch_multi_info(fmi); | |
| } | |
| return 0; | |
| } | |
| static int | |
| ts_lua_fill_one_result(lua_State *L, ts_lua_fetch_info *fi) | |
| { | |
| const char *name, *value; | |
| int name_len, value_len; | |
| char *dst; | |
| int64_t ravail; | |
| TSMBuffer bufp; | |
| TSMLoc hdrp; | |
| TSMLoc field_loc, next_field_loc; | |
| TSHttpStatus status; | |
| bufp = TSFetchRespHdrMBufGet(fi->fch); | |
| hdrp = TSFetchRespHdrMLocGet(fi->fch); | |
| // result table | |
| lua_newtable(L); | |
| // status code | |
| status = TSHttpHdrStatusGet(bufp, hdrp); | |
| lua_pushlstring(L, "status", sizeof("status") - 1); | |
| lua_pushnumber(L, status); | |
| lua_rawset(L, -3); | |
| // header | |
| lua_pushlstring(L, "header", sizeof("header") - 1); | |
| lua_newtable(L); | |
| field_loc = TSMimeHdrFieldGet(bufp, hdrp, 0); | |
| while (field_loc) { | |
| name = TSMimeHdrFieldNameGet(bufp, hdrp, field_loc, &name_len); | |
| value = TSMimeHdrFieldValueStringGet(bufp, hdrp, field_loc, -1, &value_len); | |
| lua_pushlstring(L, name, name_len); | |
| lua_pushlstring(L, value, value_len); | |
| lua_rawset(L, -3); | |
| next_field_loc = TSMimeHdrFieldNext(bufp, hdrp, field_loc); | |
| TSHandleMLocRelease(bufp, hdrp, field_loc); | |
| field_loc = next_field_loc; | |
| } | |
| lua_rawset(L, -3); | |
| // body | |
| ravail = TSIOBufferReaderAvail(fi->reader); | |
| if (ravail > 0) { | |
| lua_pushlstring(L, "body", sizeof("body") - 1); | |
| dst = (char *)TSmalloc(ravail); | |
| IOBufferReaderCopy(fi->reader, dst, ravail); | |
| lua_pushlstring(L, (char *)dst, ravail); | |
| lua_rawset(L, -3); | |
| TSfree(dst); | |
| } | |
| // truncated | |
| lua_pushlstring(L, "truncated", sizeof("truncated") - 1); | |
| if (fi->failed) { | |
| lua_pushboolean(L, 1); | |
| } else { | |
| lua_pushboolean(L, 0); | |
| } | |
| lua_rawset(L, -3); | |
| return 0; | |
| } | |
| static int | |
| ts_lua_fetch_multi_handler(TSCont contp, TSEvent event ATS_UNUSED, void *edata) | |
| { | |
| int i; | |
| lua_State *L; | |
| TSMutex lmutex; | |
| ts_lua_async_item *ai; | |
| ts_lua_cont_info *ci; | |
| ts_lua_fetch_info *fi; | |
| ts_lua_fetch_multi_info *fmi; | |
| ai = static_cast<decltype(ai)>(TSContDataGet(contp)); | |
| ci = ai->cinfo; | |
| fmi = (ts_lua_fetch_multi_info *)ai->data; | |
| fi = (ts_lua_fetch_info *)edata; | |
| L = ai->cinfo->routine.lua; | |
| lmutex = ai->cinfo->routine.mctx->mutexp; | |
| fmi->done++; | |
| if (fi->fmi != fmi && fmi->done != fmi->total) { | |
| return 0; | |
| } | |
| // all finish | |
| TSMutexLockGuard lock(lmutex); | |
| if (fmi->total == 1 && !fmi->multi) { | |
| ts_lua_fill_one_result(L, fi); | |
| TSContCall(ci->contp, TSEvent(TS_LUA_EVENT_COROUTINE_CONT), reinterpret_cast<void *>(1)); | |
| } else { | |
| lua_newtable(L); | |
| for (i = 1; i <= fmi->total; i++) { | |
| ts_lua_fill_one_result(L, &fmi->fiv[i - 1]); | |
| lua_rawseti(L, -2, i); | |
| } | |
| TSContCall(ci->contp, TSEvent(TS_LUA_EVENT_COROUTINE_CONT), reinterpret_cast<void *>(1)); | |
| } | |
| return 0; | |
| } | |
| static inline void | |
| ts_lua_destroy_fetch_multi_info(ts_lua_fetch_multi_info *fmi) | |
| { | |
| int i; | |
| ts_lua_fetch_info *fi; | |
| if (fmi == nullptr) { | |
| return; | |
| } | |
| for (i = 0; i < fmi->total; i++) { | |
| fi = &fmi->fiv[i]; | |
| if (fi->reader) { | |
| TSIOBufferReaderFree(fi->reader); | |
| } | |
| if (fi->buffer) { | |
| TSIOBufferDestroy(fi->buffer); | |
| } | |
| if (fi->fch) { | |
| TSFetchDestroy(fi->fch); | |
| } | |
| if (fi->contp) { | |
| TSContDestroy(fi->contp); | |
| } | |
| } | |
| TSfree(fmi); | |
| } | |
| static int | |
| ts_lua_fetch_multi_cleanup(ts_lua_async_item *ai) | |
| { | |
| if (ai->deleted) { | |
| return 0; | |
| } | |
| if (ai->data) { | |
| ai->data = nullptr; | |
| TSContDestroy(ai->contp); | |
| ai->contp = nullptr; | |
| } | |
| ai->deleted = 1; | |
| return 0; | |
| } |