From f8320a22d54e51052478ce6421a9cfc454a4255e Mon Sep 17 00:00:00 2001 From: Kumar Kaushik <kaushikk@vmware.com> Date: Tue, 20 Mar 2018 16:36:00 -0700 Subject: [PATCH] Support persistent connection Change-Id: I05d60c56f5f9e62e5c7b0bd6c10b7fad9e0e329a --- common/sockinterface.c | 55 +++++--- include/vmrestcommon.h | 7 + include/vmsock.h | 4 +- server/restengine/httpProtocolHead.c | 145 +++++++++++---------- server/restengine/libmain.c | 2 +- server/restengine/prototype.h | 5 - test/scripts/BUGS_TC/BUG-2076723/README | 26 ++++ .../BUGS_TC/BUG-2076723/persistentConnection.c | 121 +++++++++++++++++ transport/api/api.c | 4 +- transport/posix/prototypes.h | 2 +- transport/posix/socket.c | 27 +++- 11 files changed, 288 insertions(+), 110 deletions(-) create mode 100644 test/scripts/BUGS_TC/BUG-2076723/README create mode 100644 test/scripts/BUGS_TC/BUG-2076723/persistentConnection.c diff --git a/common/sockinterface.c b/common/sockinterface.c index 197dd48..103c69c 100644 --- a/common/sockinterface.c +++ b/common/sockinterface.c @@ -476,6 +476,7 @@ VmRESTTcpReceiveNewData( uint32_t nProcessed = 0; uint32_t nBufLen = 0; BOOLEAN bNextIO = FALSE; + BOOLEAN bKeepConnOpen = FALSE; if (!pSocket || !pRESTHandle || !pQueue) { @@ -543,6 +544,16 @@ VmRESTTcpReceiveNewData( { pSetReq = pRequest; } + else + { + /**** Verify and act on persistent connection requests ****/ + dwError = VmRESTEntertainPersistentConn( + pRESTHandle, + pRequest, + &bKeepConnOpen + ); + BAIL_ON_VMREST_ERROR(dwError); + } /**** Save state of request processing in socket context ****/ dwError = VmwSockSetRequestHandle( @@ -550,31 +561,35 @@ VmRESTTcpReceiveNewData( pSocket, pSetReq, nProcessed, - pQueue + bKeepConnOpen ); BAIL_ON_VMREST_ERROR(dwError); cleanup: - if (!bNextIO && dwError != REST_ENGINE_ERROR_DOUBLE_FAILURE) - { - VMREST_LOG_DEBUG(pRESTHandle,"%s","Calling closed connection...."); - /**** Close connection ****/ - VmRESTDisconnectClient( - pRESTHandle, - pSocket - ); - - /**** free request object memory ****/ - if (pRequest) - { - VmRESTFreeRequestHandle( - pRESTHandle, - pRequest - ); - pRequest = NULL; - } - } + if (!bNextIO && dwError != REST_ENGINE_ERROR_DOUBLE_FAILURE) + { + + if (!bKeepConnOpen) + { + VMREST_LOG_DEBUG(pRESTHandle,"%s","Calling closed connection...."); + /**** Close connection ****/ + VmRESTDisconnectClient( + pRESTHandle, + pSocket + ); + } + + /**** free request object memory ****/ + if (pRequest) + { + VmRESTFreeRequestHandle( + pRESTHandle, + pRequest + ); + pRequest = NULL; + } + } return dwError; diff --git a/include/vmrestcommon.h b/include/vmrestcommon.h index 3b7f17c..16f90ad 100644 --- a/include/vmrestcommon.h +++ b/include/vmrestcommon.h @@ -350,6 +350,13 @@ VmRESTProcessBuffer( uint32_t* nProcessed ); +uint32_t +VmRESTEntertainPersistentConn( + PVMREST_HANDLE pRESTHandle, + PREST_REQUEST pRequest, + BOOLEAN* bKeepOpen + ); + uint32_t VmRESTSendFailureResponse( PVMREST_HANDLE pRESTHandle, diff --git a/include/vmsock.h b/include/vmsock.h index 5075aa7..5213e67 100644 --- a/include/vmsock.h +++ b/include/vmsock.h @@ -265,7 +265,7 @@ VmwSockSetRequestHandle( PVM_SOCKET pSocket, PREST_REQUEST pRequest, uint32_t nProcessed, - PVM_SOCK_EVENT_QUEUE pQueue + BOOLEAN bPersistentConn ); DWORD @@ -368,7 +368,7 @@ typedef DWORD(*PFN_SET_REQUEST_HANDLE)( PVM_SOCKET pSocket, PREST_REQUEST pRequest, uint32_t nProcessed, - PVM_SOCK_EVENT_QUEUE pQueue + BOOLEAN bPersistentConn ); typedef DWORD(*PFN_GET_PEER_INFO)( diff --git a/server/restengine/httpProtocolHead.c b/server/restengine/httpProtocolHead.c index c5b9f3c..be3ff09 100644 --- a/server/restengine/httpProtocolHead.c +++ b/server/restengine/httpProtocolHead.c @@ -783,77 +783,6 @@ VmRESTSendHeaderAndPayload( goto cleanup; } -uint32_t -VmRESTCloseClient( - PVM_REST_HTTP_RESPONSE_PACKET pResPacket - ) -{ - uint32_t dwError = REST_ENGINE_SUCCESS; - char* connection = NULL; - uint32_t closeSocket = 1; - char* statusStartChar = NULL; - - if (!pResPacket) - { - dwError = VMREST_HTTP_INVALID_PARAMS; - } - BAIL_ON_VMREST_ERROR(dwError); - - /**** Look for response status ****/ - statusStartChar = pResPacket->statusLine->statusCode; - - if(statusStartChar != NULL && (*statusStartChar == '4' || *statusStartChar == '5')) - { - /**** Failure response sent, must close client ****/ - closeSocket = 1; - } - else - { - /**** Non failure response sent, respect client say on connection close ****/ - if (pResPacket->requestPacket != NULL) - { - dwError = VmRESTGetHttpHeader( - pResPacket->requestPacket, - "Connection", - &connection - ); - } - - if ((connection != NULL) && (strcmp(connection, " keep-alive") == 0)) - { - closeSocket = 0; - } - } - if (closeSocket == 1) - { - /**** TODO: Inform transport to close connection else MUST NOT close it****/ - } - - /**** Free all associcated request and response object memory ****/ - if (pResPacket->requestPacket) - { - VmRESTFreeHTTPRequestPacket( - &(pResPacket->requestPacket) - ); - pResPacket->requestPacket = NULL; - } - - VmRESTFreeHTTPResponsePacket( - &pResPacket - ); - BAIL_ON_VMREST_ERROR(dwError); - -cleanup: - if (connection != NULL) - { - VmRESTFreeMemory(connection); - connection = NULL; - } - return dwError; -error: - goto cleanup; -} - uint32_t VmRESTGetRequestHandle( PVMREST_HANDLE pRESTHandle, @@ -1360,7 +1289,6 @@ VmRESTProcessBuffer( } BAIL_ON_VMREST_ERROR(dwError); - /**** Get the request processing state ****/ currState = pRequest->state; *nBytesProcessed = 0; @@ -1692,3 +1620,76 @@ VmRESTSetHttpPayloadZeroCopy( VMREST_LOG_ERROR(pRESTHandle,"%s","Set Zero copy payload Failed"); goto cleanup; } + +uint32_t +VmRESTEntertainPersistentConn( + PVMREST_HANDLE pRESTHandle, + PREST_REQUEST pRequest, + BOOLEAN* bKeepOpen + ) +{ + uint32_t dwError = REST_ENGINE_SUCCESS; + char* pszKeepAliveRequest = NULL; + char* pszKeepAliveResponse = NULL; + BOOLEAN bKeepConnOpen = FALSE; + + if (!pRESTHandle || !pRequest || !bKeepOpen || !pRequest->pResponse) + { + VMREST_LOG_ERROR(pRESTHandle,"%s","Invalid params"); + dwError = VMREST_HTTP_INVALID_PARAMS; + } + + /**** Get client's say on persistent connection ****/ + dwError = VmRESTGetHttpHeader( + pRequest, + "Connection", + &pszKeepAliveRequest + ); + BAIL_ON_VMREST_ERROR(dwError); + + if ((pszKeepAliveRequest != NULL) && (strncmp(pszKeepAliveRequest, "keep-alive", strlen("keep-alive")) == 0)) + { + bKeepConnOpen = TRUE; + } + + /**** Inspect application response on connection (set from application callback) ****/ + if (bKeepConnOpen) + { + dwError = VmRESTGetHttpResponseHeader( + pRequest->pResponse, + "Connection", + &pszKeepAliveResponse + ); + BAIL_ON_VMREST_ERROR(dwError); + + if (!((pszKeepAliveResponse != NULL) && (strncmp(pszKeepAliveResponse, "keep-alive", strlen("keep-alive")) == 0))) + { + VMREST_LOG_WARNING(pRESTHandle,"%s","Client's request for persistent connection not entertained by server"); + bKeepConnOpen = FALSE; + } + } + + *bKeepOpen = bKeepConnOpen; + +cleanup: + + if (pszKeepAliveRequest) + { + VmRESTFreeMemory( + pszKeepAliveRequest + ); + pszKeepAliveRequest = NULL; + } + + return dwError; + +error: + + if (bKeepOpen) + { + *bKeepOpen = FALSE; + } + + goto cleanup; +} + diff --git a/server/restengine/libmain.c b/server/restengine/libmain.c index 3992b76..e227a76 100644 --- a/server/restengine/libmain.c +++ b/server/restengine/libmain.c @@ -612,7 +612,7 @@ VmRESTSetSuccessResponse( ); BAIL_ON_VMREST_ERROR(dwError); - if ((connection != NULL) && (strcmp(connection, " keep-alive") == 0)) + if ((connection != NULL) && ((strncmp(connection, "keep-alive", strlen("keep-alive")) == 0))) { dwError = VmRESTSetHttpHeader( ppResponse, diff --git a/server/restengine/prototype.h b/server/restengine/prototype.h index dfb8bda..d4dcf51 100644 --- a/server/restengine/prototype.h +++ b/server/restengine/prototype.h @@ -116,11 +116,6 @@ VmRESTTriggerAppCb( PVM_REST_HTTP_RESPONSE_PACKET* ppResponse ); -uint32_t -VmRESTCloseClient( - PVM_REST_HTTP_RESPONSE_PACKET pResPacket - ); - uint32_t VmRESTSetHttpPayloadZeroCopy( PVMREST_HANDLE pRESTHandle, diff --git a/test/scripts/BUGS_TC/BUG-2076723/README b/test/scripts/BUGS_TC/BUG-2076723/README new file mode 100644 index 0000000..79cf1a2 --- /dev/null +++ b/test/scripts/BUGS_TC/BUG-2076723/README @@ -0,0 +1,26 @@ +Bugzilla Id: 2076723 + +Category: New feature/Critical + +Description: +In secure version (HTTPS) of c-rest-engine, all connection requests are transactional. +This means each request is served via new connection. This introduces steps involving +openssl handshake which is known to cause significant performance delays. + +Requirements: +1. Re-use TCP connections originating from same host and port to server. +2. Support HTTP header "connection: keep-alive" + +Feature Testing: +Use available libcurl based client to open one single connection and send multiple +HTTP(S) request. + +Steps to test. +1. Compile the persistentConnection.c file with the following command. + "gcc -o persistentConnection persistentConnection.c -lcurl" +2. Run the test with following command. + "./persistentConnection https://<SERVER_IP>:<PORT>/v1/pkg?x=y 2>/dev/null" + + Example: + "./persistentConnection https://172.16.127.131:81/v1/pkg?x=y 2>/dev/null" + diff --git a/test/scripts/BUGS_TC/BUG-2076723/persistentConnection.c b/test/scripts/BUGS_TC/BUG-2076723/persistentConnection.c new file mode 100644 index 0000000..e0f83e6 --- /dev/null +++ b/test/scripts/BUGS_TC/BUG-2076723/persistentConnection.c @@ -0,0 +1,121 @@ +/************************************************************************** +* This test client uses libcurl to open connection to server and re-uses +* the same connection to send multiple HTTPS requests. +**************************************************************************/ + + +#include <stdio.h> +#include <unistd.h> +#include <curl/curl.h> +#include <stdlib.h> +#include <string.h> + + +struct string +{ + char *ptr; + size_t len; +}; + +void init_string(struct string *s) +{ + s->len = 0; + s->ptr = malloc(s->len+1); + if (s->ptr == NULL) + { + fprintf(stderr, "malloc() failed\n"); + exit(EXIT_FAILURE); + } + s->ptr[0] = '\0'; +} + +size_t writefunc(void *ptr, size_t size, size_t nmemb, struct string *s) +{ + size_t new_len = s->len + size*nmemb; + s->ptr = realloc(s->ptr, new_len+1); + if (s->ptr == NULL) + { + fprintf(stderr, "realloc() failed\n"); + exit(EXIT_FAILURE); + } + memcpy(s->ptr+s->len, ptr, size*nmemb); + s->ptr[new_len] = '\0'; + s->len = new_len; + return size*nmemb; +} + +int main (int argc, char *argv[]) +{ + CURL* curl = NULL; + CURLcode res = 0; + int ct = 0; + char expected[512] = {0}; + + if (argc != 2) { + printf("Wrong number of arguments supplied to test program\n"); + exit(1); + } + + struct string s; + curl_global_init(CURL_GLOBAL_ALL); + + curl = curl_easy_init(); + if(curl) + { +try: + init_string(&s); + struct curl_slist *chunk = NULL; + + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_HEADER, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + if (ct == 500) + { + chunk = curl_slist_append(chunk, "Connection:close"); + strcpy(expected,"HTTP/1.1 200 OK\r\nConnection:close\r\nContent-Length:13\r\n\r\nKumar Kaushik"); + } + else + { + chunk = curl_slist_append(chunk, "Connection:keep-alive"); + strcpy(expected,"HTTP/1.1 200 OK\r\nConnection:keep-alive\r\nContent-Length:13\r\n\r\nKumar Kaushik"); + } + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "Kumar Kaushik"); + curl_easy_setopt(curl, CURLOPT_URL, argv[1]); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + res = curl_easy_perform(curl); + if(res != CURLE_OK) + { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + } + + if (strcmp(expected, s.ptr) == 0) + { + printf("Test passed, iteration %d\n", ct); + } + else + { + printf("Test failed, iteration %d\n", ct); + } + //printf("\n===============\n%s\n==========\n", s.ptr); + free(s.ptr); + + ct++; + + if (ct <= 500) + { + goto try; + } + + curl_easy_cleanup(curl); + } + + return 0; +} + diff --git a/transport/api/api.c b/transport/api/api.c index cc2c930..5389ff0 100644 --- a/transport/api/api.c +++ b/transport/api/api.c @@ -268,12 +268,12 @@ VmwSockSetRequestHandle( PVM_SOCKET pSocket, PREST_REQUEST pRequest, uint32_t nProcessed, - PVM_SOCK_EVENT_QUEUE pQueue + BOOLEAN bPersistentConn ) { DWORD dwError = REST_ENGINE_SUCCESS; - dwError = pRESTHandle->pPackage->pfnSetRequestHandle(pRESTHandle,pSocket,pRequest, nProcessed, pQueue); + dwError = pRESTHandle->pPackage->pfnSetRequestHandle(pRESTHandle,pSocket,pRequest, nProcessed, bPersistentConn); return dwError; } diff --git a/transport/posix/prototypes.h b/transport/posix/prototypes.h index d60e5e2..b1e4b57 100644 --- a/transport/posix/prototypes.h +++ b/transport/posix/prototypes.h @@ -101,7 +101,7 @@ VmSockPosixSetRequestHandle( PVM_SOCKET pSocket, PREST_REQUEST pRequest, uint32_t nProcessed, - PVM_SOCK_EVENT_QUEUE pQueue + BOOLEAN bKeepAlive ); DWORD diff --git a/transport/posix/socket.c b/transport/posix/socket.c index 982ae5c..f50356d 100644 --- a/transport/posix/socket.c +++ b/transport/posix/socket.c @@ -1592,7 +1592,7 @@ VmSockPosixSetRequestHandle( PVM_SOCKET pSocket, PREST_REQUEST pRequest, uint32_t nProcessed, - PVM_SOCK_EVENT_QUEUE pQueue + BOOLEAN bPersistentConn ) { uint32_t dwError = REST_ENGINE_SUCCESS; @@ -1600,7 +1600,7 @@ VmSockPosixSetRequestHandle( BOOLEAN bCompleted = FALSE; struct epoll_event event = {0}; - if (!pSocket || !pRESTHandle || !pQueue) + if (!pSocket || !pRESTHandle || !pRESTHandle->pSockContext || !pRESTHandle->pSockContext->pEventQueue) { VMREST_LOG_ERROR(pRESTHandle, "%s", "Invalid params ..."); dwError = ERROR_INVALID_PARAMETER; @@ -1615,14 +1615,28 @@ VmSockPosixSetRequestHandle( if (pRequest) { pSocket->pRequest = pRequest; + pSocket->nProcessed = nProcessed; } else { pSocket->pRequest = NULL; - bCompleted = TRUE; - } - pSocket->nProcessed = nProcessed; + if (bPersistentConn) + { + /**** reset the socket object for new request *****/ + if (pSocket->pszBuffer) + { + VmRESTFreeMemory(pSocket->pszBuffer); + pSocket->pszBuffer = NULL; + } + pSocket->nProcessed = 0; + pSocket->nBufData = 0; + } + else + { + bCompleted = TRUE; + } + } if (!bCompleted) { @@ -1639,7 +1653,7 @@ VmSockPosixSetRequestHandle( event.events = event.events | EPOLLONESHOT; - if (epoll_ctl(pQueue->epollFd, EPOLL_CTL_MOD, pSocket->fd, &event) < 0) + if (epoll_ctl(pRESTHandle->pSockContext->pEventQueue->epollFd, EPOLL_CTL_MOD, pSocket->fd, &event) < 0) { dwError = VM_SOCK_POSIX_ERROR_SYS_CALL_FAILED; } @@ -1659,7 +1673,6 @@ VmSockPosixSetRequestHandle( goto cleanup; - }