From e7a87b3c82204e048f23750a18b456e567a6f436 Mon Sep 17 00:00:00 2001 From: George Hazan Date: Wed, 28 Dec 2016 16:57:55 +0300 Subject: http framework --- protocols/Discord/src/connection.cpp | 142 +++++++++++++++++++++++++++++++++++ protocols/Discord/src/http.cpp | 113 ++++++++++++++++++++++++++++ protocols/Discord/src/proto.cpp | 30 +++++++- protocols/Discord/src/proto.h | 85 +++++++++++++++++++++ protocols/Discord/src/server.cpp | 33 ++++++++ 5 files changed, 401 insertions(+), 2 deletions(-) create mode 100644 protocols/Discord/src/http.cpp create mode 100644 protocols/Discord/src/server.cpp (limited to 'protocols/Discord/src') diff --git a/protocols/Discord/src/connection.cpp b/protocols/Discord/src/connection.cpp index a7e3babd41..13a12129e2 100644 --- a/protocols/Discord/src/connection.cpp +++ b/protocols/Discord/src/connection.cpp @@ -17,7 +17,149 @@ along with this program. If not, see . #include "stdafx.h" +void CDiscordProto::ExecuteRequest(AsyncHttpRequest *pReq) +{ + CMStringA str; + + pReq->szUrl = pReq->m_szUrl.GetBuffer(); + if (!pReq->m_szParam.IsEmpty()) { + if (pReq->requestType == REQUEST_GET) { + str.Format("%s?%s", pReq->m_szUrl, pReq->m_szParam); + pReq->szUrl = str.GetBuffer(); + } + else { + pReq->pData = mir_strdup(pReq->m_szParam); + pReq->dataLength = pReq->m_szParam.GetLength(); + } + } + + pReq->flags |= NLHRF_PERSISTENT; + pReq->nlc = m_hAPIConnection; + + debugLogA("Executing request #%d:\n%s", pReq->m_iReqNum, pReq->szUrl); + NETLIBHTTPREQUEST *reply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)m_hNetlibUser, (LPARAM)pReq); + if (reply != NULL) { + if (pReq->m_pCallback != NULL) + (this->*(pReq->m_pCallback))(reply, pReq); + + m_hAPIConnection = reply->nlc; + + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)reply); + } + else { + debugLogA("Request %d failed", pReq->m_iReqNum); + + if (IsStatusConnecting(m_iStatus)) + ConnectionFailed(LOGINERR_NONETWORK); + else + ShutdownSession(); + m_hAPIConnection = NULL; + } + delete pReq; +} + +void CDiscordProto::OnLoggedIn() +{ + debugLogA("CDiscordProto::OnLoggedIn"); + m_bOnline = true; + SetServerStatus(m_iDesiredStatus); +} + +void CDiscordProto::OnLoggedOut() +{ + debugLogA("CDiscordProto::OnLoggedOut"); + m_bOnline = false; + m_hWorkerThread = NULL; + + if (m_hAPIConnection) + Netlib_CloseHandle(m_hAPIConnection); + + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; +} + +void CDiscordProto::ShutdownSession() +{ + debugLogA("CDiscordProto::ShutdownSession"); + m_bTerminated = true; + if (m_hWorkerThread) + SetEvent(m_evRequestsQueue); + OnLoggedOut(); +} + +void CDiscordProto::ConnectionFailed(int iReason) +{ + debugLogA("CDiscordProto::ConnectionFailed -> reason %d", iReason); + delSetting("AccessToken"); + + ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, iReason); + ShutdownSession(); +} + +bool CDiscordProto::TryToConnect(void) +{ + ptrW wszLogin(getWStringA(DB_KEY_EMAIL)), wszPassword(getWStringA(DB_KEY_PASSWORD)); + if (wszLogin == NULL) { + ConnectionFailed(LOGINERR_BADUSERID); + return false; + } + if (wszPassword == NULL) { + ConnectionFailed(LOGINERR_WRONGPASSWORD); + return false; + } + + Push(new AsyncHttpRequest(this, REQUEST_GET, "/user/login", &CDiscordProto::OnReceiveToken) + << WCHAR_PARAM("email", wszLogin) + << WCHAR_PARAM("password", wszPassword)); + return true; +} + void CDiscordProto::ServerThread(void*) { + m_szAccessToken = getStringA("AccessToken"); + m_hAPIConnection = NULL; + m_bTerminated = false; + + debugLogA("CDiscordProto::WorkerThread: %s", "entering"); + + if (m_szAccessToken != NULL) + // try to receive a response from server + RetrieveMyInfo(); + else if (!TryToConnect()) + return; + + while (true) { + WaitForSingleObject(m_evRequestsQueue, 1000); + if (m_bTerminated) + break; + + AsyncHttpRequest *pReq; + bool need_sleep = false; + while (true) { + { + mir_cslock lck(m_csHttpQueue); + if (m_arHttpQueue.getCount() == 0) + break; + + pReq = m_arHttpQueue[0]; + m_arHttpQueue.remove(0); + need_sleep = (m_arHttpQueue.getCount() > 1); + } + if (m_bTerminated) + break; + ExecuteRequest(pReq); + if (need_sleep) { + Sleep(330); + debugLogA("CDiscordProto::WorkerThread: %s", "need to sleep"); + } + } + } + + m_hWorkerThread = NULL; + if (m_hAPIConnection) { + Netlib_CloseHandle(m_hAPIConnection); + m_hAPIConnection = NULL; + } + debugLogA("CDiscordProto::WorkerThread: %s", "leaving"); } diff --git a/protocols/Discord/src/http.cpp b/protocols/Discord/src/http.cpp new file mode 100644 index 0000000000..5333d87a65 --- /dev/null +++ b/protocols/Discord/src/http.cpp @@ -0,0 +1,113 @@ +/* +Copyright © 2016 Miranda NG team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "stdafx.h" + +AsyncHttpRequest* CDiscordProto::Push(AsyncHttpRequest *pReq, int iTimeout) +{ + pReq->timeout = iTimeout; + { + mir_cslock lck(m_csHttpQueue); + m_arHttpQueue.insert(pReq); + } + SetEvent(m_evRequestsQueue); + return pReq; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static LONG g_reqNum = 0; + +AsyncHttpRequest::AsyncHttpRequest() +{ + cbSize = sizeof(NETLIBHTTPREQUEST); + AddHeader("Connection", "keep-alive"); + pUserInfo = NULL; + m_iErrorCode = 0; + m_pCallback = NULL; + m_iReqNum = ::InterlockedIncrement(&g_reqNum); +} + +AsyncHttpRequest::AsyncHttpRequest(CDiscordProto *ppro, int iRequestType, LPCSTR _url, HttpCallback pFunc) +{ + cbSize = sizeof(NETLIBHTTPREQUEST); + AddHeader("Connection", "keep-alive"); + + if (*_url == '/') { // relative url leads to a site + m_szUrl = "https://discardapp.com"; + m_szUrl += _url; + } + else m_szUrl = _url; + + flags = NLHRF_DUMPASTEXT | NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_SSL; + if (ppro->m_szAccessToken != NULL) + this << CHAR_PARAM("access_token", ppro->m_szAccessToken); + + requestType = iRequestType; + m_pCallback = pFunc; + pUserInfo = NULL; + m_iErrorCode = 0; + m_iReqNum = ::InterlockedIncrement(&g_reqNum); +} + +AsyncHttpRequest::~AsyncHttpRequest() +{ + for (int i = 0; i < headersCount; i++) { + mir_free(headers[i].szName); + mir_free(headers[i].szValue); + } + mir_free(headers); + mir_free(pData); +} + +void AsyncHttpRequest::AddHeader(LPCSTR szName, LPCSTR szValue) +{ + headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1)); + headers[headersCount].szName = mir_strdup(szName); + headers[headersCount].szValue = mir_strdup(szValue); + headersCount++; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT_PARAM ¶m) +{ + CMStringA &s = pReq->m_szParam; + if (!s.IsEmpty()) + s.AppendChar('&'); + s.AppendFormat("%s=%i", param.szName, param.iValue); + return pReq; +} + +AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const CHAR_PARAM ¶m) +{ + CMStringA &s = pReq->m_szParam; + if (!s.IsEmpty()) + s.AppendChar('&'); + s.AppendFormat("%s=%s", param.szName, ptrA(mir_urlEncode(param.szValue))); + return pReq; +} + +AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const WCHAR_PARAM ¶m) +{ + T2Utf szValue(param.wszValue); + CMStringA &s = pReq->m_szParam; + if (!s.IsEmpty()) + s.AppendChar('&'); + s.AppendFormat("%s=%s", param.szName, ptrA(mir_urlEncode(szValue))); + return pReq; +} diff --git a/protocols/Discord/src/proto.cpp b/protocols/Discord/src/proto.cpp index 1c557daaa7..c44cab39bc 100644 --- a/protocols/Discord/src/proto.cpp +++ b/protocols/Discord/src/proto.cpp @@ -17,8 +17,15 @@ along with this program. If not, see . #include "stdafx.h" +static int compareRequests(const AsyncHttpRequest *p1, const AsyncHttpRequest *p2) +{ + return p1->m_iReqNum - p2->m_iReqNum; +} + CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) : - PROTO(proto_name, username) + PROTO(proto_name, username), + m_arHttpQueue(10, compareRequests), + m_evRequestsQueue(CreateEvent(NULL, FALSE, FALSE, NULL)) { // Services CreateProtoService(PS_GETNAME, &CDiscordProto::GetName); @@ -26,10 +33,25 @@ CDiscordProto::CDiscordProto(const char *proto_name, const wchar_t *username) : // Events HookProtoEvent(ME_OPT_INITIALISE, &CDiscordProto::OnOptionsInit); + + // Network initialization + CMStringW descr(FORMAT, TranslateT("%s server connection"), m_tszUserName); + + NETLIBUSER nlu = { sizeof(nlu) }; + nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; + nlu.szSettingsModule = m_szModuleName; + nlu.ptszDescriptiveName = descr.GetBuffer(); + m_hNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); } CDiscordProto::~CDiscordProto() { + debugLogA("CDiscordProto::~CDiscordProto"); + Netlib_CloseHandle(m_hNetlibUser); + m_hNetlibUser = NULL; + + m_arHttpQueue.destroy(); + ::CloseHandle(m_evRequestsQueue); } DWORD_PTR CDiscordProto::GetCaps(int type, MCONTACT) @@ -84,7 +106,7 @@ int CDiscordProto::SetStatus(int iNewStatus) m_iStatus = ID_STATUS_CONNECTING; ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); - ForkThread(&CDiscordProto::ServerThread, this); + m_hWorkerThread = ForkThreadEx(&CDiscordProto::ServerThread, NULL, NULL); } else if (iNewStatus == ID_STATUS_OFFLINE) { m_iStatus = m_iDesiredStatus; @@ -105,6 +127,10 @@ int CDiscordProto::OnModulesLoaded(WPARAM, LPARAM) int CDiscordProto::OnPreShutdown(WPARAM, LPARAM) { + debugLogA("CDiscordProto::OnPreShutdown"); + + m_bTerminated = true; + SetEvent(m_evRequestsQueue); return 0; } diff --git a/protocols/Discord/src/proto.h b/protocols/Discord/src/proto.h index 26466bcfae..5a381e52f9 100644 --- a/protocols/Discord/src/proto.h +++ b/protocols/Discord/src/proto.h @@ -1,9 +1,85 @@ +class CDiscordProto; +typedef void (CDiscordProto::*HttpCallback)(NETLIBHTTPREQUEST*, struct AsyncHttpRequest*); + +struct AsyncHttpRequest : public NETLIBHTTPREQUEST, public MZeroedObject +{ + AsyncHttpRequest(); + AsyncHttpRequest(CDiscordProto*, int iRequestType, LPCSTR szUrl, HttpCallback pFunc); + ~AsyncHttpRequest(); + + void AddHeader(LPCSTR, LPCSTR); + + CMStringA m_szUrl; + CMStringA m_szParam; + HttpCallback m_pCallback; + int m_iErrorCode, m_iReqNum; + void *pUserInfo; +}; + +struct PARAM +{ + LPCSTR szName; + __forceinline PARAM(LPCSTR _name) : szName(_name) + {} +}; + +struct INT_PARAM : public PARAM +{ + int iValue; + __forceinline INT_PARAM(LPCSTR _name, int _value) : + PARAM(_name), iValue(_value) + {} +}; +AsyncHttpRequest* operator<<(AsyncHttpRequest*, const INT_PARAM&); + +struct CHAR_PARAM : public PARAM +{ + LPCSTR szValue; + __forceinline CHAR_PARAM(LPCSTR _name, LPCSTR _value) : + PARAM(_name), szValue(_value) + {} +}; +AsyncHttpRequest* operator<<(AsyncHttpRequest*, const CHAR_PARAM&); + +struct WCHAR_PARAM : public PARAM +{ + LPCWSTR wszValue; + __forceinline WCHAR_PARAM(LPCSTR _name, LPCWSTR _value) : + PARAM(_name), wszValue(_value) + {} +}; +AsyncHttpRequest* operator<<(AsyncHttpRequest*, const WCHAR_PARAM&); + +///////////////////////////////////////////////////////////////////////////////////////// + class CDiscordProto : public PROTO { + friend struct AsyncHttpRequest; + void __cdecl ServerThread(void*); void SetAllContactStatuses(int iStatus); + bool TryToConnect(void); + void ConnectionFailed(int iReason); + void ShutdownSession(void); + + ptrA m_szAccessToken; + + mir_cs m_csHttpQueue; + HANDLE m_evRequestsQueue; + LIST m_arHttpQueue; + + void ExecuteRequest(AsyncHttpRequest *pReq); + AsyncHttpRequest* Push(AsyncHttpRequest *pReq, int iTimeout = 10000); + + HANDLE + m_hAPIConnection, // working connection + m_hWorkerThread; // worker thread handle + + bool + m_bOnline, // protocol is online + m_bTerminated; // Miranda's going down public: CDiscordProto(const char*,const wchar_t*); @@ -24,4 +100,13 @@ public: int __cdecl OnModulesLoaded(WPARAM, LPARAM); int __cdecl OnPreShutdown(WPARAM, LPARAM); int __cdecl OnOptionsInit(WPARAM, LPARAM); + + void OnLoggedIn(); + void OnLoggedOut(); + + void OnReceiveToken(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + + // Misc + void RetrieveMyInfo(void); + void SetServerStatus(int iStatus); }; diff --git a/protocols/Discord/src/server.cpp b/protocols/Discord/src/server.cpp new file mode 100644 index 0000000000..4b7c2afa13 --- /dev/null +++ b/protocols/Discord/src/server.cpp @@ -0,0 +1,33 @@ +/* +Copyright © 2016 Miranda NG team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "stdafx.h" + +void CDiscordProto::RetrieveMyInfo() +{ + +} + +void CDiscordProto::SetServerStatus(int iStatus) +{ + +} + +void CDiscordProto::OnReceiveToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) +{ + +} -- cgit v1.2.3