r5sdk/r5dev/thirdparty/dirtysdk/source/proto/protohttpmanager.c
Kawe Mazidjatari b3a68ed095 Add EABase, EAThread and DirtySDK to R5sdk
DirtySDK (EA's Dirty Sockets library) will be used for the LiveAPI implementation, and depends on: EABase, EAThread.
2024-04-05 18:29:03 +02:00

2774 lines
103 KiB
C

/*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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <windows.h> // OutputDebugStringA
#else
#include <stdlib.h>
#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);
}
}
}