Software /
code /
prosody-modules
Changeset
4229:3943032533a7
mod_http_prebind: New module
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Sun, 25 Oct 2020 17:58:02 +0100 |
parents | 4228:3eb595cf847f |
children | 4230:495a23d61418 |
files | mod_http_prebind/README.markdown mod_http_prebind/mod_http_prebind.lua |
diffstat | 2 files changed, 155 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_prebind/README.markdown Sun Oct 25 17:58:02 2020 +0100 @@ -0,0 +1,14 @@ +--- +labels: +- 'Stage-Alpha' +summary: Implements BOSH pre-bind +... + +For why this can be useful, see +https://metajack.im/2009/12/14/fastest-xmpp-sessions-with-http-prebinding/ + +# To enable this module + +Add `"http_prebind"` to `modules_enabled` on an anonymous virtual host. + +This only works on anonymous ones for now.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_http_prebind/mod_http_prebind.lua Sun Oct 25 17:58:02 2020 +0100 @@ -0,0 +1,141 @@ +module:depends("http"); + +local http = require "net.http"; +local format = require "util.format".format; +local json_encode = require "util.json".encode; +local promise = require "util.promise"; +local xml = require "util.xml"; +local t_insert = table.insert; + +local function new_options(host) + return { + headers = { + ["Content-Type"] = "text/xml; charset=utf-8", + ["Host"] = host, + }, + method = "POST", + }; +end + +local function connect_to_bosh(url, hostname) + local rid = math.random(100000, 100000000) + local options = new_options(hostname); + options.body = format([[<body content='text/xml; charset=utf-8' + hold='1' + rid='%d' + to='%s' + wait='60' + xml:lang='en' + xmpp:version='1.0' + xmlns='http://jabber.org/protocol/httpbind' + xmlns:xmpp='urn:xmpp:xbosh'/>]], rid, hostname); + local rid = rid + 1; + return promise.new(function (on_fulfilled, on_error) + assert(http.request(url, options, function (body, code) + if code ~= 200 then + on_error("Failed to fetch, HTTP error code "..code); + return; + end + local body = xml.parse(body); + local sid = body.attr.sid; + local mechanisms = {}; + for mechanism in body:get_child("features", "http://etherx.jabber.org/streams") + :get_child("mechanisms", "urn:ietf:params:xml:ns:xmpp-sasl") + :childtags("mechanism", "urn:ietf:params:xml:ns:xmpp-sasl") do + mechanisms[mechanism:get_text()] = true; + end + on_fulfilled({ url = url, sid = sid, rid = rid, mechanisms = mechanisms }); + end)); + end); +end + +local function authenticate(data) + local options = new_options(); + options.body = format([[<body sid='%s' + rid='%d' + xmlns='http://jabber.org/protocol/httpbind'> + <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' + mechanism='ANONYMOUS'/> + </body>]], data.sid, data.rid); + data.rid = data.rid + 1; + return promise.new(function (on_fulfilled, on_error) + if data.mechanisms["ANONYMOUS"] == nil then + on_error("No SASL ANONYMOUS mechanism supported on this host."); + return; + end + assert(http.request(data.url, options, function (body, code) + if code ~= 200 then + on_error("Failed to fetch, HTTP error code "..code); + return; + end + local body = xml.parse(body); + local success = body:get_child("success", "urn:ietf:params:xml:ns:xmpp-sasl"); + if success then + data.mechanisms = nil; + on_fulfilled(data); + else + on_error("Authentication failed."); + end + end)); + end); +end; + +local function restart_stream(data) + local options = new_options(); + options.body = format([[ + <body sid='%s' + rid='%d' + xml:lang='en' + xmlns='http://jabber.org/protocol/httpbind' + xmlns:xmpp='urn:xmpp:xbosh' + xmpp:restart='true'/>]], data.sid, data.rid); + data.rid = data.rid + 1; + return promise.new(function (on_fulfilled, on_error) + assert(http.request(data.url, options, function (body, code) + if code ~= 200 then + on_error("Failed to fetch, HTTP error code "..code); + return; + end + local body = xml.parse(body); + on_fulfilled(data); + end)); + end); +end; + +local function bind(data) + local options = new_options(); + options.body = format([[ + <body sid='%s' + rid='%d' + xmlns='http://jabber.org/protocol/httpbind'> + <iq xmlns='jabber:client' + type='set'> + <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> + </iq> + </body>]], data.sid, data.rid); + data.rid = data.rid + 1; + return promise.new(function (on_fulfilled, on_error) + assert(http.request(data.url, options, function (body, code) + if code ~= 200 then + on_error("Failed to fetch, HTTP error code "..code); + return; + end + local body = xml.parse(body); + local jid = body:get_child("iq", "jabber:client") + :get_child("bind", "urn:ietf:params:xml:ns:xmpp-bind") + :get_child_text("jid", "urn:ietf:params:xml:ns:xmpp-bind"); + on_fulfilled(json_encode({rid = data.rid, sid = data.sid, jid = jid})); + end)); + end); +end; + +module:provides("http", { + route = { + ["GET"] = function (event) + return connect_to_bosh("http://[::1]:5280/http-bind", "anon.localhost") + :next(authenticate) + :next(restart_stream) + :next(bind); + end; + }; +});