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 after option lua_prefix (/app in our example)
  • env.REQUEST_METHOD contains the HTTP method used for requesting, e.g. GETPOSTPUT 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\\nsend("X-Foobar: 1\\r\\n\\r\\n") or send("\\r\\n")
  • to quickly serialize JSON, you can use LuCI\'s jsonc library binding 42require "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的主要内容,如果未能解决你的问题,请参考以下文章

Luci

openwrt luci web分析

从 luci Web 用户界面 (openWrt Luci) 下载文件

OpenWRT Luci 持久安装

OpenWrt LUCI 仅运行 UI

图像修复基于Lucy_Richardson迭代法图像修复matlab源码