diff --git a/Extension/extension.cpp b/Extension/extension.cpp index aa6a4e0..040e548 100644 --- a/Extension/extension.cpp +++ b/Extension/extension.cpp @@ -72,6 +72,7 @@ void SteamWorks::SDK_OnUnload() delete this->pSWHTTPNatives; delete this->pSWHTTP; + this->pSWHTTP = NULL; /* Requests freed via frame actions may outlive us; let their dtor detect this. */ delete this->pSWGameServer; delete this->pSWGameData; } diff --git a/Extension/swhttp.cpp b/Extension/swhttp.cpp index 661757a..2dacb67 100644 --- a/Extension/swhttp.cpp +++ b/Extension/swhttp.cpp @@ -23,7 +23,9 @@ static ISteamHTTP *GetHTTPPointer() return g_SteamWorks.pSWGameServer->GetHTTP(); } -SteamWorksHTTP::SteamWorksHTTP() +SteamWorksHTTP::SteamWorksHTTP() : + m_CallbackHeadersReceived(this, &SteamWorksHTTP::OnHTTPHeadersReceived), + m_CallbackDataReceived(this, &SteamWorksHTTP::OnHTTPDataReceived) { this->typeHTTP = handlesys->CreateType("HTTPHandle", this, 0, NULL, NULL, myself->GetIdentity(), NULL); } @@ -38,6 +40,46 @@ HandleType_t SteamWorksHTTP::GetHTTPHandle(void) return this->typeHTTP; } +void SteamWorksHTTP::RegisterRequest(SteamWorksHTTPRequest *pRequest) +{ + if (pRequest->request != INVALID_HTTPREQUEST_HANDLE) + { + this->m_Requests[pRequest->request] = pRequest; + } +} + +void SteamWorksHTTP::UnregisterRequest(SteamWorksHTTPRequest *pRequest) +{ + if (pRequest->request != INVALID_HTTPREQUEST_HANDLE) + { + this->m_Requests.erase(pRequest->request); + } +} + +SteamWorksHTTPRequest *SteamWorksHTTP::FindRequest(HTTPRequestHandle request) +{ + auto it = this->m_Requests.find(request); + return (it == this->m_Requests.end()) ? NULL : it->second; +} + +void SteamWorksHTTP::OnHTTPHeadersReceived(HTTPRequestHeadersReceived_t *pParam) +{ + SteamWorksHTTPRequest *pRequest = this->FindRequest(pParam->m_hRequest); + if (pRequest != NULL) + { + pRequest->OnHTTPHeadersReceived(pParam); + } +} + +void SteamWorksHTTP::OnHTTPDataReceived(HTTPRequestDataReceived_t *pParam) +{ + SteamWorksHTTPRequest *pRequest = this->FindRequest(pParam->m_hRequest); + if (pRequest != NULL) + { + pRequest->OnHTTPDataReceived(pParam); + } +} + static void DelayedDeleteSteamWorksHTTPRequest(void *object) { SteamWorksHTTPRequest *pRequest = reinterpret_cast(object); diff --git a/Extension/swhttp.h b/Extension/swhttp.h index e9e64ea..7c91ad4 100644 --- a/Extension/swhttp.h +++ b/Extension/swhttp.h @@ -21,6 +21,10 @@ #include "steam_gameserver.h" #include "smsdk_ext.h" +#include + +class SteamWorksHTTPRequest; + class SteamWorksHTTP : public IHandleTypeDispatch { @@ -31,12 +35,27 @@ class SteamWorksHTTP : public: void OnHandleDestroy(HandleType_t type, void *object); bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize); - + public: HandleType_t GetHTTPHandle(void); + /* Streaming responses report progress through HTTPRequestHeadersReceived_t / + HTTPRequestDataReceived_t. Steam delivers those as broadcast gameserver + callbacks (not as call results of the streaming API call), so a single + dispatcher here receives them and routes each one to the owning request by + its handle. Requests add/remove themselves as they are created/destroyed. */ + void RegisterRequest(SteamWorksHTTPRequest *pRequest); + void UnregisterRequest(SteamWorksHTTPRequest *pRequest); + + private: + SteamWorksHTTPRequest *FindRequest(HTTPRequestHandle request); + + STEAM_GAMESERVER_CALLBACK(SteamWorksHTTP, OnHTTPHeadersReceived, HTTPRequestHeadersReceived_t, m_CallbackHeadersReceived); + STEAM_GAMESERVER_CALLBACK(SteamWorksHTTP, OnHTTPDataReceived, HTTPRequestDataReceived_t, m_CallbackDataReceived); + private: HandleType_t typeHTTP; + std::unordered_map m_Requests; }; #include "swhttprequest.h" diff --git a/Extension/swhttprequest.cpp b/Extension/swhttprequest.cpp index d787651..d0fa71a 100644 --- a/Extension/swhttprequest.cpp +++ b/Extension/swhttprequest.cpp @@ -67,6 +67,14 @@ SteamWorksHTTPRequest::SteamWorksHTTPRequest() : request(INVALID_HTTPREQUEST_HAN SteamWorksHTTPRequest::~SteamWorksHTTPRequest() { + /* Requests are freed via a frame action, so on extension unload this can run + after the dispatcher itself has been torn down; pSWHTTP is nulled in that + case (see SDK_OnUnload), so guard against it. */ + if (g_SteamWorks.pSWHTTP != NULL) + { + g_SteamWorks.pSWHTTP->UnregisterRequest(this); + } + ISteamHTTP *pHTTP = GetHTTPPointer(); if (pHTTP != NULL) { @@ -101,7 +109,10 @@ void SteamWorksHTTPRequest::OnHTTPRequestCompleted(HTTPRequestCompleted_t *pRequ this->pCompletedForward->Execute(NULL); } -void SteamWorksHTTPRequest::OnHTTPHeadersReceived(HTTPRequestHeadersReceived_t *pRequest, bool bFailed) +/* Streaming header/data notifications are success-only callbacks (unlike the + completion call result, they carry no IO-failure flag), so bFailure is always + false here. Failures still surface through the completion callback. */ +void SteamWorksHTTPRequest::OnHTTPHeadersReceived(HTTPRequestHeadersReceived_t *pRequest) { if (this->pHeadersReceivedForward == NULL || this->pHeadersReceivedForward->GetFunctionCount() == 0) { @@ -109,13 +120,13 @@ void SteamWorksHTTPRequest::OnHTTPHeadersReceived(HTTPRequestHeadersReceived_t * } this->pHeadersReceivedForward->PushCell(this->handle); - this->pHeadersReceivedForward->PushCell(bFailed); + this->pHeadersReceivedForward->PushCell(false); this->pHeadersReceivedForward->PushCell(pRequest->m_ulContextValue >> 32); this->pHeadersReceivedForward->PushCell((pRequest->m_ulContextValue & 0x00000000FFFFFFFF)); this->pHeadersReceivedForward->Execute(NULL); } -void SteamWorksHTTPRequest::OnHTTPDataReceived(HTTPRequestDataReceived_t *pRequest, bool bFailed) +void SteamWorksHTTPRequest::OnHTTPDataReceived(HTTPRequestDataReceived_t *pRequest) { if (this->pDataReceivedForward == NULL || this->pDataReceivedForward->GetFunctionCount() == 0) { @@ -123,7 +134,7 @@ void SteamWorksHTTPRequest::OnHTTPDataReceived(HTTPRequestDataReceived_t *pReque } this->pDataReceivedForward->PushCell(this->handle); - this->pDataReceivedForward->PushCell(bFailed); + this->pDataReceivedForward->PushCell(false); this->pDataReceivedForward->PushCell(pRequest->m_cOffset); this->pDataReceivedForward->PushCell(pRequest->m_cBytesReceived); this->pDataReceivedForward->PushCell(pRequest->m_ulContextValue >> 32); @@ -159,7 +170,9 @@ static cell_t sm_CreateHTTPRequest(IPluginContext *pContext, const cell_t *param pRequest->request = request; pRequest->handle = handle; - + + g_SteamWorks.pSWHTTP->RegisterRequest(pRequest); + return handle; } @@ -296,23 +309,14 @@ static cell_t sm_SetCallbacks(IPluginContext *pContext, const cell_t *params) static void SetCallbacks(SteamAPICall_t &hCall, SteamWorksHTTPRequest *pRequest) { + /* Only completion is a call result of this send. Header/data streaming + notifications are delivered as broadcast callbacks and routed to the request + by SteamWorksHTTP's dispatcher, so there is nothing to bind to hCall here. */ if (pRequest->pCompletedForward != NULL) { pRequest->CompletedCallResult.SetGameserverFlag(); pRequest->CompletedCallResult.Set(hCall, pRequest, &SteamWorksHTTPRequest::OnHTTPRequestCompleted); } - - if (pRequest->pHeadersReceivedForward != NULL) - { - pRequest->HeadersCallResult.SetGameserverFlag(); - pRequest->HeadersCallResult.Set(hCall, pRequest, &SteamWorksHTTPRequest::OnHTTPHeadersReceived); - } - - if (pRequest->pDataReceivedForward != NULL) - { - pRequest->DataCallResult.SetGameserverFlag(); - pRequest->DataCallResult.Set(hCall, pRequest, &SteamWorksHTTPRequest::OnHTTPDataReceived); - } } static cell_t sm_SendHTTPRequestAndStreamResponse(IPluginContext *pContext, const cell_t *params) diff --git a/Extension/swhttprequest.h b/Extension/swhttprequest.h index 78d14e2..a4b12ce 100644 --- a/Extension/swhttprequest.h +++ b/Extension/swhttprequest.h @@ -32,14 +32,15 @@ class SteamWorksHTTPRequest Handle_t handle; public: + /* Completion is a genuine call result of the send API call, so it stays a + CCallResult. Headers/data arrive as broadcast callbacks and are routed here + by SteamWorksHTTP's dispatcher, hence no per-request CCallResult for them. */ void OnHTTPRequestCompleted(HTTPRequestCompleted_t *pRequest, bool bFailed); - void OnHTTPHeadersReceived(HTTPRequestHeadersReceived_t *pRequest, bool bFailed); - void OnHTTPDataReceived(HTTPRequestDataReceived_t *pRequest, bool bFailed); + void OnHTTPHeadersReceived(HTTPRequestHeadersReceived_t *pRequest); + void OnHTTPDataReceived(HTTPRequestDataReceived_t *pRequest); public: CCallResult CompletedCallResult; - CCallResult HeadersCallResult; - CCallResult DataCallResult; public: IChangeableForward *pCompletedForward; diff --git a/Pawn/includes/SteamWorks.inc b/Pawn/includes/SteamWorks.inc index a0f935f..15a7f18 100644 --- a/Pawn/includes/SteamWorks.inc +++ b/Pawn/includes/SteamWorks.inc @@ -598,7 +598,8 @@ typeset SteamWorksHTTPRequestCompleted * of trailing context parameters matches the context values set on the request. * * @param hRequest HTTP request handle. - * @param bFailure True if the request failed due to an internal or network error. + * @param bFailure Always false; headers-received is a success-only notification. A + * failed request is reported through SteamWorksHTTPRequestCompleted. * @param data1 First context value, if one was set. * @param data2 Second context value, if one was set. */ @@ -615,7 +616,8 @@ typeset SteamWorksHTTPHeadersReceived * number of trailing context parameters matches the context values set on the request. * * @param hRequest HTTP request handle. - * @param bFailure True if the request failed due to an internal or network error. + * @param bFailure Always false; data-received is a success-only notification. A + * failed request is reported through SteamWorksHTTPRequestCompleted. * @param offset Offset of this chunk within the response body. * @param bytesreceived Number of bytes in this chunk. * @param data1 First context value, if one was set.