介紹 SDK & Sample Module

模組 SDK 介紹

要寫一個新的模組需要繼承 oxool-devel 的類別 oxoolmodule

並至少要將 handleRequest 實作出來

  • handleRequest: OxOOL 會透過這個函數將 HTTP Request 給對應的模組進行後續處理,請參考下列參數
    • StreamSocket: oxool 的核心功能,透過 Socket 將 Response 送出
    • Poco::MemoryInputStream: request 的附件/參數,詳細可參考 Poco 的文件
    • Poco::Net::HTTPRequest: request headers 相關資訊,詳細可參考 Poco 的文件
    • SocketDisposition: oxool 的核心功能,作用於控制 socket 的執行續切換時的 hook

另外還有幾個延伸功能的選擇性實作

  • handleAdmin: 處理來自後端管理介面 WebSocket 的指令
  • getHTMLFile:  將後端管理網頁的網頁靜態資源傳給前端

以 Sample Module 為範例來瞭解 SDK 的概念

 

handleRequest(需要實作)

技術要點

  1. 模組處理 request 時需要 fork
  2. URL 第二個路徑為模組的名稱
void sample::handleRequest(std::weak_ptr<StreamSocket> _socket,
                           MemoryInputStream &message,
                           HTTPRequest &request,
                           SocketDisposition &disposition)
{
    Poco::URI requestUri(request.getURI());
    std::vector<std::string> reqPathSegs;
    requestUri.getPathSegments(reqPathSegs);
    std::string method = request.getMethod();
    Process::PID pid = fork();
    if (pid < 0)
    {
        quickHttpRes(_socket,
                     HTTPResponse::HTTP_SERVICE_UNAVAILABLE);
    }
    else if (pid == 0)
    {
        // This would trigger this fork exit after the socket finish write
        // note: exit point is in wsd/LOOLWSD.cpp where ClientRequestDispatcher (SocketHandler)'s performWrites()
        try
        {
            Poco::URI requestUri(request.getURI());
            std::vector<std::string> reqPathSegs;
            requestUri.getPathSegments(reqPathSegs);
            std::string method = request.getMethod();
            if (request.getMethod() == HTTPRequest::HTTP_GET && reqPathSegs.size() == 2)
            {
                quickHttpRes(_socket, HTTPResponse::HTTP_OK);
            }
            else if (request.getMethod() == HTTPRequest::HTTP_POST &&
                     reqPathSegs[1] == "sample")
            { // /lool/sample
                handlesample(_socket, request, message, disposition);
            }
            else
            {
                quickHttpRes(_socket, HTTPResponse::HTTP_NOT_FOUND);
            }
        }
        catch (std::exception &e)
        {
            std::cout << e.what() << "\n";
            logger().notice("[Exception]" + std::string(e.what()));
            quickHttpRes(_socket, HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
        }

        exit_application = true;
    }
    else
    {
        std::cout << "call from parent" << std::endl;
    }
}

 

handleAdmin: 控管後端管理介面(選擇性實作)

技術要點

  1. handleAdmin 會接到來自 WebSocket 解析過後的一串指令
  2. 指令的結構為: <modulename> <action> <data in this format: x,y,z ....>
  3. 這裡的範例是透過管理介面來管理模組的設定檔
std::string sample::handleAdmin(std::string command)
{
    /*
     *command format: module <modulename> <action> <data in this format: x,y,z ....>
     */
    auto tokenOpts = StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM;
    StringTokenizer tokens(command, " ", tokenOpts);
    std::string result = "Module Loss return";
    std::string action = tokens[2];

    // 以 json 字串傳回 sample.xml 
    if (action == "getConfig" && tokens.count() >= 4)
    {
        std::ostringstream oss;
        oss << "settings {\n";
        for (size_t i = 3; i < tokens.count(); i++)
        {
            std::string key = tokens[i];

            if (i > 3)
                oss << ",\n";

            oss << "\"" << key << "\": ";
            // 下列三種 key 是陣列形式

            if (xml_config->has(key))
            {
                std::string p_value = addSlashes(xml_config->getString(key, "")); // 讀取 value, 沒有的話預設為空字串
                std::string p_type = xml_config->getString(key + "[@type]", "");  // 讀取 type, 沒有的話預設為空字串
                if (p_type == "int" || p_type == "uint" || p_type == "bool" ||
                    p_value == "true" || p_value == "false")
                    oss << p_value;
                else
                    oss << "\"" << p_value << "\"";
            }
            else
            {
                oss << "null";
            }
        }
        oss << "\n}\n";
        result = oss.str();
    }
    else if (action == "setConfig" && tokens.count() > 1)
    {
        Poco::JSON::Object::Ptr object;
        if (JsonUtil::parseJSON(command, object))
        {
            for (Poco::JSON::Object::ConstIterator it = object->begin(); it != object->end(); ++it)
            {
                // it->first : key, it->second.toString() : value
                xml_config->setString(it->first, it->second.toString());
            }
            xml_config->save(ConfigFile);
            result = "setConfigOk";
        }
        else
        {
            result = "setConfigNothing";
        }
    }
    else
        result = "No such command for module " + tokens[1];

    return result;
}

getHTML: 返回後端管理頁面的 static resource(選擇性實作)

技術要點

  1. 基本上 getHTML 無須改動
  2. 解析在專案的靜態資源目錄 /<path-to-module>/html/ 的路徑
  3. 詳細的路徑引用請參閱 GitHub/oxool-module-sample 的 admin.html (可以看下一個小節)
std::string sample::getHTMLFile(std::string fileName)
{
    std::string filePath = "";
#ifdef DEV_DIR
    std::string dev_path(DEV_DIR);
    filePath = dev_path + "/html/";
#else
    filePath = "/var/lib/oxool/sample/html/";
#endif
    filePath += fileName;
    return filePath;
}

admin.html: 後端管理頁面範例與解說

技術要點

  1. oxool-community 採用了模板引擎,目前僅支援 {% BLOCK content %} ...TODO... {% END BLOCK %}
  2. 在 script 的 src 的結構須為: <module-name>/<path-in-module's-html>/<filename>

 

{% BLOCK content %}
<script src="sample/js/AdminSocketConfigSettings.js"> </script>
<script>
    if (window.location.protocol == "https:") {
        host = 'wss://' + window.location.host + '%SERVICE_ROOT%/lool/adminws/'
    }
    else {
        host = 'ws://' + window.location.host + '%SERVICE_ROOT%/lool/adminws/'
    }

    var configSocket = Admin.ConfigSettings(host);
</script>

<ul class="nav nav-tabs">
    <li role="presentation" class="active"><a data-toggle="tab" href="#a1" class="tabctl">API 測試</a></li>
    <li role="presentation" class=""><a data-toggle="tab" href="#a2" class="tabctl">設定檔管理</a></li>
    <li role="presentation" class=""><a data-toggle="tab" href="#a3" class="tabctl">版權聲明</a></li>
</ul>
<div class="tab-content" style="height: 100%;">
    ...
    <!--******************************版權聲明******************************-->
    <div id="a3" class="tab-pane" style="width: 100%; padding-top:10px;">
        <pre id="license" class="panel-body" style="border: 0; background-color: transparent;">
        </pre>
    </div>
</div>

<script>
    var getCookie = function (name) {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = cookies[i].trim();
            if (cookie.indexOf(name) === 0) {
                return cookie;
            }
        }
        return '';
    }
    
</script>
{% END BLOCK %}