如何對外提供api
- 作者: zhyh
- 時間: 2015-07-14 00:22:00
- 分類: TeamTalk
##如何對外提供API
1、繼續雜談
人都是有惰性的,我也不排除,下班回來後真的不想再寫東西了,隻想好好休息下,看看書,然後睡覺。不過之前答應過大家,隻能堅持寫下去。
在TT群裡也發現了一個現象,也許這也是國内IT的普片現象吧——拿來主義。什麼都希望别人給你完全提供好,自己最好不動手就可以拿到現成的成果。TT沒有做到這一點,于是很多人就來群裡問,有技術層面的和非技術層面的,每天都會遇到各種問題,很多問題真的隻是自己稍微谷歌,百度下就能知道結果的,卻非要來問下,給我的感覺是,你問出這個問題,到别人回答你的時間,你早就在網上搜到答案了。
好了言歸正傳。
2、如何提供一個接口
作為一個完整的平台,對外提供API是必不可少的,TT第二版跟第一版一個比較明顯的變化,就是相比第一版多了一個http_msg_server這個子產品,雖然這個子產品暫時沒有提供太多的功能,但是卻也提供了一個參考。不過還是有很多朋友在群裡咨詢詢問如果利用http_msg_server。今天就以提供一個發送消息給某個使用者的接口為例子講解利用http_msg_server。
這次相對于上一篇部落格,會涉及到route_server,需要對TT整個架構有個比較明确的了解。
3、TT架構圖
TT的整個架構如下圖所示:
對上圖各個子產品做個簡單的說明(之前在新版TT部署中已經做過說明)。
Android/iOS/PC:各種用戶端。
login_server:主要負責負載均衡的作用,當用戶端來請求的時候,login_server會可以配置設定一個負載最小的msg_server給用戶端。
msg_server:TT的主要服務端,負責維護各個用戶端的連結,消息轉發等功能。
route_server:負責消息路由的功能,當msg_server發現某個使用者不在本伺服器内,而又有消息需要發給他,就會将消息轉發給route_server,route_server會将消息發給相應的msg_server,由此可知,route_server也維護了一定的使用者狀态。
db_proxy_server:在TT中負責了主要的業務邏輯,主要與存儲層打交道.
msfs:小檔案存儲,負責存儲聊天過程中的圖檔,語音資訊。
http_msg_server:主要對外提供接口功能。
web:簡單的管理功能。
4、發送消息流程
本來不打算講解這一節的,但是發現很多使用者到現在還是沒能弄清楚tt的消息流程,是以趁機做個講解,也為下面講解http_msg_server發送消息做個鋪墊。
消息時序圖如下:
5、前奏
在講解如何增加消息API之前,我們先做好如下限制:
1、通過http_msg_server發送消息的url如下:http://ip:port/api/sendmsg
2、資料以post形式送出。
3、所有資料以json格式送出。{"appKey":"1234556","from_id":1,"to_id":2,"msg_content":"Hello World!"}
其中appKey是之前用來校驗調用api權限使用的,這裡大家根據自己的需要去處理。
我們首先觀察下http_msg_server的目錄結構:
lanhu:http_msg_server lanhu$ tree
.
├── AttachData.cpp
├── AttachData.h
├── CMakeLists.txt
├── DBServConn.cpp
├── DBServConn.h
├── HttpConn.cpp
├── HttpConn.h
├── HttpPdu.cpp
├── HttpPdu.h
├── HttpQuery.cpp
├── HttpQuery.h
├── RouteServConn.cpp
├── RouteServConn.h
├── http_msg_server.cpp
├── httpmsgserver.conf
└── log4cxx.properties
我們來講解各個檔案的功能:
AttachData:老檔案了,對需要放入協定中attach_data封裝。
CMakeLists.txt:cmake檔案
DBServConn:負責與db_proxy_server的連結。
HttpConn:負責解析外部傳入的調用請求。
HttpPdu:主要是解析post資料類。
HttpQuery:主要解析業務邏輯,這裡凡是http://ip:port/query/xxxx的請求都是這裡處理。
RouteServer:負責與route_server的連結。
http_msg_server:main函數入口,負責啟動各種連結,啟動監聽等功能。
httpmsgserver.conf:配置檔案
log4cxx.properties:log的配置檔案。
6、實踐
由上一節介紹我們大緻了解了http_msg_server各個檔案的作用,之前我們提到了HttpQuery主要負責/query/xxxx的請求,這裡為了簡便起見,我們/api/xxx的請求也由它來負責處理(大家完全可以模仿HttpQuery寫出一個HttpApi這樣的東西出來)。
由于時間關系,下面的我都盡量簡單去處理,也沒有經過測試,但是大緻思路一定是對的。
首先我們在HttpConn.cpp的OnRead函數中修改:
if (m_HttpParser.IsReadAll()) {
string url = m_HttpParser.GetUrl();
if (strncmp(url.c_str(), "/query/", 7) == 0) {
string content = m_HttpParser.GetBodyContent();
CHttpQuery* pQueryInstance = CHttpQuery::GetInstance();
pQueryInstance->DispatchQuery(url, content, this);
} else {
log("url unknown, url=%s ", url.c_str());
Close();
}
}
為如下形式:
if (m_HttpParser.IsReadAll()) {
string url = m_HttpParser.GetUrl();
if (strncmp(url.c_str(), "/query/", 7) == 0) {
string content = m_HttpParser.GetBodyContent();
CHttpQuery* pQueryInstance = CHttpQuery::GetInstance();
pQueryInstance->DispatchQuery(url, content, this);
} else if(strncmp(url.c_str(), "/api/", 5)) {
string content = m_HttpParser.GetBodyContent();
CHttpQuery::GetInstance()->DispatchQuery(url, content, this);
}
else {
log("url unknown, url=%s ", url.c_str());
Close();
}
}
這裡代碼很簡單,我就不具體解釋了,下面我們去HttpQuery.cpp檢視DispatchQuery函數:
void CHttpQuery::DispatchQuery(std::string& url, std::string& post_data, CHttpConn* pHttpConn)
{
++g_total_query;
log("DispatchQuery, url=%s, content=%s ", url.c_str(), post_data.c_str());
Json::Reader reader;
Json::Value value;
Json::Value root;
if ( !reader.parse(post_data, value) ) {
log("json parse failed, post_data=%s ", post_data.c_str());
pHttpConn->Close();
return;
}
string strErrorMsg;
string strAppKey;
HTTP_ERROR_CODE nRet = HTTP_ERROR_SUCCESS;
try
{
string strInterface(url.c_str() + strlen("/query/"));
strAppKey = value["app_key"].asString();
string strIp = pHttpConn->GetPeerIP();
uint32_t nUserId = value["req_user_id"].asUInt();
nRet = _CheckAuth(strAppKey, nUserId, strInterface, strIp);
}
catch ( std::runtime_error msg)
{
nRet = HTTP_ERROR_INTERFACE;
}
if(HTTP_ERROR_SUCCESS != nRet)
{
if(nRet < HTTP_ERROR_MAX)
{
root["error_code"] = nRet;
root["error_msg"] = HTTP_ERROR_MSG[nRet];
}
else
{
root["error_code"] = -1;
root["error_msg"] = "未知錯誤";
}
string strResponse = root.toStyledString();
pHttpConn->Send((void*)strResponse.c_str(), strResponse.length());
return;
}
// process post request with post content
if (strcmp(url.c_str(), "/query/CreateGroup") == 0)
{
_QueryCreateGroup(strAppKey, value, pHttpConn);
}
else if (strcmp(url.c_str(), "/query/ChangeMembers") == 0)
{
_QueryChangeMember(strAppKey, value, pHttpConn);
}
else {
log("url not support ");
pHttpConn->Close();
return;
}
}
這個函數開始的主要功能就是解析post的資料,然後更具url調用不同的處理邏輯函數。
我們在這個類中增加一個處理發送消息的函數:
static void _ApiSendMsg(uint32_t nFromId, uint32_t nToId, const string& strMsg);
接着我們修改DispatchQuery,增加一個調用發送消息的。
if (strcmp(url.c_str(), "/query/CreateGroup") == 0)
{
_QueryCreateGroup(strAppKey, value, pHttpConn);
}
else if (strcmp(url.c_str(), "/query/ChangeMembers") == 0)
{
_QueryChangeMember(strAppKey, value, pHttpConn);
}
else if (strcmp(url.c_str(), "/api/sendmsg") == 0) {
uint32_t nFromId = value["req_id"].asUInt();
uint32_t nToId = value["to_id"].asUInt();
string strMsg = value["msg_content"].asString();
_ApiSendMsg(nFromId, nToId, strMsg);
}
else {
log("url not support ");
pHttpConn->Close();
return;
}
接着我們去實作_ApiSendMsg函數:
void CHttpQuery::_ApiSendMsg(uint32_t nFromId, uint32_t nToId, const string &strMsg, CHttpConn* pHttpConn)
{
HTTP::CDBServConn* pDBConn = HTTP::get_db_serv_conn();
if(!pDBConn) {
log("no db server");
pHttpConn->Close();
}
IM::Message::IMMsgData msg;
msg.set_from_user_id(nFromId);
msg.set_msg_id(0);
msg.set_to_session_id(nToId);
msg.set_create_time(time(NULL));
msg.set_msg_type(::IM::BaseDefine::MSG_TYPE_SINGLE_TEXT);
msg.set_msg_data(strMsg);
CImPdu pdu;
pdu.SetPBMsg(&msg);
pdu.SetServiceId(IM::BaseDefine::SID_MSG);
pdu.SetCommandId(IM::BaseDefine::CID_MSG_DATA);
pDBConn->SendPdu(&pdu);
pHttpConn->Close();
}
我們已經将消息發送到db_proxy_server中去了,db_proxy_server存儲完成後會傳回,我們需要在DBServConn中增加一個處理。
void _HandleSendMsg(CImPdu* pPdu);
去DBServConn.cpp中實作:
void CDBServConn::_HandleSendMsg(CImPdu *pPdu)
{
IM::Message::IMMsgData msg;
CHECK_PB_PARSE_MSG(msg.ParseFromArray(pPdu->GetBodyData(), pPdu->GetBodyLength()));
uint32_t from_user_id = msg.from_user_id();
uint32_t to_user_id = msg.to_session_id();
uint32_t msg_id = msg.msg_id();
if (msg_id == 0) {
log("_HandleSendMsg, write db failed, %u->%u.", from_user_id, to_user_id);
return;
}
log("_HandleSendMsg, from_user_id=%u, to_user_id=%u, msg_id=%u", from_user_id, to_user_id, msg_id);
CRouteServConn* pRouteConn = get_route_serv_conn();
if (pRouteConn) {
pRouteConn->SendPdu(pPdu);
}
}
當db_proxy_server存儲完畢傳回後,http_msg_server将消息發送到route_server即可完成消息的發送了。
由于時間關系,本次講解未涉及到route_server的修改,但是基本原理與這些類似,大家可以仿照已有的功能去添加。如果有必要,下次再進行補充。