Software /
code /
prosody-modules
Comparison
mod_rest/README.md @ 6211:750d64c47ec6 draft default tip
Merge
author | Trần H. Trung <xmpp:trần.h.trung@trung.fun> |
---|---|
date | Tue, 18 Mar 2025 00:31:36 +0700 |
parent | 6003:fe081789f7b5 |
comparison
equal
deleted
inserted
replaced
6210:24316a399978 | 6211:750d64c47ec6 |
---|---|
1 --- | |
2 labels: | |
3 - 'Stage-Alpha' | |
4 summary: RESTful XMPP API | |
5 rockspec: | |
6 build: | |
7 modules: | |
8 mod_rest.jsonmap: jsonmap.lib.lua | |
9 copy_directories: | |
10 - example | |
11 - res | |
12 --- | |
13 | |
14 # Introduction | |
15 | |
16 This is yet another RESTful API for sending and receiving stanzas via | |
17 Prosody. It can be used to build bots and components implemented as HTTP | |
18 services. It is the spiritual successor to [mod_post_msg] and absorbs | |
19 use cases from [mod_http_rest] and [mod_component_http] and other such | |
20 modules floating around the Internet. | |
21 | |
22 # Usage | |
23 | |
24 You make a choice: install via VirtualHosts or as a Component. User authentication can | |
25 be used when installed via VirtualHost, and OAuth2 can be used for either. | |
26 | |
27 ## On VirtualHosts | |
28 | |
29 This enables rest on the VirtualHost domain, enabling user authentication to secure | |
30 the endpoint. Make sure that the modules_enabled section is immediately below the | |
31 VirtualHost entry so that it's not under any Component sections. EG: | |
32 | |
33 ```lua | |
34 VirtualHost "chat.example.com" | |
35 modules_enabled = {"rest"} | |
36 ``` | |
37 | |
38 ## As a Component | |
39 | |
40 If you install this as a component, you won't be able to use user authentication above, | |
41 and must use OAuth2 authentication outlined below. | |
42 | |
43 ``` {.lua} | |
44 Component "chat.example.com" "rest" | |
45 component_secret = "dmVyeSBzZWNyZXQgdG9rZW4K" | |
46 modules_enabled = {"http_oauth2"} | |
47 ``` | |
48 | |
49 ## User authentication | |
50 | |
51 To enable user authentication, edit the "admins = { }" section in prosody.cfg.lua, EG: | |
52 | |
53 ```lua | |
54 admins = { "admin@chat.example.com" } | |
55 ``` | |
56 | |
57 To set up the admin user account: | |
58 | |
59 ```lua | |
60 prosodyctl adduser admin@chat.example.com | |
61 ``` | |
62 | |
63 and lastly, drop the "@host" from the username in your http queries, EG: | |
64 | |
65 ```lua | |
66 curl \ | |
67 https://chat.example.com:5281/rest/version/chat.example.com \ | |
68 -k \ | |
69 --user admin \ | |
70 -H 'Accept: application/json' | |
71 ``` | |
72 | |
73 ## OAuth2 | |
74 | |
75 [mod_http_oauth2] can be used to grant bearer tokens which are accepted | |
76 by mod_rest. Tokens can be passed to `curl` like `--oauth2-bearer | |
77 dmVyeSBzZWNyZXQgdG9rZW4K` instead of using `--user`. | |
78 | |
79 ## Sending stanzas | |
80 | |
81 The API endpoint becomes available at the path `/rest`, so the full URL | |
82 will be something like `https://conference.chat.example.com:5281/rest`. | |
83 | |
84 To try it, simply `curl` an XML stanza payload: | |
85 | |
86 ``` {.sh} | |
87 curl https://prosody.example:5281/rest \ | |
88 --user username \ | |
89 -H 'Content-Type: application/xmpp+xml' \ | |
90 --data-binary '<message type="chat" to="user@example.org"> | |
91 <body>Hello!</body> | |
92 </message>' | |
93 ``` | |
94 | |
95 or a JSON payload: | |
96 | |
97 ``` {.sh} | |
98 curl https://prosody.example:5281/rest \ | |
99 --user username \ | |
100 -H 'Content-Type: application/json' \ | |
101 --data-binary '{ | |
102 "body" : "Hello!", | |
103 "kind" : "message", | |
104 "to" : "user@example.org", | |
105 "type" : "chat" | |
106 }' | |
107 ``` | |
108 | |
109 The `Content-Type` header is important! | |
110 | |
111 ### Parameters in path | |
112 | |
113 New alternative format with the parameters `kind`, `type`, and `to` | |
114 embedded in the path: | |
115 | |
116 ``` | |
117 curl https://prosody.example:5281/rest/message/chat/john@example.com \ | |
118 --user username \ | |
119 -H 'Content-Type: text/plain' \ | |
120 --data-binary 'Hello John!' | |
121 ``` | |
122 | |
123 ### Replies | |
124 | |
125 A POST containing an `<iq>` stanza automatically wait for the reply, | |
126 long-polling style. | |
127 | |
128 ``` {.sh} | |
129 curl https://prosody.example:5281/rest \ | |
130 --user username \ | |
131 -H 'Content-Type: application/xmpp+xml' \ | |
132 --data-binary '<iq type="get" to="example.net"> | |
133 <ping xmlns="urn:xmpp:ping"/> | |
134 </iq>' | |
135 ``` | |
136 | |
137 Replies to other kinds of stanzas that are generated by the same Prosody | |
138 instance *MAY* be returned in the HTTP response. Replies from other | |
139 entities (connected clients or remote servers) will not be returned, but | |
140 can be forwarded via the callback API described in the next section. | |
141 | |
142 ### Simple info queries | |
143 | |
144 A subset of IQ stanzas can be sent as simple GET requests | |
145 | |
146 ``` | |
147 curl https://prosody.example:5281/rest/version/example.com \ | |
148 --user username \ | |
149 -H 'Accept: application/json' | |
150 ``` | |
151 | |
152 The supported queries are | |
153 | |
154 - `archive` | |
155 - `disco` | |
156 - `extdisco` | |
157 - `items` | |
158 - `lastactivity` | |
159 - `oob` | |
160 - `payload` | |
161 - `ping` | |
162 - `stats` | |
163 - `version` | |
164 | |
165 ## Receiving stanzas | |
166 | |
167 TL;DR: Set this webhook callback URL, get XML `POST`-ed there. | |
168 | |
169 ``` {.lua} | |
170 Component "rest.example.net" "rest" | |
171 rest_callback_url = "http://my-api.example:9999/stanzas" | |
172 ``` | |
173 | |
174 The callback URL supports a few variables from the stanza being sent, | |
175 namely `{kind}` (e.g. message, presence, iq or meta) and ones | |
176 corresponding to stanza attributes: `{type}`, `{to}` and `{from}`. | |
177 | |
178 The preferred format can be indicated via the Accept header in response | |
179 to an OPTIONS probe that mod_rest does on startup, or by configuring: | |
180 | |
181 ``` {.lua} | |
182 rest_callback_content_type = "application/json" | |
183 ``` | |
184 | |
185 Example callback looks like: | |
186 | |
187 ``` {.xml} | |
188 POST /stanzas HTTP/1.1 | |
189 Content-Type: application/xmpp+xml | |
190 Content-Length: 102 | |
191 | |
192 <message to="bot@rest.example.net" from="user@example.com" type="chat"> | |
193 <body>Hello</body> | |
194 </message> | |
195 ``` | |
196 | |
197 or as JSON: | |
198 | |
199 ``` {.json} | |
200 POST /stanzas HTTP/1.1 | |
201 Content-Type: application/json | |
202 Content-Length: 133 | |
203 | |
204 { | |
205 "body" : "Hello", | |
206 "from" : "user@example.com", | |
207 "kind" : "message", | |
208 "to" : "bot@rest.example.net", | |
209 "type" : "chat" | |
210 } | |
211 ``` | |
212 | |
213 ### Which stanzas | |
214 | |
215 The set of stanzas routed to the callback is determined by these two | |
216 settings: | |
217 | |
218 `rest_callback_stanzas` | |
219 : The stanza kinds to handle, defaults to `{ "message", "presence", "iq" }` | |
220 | |
221 `rest_callback_events` | |
222 : For the selected stanza kinds, which events to handle. When loaded | |
223 on a Component, this defaults to `{ "bare", "full", "host" }`, while on | |
224 a VirtualHost the default is `{ "host" }`. | |
225 | |
226 Events correspond to which form of address was used in the `to` | |
227 attribute of the stanza. | |
228 | |
229 bare | |
230 : `localpart@hostpart` | |
231 | |
232 full | |
233 : `localpart@hostpart/resourcepart` | |
234 | |
235 host | |
236 : `hostpart` | |
237 | |
238 The following example would handle only stanzas like `<message | |
239 to="anything@hello.example"/>` | |
240 | |
241 ```lua | |
242 Component "hello.example" "rest" | |
243 rest_callback_url = "http://hello.internal.example:9003/api" | |
244 rest_callback_stanzas = { "message" } | |
245 rest_callback_events = { "bare" } | |
246 ``` | |
247 | |
248 ### Replying | |
249 | |
250 To accept the stanza without returning a reply, respond with HTTP status | |
251 code `202` or `204`. | |
252 | |
253 HTTP status codes in the `4xx` and `5xx` range are mapped to an | |
254 appropriate stanza error. | |
255 | |
256 For full control over the response, set the `Content-Type` header to | |
257 `application/xmpp+xml` and return an XMPP stanza as an XML snippet. | |
258 | |
259 ``` {.xml} | |
260 HTTP/1.1 200 Ok | |
261 Content-Type: application/xmpp+xml | |
262 | |
263 <message type="chat"> | |
264 <body>Yes, this is bot</body> | |
265 </message> | |
266 ``` | |
267 | |
268 ## Payload format | |
269 | |
270 ### JSON | |
271 | |
272 ``` {.json} | |
273 { | |
274 "body" : "Hello!", | |
275 "kind" : "message", | |
276 "type" : "chat" | |
277 } | |
278 ``` | |
279 | |
280 Further JSON object keys as follows: | |
281 | |
282 #### Messages | |
283 | |
284 `kind` | |
285 : `"message"` | |
286 | |
287 `type` | |
288 : Commonly `"chat"` for 1-to-1 messages and `"groupchat"` for group | |
289 chat messages. Others include `"normal"`, `"headline"` and | |
290 `"error"`. | |
291 | |
292 `body` | |
293 : Human-readable message text. | |
294 | |
295 `subject` | |
296 : Message subject or MUC topic. | |
297 | |
298 `html` | |
299 : HTML. | |
300 | |
301 `oob_url` | |
302 : URL of an out-of-band resource, often used for images. | |
303 | |
304 #### Presence | |
305 | |
306 `kind` | |
307 : `"presence"` | |
308 | |
309 `type` | |
310 : Empty for online or `"unavailable"` for offline. | |
311 | |
312 `show` | |
313 : [Online | |
314 status](https://xmpp.org/rfcs/rfc6121.html#presence-syntax-children-show), | |
315 `away`, `dnd` etc. | |
316 | |
317 `status` | |
318 : Human-readable status message. | |
319 | |
320 #### Info-Queries | |
321 | |
322 Only one type of payload can be included in an `iq`. | |
323 | |
324 `kind` | |
325 : `"iq"` | |
326 | |
327 `type` | |
328 : `"get"` or `"set"` for queries, `"response"` or `"error"` for | |
329 replies. | |
330 | |
331 `ping` | |
332 : Send a ping. Get a pong. Maybe. | |
333 | |
334 `disco` | |
335 : Retrieve service discovery information about an entity. | |
336 | |
337 `items` | |
338 : Discover list of items (other services, groupchats etc). | |
339 | |
340 ### XML | |
341 | |
342 ``` {.xml} | |
343 <message type="" id="" to="" from="" xml:lang=""> | |
344 ... | |
345 </message> | |
346 ``` | |
347 | |
348 An XML declaration (`<?xml?>`) **MUST NOT** be included. | |
349 | |
350 The payload MUST contain one (1) `message`, `presence` or `iq` stanza. | |
351 | |
352 The stanzas MUST NOT have an `xmlns` attribute, and the default/empty | |
353 namespace is treated as `jabber:client`. | |
354 | |
355 # Examples | |
356 | |
357 ## Python / Flask | |
358 | |
359 Simple echo bot that responds to messages as XML: | |
360 | |
361 ``` {.python} | |
362 from flask import Flask, Response, request | |
363 import xml.etree.ElementTree as ET | |
364 | |
365 app = Flask("echobot") | |
366 | |
367 | |
368 @app.before_request | |
369 def parse(): | |
370 request.stanza = ET.fromstring(request.data) | |
371 | |
372 | |
373 @app.route("/", methods=["POST"]) | |
374 def hello(): | |
375 if request.stanza.tag == "message": | |
376 return Response( | |
377 "<message><body>Yes this is bot</body></message>", | |
378 content_type="application/xmpp+xml", | |
379 ) | |
380 | |
381 return Response(status=501) | |
382 | |
383 | |
384 if __name__ == "__main__": | |
385 app.run() | |
386 ``` | |
387 | |
388 And a JSON variant: | |
389 | |
390 ``` {.python} | |
391 from flask import Flask, Response, request, jsonify | |
392 | |
393 app = Flask("echobot") | |
394 | |
395 | |
396 @app.route("/", methods=["POST"]) | |
397 def hello(): | |
398 print(request.data) | |
399 if request.is_json: | |
400 data = request.get_json() | |
401 if data["kind"] == "message": | |
402 return jsonify({"body": "hello"}) | |
403 | |
404 return Response(status=501) | |
405 | |
406 | |
407 if __name__ == "__main__": | |
408 app.run() | |
409 ``` | |
410 | |
411 Remember to set `rest_callback_content_type = "application/json"` for | |
412 this to work. | |
413 | |
414 # JSON mapping | |
415 | |
416 This section describes the JSON mapping. It can't represent any possible | |
417 stanza, for full flexibility use the XML mode. | |
418 | |
419 ## Stanza basics | |
420 | |
421 `kind` | |
422 : String representing the kind of stanza, one of `"message"`, | |
423 `"presence"` or `"iq"`. | |
424 | |
425 `type` | |
426 : String with the type of stanza, appropriate values vary depending on | |
427 `kind`, see [RFC 6121]. E.g.`"chat"` for *message* stanzas etc. | |
428 | |
429 `to` | |
430 : String containing the XMPP Address of the destination / recipient of | |
431 the stanza. | |
432 | |
433 `from` | |
434 : String containing the XMPP Address of the sender the stanza. | |
435 | |
436 `id` | |
437 : String with a reasonably unique identifier for the stanza. | |
438 | |
439 ## Basic Payloads | |
440 | |
441 ### Messages | |
442 | |
443 `body` | |
444 : String, human readable text message. | |
445 | |
446 `subject` | |
447 : String, human readable summary equivalent to an email subject or the | |
448 chat room topic in a `type:groupchat` message. | |
449 | |
450 ### Presence | |
451 | |
452 `show` | |
453 : String representing availability, e.g. `"away"`, `"dnd"`. No value | |
454 means a normal online status. See [RFC 6121] for the full list. | |
455 | |
456 `status` | |
457 : String with a human readable text message describing availability. | |
458 | |
459 ## More payloads | |
460 | |
461 ### Messages | |
462 | |
463 `state` | |
464 : String with current chat state, e.g. `"active"` (default) and | |
465 `"composing"` (typing). | |
466 | |
467 `html` | |
468 : String with HTML allowing rich formatting. **MUST** be contained in a | |
469 `<body>` element. | |
470 | |
471 `oob_url` | |
472 : String with an URL of an external resource. | |
473 | |
474 ### Presence | |
475 | |
476 `muc` | |
477 : Object with [MUC][XEP-0045] related properties. | |
478 | |
479 ### IQ | |
480 | |
481 `ping` | |
482 : Boolean, a simple ping query. "Pongs" have only basic fields | |
483 presents. | |
484 | |
485 `version` | |
486 : Map with `name`, `version` fields, and optionally an `os` field, to | |
487 describe the software. | |
488 | |
489 #### Service Discovery | |
490 | |
491 `disco` | |
492 | |
493 : Boolean `true` in a `kind:iq` `type:get` for a service discovery | |
494 query. | |
495 | |
496 Responses have a map containing an array of available features in | |
497 the `features` key and an array of "identities" in the `identities` | |
498 key. Each identity has a `category` and `type` field as well as an | |
499 optional `name` field. See [XEP-0030] for further details. | |
500 | |
501 `items` | |
502 : Boolean `true` in a `kind:iq` `type:get` for a service discovery | |
503 items list query. The response contain an array of items like | |
504 `{"jid":"xmpp.address.here","name":"Description of item"}`. | |
505 | |
506 `extensions` | |
507 : Map of extended feature discovery (see [XEP-0128]) data with | |
508 `FORM_DATA` fields as the keys pointing at maps with the rest of the | |
509 data. | |
510 | |
511 #### Ad-Hoc Commands | |
512 | |
513 Used to execute arbitrary commands on supporting entities. | |
514 | |
515 `command` | |
516 | |
517 : String representing the command `node` or Map with the following | |
518 possible fields: | |
519 | |
520 `node` | |
521 : Required string with node from disco\#items query for the | |
522 command to execute. | |
523 | |
524 `action` | |
525 : Optional enum string defaulting to `"execute"`. Multi-step | |
526 commands may involve `"next"`, `"prev"`, `"complete"` or | |
527 `"cancel"`. | |
528 | |
529 `actions` | |
530 : Set (map of strings to `true`) with available actions to proceed | |
531 with in multi-step commands. | |
532 | |
533 `status` | |
534 : String describing the status of the command, normally | |
535 `"executing"`. | |
536 | |
537 `sessionid` | |
538 : Random session ID issued by the responder to identify the | |
539 session in multi-step commands. | |
540 | |
541 `note` | |
542 : Map with `"type"` and `"text"` fields that carry simple result | |
543 information. | |
544 | |
545 `form` | |
546 : Data form with description of expected input and data types in | |
547 the next step of multi-step commands. **TODO** document format. | |
548 | |
549 `data` | |
550 : Map with only the data for result dataforms. Fields may be | |
551 strings or arrays of strings. | |
552 | |
553 ##### Example | |
554 | |
555 Discovering commands: | |
556 | |
557 ``` {.json} | |
558 { | |
559 "items" : { | |
560 "node" : "http://jabber.org/protocol/commands" | |
561 }, | |
562 "id" : "8iN9hwdAAcfTBchm", | |
563 "kind" : "iq", | |
564 "to" : "example.com", | |
565 "type" : "get" | |
566 } | |
567 ``` | |
568 | |
569 Response: | |
570 | |
571 ``` {.json} | |
572 { | |
573 "from" : "example.com", | |
574 "id" : "8iN9hwdAAcfTBchm", | |
575 "items" : [ | |
576 { | |
577 "jid" : "example.com", | |
578 "name" : "Get uptime", | |
579 "node" : "uptime" | |
580 } | |
581 ], | |
582 "kind" : "iq", | |
583 "type" : "result" | |
584 } | |
585 ``` | |
586 | |
587 Execute the command: | |
588 | |
589 ``` {.json} | |
590 { | |
591 "command" : { | |
592 "node" : "uptime" | |
593 }, | |
594 "id" : "Jv-87nRaP6Mnrp8l", | |
595 "kind" : "iq", | |
596 "to" : "example.com", | |
597 "type" : "set" | |
598 } | |
599 ``` | |
600 | |
601 Executed: | |
602 | |
603 ``` {.json} | |
604 { | |
605 "command" : { | |
606 "node" : "uptime", | |
607 "note" : { | |
608 "text" : "This server has been running for 0 days, 20 hours and 54 minutes (since Fri Feb 7 18:05:30 2020)", | |
609 "type" : "info" | |
610 }, | |
611 "sessionid" : "6380880a-93e9-4f13-8ee2-171927a40e67", | |
612 "status" : "completed" | |
613 }, | |
614 "from" : "example.com", | |
615 "id" : "Jv-87nRaP6Mnrp8l", | |
616 "kind" : "iq", | |
617 "type" : "result" | |
618 } | |
619 ``` | |
620 | |
621 # TODO | |
622 | |
623 - Describe multi-step commands with dataforms. | |
624 - Versioned API, i.e. /v1/stanzas | |
625 - Bind resource to webhook/callback | |
626 | |
627 # Compatibility | |
628 | |
629 Requires Prosody trunk / 0.12 |