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