/*H********************************************************************************/ /*! \File protohttpmanager.c \Description High-level module designed to create and manage a pool of ProtoHttp refs. A client application can submit rapid-fire http requests and ProtoHttpManager will distribute them efficiently across the ref pool internally, queuing them for efficient use of keep-alive and pipelining requests where possible. \Notes Pipelining resources: http://www.mozilla.org/projects/netlib/http/pipelining-faq.html http://www.w3.org/Protocols/HTTP/Performance/Pipeline.html \Todo Validate POST support Pipelining: - handle fewer responses than expected (?) \Copyright Copyright (c) Electronic Arts 2009-2011. \Version 1.0 05/20/2009 (jbrookes) First Version */ /********************************************************************************H*/ /*** Include files ****************************************************************/ #include #include #include #include "DirtySDK/dirtysock.h" #include "DirtySDK/dirtysock/dirtymem.h" #include "DirtySDK/dirtysock/netconn.h" #include "DirtySDK/proto/protohttpmanager.h" /*** Defines **********************************************************************/ //! maximum number of protohttp refs that can be allocated #ifdef DIRTYCODE_NX #define HTTPMANAGER_MAXREFS (48) #else #define HTTPMANAGER_MAXREFS (64) #endif //! maximum number of protohttp commands that can be queued for execution #define HTTPMANAGER_MAXCMDS (256) //! maximum number of protohttp commands that can be queued in a given httpref #define HTTPMANAGER_MAXREFQUEUE (16) //! define for final-mode diagnostic printing #define HTTPMANAGER_FINALDEBUG (!DIRTYCODE_DEBUG && FALSE) #if HTTPMANAGER_FINALDEBUG #if defined(DIRTYCODE_PC) #include // OutputDebugStringA #else #include #endif #endif /*** Macros ***********************************************************************/ /*** Type Definitions *************************************************************/ typedef enum HttpManagerHttpCmdStateE { HTTPMANAGER_CMDSTATE_IDLE = 0, //!< unallocated HTTPMANAGER_CMDSTATE_WAIT, //!< queued and waiting to start HTTPMANAGER_CMDSTATE_PIPE, //!< pipelined HTTPMANAGER_CMDSTATE_ACTV, //!< active HTTPMANAGER_CMDSTATE_DONE, //!< complete HTTPMANAGER_CMDSTATE_FAIL //!< queued execution of command failed } HttpManagerHttpCmdStateE; typedef enum HttpManagerHttpRefStateE { HTTPMANAGER_REFSTATE_NONE = 0, //!< no ref available HTTPMANAGER_REFSTATE_IDLE, //!< ref is available HTTPMANAGER_REFSTATE_BUSY, //!< ref is in use } HttpManagerHttpRefStateE; //! http ref info typedef struct HttpManagerHttpRefT { ProtoHttpRefT *pProtoHttp; //!< http ref struct HttpManagerHttpCmdT *HttpCmdQueue[HTTPMANAGER_MAXREFQUEUE]; //!< http command queue uint32_t uLastTick; //!< last timestamp ref was accessed uint8_t uHttpState; //!< http ref state int8_t iTransactions; //!< number of transactions queued int8_t iCurTransaction; //!< current transaction counter (used for callbacks) int8_t _pad; } HttpManagerHttpRefT; //! http cmd info typedef struct HttpManagerHttpCmdT { HttpManagerRefT *pHttpManager; //!< HttpManager ref, required by ProtoHttp callback HttpManagerHttpRefT *pHttpRef; //!< pointer to http ref used for this transaction int32_t iHttpHandle; //!< handle associated with this transaction (zero means unallocated) int32_t iTimeout; //!< timeout to set for http ref (zero=do not set) void *pCallbackRef; //!< user callback ref ProtoHttpWriteCbT *pWriteCb; //!< write callback ProtoHttpCustomHeaderCbT *pCustomHeaderCb; //!< custom header callback ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb; //!< receive header callback void *pUserData; //!< callback user data const char *pUrl; //!< request url char *pAppendHdr; //!< append header (optional) int32_t iResult; //!< transaction result uint32_t uQueueTick; //!< tick request was queued at uint32_t uIssueTick; //!< tick request was issued at uint32_t uComplTick; //!< tick request was completed at uint64_t uBytesXfer; //!< bytes transferred uint8_t uRequestType; //!< ProtoHttpRequestTypeE uint8_t uState; //!< transaction state uint8_t bKeepAlive; //!< TRUE if this command was queued with keep-alive uint8_t bCopiedUrl; //!< TRUE if the url was copied } HttpManagerHttpCmdT; //! http module state struct HttpManagerRefT { // module memory group int32_t iMemGroup; //!< module mem group id void *pMemGroupUserData;//!< user data associated with mem group // user callback info ProtoHttpCustomHeaderCbT *pCustomHeaderCb; //!< callback for modifying request header ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb; //!< callback for viewing recv header on recepit //! current handle int32_t iHttpHandle; //! module debuglevel int32_t iVerbose; //! transaction stats HttpManagerStatT HttpManagerStats; // module configuration variables uint8_t bPipelining; //!< pipelining enable/disable uint8_t bKeepalive; //!< keep-alive enable/disable uint8_t bPipeWithoutKeepAlive; //!< if TRUE, will pipeline without a prior keep-alive connection uint8_t bCopyUrl; //!< if TRUE, alloc memory for and copy url; else rely on caller to persist int8_t iMaxPipedUrls; //!< maximum number of Urls that may be piped in a single request uint8_t bAutoUpdate; //!< TRUE if auto-update enabled (default), else FALSE uint8_t bXhttpEnabled; //!< XHTTP enabled (Xbox 360 only) uint8_t _pad; //! number of available http refs int32_t iHttpNumRefs; //! protohttp buffer size int32_t iHttpBufSize; //! global append header (optional) char *pAppendHdr; //! protohttp module pool HttpManagerHttpRefT HttpRefs[HTTPMANAGER_MAXREFS]; //! protohttp command pool HttpManagerHttpCmdT HttpCmds[HTTPMANAGER_MAXCMDS]; }; /*** Function Prototypes **********************************************************/ /*** Variables ********************************************************************/ // Private variables #if HTTPMANAGER_FINALDEBUG int32_t _HttpManager_iStartTick = 0; #endif // Public variables /*** Private Functions ************************************************************/ #if HTTPMANAGER_FINALDEBUG /*F********************************************************************************/ /*! \Function _HttpManagerPrintfCode \Description Special final-mode PC debugging hook to display netprintf debug output. \Input *pFormat - format string \Input ... - variable-length arg list \Output int32_t - zero \Version 05/22/2009 (jbrookes) */ /********************************************************************************F*/ #undef NetPrintf #define NetPrintf(_x) _HttpManagerPrintfCode _x static int32_t _HttpManagerPrintfCode(const char *pFormat, ...) { va_list pFmtArgs; char strText[4096]; char strTick[16]; const char *pText = strText; va_start(pFmtArgs, pFormat); // check for simple formatting if ((pFormat[0] == '%') && (pFormat[1] == 's') && (pFormat[2] == 0)) { pText = va_arg(pFmtArgs, const char *); } else { vsprintf(strText, pFormat, pFmtArgs); } va_end(pFmtArgs); ds_snzprintf(strTick, sizeof(strTick), "[%d] ", NetTick()-_HttpManager_iStartTick); #if defined(DIRTYCODE_PC) OutputDebugStringA(strTick); OutputDebugStringA(pText); #else printf("%s%s", strTick, pText); #endif return(0); } #endif #if HTTPMANAGER_FINALDEBUG /*F********************************************************************************/ /*! \Function _HttpManagerPrintfVerboseCode \Description Special final-mode PC debugging hook to display netprintfverbose debug output. \Input iVerbosityLevel - module verbosity level \Input iCheckLevel - verbosity to check against for this statement \Input *pFormat - format string \Input ... - variable-length arg list \Output int32_t - zero \Version 05/26/2009 (jbrookes) */ /********************************************************************************F*/ #undef NetPrintfVerbose #define NetPrintfVerbose(_x) _HttpManagerPrintfVerboseCode _x static int32_t _HttpManagerPrintfVerboseCode(int32_t iVerbosityLevel, int32_t iCheckLevel, const char *pFormat, ...) { va_list pFmtArgs; char strText[4096]; char strTick[16]; const char *pText = strText; va_start(pFmtArgs, pFormat); // check for simple formatting if ((pFormat[0] == '%') && (pFormat[1] == 's') && (pFormat[2] == 0)) { pText = va_arg(pFmtArgs, const char *); } else { vsprintf(strText, pFormat, pFmtArgs); } va_end(pFmtArgs); if (iCheckLevel < iVerbosityLevel) { ds_snzprintf(strTick, sizeof(strTick), "[%d] ", NetTick()-_HttpManager_iStartTick); #if defined(DIRTYCODE_PC) OutputDebugStringA(strTick); OutputDebugStringA(pText); #else printf("%s%s", strTick, pText); #endif } return(0); } #endif #if DIRTYCODE_LOGGING || HTTPMANAGER_FINALDEBUG /*F********************************************************************************/ /*! \Function _HttpManagerDisplayStats \Description Display transfer stats for module. \Input *pHttpManager - httpmanager state \Version 02/21/2011 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerDisplayStats(HttpManagerRefT *pHttpManager) { // display stats NetPrintf(("httpmanager: transactions: %d\n", pHttpManager->HttpManagerStats.uNumTransactions)); if (pHttpManager->HttpManagerStats.uNumTransactions > 0) { NetPrintf(("httpmanager: keepalive transactions: %d\n", pHttpManager->HttpManagerStats.uNumKeepAliveTransactions)); NetPrintf(("httpmanager: pipelined transactions: %d\n", pHttpManager->HttpManagerStats.uNumPipelinedTransactions)); NetPrintf(("httpmanager: max active transactions: %d\n", pHttpManager->HttpManagerStats.uMaxActiveTransactions)); NetPrintf(("httpmanager: max queued transactions: %d\n", pHttpManager->HttpManagerStats.uMaxQueuedTransactions)); NetPrintf(("httpmanager: sum queue wait time: %dms\n", pHttpManager->HttpManagerStats.uSumQueueWaitLatency)); NetPrintf(("httpmanager: avg queue wait time: %dms\n", pHttpManager->HttpManagerStats.uSumQueueWaitLatency/pHttpManager->HttpManagerStats.uNumTransactions)); NetPrintf(("httpmanager: max queue wait time: %dms\n", pHttpManager->HttpManagerStats.uMaxQueueWaitLatency)); NetPrintf(("httpmanager: sum queue free time: %dms\n", pHttpManager->HttpManagerStats.uSumQueueFreeLatency)); NetPrintf(("httpmanager: avg queue free time: %dms\n", pHttpManager->HttpManagerStats.uSumQueueFreeLatency/pHttpManager->HttpManagerStats.uNumTransactions)); NetPrintf(("httpmanager: max queue free time: %dms\n", pHttpManager->HttpManagerStats.uMaxQueueFreeLatency)); NetPrintf(("httpmanager: total bytes transferred: %qd\n", pHttpManager->HttpManagerStats.uTransactionBytes)); NetPrintf(("httpmanager: total transaction time: %d\n", pHttpManager->HttpManagerStats.uTransactionTime)); NetPrintf(("httpmanager: avg bytes per second %.2f\n", (float)pHttpManager->HttpManagerStats.uTransactionBytes*1000.0f/(float)pHttpManager->HttpManagerStats.uTransactionTime)); NetPrintf(("httpmanager: avg transaction size %.2f\n", (float)pHttpManager->HttpManagerStats.uTransactionBytes/(float)pHttpManager->HttpManagerStats.uNumTransactions)); } } #endif /*F********************************************************************************/ /*! \Function _HttpManagerResetStats \Description Reset transfer stats for module. \Input *pHttpManager - httpmanager state \Version 07/26/2012 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerResetStats(HttpManagerRefT *pHttpManager) { ds_memclr(&pHttpManager->HttpManagerStats, sizeof(pHttpManager->HttpManagerStats)); } /*F********************************************************************************/ /*! \Function _HttpManagerUrlCompare \Description Compares base elements of given Urls (kind, host, port, security) and returns whether they are identical or not. \Input *pUrlA - Url to compare \Input *pUrlB - Url to compare \Output uint32_t - zero=same, else different \Version 07/01/2009 (jbrookes) First Version */ /********************************************************************************F*/ static uint32_t _HttpManagerUrlCompare(const char *pUrlA, const char *pUrlB) { char strKindA[16], strKindB[16], strHostA[128], strHostB[128]; int32_t iPortA, iPortB, iSecureA, iSecureB; ProtoHttpUrlParse(pUrlA, strKindA, sizeof(strKindA), strHostA, sizeof(strHostA), &iPortA, &iSecureA); ProtoHttpUrlParse(pUrlB, strKindB, sizeof(strKindB), strHostB, sizeof(strHostB), &iPortB, &iSecureB); return(!((ds_stricmp(strKindA, strKindB) == 0) && (ds_stricmp(strHostA, strHostB) == 0) && (iPortA == iPortB) && (iSecureA == iSecureB))); } /*F********************************************************************************/ /*! \Function _HttpManagerPipelineCheck \Description See if specified requests will pipeline. \Input *pHttpCmd - previously issued command \Input *pHttpCmd2 - command to check for piping with previous command \Output int32_t - zero=will not pipe; else will pipe \Version 07/15/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerPipelineCheck(HttpManagerHttpCmdT *pHttpCmd, HttpManagerHttpCmdT *pHttpCmd2) { // make sure request can be pipelined if ((pHttpCmd2->uRequestType != PROTOHTTP_REQUESTTYPE_GET) && (pHttpCmd2->uRequestType != PROTOHTTP_REQUESTTYPE_HEAD)) { return(0); } // make sure urls will pipe return(_HttpManagerUrlCompare(pHttpCmd->pUrl, pHttpCmd2->pUrl) == 0); } /*F********************************************************************************/ /*! \Function _HttpManagerPipelineReset \Description Reset pipe following a request failure of some kind or another with subsequent piped results that need to be reissued. \Input *pHttpManager - reference pointer \Input *pHttpRef - http ref piped transactions we need to consider for reset are queued on \Input iHttpCmdFirst - first command to reset \Version 07/20/2009 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerPipelineReset(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, int32_t iHttpCmdFirst) { HttpManagerHttpCmdT *pHttpCmd = NULL; int32_t iHttpCmd; // reset commands for reissue for (iHttpCmd = iHttpCmdFirst ; iHttpCmd < pHttpRef->iTransactions; iHttpCmd += 1) { pHttpCmd = pHttpRef->HttpCmdQueue[iHttpCmd]; if ((pHttpCmd->uState == HTTPMANAGER_CMDSTATE_ACTV) || (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_PIPE)) { NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: reset handle %d for re-issue\n", pHttpCmd->iHttpHandle)); pHttpCmd->uState = HTTPMANAGER_CMDSTATE_WAIT; pHttpCmd->bKeepAlive = FALSE; } else { break; } } if ((iHttpCmd - iHttpCmdFirst) > 0) { NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: reset %d requests on ref %2d for re-issue\n", iHttpCmd - iHttpCmdFirst, pHttpRef - pHttpCmd->pHttpManager->HttpRefs)); } } /*F********************************************************************************/ /*! \Function _HttpManagerCustomHeaderCb \Description Handle ProtoHttp custom header send callback, and pass it on to caller-registered callback handler with handle-specific callback user data pointer. \Input *pState - protohttp state \Input *pHeader - request header \Input uHeaderSize - amount of space available for header (including current header text) \Input *pData - data to be appended to header \Input iDataLen - total size of data \Input *pUserRef - HttpManagerHttpRefT for this request \Output int32_t - user callback result \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerCustomHeaderCb(ProtoHttpRefT *pState, char *pHeader, uint32_t uHeaderSize, const char *pData, int64_t iDataLen, void *pUserRef) { HttpManagerHttpRefT *pHttpRef = (HttpManagerHttpRefT *)pUserRef; HttpManagerHttpCmdT *pHttpCmd = pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]; NetPrintfVerbose((pHttpCmd->pHttpManager->iVerbose, 1, "httpmanager: calling custom header callback with userref 0x%08x for handle %d\n", (uintptr_t)pHttpCmd->pCallbackRef, pHttpCmd->iHttpHandle)); return(pHttpCmd->pHttpManager->pCustomHeaderCb(pState, pHeader, uHeaderSize, pData, iDataLen, pHttpCmd->pCallbackRef)); } /*F********************************************************************************/ /*! \Function _HttpManagerReceiveHeaderCb \Description Handle ProtoHttp header receive callback, and pass it on to caller-registered callback handler with handle-specific callback user data pointer. \Input *pState - protohttp state \Input *pHeader - response header \Input uHeaderSize - size of response header \Input *pUserRef - HttpManagerHttpRefT for this request \Output int32_t - user callback result \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerReceiveHeaderCb(ProtoHttpRefT *pState, const char *pHeader, uint32_t uHeaderSize, void *pUserRef) { HttpManagerHttpRefT *pHttpRef = (HttpManagerHttpRefT *)pUserRef; HttpManagerHttpCmdT *pHttpCmd = pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]; HttpManagerRefT *pHttpManager = pHttpCmd->pHttpManager; int32_t iResult, iHttpCmd = -1; ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb; void *pUserData; /* early out if pHttpManager is null; fifa encountred an issue In theory, if HttpManagerAlloc/HttpManagerFree are being called from different thread than ProtoHttpUpdate. When HttpManagerFree is called it clears out a structure HttpManagerHttpCmdT that contains the pointer to pHttpManager, it is then a race condition on when the header callback is called causing a crash. */ if (pHttpManager == NULL) { NetPrintf(("httpmanager: skipping _HttpManagerReceiveHeaderCb because pHttpManager is null\n")); return(0); } // check for an error response, and handle if it will affect piped commands iResult = ProtoHttpStatus(pState, 'code', NULL, 0); if (PROTOHTTP_GetResponseClass(iResult) != PROTOHTTP_RESPONSE_SUCCESSFUL) { if (PROTOHTTP_GetResponseClass(iResult) == PROTOHTTP_RESPONSE_REDIRECTION) { char strLocation[1024]; ProtoHttpGetLocationHeader(pState, pHeader, strLocation, sizeof(strLocation), NULL); NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] redirect ref %d url=%s (%d)\n", pHttpCmd->iHttpHandle, pHttpCmd->pHttpRef - pHttpManager->HttpRefs, strLocation, iResult)); } else { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] redirect ref %d (%d)\n", pHttpCmd->iHttpHandle, pHttpCmd->pHttpRef - pHttpManager->HttpRefs, iResult)); } /* if this transaction timed out (from a keep-alive connection that was closed) mark it for reissue, otherwise, we just look for subsequent piped results that need to be reissued */ if (iResult == PROTOHTTP_RESPONSE_REQUESTTIMEOUT) { iHttpCmd = 0; } if (PROTOHTTP_GetResponseClass(iResult) == PROTOHTTP_RESPONSE_REDIRECTION) { iHttpCmd = 1; } } // check for loss of piped commands due to premature close if (ProtoHttpStatus(pState, 'plst', NULL, 0) == TRUE) { NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: piped commands lost due to premature close on handle %d\n", pHttpCmd->iHttpHandle)); iHttpCmd = 1; // downgrade our pipelining support $$todo$$ - this should be tracked per site if ((PROTOHTTP_GetResponseClass(iResult) != PROTOHTTP_RESPONSE_REDIRECTION) && (pHttpManager->bPipeWithoutKeepAlive)) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager; disabling pipelining without keep-alive\n")); pHttpManager->bPipeWithoutKeepAlive = FALSE; } } // reset pipe if necessary if (iHttpCmd != -1) { _HttpManagerPipelineReset(pHttpManager, pHttpRef, iHttpCmd); } // if we have a 408 timeout response, reset ref to idle state, so we can reissue the request if (iResult == PROTOHTTP_RESPONSE_REQUESTTIMEOUT) { pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_IDLE; // since we are going to re-issue this request, we do not forward the 408 response on to the application return(0); } // request level callback takes priority to global if ((pReceiveHeaderCb = pHttpCmd->pReceiveHeaderCb) != NULL) { pUserData = pHttpCmd->pUserData; } else { pReceiveHeaderCb = pHttpManager->pReceiveHeaderCb; pUserData = pHttpCmd->pCallbackRef; } // forward to caller's receive callback, if installed if (pReceiveHeaderCb != NULL) { NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: calling receive header callback with userref 0x%08x for handle %d\n", (uintptr_t)pUserData, pHttpCmd->iHttpHandle)); return(pReceiveHeaderCb(pState, pHeader, uHeaderSize, pUserData)); } else { return(0); } } /*F********************************************************************************/ /*! \Function _HttpManagerWriteCbProcess \Description User write callback processing, if write callback is set \Input *pHttpManager - reference pointer \Input *pHttpCmd - http cmd \Version 07/30/2012 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerWriteCbProcess(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) { ProtoHttpWriteCbInfoT WriteCbInfo; char strTempRecv[1024]; int32_t iResult; ds_memclr(&WriteCbInfo, sizeof(WriteCbInfo)); WriteCbInfo.eRequestType = (ProtoHttpRequestTypeE)pHttpCmd->uRequestType; WriteCbInfo.eRequestResponse = PROTOHTTP_RESPONSE_PENDING; while ((iResult = HttpManagerRecv(pHttpManager, pHttpCmd->iHttpHandle, strTempRecv, 1, sizeof (strTempRecv))) > 0) { pHttpCmd->pWriteCb(pHttpCmd->pHttpRef->pProtoHttp, &WriteCbInfo, strTempRecv, iResult, pHttpCmd->pUserData); } if ((iResult == PROTOHTTP_RECVDONE) || (iResult == PROTOHTTP_RECVHEAD) || (iResult == PROTOHTTP_RECVFAIL)) { if (iResult != PROTOHTTP_RECVFAIL) { WriteCbInfo.eRequestResponse = (ProtoHttpResponseE)ProtoHttpStatus(pHttpCmd->pHttpRef->pProtoHttp, 'code', NULL, 0); pHttpCmd->pWriteCb(pHttpCmd->pHttpRef->pProtoHttp, &WriteCbInfo, "", iResult, pHttpCmd->pUserData); } else { pHttpCmd->pWriteCb(pHttpCmd->pHttpRef->pProtoHttp, &WriteCbInfo, "", PROTOHTTP_RECVFAIL, pHttpCmd->pUserData); } pHttpCmd->pWriteCb = NULL; pHttpCmd->pCustomHeaderCb = NULL; pHttpCmd->pReceiveHeaderCb = NULL; pHttpCmd->pUserData = NULL; } } /*F********************************************************************************/ /*! \Function _HttpManagerSetAppendHeader \Description Set append header, either on a global basis (pHttpCmd=NULL) or for a specific HTTP command. \Input *pHttpManager - reference pointer \Input *pHttpCmd - cmd to set append header for, or NULL to set global append header \Input *pAppendHdr - append header to set \Output int32_t - negative=failure, else success \Version 07/26/2012 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerSetAppendHeader(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd, const char *pAppendHdr) { char **ppAppendHdr; // point to append header we are operating on ppAppendHdr = (pHttpCmd != NULL) ? &pHttpCmd->pAppendHdr : &pHttpManager->pAppendHdr; // if there is a previous append header, free it if (*ppAppendHdr != NULL) { DirtyMemFree(*ppAppendHdr, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); *ppAppendHdr = NULL; } // set new append header, if specified if ((pAppendHdr != NULL) && (*pAppendHdr != '\0')) { int32_t iHdrLen = (int32_t)strlen(pAppendHdr); // allocate memory to buffer append header if ((*ppAppendHdr = DirtyMemAlloc(iHdrLen+1, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData)) == NULL) { NetPrintf(("httpmanager: could not allocate %d bytes for append header for handle %d\n", iHdrLen+1, (pHttpCmd != NULL) ? pHttpCmd->iHttpHandle: -1)); return(-1); } // copy to append header ds_strnzcpy(*ppAppendHdr, pAppendHdr, iHdrLen+1); } // return success return(0); } /*F********************************************************************************/ /*! \Function _HttpManagerApplyAppendHeader \Description Applies append header to the specified command. The append header set will be the command-specific header if set, else it will be the global append header if set. If neither is set, the append header is cleared. \Input *pHttpManager - reference pointer \Input *pHttpCmd - cmd to get append header for \Version 07/26/2012 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerApplyAppendHeader(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) { char *pAppendHdr = pHttpCmd->pAppendHdr; if (pAppendHdr == NULL) { pAppendHdr = pHttpManager->pAppendHdr; } ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, 'apnd', 0, 0, pAppendHdr); } /*F********************************************************************************/ /*! \Function _HttpManagerAllocHandle \Description Allocate a new handle. Valid handles range from [1...7ffffff]. \Input *pHttpManager - reference pointer \Output int32_t - new handle \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerAllocHandle(HttpManagerRefT *pHttpManager) { int32_t iHttpHandle = pHttpManager->iHttpHandle; pHttpManager->iHttpHandle = (pHttpManager->iHttpHandle + 1) & 0x7fffffff; return(iHttpHandle); } /*F********************************************************************************/ /*! \Function _HttpManagerAllocCmd \Description Allocate a new command. A command represents a single HTTP transaction request. \Input *pHttpManager - reference pointer \Output HttpManagerHttpCmdT * - new command \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ static HttpManagerHttpCmdT *_HttpManagerAllocCmd(HttpManagerRefT *pHttpManager) { HttpManagerHttpCmdT *pHttpCmd; int32_t iHttpCmd; for (iHttpCmd = 0, pHttpCmd = NULL; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) { if (pHttpManager->HttpCmds[iHttpCmd].iHttpHandle == 0) { pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; ds_memclr(pHttpCmd, sizeof(*pHttpCmd)); pHttpCmd->pHttpManager = pHttpManager; pHttpCmd->iHttpHandle = _HttpManagerAllocHandle(pHttpManager); break; } } return(pHttpCmd); } /*F********************************************************************************/ /*! \Function _HttpManagerAllocRef \Description Allocate an http ref. An http ref represents a single ProtoHttp module. In HttpManager, all ProtoHttp refs are created at startup; this function simply finds an appropriate ref to use for the specified transaction request. \Input *pHttpManager - reference pointer \Input *pHttpCmd - command \Output HttpManagerHttpRefT * - new command \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ static HttpManagerHttpRefT *_HttpManagerAllocRef(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) { HttpManagerHttpRefT *pHttpRef, *pAllocRef = NULL; HttpManagerHttpCmdT *pHttpPipeCmd; int32_t iHttpCmd, iHttpRef; char strKind[16], strHost[128]; int32_t iPort, iSecure; // parse Url into component parts ProtoHttpUrlParse(pHttpCmd->pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure); // first pass, we try to find a ref we can pipeline this request on if (pHttpManager->bPipelining) { for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { // get ref pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; // iterate through queued transactions, if any for (iHttpCmd = 0; iHttpCmd < pHttpRef->iTransactions; iHttpCmd += 1) { // ref http command we might be able to pipe against pHttpPipeCmd = pHttpRef->HttpCmdQueue[iHttpCmd]; if ((pHttpPipeCmd->uState == HTTPMANAGER_CMDSTATE_WAIT) && ((pHttpPipeCmd->bKeepAlive == TRUE) || (pHttpManager->bPipeWithoutKeepAlive == TRUE))) { if (_HttpManagerPipelineCheck(pHttpPipeCmd, pHttpCmd) && (pHttpRef->iTransactions < HTTPMANAGER_MAXREFQUEUE)) { if (iHttpCmd == (pHttpRef->iTransactions - 1)) { pAllocRef = pHttpRef; pHttpCmd->bKeepAlive = TRUE; NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] queued ref %d count=%d pipe=1 url=%s\n", pHttpCmd->iHttpHandle, iHttpRef, pAllocRef->iTransactions, pHttpCmd->pUrl)); break; } } } } } } // if not pipelined if (pAllocRef == NULL) { uint8_t aKeepAliveState[HTTPMANAGER_MAXREFS]; int32_t iBestMatchIdx, iBestMatchTick, iHttpRefTick, iCurTick; int32_t iQueueDepth; // build list of keep-alive matches (whether a ref will try keep-alive with the specified Url) for all http refs ds_memclr(aKeepAliveState, sizeof(aKeepAliveState)); for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; if ((pHttpRef->uHttpState != HTTPMANAGER_REFSTATE_NONE) && ProtoHttpCheckKeepAlive(pHttpRef->pProtoHttp, pHttpCmd->pUrl)) { aKeepAliveState[iHttpRef] = TRUE; } } // iterate through ref list in order from least heavily loaded (queue size) to most heavily loaded for (iQueueDepth = 0; (pAllocRef == NULL) && (iQueueDepth < HTTPMANAGER_MAXREFQUEUE); iQueueDepth += 1) { // first, try to find a ref at this depth with keep-alive potential for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; if ((pHttpRef->iTransactions == iQueueDepth) && (aKeepAliveState[iHttpRef] == TRUE)) { // we have a match pAllocRef = pHttpRef; pHttpCmd->bKeepAlive = TRUE; NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] queued ref %d count=%d keep=1 url=%s\n", pHttpCmd->iHttpHandle, iHttpRef, pAllocRef->iTransactions, pHttpCmd->pUrl)); break; } } // did we find one? if (pAllocRef != NULL) { break; } // second pass, just look for the least recently used ref at this depth for (iHttpRef = 0, iBestMatchIdx = -1, iBestMatchTick = -1, iCurTick = NetTick(); iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; iHttpRefTick = NetTickDiff(iCurTick, pHttpRef->uLastTick); if ((pHttpRef->iTransactions == iQueueDepth) && (iHttpRefTick > iBestMatchTick)) { // consider this for a match iBestMatchTick = iHttpRefTick; iBestMatchIdx = iHttpRef; } } // if we found a ref, use it if (iBestMatchIdx >= 0) { pAllocRef = &pHttpManager->HttpRefs[iBestMatchIdx]; pHttpCmd->bKeepAlive = FALSE; NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] queued ref %d count=%d keep=0 url=%s\n", pHttpCmd->iHttpHandle, iBestMatchIdx, pAllocRef->iTransactions, pHttpCmd->pUrl)); break; } } } // if we allocated a ref if (pAllocRef != NULL) { // update transaction stats if (pAllocRef->iTransactions > 0) { pHttpManager->HttpManagerStats.uNumQueuedTransactions += 1; if (pHttpManager->HttpManagerStats.uMaxQueuedTransactions < pHttpManager->HttpManagerStats.uNumQueuedTransactions) { pHttpManager->HttpManagerStats.uMaxQueuedTransactions = pHttpManager->HttpManagerStats.uNumQueuedTransactions; } } if (pHttpCmd->bKeepAlive) { pHttpManager->HttpManagerStats.uNumKeepAliveTransactions += 1; } // bind ref to cmd pHttpCmd->pHttpRef = pAllocRef; // add command to ref queue pHttpCmd->pHttpRef->HttpCmdQueue[pHttpCmd->pHttpRef->iTransactions++] = pHttpCmd; // update last access time pHttpCmd->pHttpRef->uLastTick = pHttpCmd->uQueueTick; } return(pAllocRef); } /*F********************************************************************************/ /*! \Function _HttpManagerGetCmd \Description Find http command referenced by specified handle. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle \Output HttpManagerHttpCmdT * - requested command \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ static HttpManagerHttpCmdT *_HttpManagerGetCmd(HttpManagerRefT *pHttpManager, int32_t iHandle) { HttpManagerHttpCmdT *pHttpCmd; int32_t iHttpCmd; // walk cmd list and find transaction with given handle for (iHttpCmd = 0, pHttpCmd = NULL; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) { if (pHttpManager->HttpCmds[iHttpCmd].iHttpHandle == iHandle) { pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; break; } } return(pHttpCmd); } /*F********************************************************************************/ /*! \Function _HttpManagerUpdateTransactionStats \Description Update stats based on a single transaction request. \Input *pHttpManager - reference pointer \Input *pHttpCmd - transaction command \Version 05/25/2009 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerUpdateTransactionStats(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) { uint32_t uLatency; pHttpManager->HttpManagerStats.uNumActiveTransactions += 1; if (pHttpManager->HttpManagerStats.uMaxActiveTransactions < pHttpManager->HttpManagerStats.uNumActiveTransactions) { pHttpManager->HttpManagerStats.uMaxActiveTransactions = pHttpManager->HttpManagerStats.uNumActiveTransactions; } pHttpCmd->uIssueTick = NetTick(); uLatency = NetTickDiff(pHttpCmd->uIssueTick, pHttpCmd->uQueueTick); if (pHttpManager->HttpManagerStats.uMaxQueueWaitLatency < uLatency) { pHttpManager->HttpManagerStats.uMaxQueueWaitLatency = uLatency; } pHttpManager->HttpManagerStats.uSumQueueWaitLatency += uLatency; pHttpManager->HttpManagerStats.uNumTransactions += 1; } /*F********************************************************************************/ /*! \Function _HttpManagerResizeInputBuffer \Description After PROTOHTTP_MINBUFF response, increase the input buffer size for the given ProtoHttp ref to allow the request buffer space to be reissued successfully. \Input *pHttpManager - reference pointer \Input *pHttpRef - http ref to resize \Output int32_t - zero=success, else failure \Version 02/15/20011 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerResizeInputBuffer(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef) { int32_t iBuffSize, iCurrSize, iReqdSize; // get current and required sizes if ((iCurrSize = ProtoHttpStatus(pHttpRef->pProtoHttp, 'imax', NULL, 0)) <= 0) { // something wrong with HTTP ref... can happen if HTTP ref is tromped, so we bail out here to prevent infinite loop below NetPrintf(("httpmanager: ProtoHttpStats(0x%08x, 'imax') returned %d\n", (uintptr_t)pHttpRef->pProtoHttp, iCurrSize)); return(-1); } iReqdSize = ProtoHttpStatus(pHttpRef->pProtoHttp, 'iovr', NULL, 0); // calc new buffer size in increments of original buffer size for (iBuffSize = iCurrSize; iBuffSize < iReqdSize; iBuffSize += iCurrSize) ; // resize the input buffer return(ProtoHttpControl(pHttpRef->pProtoHttp, 'ires', iBuffSize, 0, NULL)); } /*F********************************************************************************/ /*! \Function _HttpManagerRequestIssue \Description Issue an HTTP request \Input *pHttpManager - reference pointer \Input *pHttpCmd - transaction command \Input *pData - data associated with command, or NULL if none \Input iDataSize - size of data associated with command, or zero if none \Output int32_t - ProtoHttpRequest() result \Version 05/25/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerRequestIssue(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd, const char *pData, int64_t iDataSize) { int32_t iResult; // initiate the request, we only specify the custom header callback as the rest are implemented within this module if ((iResult = ProtoHttpRequestCb2(pHttpCmd->pHttpRef->pProtoHttp, pHttpCmd->pUrl, pData, iDataSize, (ProtoHttpRequestTypeE)pHttpCmd->uRequestType, NULL, pHttpCmd->pCustomHeaderCb, NULL, pHttpCmd->pUserData)) < 0) { return(iResult); } NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] started ref %d url=%s\n", pHttpCmd->iHttpHandle, pHttpCmd->pHttpRef - pHttpManager->HttpRefs, pHttpCmd->pUrl)); // if pipelining is enabled, a ProtoHttpGet request with NULL Url is required to flush the previous request if (pHttpManager->bPipelining && ((pHttpCmd->uRequestType == PROTOHTTP_REQUESTTYPE_HEAD) || (pHttpCmd->uRequestType == PROTOHTTP_REQUESTTYPE_GET))) { ProtoHttpGet(pHttpCmd->pHttpRef->pProtoHttp, NULL, 0); } // update stats _HttpManagerUpdateTransactionStats(pHttpManager, pHttpCmd); // update status pHttpCmd->uState = HTTPMANAGER_CMDSTATE_ACTV; return(iResult); } /*F********************************************************************************/ /*! \Function _HttpManagerRequestStart \Description Set up any initial transaction-specific settings and issue a GET or HEAD request. \Input *pHttpManager - reference pointer \Input *pHttpCmd - transaction command \Input *pData - pointer to URL data (optional, may be NULL) \Input iDataSize - size of data being uploaded (see Notes) \Output int32_t - ProtoHttpRequest() result \Version 05/21/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerRequestStart(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd, const char *pData, int64_t iDataSize) { int32_t iResult; // make sure ref is idle #if DIRTYCODE_LOGGING if (pHttpCmd->pHttpRef->uHttpState != HTTPMANAGER_REFSTATE_IDLE) { NetPrintf(("httpmanager: error; get request issued on non-idle ref\n")); } #endif // explicitly disable keep-alive if global setting has it unavailable if (pHttpManager->bKeepalive == FALSE) { ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, 'keep', 0, 0, NULL); } // set timeout if requested if (pHttpCmd->iTimeout != 0) { ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, 'time', pHttpCmd->iTimeout, 0, NULL); } // apply append header _HttpManagerApplyAppendHeader(pHttpManager, pHttpCmd); // update httpref status pHttpCmd->pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_BUSY; pHttpCmd->pHttpRef->iCurTransaction = 0; // initiate the request if ((iResult = _HttpManagerRequestIssue(pHttpManager, pHttpCmd, pData, iDataSize)) < 0) { // if there wasn't enough input buffer space to store the request, realloc the buffer and try again if ((iResult == PROTOHTTP_MINBUFF) && (_HttpManagerResizeInputBuffer(pHttpManager, pHttpCmd->pHttpRef) == 0)) { // resized input buffer; try to issue the request again iResult = _HttpManagerRequestIssue(pHttpManager, pHttpCmd, pData, iDataSize); } // fail on error if (iResult < 0) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: ProtoHttpRequest() returned %d on handle %d url=%s\n", iResult, pHttpCmd->iHttpHandle, pHttpCmd->pUrl)); pHttpCmd->uState = HTTPMANAGER_CMDSTATE_FAIL; } } return(iResult); } /*F********************************************************************************/ /*! \Function _HttpManagerRequestCheckCompletion \Description Check for transaction completion, and update state and stats accordingly. \Input *pHttpManager - reference pointer \Input *pHttpCmd - transaction command \Version 07/03/2009 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerRequestCheckCompletion(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) { HttpManagerHttpRefT *pHttpRef = pHttpCmd->pHttpRef; int32_t iHeadSize, iStatus; #if DIRTYCODE_LOGGING int32_t iRsltCode = 0; #endif // are we done yet? if ((iStatus = ProtoHttpStatus(pHttpRef->pProtoHttp, 'done', NULL, 0)) == 0) { // not done yet return; } else if (iStatus == 1) { int64_t iBodySize; ProtoHttpStatus(pHttpRef->pProtoHttp, 'body', &iBodySize, sizeof(iBodySize)); if (iBodySize != (signed)pHttpCmd->uBytesXfer) { // transaction is complete, but data has not all been received by caller return; } #if DIRTYCODE_LOGGING iRsltCode = ProtoHttpStatus(pHttpRef->pProtoHttp, 'code', NULL, 0); #endif } // remember completion time and mark transaction as complete pHttpCmd->uComplTick = NetTick(); pHttpCmd->uState = HTTPMANAGER_CMDSTATE_DONE; // get size of header if ((iHeadSize = ProtoHttpStatus(pHttpRef->pProtoHttp, 'head', NULL, 0)) < 0) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: could not get header size for stat tracking (err=%d)\n", iHeadSize)); // downgrade our pipelining support $$todo$$ - this should be tracked per site if (pHttpManager->bPipeWithoutKeepAlive) { /*$$todo: this can happen for other reasons (e.g. cert untrusted on a secure connection) but we should only be downgrading pipe without keep-alive if we get a generic socket failure or a premature Connection: Close */ NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager; disabling pipelining without keep-alive\n")); pHttpManager->bPipeWithoutKeepAlive = FALSE; } // reset the pipeline _HttpManagerPipelineReset(pHttpManager, pHttpRef, 1); iHeadSize = 0; } // update transaction stats pHttpCmd->uBytesXfer += (unsigned)iHeadSize; pHttpManager->HttpManagerStats.uTransactionBytes += pHttpCmd->uBytesXfer; pHttpManager->HttpManagerStats.uTransactionTime += NetTickDiff(pHttpCmd->uComplTick, pHttpCmd->uQueueTick); // log transaction results NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] %s ref %d url=%s (%d): %qd bytes in %dms, %.2fk/sec)\n", pHttpCmd->iHttpHandle, (iStatus == 1) ? "complete" : "failed ", pHttpRef - pHttpManager->HttpRefs, pHttpCmd->pUrl, iRsltCode, pHttpCmd->uBytesXfer, NetTickDiff(pHttpCmd->uComplTick, pHttpCmd->uIssueTick), ((float)pHttpCmd->uBytesXfer * 1000.0f) / (float)(NetTickDiff(pHttpCmd->uComplTick, pHttpCmd->uIssueTick)*1024))); } /*F********************************************************************************/ /*! \Function _HttpManagerRequestGetCmdQueueIdx \Description Get index of given command in httpref command queue. \Input *pHttpManager - reference pointer \Input *pHttpRef - http ref \Input *pHttpCmd - transaction command \Output int32_t - index of command in command queue; -1 if not in queue \Version 07/03/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerRequestGetCmdQueueIdx(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) { int32_t iHttpCmd; for (iHttpCmd = 0; (iHttpCmd < HTTPMANAGER_MAXREFQUEUE) && (pHttpRef->HttpCmdQueue[iHttpCmd] != pHttpCmd); iHttpCmd += 1) ; if (iHttpCmd == HTTPMANAGER_MAXREFQUEUE) { NetPrintf(("httpmanager: could not find handle %d in ref %d command queue!\n", pHttpCmd->iHttpHandle, pHttpRef-pHttpManager->HttpRefs)); return(-1); } return(iHttpCmd); } /*F********************************************************************************/ /*! \Function _HttpManagerRequestDelCmdFromQueue \Description Remove command from command queue and update ref state accordingly \Input *pHttpManager - reference pointer \Input *pHttpRef - httpref pointer \Input *pHttpCmd - transaction command \Todo Possible issues that need to be considered: 1) out-of-order command deletion may not be handled correctly \Version 07/03/2009 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerRequestDelCmdFromQueue(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) { int32_t iHttpCmd; // update transaction count pHttpRef->iTransactions -= 1; // update ref state if ((pHttpRef->iTransactions == 0) || (pHttpRef->HttpCmdQueue[1]->uState == HTTPMANAGER_CMDSTATE_WAIT)) { pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_IDLE; } // update stat tracking if (pHttpRef->iTransactions > 0) { pHttpManager->HttpManagerStats.uNumQueuedTransactions -= 1; } if (pHttpManager->HttpManagerStats.uNumActiveTransactions > 0) { pHttpManager->HttpManagerStats.uNumActiveTransactions -= 1; } // sanity check -- should never have an iCurTransaction != 0 here if (pHttpRef->iCurTransaction != 0) { NetPrintf(("httpmanager: warning -- current transaction index is not zero!\n", pHttpRef->iCurTransaction)); } // find ourselves in the HttpRef queue if ((iHttpCmd = _HttpManagerRequestGetCmdQueueIdx(pHttpManager, pHttpRef, pHttpCmd)) < 0) { NetPrintf(("httpmanager: error -- could not find handle %d in ref %d queue\n", pHttpCmd->iHttpHandle, pHttpRef - pHttpManager->HttpRefs)); return; } // remove command from ref command queue if (iHttpCmd < pHttpRef->iTransactions) { NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: contracting transaction queue for ref %d\n", pHttpCmd->pHttpRef - pHttpManager->HttpRefs)); memmove(&pHttpRef->HttpCmdQueue[iHttpCmd], &pHttpRef->HttpCmdQueue[iHttpCmd+1], sizeof(pHttpRef->HttpCmdQueue[0]) * (pHttpRef->iTransactions - iHttpCmd)); } pHttpRef->HttpCmdQueue[pHttpRef->iTransactions] = NULL; } /*F********************************************************************************/ /*! \Function _HttpManagerRequestPipe \Description Pipeline the given request. \Input *pHttpManager - reference pointer \Input *pHttpRef - http ref to use for transaction \Input *pHttpCmd - transaction command \Output int32_t - ProtoHttpRequest() result \Version 07/01/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerRequestPipe(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) { const char *pUrl; uint32_t uRequestType; int32_t iResult; // set up Url and request type if (pHttpCmd != NULL) { pUrl = pHttpCmd->pUrl; uRequestType = pHttpCmd->uRequestType; } else { pUrl = NULL; uRequestType = 0; } // explicitly disable keep-alive if global setting has it unavailable if (pHttpManager->bKeepalive == FALSE) { ProtoHttpControl(pHttpRef->pProtoHttp, 'keep', 0, 0, NULL); } /* set current transaction index. we have to do this before ProtoHttpRequest() as that will call into the custom header callback, which relies on the index being accurate. */ if ((pHttpRef->uHttpState == HTTPMANAGER_REFSTATE_IDLE) || (pUrl == NULL)) { pHttpRef->iCurTransaction = 0; } else { pHttpRef->iCurTransaction += 1; } // set timeout if requested (only for the first request, since this is a global setting) if ((pHttpCmd != NULL) && (pHttpCmd->iTimeout != 0) && (pHttpRef->iCurTransaction == 0)) { ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, 'time', pHttpCmd->iTimeout, 0, NULL); } // apply append header as long as this isn't a pipeline flush if (pHttpCmd != NULL) { _HttpManagerApplyAppendHeader(pHttpManager, pHttpCmd); } // initiate the request if ((iResult = ProtoHttpRequest(pHttpRef->pProtoHttp, pUrl, NULL, 0, (ProtoHttpRequestTypeE)uRequestType)) >= 0) { if (pHttpCmd != NULL) { // update stats _HttpManagerUpdateTransactionStats(pHttpManager, pHttpCmd); // if this is the first request on this ref, mark it as busy and set up the first transaction handler if (pHttpRef->uHttpState == HTTPMANAGER_REFSTATE_IDLE) { pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_BUSY; pHttpCmd->uState = HTTPMANAGER_CMDSTATE_ACTV; } else { pHttpCmd->uState = HTTPMANAGER_CMDSTATE_PIPE; pHttpManager->HttpManagerStats.uNumPipelinedTransactions += 1; } } /* if this was a flush command, reset current command, now that all of the headers have been formatted, so received header callbacks will get correct ref */ else { pHttpRef->iCurTransaction = 0; } } else if (pHttpRef->iCurTransaction > 0) { pHttpRef->iCurTransaction -= 1; } return(iResult); } /*F********************************************************************************/ /*! \Function _HttpManagerRequestPipeUpdateQueue \Description Update queue upon completion of a piped request. \Input *pHttpManager - reference pointer \Input *pHttpRef - http ref to use for transaction \Input *pHttpCmd - transaction command \Version 07/03/2009 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerRequestPipeUpdateQueue(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) { if ((pHttpRef->iTransactions > 0) && (pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]->uState == HTTPMANAGER_CMDSTATE_PIPE)) { // if the previous command completed successfully, set state of next piped transaction to ACTV if (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_DONE) { // set next handle to active state NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: handle %d set to active state\n", pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]->iHttpHandle)); pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]->uState = HTTPMANAGER_CMDSTATE_ACTV; // allow protohttp to get the next piped transaction ProtoHttpControl(pHttpRef->pProtoHttp, 'pnxt', 0, 0,NULL); } else // previous command did not complete; so we have to reset the pipe { int32_t iHttpCmd; /* if this transaction was in a sequence of pipelined transactions, we need to reset the state of the following transactions so they will be reissued */ NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: resetting state of piped requests following deletion of handle %d in state %d\n", pHttpCmd->iHttpHandle, pHttpCmd->uState)); for (iHttpCmd = pHttpRef->iCurTransaction; iHttpCmd < pHttpRef->iTransactions; iHttpCmd += 1) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: handle %d reset from state %d to state %d\n", pHttpRef->HttpCmdQueue[iHttpCmd]->iHttpHandle, pHttpRef->HttpCmdQueue[iHttpCmd]->uState, HTTPMANAGER_CMDSTATE_WAIT)); pHttpRef->HttpCmdQueue[iHttpCmd]->uState = HTTPMANAGER_CMDSTATE_WAIT; } // reset httpref to idle status pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_IDLE; } } } /*F********************************************************************************/ /*! \Function _HttpManagerCmdReleaseRef \Description Releases an HttpRef from the HttpCmd it is associated with. This allows any queued or piped transactions waiting on this ref to proceed. \Input *pHttpManager - reference pointer \Input *pHttpCmd - http command \Version 07/10/2010 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerCmdReleaseRef(HttpManagerRefT *pHttpManager, HttpManagerHttpCmdT *pHttpCmd) { uint32_t uFreeLatency; // no ref allocated? if (pHttpCmd->pHttpRef == NULL) { return; } // remove command from command queue _HttpManagerRequestDelCmdFromQueue(pHttpManager, pHttpCmd->pHttpRef, pHttpCmd); // update queue for piped operations _HttpManagerRequestPipeUpdateQueue(pHttpManager, pHttpCmd->pHttpRef, pHttpCmd); // if our request is currently active, abort it if (ProtoHttpStatus(pHttpCmd->pHttpRef->pProtoHttp, 'done', NULL, 0) == 0) { ProtoHttpAbort(pHttpCmd->pHttpRef->pProtoHttp); } // update time spent waiting to be freed if ((pHttpCmd->uComplTick != 0) && (pHttpCmd->uState != HTTPMANAGER_CMDSTATE_FAIL)) { uFreeLatency = NetTickDiff(NetTick(), pHttpCmd->uComplTick); } else { NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: warning -- no completion timestamp for handle %d being deleted (%dms since queued)\n", pHttpCmd->iHttpHandle, NetTickDiff(NetTick(), pHttpCmd->uQueueTick))); NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: url=%s\n", pHttpCmd->pUrl)); NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: state=%d\n", pHttpCmd->uState)); NetPrintfVerbose((pHttpManager->iVerbose, 1, "httpmanager: data=%qd\n", pHttpCmd->uBytesXfer)); uFreeLatency = 0; } pHttpManager->HttpManagerStats.uSumQueueFreeLatency += uFreeLatency; if (pHttpManager->HttpManagerStats.uMaxQueueFreeLatency < uFreeLatency) { pHttpManager->HttpManagerStats.uMaxQueueFreeLatency = uFreeLatency; } } /*F********************************************************************************/ /*! \Function _HttpManagerPipeline \Description Try to pipeline a set of requests. \Input *pHttpManager - reference pointer \Input *pHttpRef - ref to execute commands on \Input *pHttpCmd - first command in ref command queue \Output int32_t - zero=no requests issued \Version 06/08/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerPipeline(HttpManagerRefT *pHttpManager, HttpManagerHttpRefT *pHttpRef, HttpManagerHttpCmdT *pHttpCmd) { HttpManagerHttpCmdT *pHttpCmd2; int32_t iHttpCmd, iResult; // not pipelining? if (!pHttpManager->bPipelining) { return(0); } // make sure request type can be pipelined if ((pHttpCmd->uRequestType != PROTOHTTP_REQUESTTYPE_GET) && (pHttpCmd->uRequestType != PROTOHTTP_REQUESTTYPE_HEAD)) { return(0); } // if we require keep-alive to pipeline, check keep-alive status if (!pHttpManager->bPipeWithoutKeepAlive && (ProtoHttpCheckKeepAlive(pHttpRef->pProtoHttp, pHttpCmd->pUrl) == 0)) { return(0); } // execute the first transaction if ((iResult = _HttpManagerRequestPipe(pHttpManager, pHttpRef, pHttpCmd)) >= 0) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] started ref %d url=%s\n", pHttpCmd->iHttpHandle, pHttpRef - pHttpManager->HttpRefs, pHttpCmd->pUrl)); // iterate through transaction queue and issue any pending requests that are queued for pipelining for (iHttpCmd = 1; (iHttpCmd < pHttpRef->iTransactions) && (iHttpCmd < pHttpManager->iMaxPipedUrls); iHttpCmd += 1) { // ref next transaction in queue pHttpCmd2 = pHttpRef->HttpCmdQueue[iHttpCmd]; // check to see requests pipeline, and if so try and pipeline it if (_HttpManagerPipelineCheck(pHttpCmd, pHttpCmd2) == 0) { break; } if ((iResult = _HttpManagerRequestPipe(pHttpManager, pHttpRef, pHttpCmd2)) < 0) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: could not pipe handle %d; request will be retried on a new ref\n", pHttpCmd2->iHttpHandle)); break; } NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: [%d] piped ref %d url=%s\n", pHttpCmd2->iHttpHandle, pHttpRef - pHttpManager->HttpRefs, pHttpCmd2->pUrl)); // update trailing ref pHttpCmd = pHttpCmd2; } // issue queued request(s) _HttpManagerRequestPipe(pHttpManager, pHttpRef, NULL); } else if ((iResult == PROTOHTTP_MINBUFF) && (_HttpManagerResizeInputBuffer(pHttpManager, pHttpRef) == 0)) { // resized input buffer, let _HttpManagerRequestCb() re-issue it for us without pipelining return(0); } else { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: ProtoHttpRequest() returned %d on handle %d url=%s\n", iResult, pHttpCmd->iHttpHandle, pHttpCmd->pUrl)); pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_BUSY; pHttpRef->iCurTransaction = 0; pHttpCmd->uState = HTTPMANAGER_CMDSTATE_FAIL; } return(1); } /*F********************************************************************************/ /*! \Function _HttpManagerRequestCb \Description Handle an HTTP request. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle \Input *pUrl - the URL that identifies the POST action. \Input *pData - pointer to URL data (optional, may be NULL) \Input iDataSize - size of data being uploaded \Input eRequestType - http request type \Input *pWriteCb - write callback (optional) \Input *pCustomHeaderCb - custom header callback (optional) \Input *pReceiveHeaderCb- receive header callback (optional) \Input *pUserData - user data for callbacks (optional) \Output int32_t - negative=error, else number of bytes written \Version 06/08/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerRequestCb(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, const char *pData, int64_t iDataSize, ProtoHttpRequestTypeE eRequestType, ProtoHttpWriteCbT *pWriteCb, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData) { HttpManagerHttpCmdT *pHttpCmd; // get referenced transaction if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) { NetPrintf(("httpmanager: unrecognized transaction %d in request\n", iHandle)); return(-1); } // remember request timestamp pHttpCmd->uQueueTick = NetTick(); // store request info pHttpCmd->uRequestType = (uint8_t)eRequestType; pHttpCmd->pWriteCb = pWriteCb; pHttpCmd->pCustomHeaderCb = pCustomHeaderCb; pHttpCmd->pReceiveHeaderCb = pReceiveHeaderCb; pHttpCmd->pUserData = pUserData; // make a copy of url? if (pHttpManager->bCopyUrl) { int32_t iStrLen = (int32_t)strlen(pUrl); pHttpCmd->pUrl = DirtyMemAlloc(iStrLen+1, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); ds_strnzcpy((char *)pHttpCmd->pUrl, pUrl, iStrLen+1); pHttpCmd->bCopiedUrl = TRUE; } else { pHttpCmd->pUrl = pUrl; pHttpCmd->bCopiedUrl = FALSE; } // allocate a ref for the transaction if (_HttpManagerAllocRef(pHttpManager, pHttpCmd) == NULL) { // unable to allocate ref, but HttpManager will try again return(0); } /* if this is the only transaction queued and we are not pipelining, or the request type is not a GET or a HEAD (the only request types we pipeline) */ if (((pHttpCmd->pHttpRef->iTransactions == 1) && !pHttpManager->bPipelining) || ((eRequestType != PROTOHTTP_REQUESTTYPE_GET) && (eRequestType != PROTOHTTP_REQUESTTYPE_HEAD))) { // issue the request return(_HttpManagerRequestStart(pHttpManager, pHttpCmd, pData, iDataSize)); } else { pHttpCmd->uState = HTTPMANAGER_CMDSTATE_WAIT; return(0); } } /*F********************************************************************************/ /*! \Function _HttpManagerIdle \Description NetConn idle function to update the HttpManager module. \Input *pData - pointer to module state \Input uTick - current tick count \Notes This function is installed as a NetConn Idle function. NetConnIdle() must be regularly polled for this function to be called. \Version 1.0 07/23/2009 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerIdle(void *pData, uint32_t uTick) { HttpManagerRefT *pHttpManager = (HttpManagerRefT *)pData; if (pHttpManager->bAutoUpdate) { HttpManagerUpdate(pHttpManager); } } /*F********************************************************************************/ /*! \Function _HttpManagerCreateRef \Description Create a new http ref \Input *pHttpManager - module state \Input iHttpRef - index of ref to create \Output int32_t - zero=success, negative=failure \Version 01/31/2011 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerCreateRef(HttpManagerRefT *pHttpManager, int32_t iHttpRef) { HttpManagerHttpRefT *pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; // create protohttp ref if ((pHttpRef->pProtoHttp = ProtoHttpCreate(pHttpManager->iHttpBufSize)) == NULL) { NetPrintf(("httpmanager: could not allocate http ref %d\n", iHttpRef)); return(-1); } // temporarily snuff debug output ProtoHttpControl(pHttpRef->pProtoHttp, 'spam', 0, 0, NULL); // default keep-alive to enabled ProtoHttpControl(pHttpRef->pProtoHttp, 'keep', 1, 0, NULL); // set pipelining enable/disable ProtoHttpControl(pHttpRef->pProtoHttp, 'pipe', pHttpManager->bPipelining, 0, NULL); // set up callbacks ProtoHttpCallback(pHttpRef->pProtoHttp, pHttpManager->pCustomHeaderCb ? _HttpManagerCustomHeaderCb : NULL, _HttpManagerReceiveHeaderCb, pHttpRef); // set default debug level ProtoHttpControl(pHttpRef->pProtoHttp, 'spam', pHttpManager->iVerbose, 0, NULL); // init to idle state pHttpRef->uHttpState = HTTPMANAGER_REFSTATE_IDLE; // init last access time pHttpRef->uLastTick = NetTick(); // return success to caller return(0); } /*F********************************************************************************/ /*! \Function _HttpManagerDestroyRef \Description Destroy an allocated http ref \Input *pHttpManager - module state \Input iHttpRef - index of ref to destroy \Version 01/31/2011 (jbrookes) */ /********************************************************************************F*/ static void _HttpManagerDestroyRef(HttpManagerRefT *pHttpManager, int32_t iHttpRef) { HttpManagerHttpRefT *pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; HttpManagerHttpCmdT *pHttpCmd; int32_t iTransaction; // early out if ref is null if (pHttpRef->pProtoHttp == NULL) { return; } // invalidate any commands that referenced this ref for (iTransaction = 0; iTransaction < pHttpRef->iTransactions; iTransaction += 1) { pHttpCmd = pHttpRef->HttpCmdQueue[iTransaction]; if (pHttpCmd->pHttpRef == pHttpRef) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: destroying active ref %2d; handle %d is being terminated\n", iHttpRef, pHttpCmd->iHttpHandle)); pHttpCmd->pHttpRef = NULL; pHttpCmd->uState = HTTPMANAGER_CMDSTATE_FAIL; } } // destroy ref NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: destroying http ref %d\n", iHttpRef)); ProtoHttpDestroy(pHttpRef->pProtoHttp); // clear state ds_memclr(pHttpRef, sizeof(*pHttpRef)); } /*F********************************************************************************/ /*! \Function _HttpManagerSizeRefPool \Description Create or destroy ProtoHttp refs based on current pool size and new pool size \Input *pHttpManager - module state \Input iHttpNumRefs - number of desired protohttp refs \Output int32_t - zero=success, negative=failure \Version 01/31/2011 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpManagerSizeRefPool(HttpManagerRefT *pHttpManager, int32_t iHttpNumRefs) { int32_t iHttpRef; // validate request size if (iHttpNumRefs > HTTPMANAGER_MAXREFS) { NetPrintf(("httpmanager: clamping 'pool' request to max %d refs\n", HTTPMANAGER_MAXREFS)); iHttpNumRefs = HTTPMANAGER_MAXREFS; } else if (iHttpNumRefs < 1) { NetPrintf(("httpmanager: clamping 'pool' request to min of one ref\n")); iHttpNumRefs = 1; } // increase size of ref pool? if (pHttpManager->iHttpNumRefs < iHttpNumRefs) { int32_t iResult; DirtyMemGroupEnter(pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); for (iHttpRef = pHttpManager->iHttpNumRefs, iResult = 0; iHttpRef < iHttpNumRefs; iHttpRef += 1) { if ((iResult = _HttpManagerCreateRef(pHttpManager, iHttpRef)) < 0) { break; } } DirtyMemGroupLeave(); if (iResult < 0) { // could not allocate http ref return(-1); } } else if (pHttpManager->iHttpNumRefs > iHttpNumRefs) // decrease size of ref pool { for (iHttpRef = pHttpManager->iHttpNumRefs - 1; iHttpRef >= iHttpNumRefs; iHttpRef -= 1) { _HttpManagerDestroyRef(pHttpManager, iHttpRef); } } // save new ref count, return success pHttpManager->iHttpNumRefs = iHttpNumRefs; return(0); } /*** Public Functions *************************************************************/ /*F********************************************************************************/ /*! \Function HttpManagerCreate \Description Allocate module state and prepare for use \Input iHttpBufSize - length of receive buffer for each protohttp ref \Input iHttpNumRefs - number of protohttp modules to allocate \Output HttpManagerRefT * - pointer to module state, or NULL \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ HttpManagerRefT *HttpManagerCreate(int32_t iHttpBufSize, int32_t iHttpNumRefs) { HttpManagerRefT *pHttpManager; void *pMemGroupUserData; int32_t iMemGroup; // Query current mem group data DirtyMemGroupQuery(&iMemGroup, &pMemGroupUserData); // clamp refcount if (iHttpNumRefs > HTTPMANAGER_MAXREFS) { NetPrintf(("httpmanager: %d requested refs exceeds max of %d; clamping\n", iHttpNumRefs, HTTPMANAGER_MAXREFS)); iHttpNumRefs = HTTPMANAGER_MAXREFS; } // allocate the module state if ((pHttpManager = DirtyMemAlloc(sizeof(*pHttpManager), HTTPMGR_MEMID, iMemGroup, pMemGroupUserData)) == NULL) { NetPrintf(("httpmanager: unable to allocate module state\n")); return(NULL); } ds_memclr(pHttpManager, sizeof(*pHttpManager)); // save parms & set defaults pHttpManager->iMemGroup = iMemGroup; pHttpManager->pMemGroupUserData = pMemGroupUserData; pHttpManager->iHttpBufSize = iHttpBufSize; pHttpManager->iHttpHandle = 1; // zero is reserved as invalid #if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_PS4) && !defined(DIRTYCODE_GDK) pHttpManager->bPipelining = TRUE; #endif pHttpManager->bKeepalive = TRUE; pHttpManager->bPipeWithoutKeepAlive = TRUE; pHttpManager->iMaxPipedUrls = 4; pHttpManager->bCopyUrl = TRUE; pHttpManager->bAutoUpdate = TRUE; pHttpManager->iVerbose = 1; // allocate protohttp refs if (_HttpManagerSizeRefPool(pHttpManager, iHttpNumRefs) < 0) { NetPrintf(("httpmanager: could not allocate ref pool\n")); HttpManagerDestroy(pHttpManager); return(NULL); } // add httpmanager task handle NetConnIdleAdd(_HttpManagerIdle, pHttpManager); #if HTTPMANAGER_FINALDEBUG _HttpManager_iStartTick = NetTick(); #endif // return new module state return(pHttpManager); } /*F********************************************************************************/ /*! \Function HttpManagerCallback \Description Set header callbacks. \Input *pHttpManager - module state \Input *pCustomHeaderCb - pointer to custom send header callback \Input *pReceiveHeaderCb- pointer to recv header callback \Notes See ProtoHttpCallback documentation for a description of the callbacks specified here. \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ void HttpManagerCallback(HttpManagerRefT *pHttpManager, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb) { HttpManagerHttpRefT *pHttpRef; int32_t iHttpRef; // save callback info pHttpManager->pCustomHeaderCb = pCustomHeaderCb; pHttpManager->pReceiveHeaderCb = pReceiveHeaderCb; // update protohttp refs with callback info for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; ProtoHttpCallback(pHttpRef->pProtoHttp, pHttpManager->pCustomHeaderCb ? _HttpManagerCustomHeaderCb : NULL, _HttpManagerReceiveHeaderCb, pHttpRef); } } /*F********************************************************************************/ /*! \Function HttpManagerDestroy \Description Destroy the module and release its state \Input *pHttpManager - reference pointer \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ void HttpManagerDestroy(HttpManagerRefT *pHttpManager) { HttpManagerHttpCmdT *pHttpCmd; int32_t iHttpCmd, iHttpRef; // del httpmanager task handle NetConnIdleDel(_HttpManagerIdle, pHttpManager); // if append header is set, free it if (pHttpManager->pAppendHdr != NULL) { DirtyMemFree(pHttpManager->pAppendHdr, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); } // destroy protohttp modules for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { _HttpManagerDestroyRef(pHttpManager, iHttpRef); } // clean up command list for (iHttpCmd = 0; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) { pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; if (pHttpCmd->iHttpHandle != 0) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: warning; handle %d not cleaned up\n", pHttpCmd->iHttpHandle)); HttpManagerFree(pHttpManager, pHttpCmd->iHttpHandle); } } // display stats #if DIRTYCODE_LOGGING || HTTPMANAGER_FINALDEBUG if (pHttpManager->iVerbose > 0) { _HttpManagerDisplayStats(pHttpManager); } #endif // destroy module state DirtyMemFree(pHttpManager, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); } /*F********************************************************************************/ /*! \Function HttpManagerAlloc \Description Allocate a new transaction handle \Input *pHttpManager - reference pointer \Output int32_t - negative=failure, else transaction handle \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ int32_t HttpManagerAlloc(HttpManagerRefT *pHttpManager) { HttpManagerHttpCmdT *pHttpCmd; // get an unallocated ProtoHttp ref if ((pHttpCmd = _HttpManagerAllocCmd(pHttpManager)) == NULL) { NetPrintf(("httpmanager: could not allocate new http transaction for HttpManagerGet() request\n")); return(-1); } // return transaction handle to caller NetPrintfVerbose((pHttpManager->iVerbose, 2, "httpmanager: allocated handle %d\n", pHttpCmd->iHttpHandle)); return(pHttpCmd->iHttpHandle); } /*F********************************************************************************/ /*! \Function HttpManagerFree \Description Release a new transaction handle \Input *pHttpManager - reference pointer \Input iHandle - transaction handle \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ void HttpManagerFree(HttpManagerRefT *pHttpManager, int32_t iHandle) { HttpManagerHttpCmdT *pHttpCmd; // get referenced transaction if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) { NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerFree()\n", iHandle)); return; } NetPrintfVerbose((pHttpManager->iVerbose, 2, "httpmanager: releasing handle %d\n", iHandle)); // release the ref _HttpManagerCmdReleaseRef(pHttpManager, pHttpCmd); // free url, if it was copied if (pHttpCmd->bCopiedUrl == TRUE) { if (pHttpCmd->pUrl != NULL) { DirtyMemFree((void *)pHttpCmd->pUrl, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); } else { NetPrintf(("httpmanager: warning; copied url null before free\n")); } } // free append buffer, if present if (pHttpCmd->pAppendHdr != NULL) { DirtyMemFree(pHttpCmd->pAppendHdr, HTTPMGR_MEMID, pHttpManager->iMemGroup, pHttpManager->pMemGroupUserData); } // clear ref ds_memclr(pHttpCmd, sizeof(*pHttpCmd)); } /*F********************************************************************************/ /*! \Function HttpManagerGet \Description Initiate an HTTP transaction. Pass in a URL and the module starts a transfer from the appropriate server. \Input *pHttpManager - module state \Input iHandle - transaction handle \Input *pUrl - the url to retrieve \Input bHeadOnly - if TRUE only get header \Output int32_t - negative=failure, else success \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ int32_t HttpManagerGet(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, uint32_t bHeadOnly) { return(_HttpManagerRequestCb(pHttpManager, iHandle, pUrl, NULL, 0, bHeadOnly ? PROTOHTTP_REQUESTTYPE_HEAD : PROTOHTTP_REQUESTTYPE_GET, NULL, NULL, NULL, NULL)); } /*F********************************************************************************/ /*! \Function HttpManagerRecv \Description Return the actual url data. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle (returned by HttpManagerGet()) \Input *pBuffer - buffer to store data in \Input iBufMin - minimum number of bytes to return (returns zero if this number is not available) \Input iBufMax - maximum number of bytes to return (buffer size) \Output int32_t - negative=error, zero=no data available or bufmax <= 0, positive=number of bytes read \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ int32_t HttpManagerRecv(HttpManagerRefT *pHttpManager, int32_t iHandle, char *pBuffer, int32_t iBufMin, int32_t iBufMax) { HttpManagerHttpCmdT *pHttpCmd; // get referenced transaction if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) { NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerRecv()\n", iHandle)); return(-1); } // wait until active if (pHttpCmd->uState < HTTPMANAGER_CMDSTATE_ACTV) { return(0); } // early-out if queued execution of request failed if (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_FAIL) { return(PROTOHTTP_RECVFAIL); } // update the ref ProtoHttpUpdate(pHttpCmd->pHttpRef->pProtoHttp); // execute the receive if ((pHttpCmd->iResult = ProtoHttpRecv(pHttpCmd->pHttpRef->pProtoHttp, pBuffer, iBufMin, iBufMax)) > 0) { // track bytes received pHttpCmd->uBytesXfer += (unsigned)pHttpCmd->iResult; } else if ((pHttpCmd->iResult == PROTOHTTP_MINBUFF) && (_HttpManagerResizeInputBuffer(pHttpManager, pHttpCmd->pHttpRef) == 0)) { // resized input buffer; swallow the error result and try again next go-around pHttpCmd->iResult = 0; } // check for transaction completion if (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_ACTV) { _HttpManagerRequestCheckCompletion(pHttpManager, pHttpCmd); } // update last access time { uint32_t uCurTick = NetTick(); if (NetTickDiff(uCurTick, pHttpCmd->pHttpRef->uLastTick) > 1000) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: warning -- ref %d last updated %dms previous (receive)\n", pHttpCmd->pHttpRef - pHttpManager->HttpRefs, NetTickDiff(uCurTick, pHttpCmd->pHttpRef->uLastTick))); } pHttpCmd->pHttpRef->uLastTick = uCurTick; } // return result to caller return(pHttpCmd->iResult); } /*F********************************************************************************/ /*! \Function HttpManagerRecvAll \Description Return all of the url data. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle (returned by HttpManagerGet()) \Input *pBuffer - buffer to store data in \Input iBufSize - size of buffer \Output int32_t - PROTOHTTP_RECV*, or positive=bytes in response \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ int32_t HttpManagerRecvAll(HttpManagerRefT *pHttpManager, int32_t iHandle, char *pBuffer, int32_t iBufSize) { HttpManagerHttpCmdT *pHttpCmd; // get referenced transaction if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) { NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerRecv()\n", iHandle)); return(-1); } // if transaction is not active, return no data available if (pHttpCmd->uState != HTTPMANAGER_CMDSTATE_ACTV) { return(0); } // pass through to http ref if ((pHttpCmd->iResult = ProtoHttpRecvAll(pHttpCmd->pHttpRef->pProtoHttp, pBuffer, iBufSize)) == PROTOHTTP_MINBUFF) { // increase the input buffer size, for next go-around if (_HttpManagerResizeInputBuffer(pHttpManager, pHttpCmd->pHttpRef) == 0) { // swallow the error result and try again next go-around pHttpCmd->iResult = 0; } } // return result to caller return(pHttpCmd->iResult); } /*F********************************************************************************/ /*! \Function HttpManagerPost \Description Initiate transfer of data to to the server via a HTTP POST command. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle \Input *pUrl - the URL that identifies the POST action. \Input *pData - pointer to URL data (optional, may be NULL) \Input iDataSize - size of data being uploaded (see Notes) \Input bDoPut - if TRUE, do a PUT instead of a POST \Output int32_t - negative=failure, else number of data bytes sent \Notes See ProtoHttpPost() documentation. \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ int32_t HttpManagerPost(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, const char *pData, int64_t iDataSize, uint32_t bDoPut) { return(_HttpManagerRequestCb(pHttpManager, iHandle, pUrl, pData, iDataSize, bDoPut ? PROTOHTTP_REQUESTTYPE_PUT : PROTOHTTP_REQUESTTYPE_POST, NULL, NULL, NULL, NULL)); } /*F********************************************************************************/ /*! \Function HttpManagerSend \Description Send data during an ongoing Post transaction. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle \Input *pData - pointer to data to send \Input iDataSize - size of data being sent \Output int32_t - negative=failure, else number of data bytes sent \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ int32_t HttpManagerSend(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pData, int32_t iDataSize) { HttpManagerHttpCmdT *pHttpCmd; // get referenced transaction if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) { NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerRecv()\n", iHandle)); return(-1); } // if transaction is not active, return no data available if (pHttpCmd->uState != HTTPMANAGER_CMDSTATE_ACTV) { return(0); } // pass through to http ref return(ProtoHttpSend(pHttpCmd->pHttpRef->pProtoHttp, pData, iDataSize)); } /*F********************************************************************************/ /*! \Function HttpManagerRequestCb2 \Description Make an HTTP request. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle \Input *pUrl - the URL that identifies the POST action. \Input *pData - pointer to URL data (optional, may be NULL) \Input iDataSize - size of data being uploaded \Input eRequestType - http request type \Input *pWriteCb - write callback (optional) \Input *pCustomHeaderCb - custom header callback (optional) \Input *pReceiveHeaderCb- receive header callback (optional) \Input *pUserData - user data for write callback \Output int32_t - negative=error, else number of bytes written \Version 09/11/2017 (eesponda) */ /********************************************************************************F*/ int32_t HttpManagerRequestCb2(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl, const char *pData, int64_t iDataSize, ProtoHttpRequestTypeE eRequestType, ProtoHttpWriteCbT *pWriteCb, ProtoHttpCustomHeaderCbT *pCustomHeaderCb, ProtoHttpReceiveHeaderCbT *pReceiveHeaderCb, void *pUserData) { return(_HttpManagerRequestCb(pHttpManager, iHandle, pUrl, pData, iDataSize, eRequestType, pWriteCb, pCustomHeaderCb, pReceiveHeaderCb, pUserData)); } /*F********************************************************************************/ /*! \Function HttpManagerSetBaseUrl \Description Set base url that will be used for any relative url references. \Input *pHttpManager - reference pointer \Input iHandle - handle to set base url for \Input *pUrl - base url \Version 02/03/2010 (jbrookes) */ /********************************************************************************F*/ void HttpManagerSetBaseUrl(HttpManagerRefT *pHttpManager, int32_t iHandle, const char *pUrl) { HttpManagerHttpCmdT *pHttpCmd; // get referenced transaction if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) { NetPrintf(("httpmanager: unrecognized transaction %d in HttpManagerSetBaseUrl()\n", iHandle)); return; } // pass through to http ref if ((pHttpCmd->pHttpRef != NULL) && (pHttpCmd->pHttpRef->pProtoHttp != NULL)) { ProtoHttpSetBaseUrl(pHttpCmd->pHttpRef->pProtoHttp, pUrl); } } /*F********************************************************************************/ /*! \Function HttpManagerControl \Description HttpManager control function. Different selectors control different behaviors. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle \Input iSelect - control selector \Input iValue - selector specific \Input iValue2 - selector specific \Input *pValue - selector specific \Output int32_t - selector specific \Notes If a handle is specified (iHandle > 0), HttpManagerControl() will forward the call to ProtoHttpControl() for the appropriate ref. HttpManager control selectors are as follows: \verbatim SELECTOR DESCRIPTION 'apnd' Header append feature (see ProtoHttp doc for details). If a handle is specified the scope is command-specific, otherwise it is global. A command-specific append header takes precedence over a global append header. 'auto' enable/disable auto-update (polling of HttpManagerUpdate by NetConnIdle(); default enabled) 'cbup' sets callback user data pointer (pValue=callback) 'copy' sets if urls are copied internally or not (default=true) 'maxp' sets max pipeline depth (default 4) 'pipe' pipelining enable/disable 'pwka' pipelining without keep-alive enable/disable 'spam' manager-level debug verbosity 'stcl' clear httpmanager stats. \endverbatim \Version 05/20/2009 (jbrookes) */ /*******************************************************************************F*/ int32_t HttpManagerControl(HttpManagerRefT *pHttpManager, int32_t iHandle, int32_t iSelect, int32_t iValue, int32_t iValue2, void *pValue) { HttpManagerHttpCmdT *pHttpCmd = NULL; int32_t iHttpRef; // if we are given a handle, resolve http cmd if ((iHandle > 0) && ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL)) { NetPrintf(("httpmanager: HttpManagerControl(%d, '%C') failed; unrecognized handle\n", iHandle, iSelect)); return(-1); } // commands that work with or without a cmd // set append header if (iSelect == 'apnd') { return(_HttpManagerSetAppendHeader(pHttpManager, pHttpCmd, pValue)); } // command-specific selector if (pHttpCmd != NULL) { // put httpmanager control selectors here if (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_IDLE) { // set callback user data pointer if (iSelect == 'cbup') { NetPrintfVerbose((pHttpManager->iVerbose, 2, "httpmanager: setting callback for handle %d to 0x%08x\n", iHandle, pValue)); pHttpCmd->pCallbackRef = pValue; return(0); } // set timeout if (iSelect == 'time') { pHttpCmd->iTimeout = iValue; return(0); } } // if it's not an httpmanager control selector, and we have a ProtoHttp ref, pass it through to ProtoHttp if ((pHttpCmd->pHttpRef != NULL) && (pHttpCmd->pHttpRef->pProtoHttp != NULL)) { return(ProtoHttpControl(pHttpCmd->pHttpRef->pProtoHttp, iSelect, iValue, iValue2, pValue)); } } else // global (not command-specific) selectors { // enable/disable auto-update (polling of HttpManagerUpdate by NetConnIdle()) if (iSelect == 'auto') { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: auto-update %s\n", iValue ? "enabled" : "disabled")); pHttpManager->bAutoUpdate = iValue ? TRUE : FALSE; return(0); } // copy url setting enable/disable if (iSelect == 'copy') { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: urlcopy %s\n", iValue ? "enabled" : "disabled")); pHttpManager->bCopyUrl = iValue ? TRUE : FALSE; return(0); } // set max pipeline depth if (iSelect == 'maxp') { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: setting max pipeline depth to %d\n", iValue)); pHttpManager->iMaxPipedUrls = (int8_t)iValue; return(0); } // set pipelining setting #if !defined(DIRTYCODE_XBOXONE) && !defined(DIRTYCODE_PS4) && !defined(DIRTYCODE_GDK) if (iSelect == 'pipe') { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: pipelining %s\n", iValue ? "enabled" : "disabled")); pHttpManager->bPipelining = iValue ? TRUE : FALSE; // intentional fall-through to pass down to protohttp refs } #endif if (iSelect == 'pool') { return(_HttpManagerSizeRefPool(pHttpManager, iValue)); } // set pipelining without keep-alive if (iSelect == 'pwka') { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: pipelining without keep-alive %s\n", iValue ? "enabled" : "disabled")); pHttpManager->bPipeWithoutKeepAlive = iValue ? TRUE : FALSE; return(0); } // manager-level debug verbosity if (iSelect == 'spam') { pHttpManager->iVerbose = iValue; if (iValue > 0) { // set protohttp spam level one lower than httpmanager's iValue -= 1; } // intentional fall-through to pass down to protohttp refs } // clear stats if (iSelect == 'stcl') { _HttpManagerResetStats(pHttpManager); return(0); } // if we haven't gotten it by now, it's an option to be applied to all http refs for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { if (pHttpManager->HttpRefs[iHttpRef].pProtoHttp != NULL) { ProtoHttpControl(pHttpManager->HttpRefs[iHttpRef].pProtoHttp, iSelect, iValue, iValue2, pValue); } } return(0); } // unhandled NetPrintf(("httpmanager: HttpManagerControl(%d, '%C') unhandled\n", iHandle, iSelect)); return(-1); } /*F********************************************************************************/ /*! \Function HttpManagerStatus \Description Return status of current HTTP transfer. Status type depends on selector. \Input *pHttpManager - reference pointer \Input iHandle - transaction handle \Input iSelect - info selector (see Notes) \Input *pBuffer - [in/out] input and/or storage for selector-specific output \Input iBufSize - size of buffer \Output int32_t - selector specific \Notes If a handle is specified, HttpManagerStatus() will call ProtoHttpStatus() for the appropriate ref. HttpManager status selectors are as follows: \verbatim SELECTOR DESCRIPTION 'busy' Returns number of busy refs 'hndl' Get HttpManager handle from ProtoHttp ref (passed in pBuffer) 'href' Get ProtoHttp ref from HttpManager handle (returned in pBuffer, if specified) 'stat' If pBuffer is NULL, display httpmanager stats (debug only); else copy stats to output buffer 'urls' Copies url from specified command into output buffer \endverbatim \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ int32_t HttpManagerStatus(HttpManagerRefT *pHttpManager, int32_t iHandle, int32_t iSelect, void *pBuffer, int32_t iBufSize) { // if the user wants status of a specific transaction? if (iHandle > 0) { HttpManagerHttpCmdT *pHttpCmd; // get referenced transaction if ((pHttpCmd = _HttpManagerGetCmd(pHttpManager, iHandle)) == NULL) { NetPrintf(("httpmanager: HttpManagerStatus(%d, '%C') failed; unrecognized handle\n", iHandle, iSelect)); return(-1); } // get href from handle if (iSelect == 'href') { if ((pHttpCmd->pHttpRef != NULL) && (pHttpCmd->pHttpRef->pProtoHttp != NULL)) { if ((pBuffer != NULL) && (iBufSize == sizeof(void *))) { ds_memcpy(pBuffer, &pHttpCmd->pHttpRef->pProtoHttp, sizeof(void *)); } return(0); } return(-1); } // copy url to status buffer if (iSelect == 'urls') { ds_strnzcpy(pBuffer, pHttpCmd->pUrl, iBufSize); return(0); } // only allow status of active transactions (otherwise this transaction does not own the ref) if (pHttpCmd->uState < HTTPMANAGER_CMDSTATE_ACTV) { if ((iSelect == 'done') || (iSelect == 'data') || (iSelect == 'time')) { return(0); } if ((iSelect == 'body') || (iSelect == 'code') || (iSelect == 'htxt')) { return(-1); } NetPrintf(("httpmanager: HttpManagerStatus(%d, '%C') failed; handle not active or done (state=%d)\n", iHandle, iSelect, pHttpCmd->uState)); return(-1); } // if it's not an httpmanager status selector, and we have a ProtoHttp ref, pass it through to ProtoHttp if ((pHttpCmd->pHttpRef != NULL) && (pHttpCmd->pHttpRef->pProtoHttp != NULL)) { return(ProtoHttpStatus(pHttpCmd->pHttpRef->pProtoHttp, iSelect, pBuffer, iBufSize)); } } else { // return count of the number of busy refs if (iSelect == 'busy') { HttpManagerHttpRefT *pHttpRef; int32_t iHttpRef, iBusyRefs; for (iHttpRef = 0, iBusyRefs = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; if (pHttpRef->uHttpState == HTTPMANAGER_REFSTATE_BUSY) { iBusyRefs += 1; } } return(iBusyRefs); } // get handle from href if (iSelect == 'hndl') { if ((pBuffer != NULL) && (iBufSize == sizeof(void *))) { HttpManagerHttpRefT *pHttpRef; ProtoHttpRefT *pProtoHttpFind; int32_t iHttpRef; // get httpref from buffer ds_memcpy(&pProtoHttpFind, pBuffer, sizeof(void *)); // find it for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; if ((pHttpRef->pProtoHttp == pProtoHttpFind) && (pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction] != NULL)) { // return active handle return(pHttpRef->HttpCmdQueue[pHttpRef->iCurTransaction]->iHttpHandle); } } } // not found return(-1); } // display/fetch stats if (iSelect == 'stat') { if (pBuffer != NULL) { if (iBufSize == sizeof(pHttpManager->HttpManagerStats)) { ds_memcpy(pBuffer, &pHttpManager->HttpManagerStats, sizeof(pHttpManager->HttpManagerStats)); return(0); } else { return(-1); } } #if DIRTYCODE_LOGGING || HTTPMANAGER_FINALDEBUG else { _HttpManagerDisplayStats(pHttpManager); } #endif return(0); } } // unhandled NetPrintf(("httpmanager: HttpManagerStatus(%d, '%C') unhandled\n", iHandle, iSelect)); return(-1); } /*F********************************************************************************/ /*! \Function HttpManagerUpdate \Description Give time to module to do its thing (should be called periodically to allow module to perform work) \Input *pHttpManager - reference pointer \Version 05/20/2009 (jbrookes) */ /********************************************************************************F*/ void HttpManagerUpdate(HttpManagerRefT *pHttpManager) { HttpManagerHttpRefT *pHttpRef; HttpManagerHttpCmdT *pHttpCmd; int32_t iHttpCmd, iHttpRef; // look for unassigned commands (these happen when we make a request, if we have filled up our available HttpRef command slots) for (iHttpCmd = 0; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) { pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; if ((pHttpCmd->pUrl != NULL) && (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_IDLE) && (pHttpCmd->pHttpRef == NULL)) { // allocate a ref for the transaction if (_HttpManagerAllocRef(pHttpManager, pHttpCmd) == NULL) { break; } NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: deferred alloc for handle %d\n", pHttpCmd->iHttpHandle)); // promote to wait state pHttpCmd->uState = HTTPMANAGER_CMDSTATE_WAIT; } } // update protohttp modules for (iHttpRef = 0; iHttpRef < pHttpManager->iHttpNumRefs; iHttpRef += 1) { uint32_t uCurTick; pHttpRef = &pHttpManager->HttpRefs[iHttpRef]; // check for idle ref that has one or more transactions queued if ((pHttpRef->uHttpState == HTTPMANAGER_REFSTATE_IDLE) && (pHttpRef->iTransactions > 0)) { // reference first queued transaction pHttpCmd = pHttpRef->HttpCmdQueue[0]; // try to pipeline the command; if we can't, start the request if (_HttpManagerPipeline(pHttpManager, pHttpRef, pHttpCmd) == 0) { // execute first queued transaction _HttpManagerRequestStart(pHttpManager, pHttpCmd, NULL, 0); } } // update refs uCurTick = NetTick(); ProtoHttpUpdate(pHttpRef->pProtoHttp); if (NetTickDiff(uCurTick, pHttpRef->uLastTick) > 1000) { NetPrintfVerbose((pHttpManager->iVerbose, 0, "httpmanager: warning -- ref %d last updated %dms previous (update)\n", iHttpRef, NetTickDiff(uCurTick, pHttpRef->uLastTick))); } pHttpRef->uLastTick = uCurTick; } // process any refs with write callbacks to update for (iHttpCmd = 0; iHttpCmd < HTTPMANAGER_MAXCMDS; iHttpCmd += 1) { pHttpCmd = &pHttpManager->HttpCmds[iHttpCmd]; if ((pHttpCmd->pWriteCb != NULL) && (pHttpCmd->uState == HTTPMANAGER_CMDSTATE_ACTV)) { _HttpManagerWriteCbProcess(pHttpManager, pHttpCmd); } } }