root/luci/trunk/libs/web/luasrc/http.lua @ 6391

Revision 6391, 7.9 KB (checked in by jow, 3 years ago)

libs/web: add luci.http.write_json()

  • Property svn:keywords set to Id
Line 
1--[[
2LuCI - HTTP-Interaction
3
4Description:
5HTTP-Header manipulator and form variable preprocessor
6
7FileId:
8$Id$
9
10License:
11Copyright 2008 Steven Barth <steven@midlink.org>
12
13Licensed under the Apache License, Version 2.0 (the "License");
14you may not use this file except in compliance with the License.
15You may obtain a copy of the License at
16
17    http://www.apache.org/licenses/LICENSE-2.0
18
19Unless required by applicable law or agreed to in writing, software
20distributed under the License is distributed on an "AS IS" BASIS,
21WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22See the License for the specific language governing permissions and
23limitations under the License.
24
25]]--
26
27local ltn12 = require "luci.ltn12"
28local protocol = require "luci.http.protocol"
29local util  = require "luci.util"
30local string = require "string"
31local coroutine = require "coroutine"
32local table = require "table"
33
34local ipairs, pairs, next, type, tostring, error =
35    ipairs, pairs, next, type, tostring, error
36
37--- LuCI Web Framework high-level HTTP functions.
38module "luci.http"
39
40context = util.threadlocal()
41
42Request = util.class()
43function Request.__init__(self, env, sourcein, sinkerr)
44    self.input = sourcein
45    self.error = sinkerr
46
47
48    -- File handler
49    self.filehandler = function() end
50
51    -- HTTP-Message table
52    self.message = {
53        env = env,
54        headers = {},
55        params = protocol.urldecode_params(env.QUERY_STRING or ""),
56    }
57
58    self.parsed_input = false
59end
60
61function Request.formvalue(self, name, noparse)
62    if not noparse and not self.parsed_input then
63        self:_parse_input()
64    end
65
66    if name then
67        return self.message.params[name]
68    else
69        return self.message.params
70    end
71end
72
73function Request.formvaluetable(self, prefix)
74    local vals = {}
75    prefix = prefix and prefix .. "." or "."
76
77    if not self.parsed_input then
78        self:_parse_input()
79    end
80
81    local void = self.message.params[nil]
82    for k, v in pairs(self.message.params) do
83        if k:find(prefix, 1, true) == 1 then
84            vals[k:sub(#prefix + 1)] = tostring(v)
85        end
86    end
87
88    return vals
89end
90
91function Request.content(self)
92    if not self.parsed_input then
93        self:_parse_input()
94    end
95
96    return self.message.content, self.message.content_length
97end
98
99function Request.getcookie(self, name)
100  local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";")
101  local p = ";" .. name .. "=(.-);"
102  local i, j, value = c:find(p)
103  return value and urldecode(value)
104end
105
106function Request.getenv(self, name)
107    if name then
108        return self.message.env[name]
109    else
110        return self.message.env
111    end
112end
113
114function Request.setfilehandler(self, callback)
115    self.filehandler = callback
116end
117
118function Request._parse_input(self)
119    protocol.parse_message_body(
120         self.input,
121         self.message,
122         self.filehandler
123    )
124    self.parsed_input = true
125end
126
127--- Close the HTTP-Connection.
128function close()
129    if not context.eoh then
130        context.eoh = true
131        coroutine.yield(3)
132    end
133
134    if not context.closed then
135        context.closed = true
136        coroutine.yield(5)
137    end
138end
139
140--- Return the request content if the request was of unknown type.
141-- @return  HTTP request body
142-- @return  HTTP request body length
143function content()
144    return context.request:content()
145end
146
147--- Get a certain HTTP input value or a table of all input values.
148-- @param name      Name of the GET or POST variable to fetch
149-- @param noparse   Don't parse POST data before getting the value
150-- @return          HTTP input value or table of all input value
151function formvalue(name, noparse)
152    return context.request:formvalue(name, noparse)
153end
154
155--- Get a table of all HTTP input values with a certain prefix.
156-- @param prefix    Prefix
157-- @return          Table of all HTTP input values with given prefix
158function formvaluetable(prefix)
159    return context.request:formvaluetable(prefix)
160end
161
162--- Get the value of a certain HTTP-Cookie.
163-- @param name      Cookie Name
164-- @return          String containing cookie data
165function getcookie(name)
166    return context.request:getcookie(name)
167end
168
169--- Get the value of a certain HTTP environment variable
170-- or the environment table itself.
171-- @param name      Environment variable
172-- @return          HTTP environment value or environment table
173function getenv(name)
174    return context.request:getenv(name)
175end
176
177--- Set a handler function for incoming user file uploads.
178-- @param callback  Handler function
179function setfilehandler(callback)
180    return context.request:setfilehandler(callback)
181end
182
183--- Send a HTTP-Header.
184-- @param key   Header key
185-- @param value Header value
186function header(key, value)
187    if not context.headers then
188        context.headers = {}
189    end
190    context.headers[key:lower()] = value
191    coroutine.yield(2, key, value)
192end
193
194--- Set the mime type of following content data.
195-- @param mime  Mimetype of following content
196function prepare_content(mime)
197    if not context.headers or not context.headers["content-type"] then
198        if mime == "application/xhtml+xml" then
199            if not getenv("HTTP_ACCEPT") or
200              not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
201                mime = "text/html; charset=UTF-8"
202            end
203            header("Vary", "Accept")
204        end
205        header("Content-Type", mime)
206    end
207end
208
209--- Get the RAW HTTP input source
210-- @return  HTTP LTN12 source
211function source()
212    return context.request.input
213end
214
215--- Set the HTTP status code and status message.
216-- @param code      Status code
217-- @param message   Status message
218function status(code, message)
219    code = code or 200
220    message = message or "OK"
221    context.status = code
222    coroutine.yield(1, code, message)
223end
224
225--- Send a chunk of content data to the client.
226-- This function is as a valid LTN12 sink.
227-- If the content chunk is nil this function will automatically invoke close.
228-- @param content   Content chunk
229-- @param src_err   Error object from source (optional)
230-- @see close
231function write(content, src_err)
232    if not content then
233        if src_err then
234            error(src_err)
235        else
236            close()
237        end
238        return true
239    elseif #content == 0 then
240        return true
241    else
242        if not context.eoh then
243            if not context.status then
244                status()
245            end
246            if not context.headers or not context.headers["content-type"] then
247                header("Content-Type", "text/html; charset=utf-8")
248            end
249            if not context.headers["cache-control"] then
250                header("Cache-Control", "no-cache")
251                header("Expires", "0")
252            end
253
254
255            context.eoh = true
256            coroutine.yield(3)
257        end
258        coroutine.yield(4, content)
259        return true
260    end
261end
262
263--- Splice data from a filedescriptor to the client.
264-- @param fp    File descriptor
265-- @param size  Bytes to splice (optional)
266function splice(fd, size)
267    coroutine.yield(6, fd, size)
268end
269
270--- Redirects the client to a new URL and closes the connection.
271-- @param url   Target URL
272function redirect(url)
273    status(302, "Found")
274    header("Location", url)
275    close()
276end
277
278--- Create a querystring out of a table of key - value pairs.
279-- @param table     Query string source table
280-- @return          Encoded HTTP query string
281function build_querystring(q)
282    local s = { "?" }
283
284    for k, v in pairs(q) do
285        if #s > 1 then s[#s+1] = "&" end
286
287        s[#s+1] = urldecode(k)
288        s[#s+1] = "="
289        s[#s+1] = urldecode(v)
290    end
291
292    return table.concat(s, "")
293end
294
295--- Return the URL-decoded equivalent of a string.
296-- @param str       URL-encoded string
297-- @param no_plus   Don't decode + to " "
298-- @return          URL-decoded string
299-- @see urlencode
300urldecode = protocol.urldecode
301
302--- Return the URL-encoded equivalent of a string.
303-- @param str       Source string
304-- @return          URL-encoded string
305-- @see urldecode
306urlencode = protocol.urlencode
307
308--- Send the given data as JSON encoded string.
309-- @param data      Data to send
310function write_json(x)
311    if x == nil then
312        write("null")
313    elseif type(x) == "table" then
314        local k, v
315        if type(next(x)) == "number" then
316            write("[ ")
317            for k, v in ipairs(x) do
318                write_json(v)
319                if next(x, k) then
320                    write(", ")
321                end
322            end
323            write(" ]")
324        else
325            write("{ ")
326            for k, v in pairs(x) do
327            write("%q: " % k)
328                write_json(v)
329                if next(x, k) then
330                    write(", ")
331                end
332            end
333            write(" }")
334        end
335    elseif type(x) == "number" or type(x) == "boolean" then
336        write(tostring(x))
337    elseif type(x) == "string" then
338        write("%q" % tostring(x))
339    end
340end
Note: See TracBrowser for help on using the browser.