libs/web: add luci.http.write_json()
[oweals/luci.git] / libs / web / luasrc / http.lua
index 5b922d99438b14863b87b8b8901c8f6fc7e97cbe..85854054840f5078ed71199f21e930e94f5be893 100644 (file)
@@ -12,9 +12,9 @@ Copyright 2008 Steven Barth <steven@midlink.org>
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
-You may obtain a copy of the License at 
+You may obtain a copy of the License at
 
 
-       http://www.apache.org/licenses/LICENSE-2.0 
+       http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
@@ -24,15 +24,22 @@ limitations under the License.
 
 ]]--
 
 
 ]]--
 
+local ltn12 = require "luci.ltn12"
+local protocol = require "luci.http.protocol"
+local util  = require "luci.util"
+local string = require "string"
+local coroutine = require "coroutine"
+local table = require "table"
+
+local ipairs, pairs, next, type, tostring, error =
+       ipairs, pairs, next, type, tostring, error
+
 --- LuCI Web Framework high-level HTTP functions.
 --- LuCI Web Framework high-level HTTP functions.
-module("luci.http", package.seeall)
-local ltn12 = require("luci.ltn12")
-require("luci.http.protocol")
-require("luci.util")
+module "luci.http"
 
 
-context = luci.util.threadlocal()
+context = util.threadlocal()
 
 
-Request = luci.util.class()
+Request = util.class()
 function Request.__init__(self, env, sourcein, sinkerr)
        self.input = sourcein
        self.error = sinkerr
 function Request.__init__(self, env, sourcein, sinkerr)
        self.input = sourcein
        self.error = sinkerr
@@ -40,14 +47,14 @@ function Request.__init__(self, env, sourcein, sinkerr)
 
        -- File handler
        self.filehandler = function() end
 
        -- File handler
        self.filehandler = function() end
-       
+
        -- HTTP-Message table
        self.message = {
                env = env,
                headers = {},
        -- HTTP-Message table
        self.message = {
                env = env,
                headers = {},
-               params = luci.http.protocol.urldecode_params(env.QUERY_STRING or ""),
+               params = protocol.urldecode_params(env.QUERY_STRING or ""),
        }
        }
-       
+
        self.parsed_input = false
 end
 
        self.parsed_input = false
 end
 
@@ -55,7 +62,7 @@ function Request.formvalue(self, name, noparse)
        if not noparse and not self.parsed_input then
                self:_parse_input()
        end
        if not noparse and not self.parsed_input then
                self:_parse_input()
        end
-       
+
        if name then
                return self.message.params[name]
        else
        if name then
                return self.message.params[name]
        else
@@ -66,21 +73,29 @@ end
 function Request.formvaluetable(self, prefix)
        local vals = {}
        prefix = prefix and prefix .. "." or "."
 function Request.formvaluetable(self, prefix)
        local vals = {}
        prefix = prefix and prefix .. "." or "."
-       
+
        if not self.parsed_input then
                self:_parse_input()
        end
        if not self.parsed_input then
                self:_parse_input()
        end
