Software / code / prosody-modules
Comparison
mod_rest/mod_rest.lua @ 3813:aa1ad69c7c10
mod_rest: Add JSON support
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Wed, 01 Jan 2020 16:21:28 +0100 |
| parent | 3812:f027b8b1e794 |
| child | 3814:0dede5b0ab27 |
comparison
equal
deleted
inserted
replaced
| 3812:f027b8b1e794 | 3813:aa1ad69c7c10 |
|---|---|
| 6 | 6 |
| 7 local errors = require "util.error"; | 7 local errors = require "util.error"; |
| 8 local http = require "net.http"; | 8 local http = require "net.http"; |
| 9 local id = require "util.id"; | 9 local id = require "util.id"; |
| 10 local jid = require "util.jid"; | 10 local jid = require "util.jid"; |
| 11 local json = require "util.json"; | |
| 11 local st = require "util.stanza"; | 12 local st = require "util.stanza"; |
| 12 local xml = require "util.xml"; | 13 local xml = require "util.xml"; |
| 13 | 14 |
| 14 local allow_any_source = module:get_host_type() == "component"; | 15 local allow_any_source = module:get_host_type() == "component"; |
| 15 local validate_from_addresses = module:get_option_boolean("validate_from_addresses", true); | 16 local validate_from_addresses = module:get_option_boolean("validate_from_addresses", true); |
| 16 local secret = assert(module:get_option_string("rest_credentials"), "rest_credentials is a required setting"); | 17 local secret = assert(module:get_option_string("rest_credentials"), "rest_credentials is a required setting"); |
| 17 local auth_type = assert(secret:match("^%S+"), "Format of rest_credentials MUST be like 'Bearer secret'"); | 18 local auth_type = assert(secret:match("^%S+"), "Format of rest_credentials MUST be like 'Bearer secret'"); |
| 18 assert(auth_type == "Bearer", "Only 'Bearer' is supported in rest_credentials"); | 19 assert(auth_type == "Bearer", "Only 'Bearer' is supported in rest_credentials"); |
| 19 | 20 |
| 21 local jsonmap = module:require"jsonmap"; | |
| 20 -- Bearer token | 22 -- Bearer token |
| 21 local function check_credentials(request) | 23 local function check_credentials(request) |
| 22 return request.headers.authorization == secret; | 24 return request.headers.authorization == secret; |
| 23 end | 25 end |
| 24 | 26 |
| 25 local function parse(mimetype, data) | 27 local function parse(mimetype, data) |
| 26 mimetype = mimetype:match("^[^; ]*"); | 28 mimetype = mimetype:match("^[^; ]*"); |
| 27 if mimetype == "application/xmpp+xml" then | 29 if mimetype == "application/xmpp+xml" then |
| 28 return xml.parse(data); | 30 return xml.parse(data); |
| 31 elseif mimetype == "application/json" then | |
| 32 local parsed, err = json.decode(data); | |
| 33 if not parsed then | |
| 34 return parsed, err; | |
| 35 end | |
| 36 return jsonmap.json2st(parsed); | |
| 29 elseif mimetype == "text/plain" then | 37 elseif mimetype == "text/plain" then |
| 30 return st.message({ type = "chat" }, data); | 38 return st.message({ type = "chat" }, data); |
| 31 end | 39 end |
| 32 return nil, "unknown-payload-type"; | 40 return nil, "unknown-payload-type"; |
| 33 end | 41 end |
| 34 | 42 |
| 35 local function decide_type() | 43 local supported_types = { "application/xmpp+xml", "application/json" }; |
| 36 return "application/xmpp+xml"; | 44 |
| 45 local function decide_type(accept) | |
| 46 -- assumes the accept header is sorted | |
| 47 local ret = supported_types[1]; | |
| 48 if not accept then | |
| 49 return ret; | |
| 50 end | |
| 51 for i = 2, #supported_types do | |
| 52 if (accept:find(supported_types[i], 1, true) or 1000) < (accept:find(ret, 1, true) or 1000) then | |
| 53 ret = supported_types[i]; | |
| 54 end | |
| 55 end | |
| 56 return ret; | |
| 37 end | 57 end |
| 38 | 58 |
| 39 local function encode(type, s) | 59 local function encode(type, s) |
| 60 if type == "application/json" then | |
| 61 return json.encode(jsonmap.st2json(s)); | |
| 62 elseif type == "text/plain" then | |
| 63 return s:get_child_text("body") or ""; | |
| 64 end | |
| 40 return tostring(s); | 65 return tostring(s); |
| 41 end | 66 end |
| 42 | 67 |
| 43 local function handle_post(event) | 68 local function handle_post(event) |
| 44 local request, response = event.request, event.response; | 69 local request, response = event.request, event.response; |
| 126 | 151 |
| 127 -- Forward stanzas from XMPP to HTTP and return any reply | 152 -- Forward stanzas from XMPP to HTTP and return any reply |
| 128 local rest_url = module:get_option_string("rest_callback_url", nil); | 153 local rest_url = module:get_option_string("rest_callback_url", nil); |
| 129 if rest_url then | 154 if rest_url then |
| 130 local send_type = module:get_option_string("rest_callback_content_type", "application/xmpp+xml"); | 155 local send_type = module:get_option_string("rest_callback_content_type", "application/xmpp+xml"); |
| 156 if send_type == "json" then | |
| 157 send_type = "application/json"; | |
| 158 end | |
| 131 | 159 |
| 132 local code2err = { | 160 local code2err = { |
| 133 [400] = { condition = "bad-request"; type = "modify" }; | 161 [400] = { condition = "bad-request"; type = "modify" }; |
| 134 [401] = { condition = "not-authorized"; type = "auth" }; | 162 [401] = { condition = "not-authorized"; type = "auth" }; |
| 135 [402] = { condition = "not-authorized"; type = "auth" }; | 163 [402] = { condition = "not-authorized"; type = "auth" }; |
| 174 http.request(rest_url, { | 202 http.request(rest_url, { |
| 175 body = request_body, | 203 body = request_body, |
| 176 headers = { | 204 headers = { |
| 177 ["Content-Type"] = send_type, | 205 ["Content-Type"] = send_type, |
| 178 ["Content-Language"] = stanza.attr["xml:lang"], | 206 ["Content-Language"] = stanza.attr["xml:lang"], |
| 179 Accept = "application/xmpp+xml, text/plain", | 207 Accept = table.concat(supported_types, ", "); |
| 180 }, | 208 }, |
| 181 }, function (body, code, response) | 209 }, function (body, code, response) |
| 182 if (code == 202 or code == 204) and not reply_needed then | 210 if (code == 202 or code == 204) and not reply_needed then |
| 183 -- Delivered, no reply | 211 -- Delivered, no reply |
| 184 return; | 212 return; |