介紹 SDK & Sample Module
模組 SDK 介紹
要寫一個新的模組需要繼承 oxool-devel 的類別 oxoolmodule
並至少要將 handleRequest 實作出來
- handleRequest: OxOOL 會透過這個函數將 HTTP Request 給對應的模組進行後續處理,請參考下列參數
另外還有幾個延伸功能的選擇性實作
- handleAdmin: 處理來自後端管理介面 WebSocket 的指令
- getHTMLFile: 將後端管理網頁的網頁靜態資源傳給前端
以 Sample Module 為範例來瞭解 SDK 的概念
handleRequest(需要實作)
技術要點
- 模組處理 request 時需要 fork
- 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: 控管後端管理介面(選擇性實作)
技術要點
- handleAdmin 會接到來自 WebSocket 解析過後的一串指令
- 指令的結構為: <modulename> <action> <data in this format: x,y,z ....>
- 這裡的範例是透過管理介面來管理模組的設定檔
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(選擇性實作)
技術要點
- 基本上 getHTML 無須改動
- 解析在專案的靜態資源目錄 /<path-to-module>/html/ 的路徑
- 詳細的路徑引用請參閱 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: 後端管理頁面範例與解說
技術要點
- oxool-community 採用了模板引擎,目前僅支援 {% BLOCK content %} ...TODO... {% END BLOCK %}
- 在 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 %}
No Comments