| /* | |
| 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_http_intercept.h" | |
| static int ts_lua_http_intercept(lua_State *L); | |
| static int ts_lua_http_server_intercept(lua_State *L); | |
| static int ts_lua_http_intercept_entry(TSCont contp, TSEvent event, void *edata); | |
| static void ts_lua_http_intercept_process(ts_lua_http_intercept_ctx *ictx, TSCont contp, TSVConn conn); | |
| static void ts_lua_http_intercept_setup_read(ts_lua_http_intercept_ctx *ictx); | |
| static void ts_lua_http_intercept_setup_write(ts_lua_http_intercept_ctx *ictx); | |
| static int ts_lua_http_intercept_handler(TSCont contp, TSEvent event, void *edata); | |
| static int ts_lua_http_intercept_run_coroutine(ts_lua_http_intercept_ctx *ictx, int n); | |
| static int ts_lua_http_intercept_process_read(TSEvent event, ts_lua_http_intercept_ctx *ictx); | |
| static int ts_lua_http_intercept_process_write(TSEvent event, ts_lua_http_intercept_ctx *ictx); | |
| static int ts_lua_say(lua_State *L); | |
| static int ts_lua_flush(lua_State *L); | |
| static int ts_lua_flush_wakeup(ts_lua_http_intercept_ctx *ictx); | |
| static int ts_lua_flush_wakeup_handler(TSCont contp, TSEvent event, void *edata); | |
| static int ts_lua_flush_cleanup(ts_lua_async_item *ai); | |
| void | |
| ts_lua_inject_http_intercept_api(lua_State *L) | |
| { | |
| /* ts.intercept */ | |
| lua_pushcfunction(L, ts_lua_http_intercept); | |
| lua_setfield(L, -2, "intercept"); | |
| /* ts.server_intercept */ | |
| lua_pushcfunction(L, ts_lua_http_server_intercept); | |
| lua_setfield(L, -2, "server_intercept"); | |
| } | |
| void | |
| ts_lua_inject_intercept_api(lua_State *L) | |
| { | |
| /* ts.say(...) */ | |
| lua_pushcfunction(L, ts_lua_say); | |
| lua_setfield(L, -2, "say"); | |
| /* ts.flush(...) */ | |
| lua_pushcfunction(L, ts_lua_flush); | |
| lua_setfield(L, -2, "flush"); | |
| } | |
| static int | |
| ts_lua_http_intercept(lua_State *L) | |
| { | |
| TSCont contp; | |
| int type, n; | |
| ts_lua_http_ctx *http_ctx; | |
| ts_lua_http_intercept_ctx *ictx; | |
| GET_HTTP_CONTEXT(http_ctx, L); | |
| n = lua_gettop(L); | |
| if (n < 1) { | |
| TSError("[ts_lua][%s] ts.http.intercept need at least one param", __FUNCTION__); | |
| return 0; | |
| } | |
| type = lua_type(L, 1); | |
| if (type != LUA_TFUNCTION) { | |
| TSError("[ts_lua][%s] ts.http.intercept should use function as param, but there is %s", __FUNCTION__, lua_typename(L, type)); | |
| return 0; | |
| } | |
| ictx = ts_lua_create_http_intercept_ctx(L, http_ctx, n); | |
| contp = TSContCreate(ts_lua_http_intercept_entry, TSMutexCreate()); | |
| TSContDataSet(contp, ictx); | |
| TSHttpTxnIntercept(contp, http_ctx->txnp); | |
| http_ctx->has_hook = 1; | |
| return 0; | |
| } | |
| static int | |
| ts_lua_http_server_intercept(lua_State *L) | |
| { | |
| TSCont contp; | |
| int type, n; | |
| ts_lua_http_ctx *http_ctx; | |
| ts_lua_http_intercept_ctx *ictx; | |
| GET_HTTP_CONTEXT(http_ctx, L); | |
| n = lua_gettop(L); | |
| if (n < 1) { | |
| TSError("[ts_lua][%s] ts.http.server_intercept need at least one param", __FUNCTION__); | |
| return 0; | |
| } | |
| type = lua_type(L, 1); | |
| if (type != LUA_TFUNCTION) { | |
| TSError("[ts_lua][%s] ts.http.server_intercept should use function as param, but there is %s", __FUNCTION__, | |
| lua_typename(L, type)); | |
| return 0; | |
| } | |
| ictx = ts_lua_create_http_intercept_ctx(L, http_ctx, n); | |
| contp = TSContCreate(ts_lua_http_intercept_entry, TSMutexCreate()); | |
| TSContDataSet(contp, ictx); | |
| TSHttpTxnServerIntercept(contp, http_ctx->txnp); | |
| http_ctx->has_hook = 1; | |
| return 0; | |
| } | |
| static int | |
| ts_lua_http_intercept_entry(TSCont contp, TSEvent event, void *edata) | |
| { | |
| ts_lua_http_intercept_ctx *ictx; | |
| ictx = (ts_lua_http_intercept_ctx *)TSContDataGet(contp); | |
| switch (event) { | |
| case TS_EVENT_NET_ACCEPT_FAILED: | |
| if (edata) { | |
| TSVConnClose((TSVConn)edata); | |
| } | |
| ts_lua_destroy_http_intercept_ctx(ictx); | |
| break; | |
| case TS_EVENT_NET_ACCEPT: | |
| ts_lua_http_intercept_process(ictx, contp, (TSVConn)edata); | |
| break; | |
| default: | |
| break; | |
| } | |
| TSContDestroy(contp); | |
| return 0; | |
| } | |
| static void | |
| ts_lua_http_intercept_process(ts_lua_http_intercept_ctx *ictx, TSCont contp, TSVConn conn) | |
| { | |
| int n; | |
| lua_State *L; | |
| TSMutex mtxp; | |
| ts_lua_cont_info *ci; | |
| ci = &ictx->cinfo; | |
| mtxp = ictx->cinfo.routine.mctx->mutexp; | |
| ci->mutex = TSContMutexGet(contp); | |
| ci->contp = TSContCreate(ts_lua_http_intercept_handler, TSContMutexGet(contp)); | |
| TSContDataSet(ci->contp, ictx); | |
| ictx->net_vc = conn; | |
| // set up read. | |
| ts_lua_http_intercept_setup_read(ictx); | |
| // set up write. | |
| ts_lua_http_intercept_setup_write(ictx); | |
| // invoke function here | |
| L = ci->routine.lua; | |
| TSMutexLock(mtxp); | |
| n = lua_gettop(L); | |
| ts_lua_http_intercept_run_coroutine(ictx, n - 1); | |
| TSMutexUnlock(mtxp); | |
| } | |
| static void | |
| ts_lua_http_intercept_setup_read(ts_lua_http_intercept_ctx *ictx) | |
| { | |
| ictx->input.buffer = TSIOBufferCreate(); | |
| ictx->input.reader = TSIOBufferReaderAlloc(ictx->input.buffer); | |
| ictx->input.vio = TSVConnRead(ictx->net_vc, ictx->cinfo.contp, ictx->input.buffer, INT64_MAX); | |
| } | |
| static void | |
| ts_lua_http_intercept_setup_write(ts_lua_http_intercept_ctx *ictx) | |
| { | |
| ictx->output.buffer = TSIOBufferCreate(); | |
| ictx->output.reader = TSIOBufferReaderAlloc(ictx->output.buffer); | |
| ictx->output.vio = TSVConnWrite(ictx->net_vc, ictx->cinfo.contp, ictx->output.reader, INT64_MAX); | |
| } | |
| static int | |
| ts_lua_http_intercept_handler(TSCont contp, TSEvent event, void *edata) | |
| { | |
| int ret, n; | |
| TSMutex mtxp; | |
| ts_lua_http_intercept_ctx *ictx; | |
| ictx = (ts_lua_http_intercept_ctx *)TSContDataGet(contp); | |
| mtxp = nullptr; | |
| if (edata == ictx->input.vio) { | |
| ret = ts_lua_http_intercept_process_read(event, ictx); | |
| } else if (edata == ictx->output.vio) { | |
| ret = ts_lua_http_intercept_process_write(event, ictx); | |
| } else { | |
| mtxp = ictx->cinfo.routine.mctx->mutexp; | |
| n = (intptr_t)edata; | |
| TSMutexLock(mtxp); | |
| ret = ts_lua_http_intercept_run_coroutine(ictx, n); | |
| TSMutexUnlock(mtxp); | |
| } | |
| if (ret || (ictx->send_complete && ictx->recv_complete)) { | |
| ts_lua_destroy_http_intercept_ctx(ictx); | |
| } | |
| return 0; | |
| } | |
| static int | |
| ts_lua_http_intercept_run_coroutine(ts_lua_http_intercept_ctx *ictx, int n) | |
| { | |
| int ret; | |
| int64_t avail; | |
| int64_t done; | |
| ts_lua_cont_info *ci; | |
| lua_State *L; | |
| ci = &ictx->cinfo; | |
| L = ci->routine.lua; | |
| ts_lua_set_cont_info(L, ci); | |
| ret = lua_resume(L, n); | |
| switch (ret) { | |
| case 0: // finished | |
| avail = TSIOBufferReaderAvail(ictx->output.reader); | |
| done = TSVIONDoneGet(ictx->output.vio); | |
| TSVIONBytesSet(ictx->output.vio, avail + done); | |
| ictx->all_ready = 1; | |
| if (avail) { | |
| TSVIOReenable(ictx->output.vio); | |
| } else { | |
| ictx->send_complete = 1; | |
| } | |
| break; | |
| case 1: // yield | |
| break; | |
| default: // error | |
| TSError("[ts_lua][%s] lua_resume failed: %s", __FUNCTION__, lua_tostring(L, -1)); | |
| lua_pop(L, 1); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int | |
| ts_lua_http_intercept_process_read(TSEvent event, ts_lua_http_intercept_ctx *ictx) | |
| { | |
| int64_t avail = TSIOBufferReaderAvail(ictx->input.reader); | |
| TSIOBufferReaderConsume(ictx->input.reader, avail); | |
| switch (event) { | |
| case TS_EVENT_VCONN_READ_READY: | |
| TSVConnShutdown(ictx->net_vc, 1, 0); | |
| ictx->recv_complete = 1; | |
| break; // READ_READY_READY is not equal to EOS break statement is probably missing? | |
| case TS_EVENT_VCONN_READ_COMPLETE: | |
| case TS_EVENT_VCONN_EOS: | |
| ictx->recv_complete = 1; | |
| break; | |
| default: | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int | |
| ts_lua_http_intercept_process_write(TSEvent event, ts_lua_http_intercept_ctx *ictx) | |
| { | |
| int64_t done, avail; | |
| switch (event) { | |
| case TS_EVENT_VCONN_WRITE_READY: | |
| avail = TSIOBufferReaderAvail(ictx->output.reader); | |
| if (ictx->all_ready) { | |
| TSVIOReenable(ictx->output.vio); | |
| } else if (ictx->to_flush > 0) { // ts.flush() | |
| done = TSVIONDoneGet(ictx->output.vio); | |
| if (ictx->to_flush > done) { | |
| TSVIOReenable(ictx->output.vio); | |
| } else { // we had flush all the data we want | |
| ictx->to_flush = 0; | |
| ts_lua_flush_wakeup(ictx); // wake up | |
| } | |
| } else if (avail > 0) { | |
| TSVIOReenable(ictx->output.vio); | |
| } | |
| break; | |
| case TS_EVENT_VCONN_WRITE_COMPLETE: | |
| ictx->send_complete = 1; | |
| break; | |
| case TS_EVENT_ERROR: | |
| default: | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int | |
| ts_lua_say(lua_State *L) | |
| { | |
| const char *data; | |
| size_t len; | |
| ts_lua_http_intercept_ctx *ictx; | |
| ictx = ts_lua_get_http_intercept_ctx(L); | |
| if (ictx == nullptr) { | |
| TSError("[ts_lua][%s] missing ictx", __FUNCTION__); | |
| TSReleaseAssert(!"Unexpected fetch of intercept_ctx"); | |
| return 0; | |
| } | |
| data = luaL_checklstring(L, 1, &len); | |
| if (len > 0) { | |
| TSIOBufferWrite(ictx->output.buffer, data, len); | |
| TSVIOReenable(ictx->output.vio); | |
| } | |
| return 0; | |
| } | |
| static int | |
| ts_lua_flush(lua_State *L) | |
| { | |
| int64_t avail; | |
| ts_lua_http_intercept_ctx *ictx; | |
| ictx = ts_lua_get_http_intercept_ctx(L); | |
| if (ictx == nullptr) { | |
| TSError("[ts_lua][%s] missing ictx", __FUNCTION__); | |
| TSReleaseAssert(!"Unexpected fetch of intercept_ctx"); | |
| return 0; | |
| } | |
| avail = TSIOBufferReaderAvail(ictx->output.reader); | |
| if (avail > 0) { | |
| ictx->to_flush = TSVIONDoneGet(ictx->output.vio) + TSIOBufferReaderAvail(ictx->output.reader); | |
| TSVIOReenable(ictx->output.vio); | |
| return lua_yield(L, 0); | |
| } | |
| return 0; | |
| } | |
| static int | |
| ts_lua_flush_wakeup(ts_lua_http_intercept_ctx *ictx) | |
| { | |
| ts_lua_async_item *ai; | |
| ts_lua_cont_info *ci; | |
| TSAction action; | |
| TSCont contp; | |
| ci = &ictx->cinfo; | |
| contp = TSContCreate(ts_lua_flush_wakeup_handler, ci->mutex); | |
| action = TSContScheduleOnPool(contp, 0, TS_THREAD_POOL_NET); | |
| ai = ts_lua_async_create_item(contp, ts_lua_flush_cleanup, (void *)action, ci); | |
| TSContDataSet(contp, ai); | |
| return 0; | |
| } | |
| static int | |
| ts_lua_flush_wakeup_handler(TSCont contp, TSEvent event ATS_UNUSED, void *edata ATS_UNUSED) | |
| { | |
| ts_lua_async_item *ai; | |
| ts_lua_cont_info *ci; | |
| ai = static_cast<decltype(ai)>(TSContDataGet(contp)); | |
| ci = ai->cinfo; | |
| ai->data = nullptr; | |
| ts_lua_flush_cleanup(ai); | |
| TSContCall(ci->contp, TSEvent(TS_LUA_EVENT_COROUTINE_CONT), 0); | |
| return 0; | |
| } | |
| static int | |
| ts_lua_flush_cleanup(ts_lua_async_item *ai) | |
| { | |
| if (ai->deleted) { | |
| return 0; | |
| } | |
| if (ai->data) { | |
| TSActionCancel((TSAction)ai->data); | |
| ai->data = nullptr; | |
| } | |
| TSContDestroy(ai->contp); | |
| ai->deleted = 1; | |
| return 0; | |
| } |