Mercurial > 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 |
parents | f027b8b1e794 |
children | 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; |