| /** @file |
| |
| Implements unit test for SDK APIs |
| |
| @section license License |
| |
| 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 "Regression.h" |
| #include "api/ts/ts.h" |
| |
| #include <arpa/inet.h> /* For htonl */ |
| #include "P_Net.h" |
| |
| #define SDBG_TAG "SockServer" |
| #define CDBG_TAG "SockClient" |
| |
| #ifndef MIN |
| #define MIN(_x, _y) ((_x < _y) ? _x : _y) |
| #endif |
| #define IP(a,b,c,d) htonl((a) << 24 | (b) << 16 | (c) << 8 | (d)) |
| |
| #define SET_TEST_HANDLER(_d, _s) {_d = _s;} |
| |
| #define MAGIC_ALIVE 0xfeedbaba |
| #define MAGIC_DEAD 0xdeadbeef |
| |
| #define SYNSERVER_LISTEN_PORT 3300 |
| |
| #define PROXY_CONFIG_NAME_HTTP_PORT "proxy.config.http.server_port" |
| #define PROXY_HTTP_DEFAULT_PORT 8080 |
| |
| #define REQUEST_MAX_SIZE 4095 |
| #define RESPONSE_MAX_SIZE 4095 |
| |
| #define HTTP_REQUEST_END "\r\n\r\n" |
| |
| // each request/response includes an identifier as a Mime field |
| #define X_REQUEST_ID "X-Request-ID" |
| #define X_RESPONSE_ID "X-Response-ID" |
| |
| #define ERROR_BODY "TESTING ERROR PAGE" |
| #define TRANSFORM_APPEND_STRING "This is a transformed response" |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // STRUCTURES |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| typedef int (*TxnHandler) (TSCont contp, TSEvent event, void *data); |
| |
| /* Server transaction structure */ |
| typedef struct |
| { |
| TSVConn vconn; |
| |
| TSVIO read_vio; |
| TSIOBuffer req_buffer; |
| TSIOBufferReader req_reader; |
| |
| TSVIO write_vio; |
| TSIOBuffer resp_buffer; |
| TSIOBufferReader resp_reader; |
| |
| char request[REQUEST_MAX_SIZE + 1]; |
| int request_len; |
| |
| TxnHandler current_handler; |
| unsigned int magic; |
| } ServerTxn; |
| |
| /* Server structure */ |
| typedef struct |
| { |
| int accept_port; |
| TSAction accept_action; |
| TSCont accept_cont; |
| unsigned int magic; |
| } SocketServer; |
| |
| typedef enum |
| { |
| REQUEST_SUCCESS, |
| REQUEST_INPROGRESS, |
| REQUEST_FAILURE |
| } RequestStatus; |
| |
| /* Client structure */ |
| typedef struct |
| { |
| TSVConn vconn; |
| |
| TSVIO read_vio; |
| TSIOBuffer req_buffer; |
| TSIOBufferReader req_reader; |
| |
| TSVIO write_vio; |
| TSIOBuffer resp_buffer; |
| TSIOBufferReader resp_reader; |
| |
| char *request; |
| char response[RESPONSE_MAX_SIZE + 1]; |
| int response_len; |
| |
| RequestStatus status; |
| |
| int connect_port; |
| int local_port; |
| uint64_t connect_ip; |
| TSAction connect_action; |
| |
| TxnHandler current_handler; |
| |
| unsigned int magic; |
| } ClientTxn; |
| |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // DECLARATIONS |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| /* utility */ |
| static char *get_body_ptr(const char *request); |
| static char *generate_request(int test_case); |
| static char *generate_response(const char *request); |
| static int get_request_id(TSHttpTxn txnp); |
| |
| |
| /* client side */ |
| static ClientTxn *synclient_txn_create(void); |
| static int synclient_txn_delete(ClientTxn * txn); |
| static int synclient_txn_close(TSCont contp); |
| static int synclient_txn_send_request(ClientTxn * txn, char *request); |
| static int synclient_txn_send_request_to_vc(ClientTxn * txn, char *request, TSVConn vc); |
| static int synclient_txn_read_response(TSCont contp); |
| static int synclient_txn_read_response_handler(TSCont contp, TSEvent event, void *data); |
| static int synclient_txn_write_request(TSCont contp); |
| static int synclient_txn_write_request_handler(TSCont contp, TSEvent event, void *data); |
| static int synclient_txn_connect_handler(TSCont contp, TSEvent event, void *data); |
| static int synclient_txn_main_handler(TSCont contp, TSEvent event, void *data); |
| |
| /* Server side */ |
| SocketServer *synserver_create(int port); |
| static int synserver_start(SocketServer * s); |
| static int synserver_stop(SocketServer * s); |
| static int synserver_delete(SocketServer * s); |
| static int synserver_accept_handler(TSCont contp, TSEvent event, void *data); |
| static int synserver_txn_close(TSCont contp); |
| static int synserver_txn_write_response(TSCont contp); |
| static int synserver_txn_write_response_handler(TSCont contp, TSEvent event, void *data); |
| static int synserver_txn_read_request(TSCont contp); |
| static int synserver_txn_read_request_handler(TSCont contp, TSEvent event, void *data); |
| static int synserver_txn_main_handler(TSCont contp, TSEvent event, void *data); |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // REQUESTS/RESPONSES GENERATION |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| static char * |
| get_body_ptr(const char *request) |
| { |
| char *ptr = (char *) strstr((const char *) request, (const char *) "\r\n\r\n"); |
| return (ptr != NULL) ? (ptr + 4) : NULL; |
| } |
| |
| |
| /* Caller must free returned request */ |
| static char * |
| generate_request(int test_case) |
| { |
| |
| // We define request formats. |
| // Each format has an X-Request-ID field that contains the id of the testcase |
| #define HTTP_REQUEST_DEFAULT_FORMAT "GET http://127.0.0.1:%d/default.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "\r\n" |
| |
| #define HTTP_REQUEST_FORMAT1 "GET http://127.0.0.1:%d/format1.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "\r\n" |
| |
| #define HTTP_REQUEST_FORMAT2 "GET http://127.0.0.1:%d/format2.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "Content-Type: text/html\r\n" \ |
| "\r\n" |
| #define HTTP_REQUEST_FORMAT3 "GET http://127.0.0.1:%d/format3.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "Response: Error\r\n" \ |
| "\r\n" |
| #define HTTP_REQUEST_FORMAT4 "GET http://127.0.0.1:%d/format4.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "Request:%d\r\n" \ |
| "\r\n" |
| #define HTTP_REQUEST_FORMAT5 "GET http://127.0.0.1:%d/format5.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "Request:%d\r\n" \ |
| "\r\n" |
| #define HTTP_REQUEST_FORMAT6 "GET http://127.0.0.1:%d/format.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "Accept-Language: English\r\n" \ |
| "\r\n" |
| #define HTTP_REQUEST_FORMAT7 "GET http://127.0.0.1:%d/format.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "Accept-Language: French\r\n" \ |
| "\r\n" |
| #define HTTP_REQUEST_FORMAT8 "GET http://127.0.0.1:%d/format.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "Accept-Language: English,French\r\n" \ |
| "\r\n" |
| #define HTTP_REQUEST_FORMAT9 "GET http://www.inktomi.com/format9.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "\r\n" |
| #define HTTP_REQUEST_FORMAT10 "GET http://www.inktomi.com/format10.html HTTP/1.0\r\n" \ |
| "X-Request-ID: %d\r\n" \ |
| "\r\n" |
| |
| char *request = (char *) TSmalloc(REQUEST_MAX_SIZE + 1); |
| |
| switch (test_case) { |
| case 1: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT1, SYNSERVER_LISTEN_PORT, test_case); |
| break; |
| case 2: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT2, SYNSERVER_LISTEN_PORT, test_case); |
| break; |
| case 3: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT3, SYNSERVER_LISTEN_PORT, test_case); |
| break; |
| case 4: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT4, SYNSERVER_LISTEN_PORT, test_case, 1); |
| break; |
| case 5: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT5, SYNSERVER_LISTEN_PORT, test_case, 2); |
| break; |
| case 6: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT6, SYNSERVER_LISTEN_PORT, test_case); |
| break; |
| case 7: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT7, SYNSERVER_LISTEN_PORT, test_case - 1); |
| break; |
| case 8: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT8, SYNSERVER_LISTEN_PORT, test_case - 2); |
| break; |
| case 9: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT9, test_case); |
| break; |
| case 10: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_FORMAT10, test_case); |
| break; |
| default: |
| snprintf(request, REQUEST_MAX_SIZE + 1, HTTP_REQUEST_DEFAULT_FORMAT, SYNSERVER_LISTEN_PORT, test_case); |
| break; |
| } |
| |
| return request; |
| } |
| |
| |
| /* Caller must free returned response */ |
| static char * |
| generate_response(const char *request) |
| { |
| // define format for response |
| // Each response contains a field X-Response-ID that contains the id of the testcase |
| #define HTTP_REQUEST_TESTCASE_FORMAT "GET %1024s HTTP/1.%d\r\n" \ |
| "X-Request-ID: %d\r\n" |
| |
| #define HTTP_RESPONSE_DEFAULT_FORMAT "HTTP/1.0 200 OK\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "Cache-Control: max-age=86400\r\n" \ |
| "Content-Type: text/html\r\n" \ |
| "\r\n" \ |
| "Default body" |
| |
| #define HTTP_RESPONSE_FORMAT1 "HTTP/1.0 200 OK\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "Content-Type: text/html\r\n" \ |
| "Cache-Control: no-cache\r\n" \ |
| "\r\n" \ |
| "Body for response 1" |
| |
| #define HTTP_RESPONSE_FORMAT2 "HTTP/1.0 200 OK\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "Cache-Control: max-age=86400\r\n" \ |
| "Content-Type: text/html\r\n" \ |
| "\r\n" \ |
| "Body for response 2" |
| #define HTTP_RESPONSE_FORMAT4 "HTTP/1.0 200 OK\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "Cache-Control: max-age=86400\r\n" \ |
| "Content-Type: text/html\r\n" \ |
| "\r\n" \ |
| "Body for response 4" |
| #define HTTP_RESPONSE_FORMAT5 "HTTP/1.0 200 OK\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "Content-Type: text/html\r\n" \ |
| "\r\n" \ |
| "Body for response 5" |
| #define HTTP_RESPONSE_FORMAT6 "HTTP/1.0 200 OK\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "Cache-Control: max-age=86400\r\n" \ |
| "Content-Language: English\r\n" \ |
| "\r\n" \ |
| "Body for response 6" |
| #define HTTP_RESPONSE_FORMAT7 "HTTP/1.0 200 OK\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "Cache-Control: max-age=86400\r\n" \ |
| "Content-Language: French\r\n" \ |
| "\r\n" \ |
| "Body for response 7" |
| |
| #define HTTP_RESPONSE_FORMAT8 "HTTP/1.0 200 OK\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "Cache-Control: max-age=86400\r\n" \ |
| "Content-Language: French, English\r\n" \ |
| "\r\n" \ |
| "Body for response 8" |
| |
| #define HTTP_RESPONSE_FORMAT9 "HTTP/1.0 200 OK\r\n" \ |
| "Cache-Control: max-age=86400\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "\r\n" \ |
| "Body for response 9" |
| |
| #define HTTP_RESPONSE_FORMAT10 "HTTP/1.0 200 OK\r\n" \ |
| "Cache-Control: max-age=86400\r\n" \ |
| "X-Response-ID: %d\r\n" \ |
| "\r\n" \ |
| "Body for response 10" |
| |
| |
| int test_case, match, http_version; |
| |
| char *response = (char *) TSmalloc(RESPONSE_MAX_SIZE + 1); |
| char url[1024]; |
| |
| // coverity[secure_coding] |
| match = sscanf(request, HTTP_REQUEST_TESTCASE_FORMAT, url, &http_version, &test_case); |
| if (match == 3) { |
| switch (test_case) { |
| case 1: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT1, test_case); |
| break; |
| case 2: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT2, test_case); |
| break; |
| case 4: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT4, test_case); |
| break; |
| case 5: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT5, test_case); |
| break; |
| case 6: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT6, test_case); |
| break; |
| case 7: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT7, test_case); |
| break; |
| case 8: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT8, test_case); |
| break; |
| case 9: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT9, test_case); |
| break; |
| case 10: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_FORMAT10, test_case); |
| break; |
| default: |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_DEFAULT_FORMAT, test_case); |
| break; |
| } |
| } else { |
| /* Didin't recognize a testcase request. send the default response */ |
| snprintf(response, RESPONSE_MAX_SIZE + 1, HTTP_RESPONSE_DEFAULT_FORMAT, test_case); |
| } |
| |
| return response; |
| } |
| |
| |
| // This routine can be called by tests, from the READ_REQUEST_HDR_HOOK |
| // to figure out the id of a test message |
| // Returns id/-1 in case of error |
| static int |
| get_request_id(TSHttpTxn txnp) |
| { |
| TSMBuffer bufp; |
| TSMLoc hdr_loc, id_loc; |
| int id = -1; |
| int ret_val; |
| |
| if (!TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc)) { |
| return -1; |
| } |
| |
| id_loc = TSMimeHdrFieldFind(bufp, hdr_loc, X_REQUEST_ID, -1); |
| if ((id_loc == TS_NULL_MLOC) || (id_loc == TS_ERROR_PTR)) { |
| TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); |
| return -1; |
| } |
| |
| ret_val = TSMimeHdrFieldValueIntGet(bufp, hdr_loc, id_loc, 0, &id); |
| if (ret_val == TS_ERROR) { |
| TSHandleMLocRelease(bufp, hdr_loc, id_loc); |
| TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); |
| return -1; |
| } |
| |
| TSHandleMLocRelease(bufp, hdr_loc, id_loc); |
| TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); |
| return id; |
| } |
| |
| |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // SOCKET CLIENT |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| static ClientTxn * |
| synclient_txn_create(void) |
| { |
| TSMgmtInt proxy_port; |
| |
| ClientTxn *txn = (ClientTxn *) TSmalloc(sizeof(ClientTxn)); |
| if (!TSMgmtIntGet(PROXY_CONFIG_NAME_HTTP_PORT, &proxy_port)) { |
| proxy_port = PROXY_HTTP_DEFAULT_PORT; |
| } |
| txn->connect_port = (int) proxy_port; |
| txn->local_port = (int) 0; |
| txn->connect_ip = IP(127, 0, 0, 1); |
| txn->status = REQUEST_INPROGRESS; |
| txn->request = NULL; |
| txn->vconn = NULL; |
| txn->req_buffer = NULL; |
| txn->req_reader = NULL; |
| txn->resp_buffer = NULL; |
| txn->resp_reader = NULL; |
| txn->magic = MAGIC_ALIVE; |
| txn->connect_action = NULL; |
| |
| TSDebug(CDBG_TAG, "Connecting to proxy 127.0.0.1 on port %d", (int) proxy_port); |
| return txn; |
| } |
| |
| static int |
| synclient_txn_delete(ClientTxn * txn) |
| { |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| if (txn->connect_action && !TSActionDone(txn->connect_action)) { |
| TSActionCancel(txn->connect_action); |
| txn->connect_action = NULL; |
| } |
| if (txn->request) { |
| free(txn->request); |
| } |
| txn->magic = MAGIC_DEAD; |
| TSfree(txn); |
| return 1; |
| } |
| |
| static int |
| synclient_txn_close(TSCont contp) |
| { |
| ClientTxn *txn = (ClientTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| if (txn->vconn != NULL) { |
| TSVConnClose(txn->vconn); |
| } |
| if (txn->req_buffer != NULL) { |
| TSIOBufferDestroy(txn->req_buffer); |
| } |
| if (txn->resp_buffer != NULL) { |
| TSIOBufferDestroy(txn->resp_buffer); |
| } |
| |
| TSContDestroy(contp); |
| |
| TSDebug(CDBG_TAG, "Client Txn destroyed"); |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| static int |
| synclient_txn_send_request(ClientTxn * txn, char *request) |
| { |
| TSCont cont; |
| |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| txn->request = strdup(request); |
| SET_TEST_HANDLER(txn->current_handler, synclient_txn_connect_handler); |
| |
| cont = TSContCreate(synclient_txn_main_handler, TSMutexCreate()); |
| TSContDataSet(cont, txn); |
| TSNetConnect(cont, txn->connect_ip, txn->connect_port); |
| return 1; |
| } |
| |
| /* This can be used to send a request to a specific VC */ |
| static int |
| synclient_txn_send_request_to_vc(ClientTxn * txn, char *request, TSVConn vc) |
| { |
| TSCont cont; |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| txn->request = strdup(request); |
| SET_TEST_HANDLER(txn->current_handler, synclient_txn_connect_handler); |
| |
| cont = TSContCreate(synclient_txn_main_handler, TSMutexCreate()); |
| TSContDataSet(cont, txn); |
| |
| TSContCall(cont, TS_EVENT_NET_CONNECT, vc); |
| return 1; |
| } |
| |
| |
| static int |
| synclient_txn_read_response(TSCont contp) |
| { |
| ClientTxn *txn = (ClientTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| TSIOBufferBlock block = TSIOBufferReaderStart(txn->resp_reader); |
| while (block != NULL) { |
| int64_t blocklen; |
| const char *blockptr = TSIOBufferBlockReadStart(block, txn->resp_reader, &blocklen); |
| |
| if (txn->response_len+blocklen <= RESPONSE_MAX_SIZE) { |
| memcpy((char *) (txn->response + txn->response_len), blockptr, blocklen); |
| txn->response_len += blocklen; |
| } else { |
| TSError("Error: Response length %d > response buffer size %d", txn->response_len+blocklen, RESPONSE_MAX_SIZE); |
| } |
| |
| block = TSIOBufferBlockNext(block); |
| } |
| |
| txn->response[txn->response_len + 1] = '\0'; |
| TSDebug(CDBG_TAG, "Response = |%s|, req len = %d", txn->response, txn->response_len); |
| |
| return 1; |
| } |
| |
| static int |
| synclient_txn_read_response_handler(TSCont contp, TSEvent event, void *data) |
| { |
| NOWARN_UNUSED(data); |
| ClientTxn *txn = (ClientTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| int64_t avail; |
| |
| switch (event) { |
| case TS_EVENT_VCONN_READ_READY: |
| case TS_EVENT_VCONN_READ_COMPLETE: |
| if (event == TS_EVENT_VCONN_READ_READY) { |
| TSDebug(CDBG_TAG, "READ_READY"); |
| } else { |
| TSDebug(CDBG_TAG, "READ_COMPLETE"); |
| } |
| |
| avail = TSIOBufferReaderAvail(txn->resp_reader); |
| TSDebug(CDBG_TAG, "%d bytes available in buffer", avail); |
| |
| if (avail > 0) { |
| synclient_txn_read_response(contp); |
| TSIOBufferReaderConsume(txn->resp_reader, avail); |
| } |
| |
| TSVIOReenable(txn->read_vio); |
| break; |
| |
| case TS_EVENT_VCONN_EOS: |
| TSDebug(CDBG_TAG, "READ_EOS"); |
| // Connection closed. In HTTP/1.0 it means we're done for this request. |
| txn->status = REQUEST_SUCCESS; |
| return synclient_txn_close(contp); |
| break; |
| |
| case TS_EVENT_ERROR: |
| TSDebug(CDBG_TAG, "READ_ERROR"); |
| txn->status = REQUEST_FAILURE; |
| return synclient_txn_close(contp); |
| break; |
| |
| default: |
| TSAssert(!"Invalid event"); |
| break; |
| } |
| return 1; |
| } |
| |
| |
| static int |
| synclient_txn_write_request(TSCont contp) |
| { |
| ClientTxn *txn = (ClientTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| TSIOBufferBlock block; |
| char *ptr_block; |
| int64_t len, ndone, ntodo, towrite, avail; |
| |
| len = strlen(txn->request); |
| |
| ndone = 0; |
| ntodo = len; |
| while (ntodo > 0) { |
| block = TSIOBufferStart(txn->req_buffer); |
| ptr_block = TSIOBufferBlockWriteStart(block, &avail); |
| towrite = MIN(ntodo, avail); |
| memcpy(ptr_block, txn->request + ndone, towrite); |
| TSIOBufferProduce(txn->req_buffer, towrite); |
| ntodo -= towrite; |
| ndone += towrite; |
| } |
| |
| /* Start writing the response */ |
| TSDebug(CDBG_TAG, "Writing |%s| (%d) bytes", txn->request, len); |
| txn->write_vio = TSVConnWrite(txn->vconn, contp, txn->req_reader, len); |
| |
| return 1; |
| } |
| |
| static int |
| synclient_txn_write_request_handler(TSCont contp, TSEvent event, void *data) |
| { |
| NOWARN_UNUSED(data); |
| ClientTxn *txn = (ClientTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| switch (event) { |
| case TS_EVENT_VCONN_WRITE_READY: |
| TSDebug(CDBG_TAG, "WRITE_READY"); |
| TSVIOReenable(txn->write_vio); |
| break; |
| |
| case TS_EVENT_VCONN_WRITE_COMPLETE: |
| TSDebug(CDBG_TAG, "WRITE_COMPLETE"); |
| // Weird: synclient should not close the write part of vconn. |
| // Otherwise some strangeness... |
| |
| /* Start reading */ |
| SET_TEST_HANDLER(txn->current_handler, synclient_txn_read_response_handler); |
| txn->read_vio = TSVConnRead(txn->vconn, contp, txn->resp_buffer, INT64_MAX); |
| break; |
| |
| case TS_EVENT_VCONN_EOS: |
| TSDebug(CDBG_TAG, "WRITE_EOS"); |
| txn->status = REQUEST_FAILURE; |
| return synclient_txn_close(contp); |
| break; |
| |
| case TS_EVENT_ERROR: |
| TSDebug(CDBG_TAG, "WRITE_ERROR"); |
| txn->status = REQUEST_FAILURE; |
| return synclient_txn_close(contp); |
| break; |
| |
| default: |
| TSAssert(!"Invalid event"); |
| break; |
| } |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| |
| static int |
| synclient_txn_connect_handler(TSCont contp, TSEvent event, void *data) |
| { |
| TSAssert((event == TS_EVENT_NET_CONNECT) || (event == TS_EVENT_NET_CONNECT_FAILED)); |
| |
| ClientTxn *txn = (ClientTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| if (event == TS_EVENT_NET_CONNECT) { |
| TSDebug(CDBG_TAG, "NET_CONNECT"); |
| |
| txn->req_buffer = TSIOBufferCreate(); |
| txn->req_reader = TSIOBufferReaderAlloc(txn->req_buffer); |
| txn->resp_buffer = TSIOBufferCreate(); |
| txn->resp_reader = TSIOBufferReaderAlloc(txn->resp_buffer); |
| |
| txn->response[0] = '\0'; |
| txn->response_len = 0; |
| |
| txn->vconn = (TSVConn) data; |
| txn->local_port = (int) ((NetVConnection *) data)->get_local_port(); |
| |
| txn->write_vio = NULL; |
| txn->read_vio = NULL; |
| |
| /* start writing */ |
| SET_TEST_HANDLER(txn->current_handler, synclient_txn_write_request_handler); |
| synclient_txn_write_request(contp); |
| |
| return TS_EVENT_IMMEDIATE; |
| } else { |
| TSDebug(CDBG_TAG, "NET_CONNECT_FAILED"); |
| txn->status = REQUEST_FAILURE; |
| synclient_txn_close(contp); |
| } |
| |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| |
| static int |
| synclient_txn_main_handler(TSCont contp, TSEvent event, void *data) |
| { |
| ClientTxn *txn = (ClientTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| TxnHandler handler = txn->current_handler; |
| return (*handler) (contp, event, data); |
| } |
| |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // SOCKET SERVER |
| ////////////////////////////////////////////////////////////////////////////// |
| |
| SocketServer * |
| synserver_create(int port) |
| { |
| SocketServer *s = (SocketServer *) TSmalloc(sizeof(SocketServer)); |
| s->magic = MAGIC_ALIVE; |
| s->accept_port = port; |
| s->accept_action = NULL; |
| s->accept_cont = TSContCreate(synserver_accept_handler, TSMutexCreate()); |
| TSContDataSet(s->accept_cont, s); |
| return s; |
| } |
| |
| static int |
| synserver_start(SocketServer * s) |
| { |
| TSAssert(s->magic == MAGIC_ALIVE); |
| s->accept_action = TSNetAccept(s->accept_cont, s->accept_port); |
| return 1; |
| } |
| |
| static int |
| synserver_stop(SocketServer * s) |
| { |
| TSAssert(s->magic == MAGIC_ALIVE); |
| if (s->accept_action && !TSActionDone(s->accept_action)) { |
| TSActionCancel(s->accept_action); |
| s->accept_action = NULL; |
| TSDebug(SDBG_TAG, "Had to cancel action"); |
| } |
| TSDebug(SDBG_TAG, "stopped"); |
| return 1; |
| } |
| |
| static int |
| synserver_delete(SocketServer * s) |
| { |
| TSAssert(s->magic == MAGIC_ALIVE); |
| synserver_stop(s); |
| |
| if (s->accept_cont) { |
| TSContDestroy(s->accept_cont); |
| s->accept_cont = NULL; |
| TSDebug(SDBG_TAG, "destroyed accept cont"); |
| } |
| s->magic = MAGIC_DEAD; |
| TSfree(s); |
| TSDebug(SDBG_TAG, "deleted server"); |
| return 1; |
| } |
| |
| static int |
| synserver_accept_handler(TSCont contp, TSEvent event, void *data) |
| { |
| TSAssert((event == TS_EVENT_NET_ACCEPT) || (event == TS_EVENT_NET_ACCEPT_FAILED)); |
| |
| SocketServer *s = (SocketServer *) TSContDataGet(contp); |
| TSAssert(s->magic == MAGIC_ALIVE); |
| |
| if (event == TS_EVENT_NET_ACCEPT_FAILED) { |
| ink_release_assert(!"Synserver must be able to bind to a port, check system netstat"); |
| TSDebug(SDBG_TAG, "NET_ACCEPT_FAILED"); |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| TSDebug(SDBG_TAG, "NET_ACCEPT"); |
| |
| /* Create a new transaction */ |
| ServerTxn *txn = (ServerTxn *) TSmalloc(sizeof(ServerTxn)); |
| txn->magic = MAGIC_ALIVE; |
| |
| SET_TEST_HANDLER(txn->current_handler, synserver_txn_read_request_handler); |
| |
| TSCont txn_cont = TSContCreate(synserver_txn_main_handler, TSMutexCreate()); |
| TSContDataSet(txn_cont, txn); |
| |
| txn->req_buffer = TSIOBufferCreate(); |
| txn->req_reader = TSIOBufferReaderAlloc(txn->req_buffer); |
| |
| txn->resp_buffer = TSIOBufferCreate(); |
| txn->resp_reader = TSIOBufferReaderAlloc(txn->resp_buffer); |
| |
| txn->request[0] = '\0'; |
| txn->request_len = 0; |
| |
| txn->vconn = (TSVConn) data; |
| |
| txn->write_vio = NULL; |
| |
| /* start reading */ |
| txn->read_vio = TSVConnRead(txn->vconn, txn_cont, txn->req_buffer, INT64_MAX); |
| |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| |
| static int |
| synserver_txn_close(TSCont contp) |
| { |
| ServerTxn *txn = (ServerTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| if (txn->vconn != NULL) { |
| TSVConnClose(txn->vconn); |
| } |
| if (txn->req_buffer) { |
| TSIOBufferDestroy(txn->req_buffer); |
| } |
| if (txn->resp_buffer) { |
| TSIOBufferDestroy(txn->resp_buffer); |
| } |
| |
| txn->magic = MAGIC_DEAD; |
| TSfree(txn); |
| TSContDestroy(contp); |
| |
| TSDebug(SDBG_TAG, "Server Txn destroyed"); |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| |
| static int |
| synserver_txn_write_response(TSCont contp) |
| { |
| ServerTxn *txn = (ServerTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| SET_TEST_HANDLER(txn->current_handler, synserver_txn_write_response_handler); |
| |
| TSIOBufferBlock block; |
| char *ptr_block; |
| int64_t len, ndone, ntodo, towrite, avail; |
| char *response; |
| |
| response = generate_response(txn->request); |
| len = strlen(response); |
| |
| ndone = 0; |
| ntodo = len; |
| while (ntodo > 0) { |
| block = TSIOBufferStart(txn->resp_buffer); |
| ptr_block = TSIOBufferBlockWriteStart(block, &avail); |
| towrite = MIN(ntodo, avail); |
| memcpy(ptr_block, response + ndone, towrite); |
| TSIOBufferProduce(txn->resp_buffer, towrite); |
| ntodo -= towrite; |
| ndone += towrite; |
| } |
| |
| /* Start writing the response */ |
| TSDebug(SDBG_TAG, "Writing response: |%s| (%d) bytes)", response, len); |
| txn->write_vio = TSVConnWrite(txn->vconn, contp, txn->resp_reader, len); |
| |
| /* Now that response is in IOBuffer, free up response */ |
| TSfree(response); |
| |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| |
| static int |
| synserver_txn_write_response_handler(TSCont contp, TSEvent event, void *data) |
| { |
| NOWARN_UNUSED(data); |
| ServerTxn *txn = (ServerTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| switch (event) { |
| case TS_EVENT_VCONN_WRITE_READY: |
| TSDebug(SDBG_TAG, "WRITE_READY"); |
| TSVIOReenable(txn->write_vio); |
| break; |
| |
| case TS_EVENT_VCONN_WRITE_COMPLETE: |
| TSDebug(SDBG_TAG, "WRITE_COMPLETE"); |
| TSVConnShutdown(txn->vconn, 0, 1); |
| return synserver_txn_close(contp); |
| break; |
| |
| case TS_EVENT_VCONN_EOS: |
| TSDebug(SDBG_TAG, "WRITE_EOS"); |
| return synserver_txn_close(contp); |
| break; |
| |
| case TS_EVENT_ERROR: |
| TSDebug(SDBG_TAG, "WRITE_ERROR"); |
| return synserver_txn_close(contp); |
| break; |
| |
| default: |
| TSAssert(!"Invalid event"); |
| break; |
| } |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| |
| static int |
| synserver_txn_read_request(TSCont contp) |
| { |
| ServerTxn *txn = (ServerTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| int end; |
| TSIOBufferBlock block = TSIOBufferReaderStart(txn->req_reader); |
| |
| while (block != NULL) { |
| int64_t blocklen; |
| const char *blockptr = TSIOBufferBlockReadStart(block, txn->req_reader, &blocklen); |
| |
| if (txn->request_len+blocklen <= REQUEST_MAX_SIZE) { |
| memcpy((char *) (txn->request + txn->request_len), blockptr, blocklen); |
| txn->request_len += blocklen; |
| } else { |
| TSError("Error: Request length %d > request buffer size %d", txn->request_len+blocklen, REQUEST_MAX_SIZE); |
| } |
| |
| block = TSIOBufferBlockNext(block); |
| } |
| |
| txn->request[txn->request_len] = '\0'; |
| TSDebug(SDBG_TAG, "Request = |%s|, req len = %d", txn->request, txn->request_len); |
| |
| end = (strstr(txn->request, HTTP_REQUEST_END) != NULL); |
| TSDebug(SDBG_TAG, "End of request = %d", end); |
| |
| return end; |
| } |
| |
| static int |
| synserver_txn_read_request_handler(TSCont contp, TSEvent event, void *data) |
| { |
| NOWARN_UNUSED(data); |
| ServerTxn *txn = (ServerTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| int64_t avail; |
| int end_of_request; |
| |
| switch (event) { |
| case TS_EVENT_VCONN_READ_READY: |
| case TS_EVENT_VCONN_READ_COMPLETE: |
| TSDebug(SDBG_TAG, (event == TS_EVENT_VCONN_READ_READY) ? "READ_READY" : "READ_COMPLETE"); |
| avail = TSIOBufferReaderAvail(txn->req_reader); |
| TSDebug(SDBG_TAG, "%d bytes available in buffer", avail); |
| |
| if (avail > 0) { |
| end_of_request = synserver_txn_read_request(contp); |
| TSIOBufferReaderConsume(txn->req_reader, avail); |
| |
| if (end_of_request) { |
| TSVConnShutdown(txn->vconn, 1, 0); |
| return synserver_txn_write_response(contp); |
| } |
| } |
| |
| TSVIOReenable(txn->read_vio); |
| break; |
| |
| case TS_EVENT_VCONN_EOS: |
| TSDebug(SDBG_TAG, "READ_EOS"); |
| return synserver_txn_close(contp); |
| break; |
| |
| case TS_EVENT_ERROR: |
| TSDebug(SDBG_TAG, "READ_ERROR"); |
| return synserver_txn_close(contp); |
| break; |
| |
| default: |
| TSAssert(!"Invalid event"); |
| break; |
| } |
| return TS_EVENT_IMMEDIATE; |
| } |
| |
| |
| static int |
| synserver_txn_main_handler(TSCont contp, TSEvent event, void *data) |
| { |
| ServerTxn *txn = (ServerTxn *) TSContDataGet(contp); |
| TSAssert(txn->magic == MAGIC_ALIVE); |
| |
| TxnHandler handler = txn->current_handler; |
| return (*handler) (contp, event, data); |
| } |
| |