-       
+
        local void = self.message.params[nil]
        for k, v in pairs(self.message.params) do
                if k:find(prefix, 1, true) == 1 then
                        vals[k:sub(#prefix + 1)] = tostring(v)
                end
        end
        local void = self.message.params[nil]
        for k, v in pairs(self.message.params) do
                if k:find(prefix, 1, true) == 1 then
                        vals[k:sub(#prefix + 1)] = tostring(v)
                end
        end
-       
+
        return vals
 end
 
        return vals
 end
 
+function Request.content(self)
+       if not self.parsed_input then
+               self:_parse_input()
+       end
+
+       return self.message.content, self.message.content_length
+end
+
 function Request.getcookie(self, name)
   local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";")
   local p = ";" .. name .. "=(.-);"
 function Request.getcookie(self, name)
   local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";")
   local p = ";" .. name .. "=(.-);"
@@ -101,7 +116,7 @@ function Request.setfilehandler(self, callback)
 end
 
 function Request._parse_input(self)
 end
 
 function Request._parse_input(self)
-       luci.http.protocol.parse_message_body(
+       protocol.parse_message_body(
                 self.input,
                 self.message,
                 self.filehandler
                 self.input,
                 self.message,
                 self.filehandler
@@ -115,13 +130,20 @@ function close()
                context.eoh = true
                coroutine.yield(3)
        end
                context.eoh = true
                coroutine.yield(3)
        end
-       
+
        if not context.closed then
                context.closed = true
                coroutine.yield(5)
        end
 end
 
        if not context.closed then
                context.closed = true
                coroutine.yield(5)
        end
 end
 
+--- Return the request content if the request was of unknown type.
+-- @return     HTTP request body
+-- @return     HTTP request body length
+function content()
+       return context.request:content()
+end
+
 --- Get a certain HTTP input value or a table of all input values.
 -- @param name         Name of the GET or POST variable to fetch
 -- @param noparse      Don't parse POST data before getting the value
 --- Get a certain HTTP input value or a table of all input values.
 -- @param name         Name of the GET or POST variable to fetch
 -- @param noparse      Don't parse POST data before getting the value
@@ -144,7 +166,7 @@ function getcookie(name)
        return context.request:getcookie(name)
 end
 
        return context.request:getcookie(name)
 end
 
---- Get the value of a certain HTTP environment variable 
+--- Get the value of a certain HTTP environment variable
 -- or the environment table itself.
 -- @param name         Environment variable
 -- @return                     HTTP environment value or environment table
 -- or the environment table itself.
 -- @param name         Environment variable
 -- @return                     HTTP environment value or environment table
@@ -172,14 +194,22 @@ end
 --- Set the mime type of following content data.
 -- @param mime Mimetype of following content
 function prepare_content(mime)
 --- Set the mime type of following content data.
 -- @param mime Mimetype of following content
 function prepare_content(mime)
-       if mime == "application/xhtml+xml" then
-               if not getenv("HTTP_ACCEPT") or
-                 not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
-                       mime = "text/html; charset=UTF-8"
+       if not context.headers or not context.headers["content-type"] then
+               if mime == "application/xhtml+xml" then
+                       if not getenv("HTTP_ACCEPT") or
+                         not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
+                               mime = "text/html; charset=UTF-8"
+                       end
+                       header("Vary", "Accept")
                end
                end
-               header("Vary", "Accept")
+               header("Content-Type", mime)
        end
        end
-       header("Content-Type", mime)
+end
+
+--- Get the RAW HTTP input source
+-- @return     HTTP LTN12 source
+function source()
+       return context.request.input
 end
 
 --- Set the HTTP status code and status message.
 end
 
 --- Set the HTTP status code and status message.
@@ -220,8 +250,8 @@ function write(content, src_err)
                                header("Cache-Control", "no-cache")
                                header("Expires", "0")
                        end
                                header("Cache-Control", "no-cache")
                                header("Expires", "0")
                        end
-                       
-                       
+
+
                        context.eoh = true
                        coroutine.yield(3)
                end
                        context.eoh = true
                        coroutine.yield(3)
                end
@@ -230,6 +260,13 @@ function write(content, src_err)
        end
 end
 
        end
 end
 
+--- Splice data from a filedescriptor to the client.
+-- @param fp   File descriptor
+-- @param size Bytes to splice (optional)
+function splice(fd, size)
+       coroutine.yield(6, fd, size)
+end
+
 --- Redirects the client to a new URL and closes the connection.
 -- @param url  Target URL
 function redirect(url)
 --- Redirects the client to a new URL and closes the connection.
 -- @param url  Target URL
 function redirect(url)
@@ -241,14 +278,18 @@ end
 --- Create a querystring out of a table of key - value pairs.
 -- @param table                Query string source table
 -- @return                     Encoded HTTP query string
 --- Create a querystring out of a table of key - value pairs.
 -- @param table                Query string source table
 -- @return                     Encoded HTTP query string
-function build_querystring(table)
-       local s="?"
-       
-       for k, v in pairs(table) do
-               s = s .. urlencode(k) .. "=" .. urlencode(v) .. "&"
+function build_querystring(q)
+       local s = { "?" }
+
+       for k, v in pairs(q) do
+               if #s > 1 then s[#s+1] = "&" end
+
+               s[#s+1] = urldecode(k)
+               s[#s+1] = "="
+               s[#s+1] = urldecode(v)
        end
        end
-       
-       return s
+
+       return table.concat(s, "")
 end
 
 --- Return the URL-decoded equivalent of a string.
 end
 
 --- Return the URL-decoded equivalent of a string.
@@ -256,10 +297,44 @@ end
 -- @param no_plus      Don't decode + to " "
 -- @return                     URL-decoded string
 -- @see urlencode
 -- @param no_plus      Don't decode + to " "
 -- @return                     URL-decoded string
 -- @see urlencode
-urldecode = luci.http.protocol.urldecode
+urldecode = protocol.urldecode
 
 --- Return the URL-encoded equivalent of a string.
 -- @param str          Source string
 -- @return                     URL-encoded string
 -- @see urldecode
 
 --- Return the URL-encoded equivalent of a string.
 -- @param str          Source string
 -- @return                     URL-encoded string
 -- @see urldecode
-urlencode = luci.http.protocol.urlencode
+urlencode = protocol.urlencode
+
+--- Send the given data as JSON encoded string.
+-- @param data         Data to send
+function write_json(x)
+       if x == nil then
+               write("null")
+       elseif type(x) == "table" then
+               local k, v
+               if type(next(x)) == "number" then
+                       write("[ ")
+                       for k, v in ipairs(x) do
+                               write_json(v)
+                               if next(x, k) then
+                                       write(", ")
+                               end
+                       end
+                       write(" ]")
+               else
+                       write("{ ")
+                       for k, v in pairs(x) do
+                       write("%q: " % k)
+                               write_json(v)
+                               if next(x, k) then
+                                       write(", ")
+                               end
+                       end
+                       write(" }")
+               end
+       elseif type(x) == "number" or type(x) == "boolean" then
+               write(tostring(x))
+       elseif type(x) == "string" then
+               write("%q" % tostring(x))
+       end
+end