天天看點

TeamTalk源碼分析之http_msg_server對外提供API如何對外提供api

如何對外提供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的整個架構如下圖所示:

TeamTalk源碼分析之http_msg_server對外提供API如何對外提供api

對上圖各個子產品做個簡單的說明(之前在新版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發送消息做個鋪墊。

消息時序圖如下:

TeamTalk源碼分析之http_msg_server對外提供API如何對外提供api

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的修改,但是基本原理與這些類似,大家可以仿照已有的功能去添加。如果有必要,下次再進行補充。

繼續閱讀