uhttp luci simple-app
Posted 云水
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uhttp luci simple-app相关的知识,希望对你有一定的参考价值。
uhttp luci simple-app
see https://forum.openwrt.org/t/rest-api-supported-in-openwrt/17210/6
You probably do want Luci, or at least to take a look at how Luci does it.
POST data is in the body of the request. Luci creates a ltn12 compatible source to read it and passes it to the http.Request constructor (same with CGI).
The Request class calls protocol.parse_message_body which does most of the work. It stores the results in the params
field of the Request. You can then access them with the familiar formvalue method (source - you can see that the first call calls _parse_input
that we saw earlier).
gives some clues and I spent some time dissecting how Luci is doing it.
Supposedly, you should create a "request", and use that to parse the parameters.
local req = luci.http.Request(
renv, recv, luci.ltn12.sink.file(io.stderr)
)
and then access the parsed parameters as
req.formvalue("email")
This method is working for parameters passed in a GET (i.e. http://192.168.1.70/lua/?email=blah 51)
But not working when passed in a POST (seems req is empty).
In a traditional CGI application you do need to take care of parsing parameters and POST bodies yourself. URL query parameters are passed via the QUERY_STRING
variable (os.getenv("QUERY_STRING")
) while POST bodies in either application/x-www-form-urlencoded
or multipart/form-data
format are fed to the invoked program via stdin.
This has nothing to do with uhttpd or lighttpd but with the basic operation principle of plain CGI. Usually you have HTTP parsing support directly built into the language (e.g. with PHP) or it is available as separate library (e.g. CGI.pm
for Perl).
The simplest uhttpd embedded Lua application which does not built upon LuCI but uses LuCI\'s HTTP abstraction library is this:
root@OpenWrt:~# cat /root/simple-app.lua
require "luci.http"
function handle_request(env)
local renv =
CONTENT_LENGTH = env.CONTENT_LENGTH,
CONTENT_TYPE = env.CONTENT_TYPE,
REQUEST_METHOD = env.REQUEST_METHOD,
REQUEST_URI = env.REQUEST_URI,
PATH_INFO = env.PATH_INFO,
SCRIPT_NAME = env.SCRIPT_NAME:gsub("/+$", ""),
SCRIPT_FILENAME = env.SCRIPT_NAME,
SERVER_PROTOCOL = env.SERVER_PROTOCOL,
QUERY_STRING = env.QUERY_STRING
local k, v
for k, v in pairs(env.headers) do
k = k:upper():gsub("%-", "_")
renv["HTTP_" .. k] = v
end
local len = tonumber(env.CONTENT_LENGTH) or 0
local function recv()
if len > 0 then
local rlen, rbuf = uhttpd.recv(4096)
if rlen >= 0 then
len = len - rlen
return rbuf
end
end
return nil
end
local send = uhttpd.send
local req = luci.http.Request(renv, recv, function(s) io.stderr:write(s) end)
send("Status: 200 OK\\r\\n")
send("Content-Type: text/html\\r\\n\\r\\n")
send("<h1>Headers</h1>\\n")
for k, v in pairs(env.headers) do
send(string.format("<strong>%s</strong>: %s<br>\\n", k, v))
end
send("<h1>Environment</h1>\\n")
for k, v in pairs(env) do
if type(v) == "string" then
send(string.format("<code>%s=%s</code><br>\\n", k, v))
end
end
send("<h1>Parameters</h1>\\n")
for k, v in pairs(req:formvalue()) do -- invoking :formvalue() without name will return a table of all args
send(string.format("<strong>%s</strong>: %s<br>\\n", k, v))
end
end
Register it in uhttpd using
option lua_prefix \'/app\'
option lua_handler \'/root/simple-app.lua\'
When you invoke uhttpd from the outside, e.g. using curl -F foo=bar -F bar=baz -v http://192.168.1.1/app
, you should see a response like this:
* Hostname was NOT found in DNS cache
* Trying 192.168.1.1...
* Connected to 192.168.1.1 (192.168.1.1) port 80 (#0)
> POST /app/ HTTP/1.1
> User-Agent: curl/7.38.0
> Host: 192.168.1.1
> Accept: */*
> Content-Length: 236
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------0773a465fc34530a
>
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Connection: close
< Transfer-Encoding: chunked
< Content-Type: text/html
<
<h1>Headers</h1>
<strong>host</strong>: 192.168.1.1<br>
<strong>expect</strong>: 100-continue<br>
<strong>URL</strong>: /app/<br>
<strong>user-agent</strong>: curl/7.38.0<br>
<strong>content-type</strong>: multipart/form-data; boundary=------------------------0773a465fc34530a<br>
<strong>content-length</strong>: 236<br>
<strong>accept</strong>: */*<br>
<h1>Environment</h1>
<code>SERVER_NAME=192.168.1.1</code><br>
<code>SCRIPT_NAME=/app</code><br>
<code>QUERY_STRING=</code><br>
<code>SERVER_ADDR=192.168.1.1</code><br>
<code>GATEWAY_INTERFACE=CGI/1.1</code><br>
<code>REMOTE_ADDR=192.168.1.7</code><br>
<code>CONTENT_LENGTH=236</code><br>
<code>SERVER_PORT=80</code><br>
<code>SCRIPT_FILENAME=/root/simple-app.lua</code><br>
<code>REQUEST_URI=/app/</code><br>
<code>SERVER_PROTOCOL=HTTP/1.1</code><br>
<code>REMOTE_HOST=192.168.1.7</code><br>
<code>REDIRECT_STATUS=200</code><br>
<code>SERVER_SOFTWARE=uhttpd</code><br>
<code>HTTP_HOST=192.168.1.1</code><br>
<code>REMOTE_PORT=40280</code><br>
<code>HTTP_ACCEPT=*/*</code><br>
<code>PATH_INFO=/</code><br>
<code>HTTP_USER_AGENT=curl/7.38.0</code><br>
<code>CONTENT_TYPE=multipart/form-data; boundary=------------------------0773a465fc34530a</code><br>
<code>REQUEST_METHOD=POST</code><br>
<h1>Parameters</h1>
<strong>bar</strong>: baz<br>
<strong>foo</strong>: bar<br>
* Closing connection 0
jow@jow:~$
Given these basic building blocks you can now start doing RESTy stuff:
env.PATH_INFO
refers to the relative path portion afteroption lua_prefix
(/app
in our example)env.REQUEST_METHOD
contains the HTTP method used for requesting, e.g.GET
,POST
,PUT
etc.- to set the response HTTP status, use the
Status
pseudo header:send("Status: 503 Server Error\\r\\n\\r\\n")
- to set the response content type, send a
Content-Type
header:send("Content-Type: application/json; charset=UTF-8\\r\\n")
- to terminate the header block and start with content, send a sole
\\r\\n
or end the last header with\\r\\n\\r\\n
:send("X-Foobar: 1\\r\\n\\r\\n")
orsend("\\r\\n")
- to quickly serialize JSON, you can use LuCI\'s jsonc library binding 42:
require "luci.jsonc"; send(luci.jsonc.stringify( some = complex = data = "structure", foo = true, bar = 1, 2, 3 ))
Btw, Lua has some interesting string quoting possibilities which help to unclutter your code. You can wrap your strings in [[
and ]]
which will act like double quotes. Using these, your example above would become:
uhttpd.send("Status: 200 OK\\r\\n")
uhttpd.send("Content-Type: text/html\\r\\n\\r\\n")
uhttpd.send([[
<!DOCTYPE html>
<html>
<body>
<form action="/lua/" method="post">
<input type="email" name="email" placeholder="email@example.com" />
<input type="submit" value="submit" />
</form>
</body>
</html>
]])
=========== End
openwrt luci web分析
openwrt luci web分析
来源 https://www.jianshu.com/p/596485f95cf2
www/cbi-bin/luci
#!/usr/bin/lua --cgi的执行命令的路径
require"luci.cacheloader" --导入cacheloader包
require"luci.sgi.cgi" --导入sgi.cgi包 luci.dispatcher.indexcache = "/tmp/luci-indexcache" - -cache缓存路径地址
luci.sgi.cgi.run() --执行run方法,此方法位于*/luci/sgi/cgi.lua中
run方法的主要任务就是在安全的环境中打开开始页面(登录页面),在run中,最主要的功能还是在dispatch.lua中完成。
LUCI的MVC
用户管理:
在luci的官方网站说明了luci是一个MVC架构的框架,这个MVC做的可扩展性很好,可以完全的统一的写自己的html网页,而且他对shell的支持相当的到位,(因为luci是lua写的,lua是C的儿子嘛,与shell是兄弟)。
在登录界面用户名的选择很重要,luci是一个单用户框架,公用的模块放置在/luci/controller/下面,各个用户的模块放置在/luci/controller/下面对应的文件夹里面,比如 admin登录,最终的页面只显示/luci/controller/admin下面的菜单。这样既有效的管理了不同管理员的权限。
controller
controller主要用于控制页面按钮位置,以及调用的功能.首先来编辑这个文件.
$vim ~/temp/addtest/files/usr/lib/lua/luci/controller/addtest.lua
代码如下:
module("luci.controller.addtest",package.seeall)
function index()
entry({"admin","system","addtest"},alias("admin","system","addtest","set"),_("AddTest"),99).index=true
entry({"admin","system","addtest","set"},cbi("addtest"),_("Set"),1)
entry({"admin","system","addtest","info"},call("action_info"),_("Info"),2)
end
function action_info()
if not nixio.fs.access("/tmp/addtest") then
return
end
local info = nixio.fs.readfile("/tmp/addtest")
luci.template.render("addtest_info",{info=info})
end
格式模板:
module("luci.controller.控制器名", package.seeall)
function index()
entry(路径, 调用目标, _("显示名称"), 显示顺序)
end
这个脚本文件可以分为3块:第1行,37行,916行
第1行
说明了模块的名称,本文在controller目录下创建了addtest.lua文件,将模板中的控制器名替换为addtest即可.
第3行
第3~7行定义按钮的位置,调用的功能,显示名称.其中第3行和第7行是固定的模板格式,不需要修改
第4行
entry表示添加新的模块.
第一个参数{"admin","system","addtest"}表示按钮的位置.admin表示我们这个功能只有以管理员身份登录页面才可以看到.system表示一级菜单名,addtest则是一级菜单下的子菜单.
第二个参数alias("admin","system","addtest","set")表示调用的功能.这个按钮没有独立的功能,而是将它关联到它的下一级子菜单set.
第三个参数_("AddTest")表示显示名称,可选.如果页面按钮想做成中文,可以在这里设置.
第四个参数99表示显示顺序的优先级,Luci根据这个值为同一父菜单的所有子菜单排序.
第5行
第一个参数{"admin","system","addtest","set"}表示在addtest下再增加一个子选项set.
第二个参数cbi("addtest")表示调用cbi模块,这里将会调用到/usr/lib/lua/luci/model/cbi/addtest.lua
第6行
第二个参数call("action_info")表示执行指定方法,这里将会调用我们下面写的acttion_info函数.
备注
关于entry第二个参数调用目标.我们还有一个template没有涉及,它表示访问指定页面.比如template(addtest_info)将会直接访问/usr/lib/lua/luci/view/addtest_info.htm.
9~16行
这里使用lua语言调用nixio接口写了一个简单的函数,首先判断文件是否存在,然后读取其中的内容赋值给变量info,最后访问指定页面/usr/lib/lua/luci/view/addtest_info.htm,同时将变量info传递过去.
luci接口手册
nixio接口手册
uci
UCI是openwrt的配置管理机制,它将配置统一放到/etc/config
文件夹下.详细地介绍请参考这里.
下面来编辑这个文件
$vim ~/temp/addtest/files/etc/config/addtest
代码如下:
config arguments
option interval ‘‘
option content ‘‘
Section开始语法: config ‘类型‘ ‘名字‘
参数定义语法: option ‘键‘ ‘值‘
列表定义语法: list ‘集合名字‘ ‘值‘
简单解释下,我们在/etc/config下新建一个名为addtest的配置文件,其中类型为arguments,名字省略.有两个键,一个名为interval用来存时间间隔.一个名为content用来存准备周期性输入的内容.
model
在controller提到cbi会调用到model文件夹中的addtest.lua文件.下面来编辑它
$vim ~/temp/addtest/files/usr/lib/lua/luci/model/cbi/addtest.lua
代码如下:
m=Map("addtest",translate("Luci practice"),translate("fat cheng‘s test"))
s=m:section(TypedSection,"arguments","")
s.addremove=true
s.anonymous=false
s:option(Flag,"enable",translate("Enable"))
s:option(Value,"interval",translate("Interval"))
s:option(Value,"content",translate("Content"))
local apply=luci.http.formvalue("cbi.apply")
if apply then
io.popen("/etc/init.d/addtestd restart")
end
return m
来解释下这个文件
第1行
模板m = Map("配置文件文件名", "配置页面标题", "配置页面说明")
第一个参数:上一步我们新建配置文件/etc/config/addtest.这里就是建立与配置文件的联系.
第二,三两个参数,则是页面的主标题和副标题.还不清楚的话,翻上去看看最终效果图,看看它们在哪里.
第3行
在一个配置文件中可能有很多Section,所以我们需要创建与配置文件中我们想要的Section的联系.
有两种方式可以选择:NamedSection(name,type,title,description)和TypedSection(type,title,description),前者根据配置文件中的Section名,而后者根据配置文件中的Section类型.我们选用了第二种.
第4行
设定不允许增加或删除Section
第5行
设定显示Section的名称,这里建议你可以试试设定为true,看看会发生什么.
7~9行
接着则是建立与Section中的option之间的联系.模板s:option(交互形式,option键值,显示名称).
第一个参数:常见的交互形式有Value(文本框),ListValue(下拉框),Flag(选择框).,不知道为啥我打不开官方文档,这里也可以参考
第二个参数表示在配置文件中的option的键值
第三个参数表示,你希望在页面上呈现的名称.
创建后开发者无需考虑读取以及写入配置文件的问题,系统会自动处理.
11~14行
系统会为我们在页面上自动创建一些按钮Save&Apply,Save,Reset.我们仅仅将配置写入/etc/config下对应的文件是不够的,我们还希望可以根据这个配置进行一些操作.
这部分代码的作用是,当你按下页面的apply按钮后,相当于在串口shell下输入/etc/init.d/addtestd restart
init.d
上面已经可以读写配置了,怎么根据配置来进行操作呢?来编辑~/temp/addtest/files/etc/init.d/addtestd这个文件.
代码如下:
#!/bin/sh /etc/rc.common
START=50
run_addtest()
{
local enable
config_get_bool enable $1 enable
if [ $enable ]; then
local interval
local content
config_get interval $1 interval
config_get content $1 content
addtest $interval $content
fi
}
start()
{
config_load addtest
config_foreach run_addtest arguments
}
stop()
{
result=`pidof addtest`
kill -9 $result
echo "addtest has stoped"
}
第1行
Linux 系统根据 “#!” 及该字串后面的信息确定该文件的类型,表示这个文件需要由/bin/sh和/etc/rc.common来解释执行.
第2行
表示启动的优先级,这里暂时用不到
4~17行
是一个函数,主要作用是读取/etc/config/addtest中的内容,然后根据是否打开开关在第15行将配置传递给可执行文件addtest,由它根据配置执行指定的操作.
读取配置的方法,我强烈推荐你阅读官方文档,精炼而简洁.
获取布尔值类型:config_get_bool 变量名 Section名 Section参数名
获取变量值:config_get 变量名 Section名 Section参数名
19~23行
对应于/etc/init.d/addtestd start.首先使用config_load 配置文件名的方法载入配置文件,然后使用config_foreach 遍历函数名 Section类型的方法,遍历配置文件中的Section.
25~30行
对应于/etc/init.d/addtestd stop.找到addtest这个进程的进程号,然后杀死它
备注
前面提到的/etc/init.d/addtestd restart中的restart命令,在/etc/rc.common进行了定义,简单来讲就是先执行了stop命令,再执行start命令.
最后务必执行$sudo chmod 755 ~/temp/addtest/files/etc/init.d/addtestd.
src
前面提到run_addtest调用可执行文件addtest,现在我们编辑这部分内容
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int index;
for(index=0; index<10; index++)
{
FILE *fp=fopen("/tmp/addtest","at");
system("date >> /tmp/addtest");
fprintf(fp, "%s
", argv[2]);
fclose(fp);
printf("interval=%d
",atoi(argv[1]));
sleep( atoi(argv[1]) );
}
return 0;
}
通过这个可执行文件,我们周期性地将时间戳和内容写入了/tmp/addtest文件.
最后我们写一个简单的Makefile:
$vim $vim ~/temp/addtest/files/src/Makefile
addtest : addtest.o
$(CC) addtest.o -o addtest
addtest.o : addtest.c
$(CC) -c addtest.c
clean :
rm *.o addtest
View
前面已经根据配置将指定的内容周期性地写入了/tmp/addtest.在controller中我们的函数action_info读取了/tmp/addtest中的内容并访问指定页面/usr/lib/lua/luci/view/addtest_info.htm,同时将读取的内容通过变量info传递过去.
下面我们来编辑这个页面,
$vim ~/temp/addtest/files/usr/lib/lua/luci/view/addtest_info.htm
<%+header%>
<h2><a id="content" name="content"><%:Addtest Info%></a></h2>
<div id="content_addtest_info">
<textarea readonly="readonly" wrap="off" rows="<%=info:cmatch("
")+2%>" id="info"><%=info:pcdata()%></textarea>
</div>
<%+footer%>
Makefile
用一个Makefie文件将它们打包生成一个ipk文件.
include $(TOPDIR)/rules.mk
PKG_NAME:=addtest
PKG_VERSION=1.0
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/addtest
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Addtest--print something to /var/addtest
endef
define Package/addtest/description
It‘s a test,print something to /var/addtest cyclicaliy
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Package/addtest/postinst
#!/bin/sh
rm -rf /tmp/luci*
endef
define Build/Configure
endef
define Build/Compile
$(call Build/Compile/Default)
endef
define Package/$(PKG_NAME)/install
$(CP) ./files/* $(1)/
$(INSTALL_DIR) $(1)/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/addtest $(1)/bin
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
================ End
以上是关于uhttp luci simple-app的主要内容,如果未能解决你的问题,请参考以下文章