1 2-- Requires a host 'localhost' with SASL ANONYMOUS 3 4local bosh_url = "http://localhost:5280/http-bind" 5 6local logger = require "util.logger"; 7 8local debug = false; 9 10local print = print; 11if debug then 12 logger.add_simple_sink(print, { 13 --"debug"; 14 "info"; 15 "warn"; 16 "error"; 17 }); 18else 19 print = function () end 20end 21 22describe("#mod_bosh", function () 23 local server = require "net.server_select"; 24 package.loaded["net.server"] = server; 25 local async = require "util.async"; 26 local timer = require "util.timer"; 27 local http = require "net.http".new({ suppress_errors = false }); 28 29 local function sleep(n) 30 local wait, done = async.waiter(); 31 timer.add_task(n, function () done() end); 32 wait(); 33 end 34 35 local st = require "util.stanza"; 36 local xml = require "util.xml"; 37 38 local function request(url, opt, cb, auto_wait) 39 local wait, done = async.waiter(); 40 local ok, err; 41 http:request(url, opt, function (...) 42 ok, err = pcall(cb, ...); 43 if not ok then print("CAUGHT", err) end 44 done(); 45 end); 46 local function err_wait(throw) 47 wait(); 48 if throw ~= false and not ok then 49 error(err); 50 end 51 return ok, err; 52 end 53 if auto_wait == false then 54 return err_wait; 55 else 56 err_wait(); 57 end 58 end 59 60 local function run_async(f) 61 local err; 62 local r = async.runner(); 63 r:onerror(function (_, err_) 64 print("EER", err_) 65 err = err_; 66 server.setquitting("once"); 67 end) 68 :onwaiting(function () 69 --server.loop(); 70 end) 71 :run(function () 72 f() 73 server.setquitting("once"); 74 end); 75 server.loop(); 76 if err then 77 error(err); 78 end 79 if r.state ~= "ready" then 80 error("Runner in unexpected state: "..r.state); 81 end 82 end 83 84 it("test endpoint should be reachable", function () 85 -- This is partly just to ensure the other tests have a chance to succeed 86 -- (i.e. the BOSH endpoint is up and functioning) 87 local function test() 88 request(bosh_url, nil, function (resp, code) 89 if code ~= 200 then 90 error("Unable to reach BOSH endpoint "..bosh_url); 91 end 92 assert.is_string(resp); 93 end); 94 end 95 run_async(test); 96 end); 97 98 it("should respond to past rids with past responses", function () 99 local resp_1000_1, resp_1000_2 = "1", "2"; 100 101 local function test_bosh() 102 local sid; 103 104 -- Set up BOSH session 105 request(bosh_url, { 106 body = tostring(st.stanza("body", { 107 to = "localhost"; 108 from = "test@localhost"; 109 content = "text/xml; charset=utf-8"; 110 hold = "1"; 111 rid = "998"; 112 wait = "10"; 113 ["xml:lang"] = "en"; 114 ["xmpp:version"] = "1.0"; 115 xmlns = "http://jabber.org/protocol/httpbind"; 116 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 117 }) 118 :tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() 119 :tag("iq", { xmlns = "jabber:client", type = "set", id = "bind1" }) 120 :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }) 121 :tag("resource"):text("bosh-test1"):up() 122 :up() 123 :up() 124 ); 125 }, function (response_body) 126 local resp = xml.parse(response_body); 127 if not response_body:find("<jid>", 1, true) then 128 print("ERR", resp:pretty_print()); 129 error("Failed to set up BOSH session"); 130 end 131 sid = assert(resp.attr.sid); 132 print("SID", sid); 133 end); 134 135 -- Receive some additional post-login stuff 136 request(bosh_url, { 137 body = tostring(st.stanza("body", { 138 sid = sid; 139 rid = "999"; 140 content = "text/xml; charset=utf-8"; 141 ["xml:lang"] = "en"; 142 xmlns = "http://jabber.org/protocol/httpbind"; 143 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 144 }) 145 ) 146 }, function (response_body) 147 local resp = xml.parse(response_body); 148 print("RESP 999", resp:pretty_print()); 149 end); 150 151 -- Send first long poll 152 print "SEND 1000#1" 153 local wait1000 = request(bosh_url, { 154 body = tostring(st.stanza("body", { 155 sid = sid; 156 rid = "1000"; 157 content = "text/xml; charset=utf-8"; 158 ["xml:lang"] = "en"; 159 xmlns = "http://jabber.org/protocol/httpbind"; 160 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 161 })) 162 }, function (response_body) 163 local resp = xml.parse(response_body); 164 resp_1000_1 = resp; 165 print("RESP 1000#1", resp:pretty_print()); 166 end, false); 167 168 -- Wait a couple of seconds 169 sleep(2) 170 171 -- Send an early request, causing rid 1000 to return early 172 print "SEND 1001" 173 local wait1001 = request(bosh_url, { 174 body = tostring(st.stanza("body", { 175 sid = sid; 176 rid = "1001"; 177 content = "text/xml; charset=utf-8"; 178 ["xml:lang"] = "en"; 179 xmlns = "http://jabber.org/protocol/httpbind"; 180 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 181 })) 182 }, function (response_body) 183 local resp = xml.parse(response_body); 184 print("RESP 1001", resp:pretty_print()); 185 end, false); 186 -- Ensure we've received the response for rid 1000 187 wait1000(); 188 189 -- Sleep a couple of seconds 190 print "...pause..." 191 sleep(2); 192 193 -- Re-send rid 1000, we should get the same response 194 print "SEND 1000#2" 195 request(bosh_url, { 196 body = tostring(st.stanza("body", { 197 sid = sid; 198 rid = "1000"; 199 content = "text/xml; charset=utf-8"; 200 ["xml:lang"] = "en"; 201 xmlns = "http://jabber.org/protocol/httpbind"; 202 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 203 })) 204 }, function (response_body) 205 local resp = xml.parse(response_body); 206 resp_1000_2 = resp; 207 print("RESP 1000#2", resp:pretty_print()); 208 end); 209 210 local wait_final = request(bosh_url, { 211 body = tostring(st.stanza("body", { 212 sid = sid; 213 rid = "1002"; 214 type = "terminate"; 215 content = "text/xml; charset=utf-8"; 216 ["xml:lang"] = "en"; 217 xmlns = "http://jabber.org/protocol/httpbind"; 218 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 219 })) 220 }, function () 221 end, false); 222 223 print "WAIT 1001" 224 wait1001(); 225 wait_final(); 226 print "DONE ALL" 227 end 228 run_async(test_bosh); 229 assert.truthy(resp_1000_1); 230 assert.same(resp_1000_1, resp_1000_2); 231 end); 232 233 it("should handle out-of-order requests", function () 234 local function test() 235 local sid; 236 -- Set up BOSH session 237 local wait, done = async.waiter(); 238 http:request(bosh_url, { 239 body = tostring(st.stanza("body", { 240 to = "localhost"; 241 from = "test@localhost"; 242 content = "text/xml; charset=utf-8"; 243 hold = "1"; 244 rid = "1"; 245 wait = "10"; 246 ["xml:lang"] = "en"; 247 ["xmpp:version"] = "1.0"; 248 xmlns = "http://jabber.org/protocol/httpbind"; 249 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 250 })); 251 }, function (response_body) 252 local resp = xml.parse(response_body); 253 sid = assert(resp.attr.sid, "Failed to set up BOSH session"); 254 print("SID", sid); 255 done(); 256 end); 257 print "WAIT 1" 258 wait(); 259 print "DONE 1" 260 261 local rid2_response_received = false; 262 263 -- Temporarily skip rid 2, to simulate missed request 264 local wait3, done3 = async.waiter(); 265 http:request(bosh_url, { 266 body = tostring(st.stanza("body", { 267 sid = sid; 268 rid = "3"; 269 content = "text/xml; charset=utf-8"; 270 ["xml:lang"] = "en"; 271 xmlns = "http://jabber.org/protocol/httpbind"; 272 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 273 }):tag("iq", { xmlns = "jabber:client", type = "set", id = "bind" }) 274 :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }):up() 275 :up() 276 ) 277 }, function (response_body) 278 local resp = xml.parse(response_body); 279 print("RESP 3", resp:pretty_print()); 280 done3(); 281 -- The server should not respond to this request until 282 -- it has responded to rid 2 283 assert.is_true(rid2_response_received); 284 end); 285 286 print "SLEEPING" 287 sleep(2); 288 print "SLEPT" 289 290 -- Send the "missed" rid 2 291 local wait2, done2 = async.waiter(); 292 http:request(bosh_url, { 293 body = tostring(st.stanza("body", { 294 sid = sid; 295 rid = "2"; 296 content = "text/xml; charset=utf-8"; 297 ["xml:lang"] = "en"; 298 xmlns = "http://jabber.org/protocol/httpbind"; 299 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 300 }):tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() 301 ) 302 }, function (response_body) 303 local resp = xml.parse(response_body); 304 print("RESP 2", resp:pretty_print()); 305 rid2_response_received = true; 306 done2(); 307 end); 308 print "WAIT 2" 309 wait2(); 310 print "WAIT 3" 311 wait3(); 312 print "QUIT" 313 end 314 run_async(test); 315 end); 316 317 it("should work", function () 318 local function test() 319 local sid; 320 -- Set up BOSH session 321 local wait, done = async.waiter(); 322 http:request(bosh_url, { 323 body = tostring(st.stanza("body", { 324 to = "localhost"; 325 from = "test@localhost"; 326 content = "text/xml; charset=utf-8"; 327 hold = "1"; 328 rid = "1"; 329 wait = "10"; 330 ["xml:lang"] = "en"; 331 ["xmpp:version"] = "1.0"; 332 xmlns = "http://jabber.org/protocol/httpbind"; 333 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 334 })); 335 }, function (response_body) 336 local resp = xml.parse(response_body); 337 sid = assert(resp.attr.sid, "Failed to set up BOSH session"); 338 print("SID", sid); 339 done(); 340 end); 341 print "WAIT 1" 342 wait(); 343 print "DONE 1" 344 345 local rid2_response_received = false; 346 347 -- Send the "missed" rid 2 348 local wait2, done2 = async.waiter(); 349 http:request(bosh_url, { 350 body = tostring(st.stanza("body", { 351 sid = sid; 352 rid = "2"; 353 content = "text/xml; charset=utf-8"; 354 ["xml:lang"] = "en"; 355 xmlns = "http://jabber.org/protocol/httpbind"; 356 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 357 }):tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() 358 ) 359 }, function (response_body) 360 local resp = xml.parse(response_body); 361 print("RESP 2", resp:pretty_print()); 362 rid2_response_received = true; 363 done2(); 364 end); 365 366 local wait3, done3 = async.waiter(); 367 http:request(bosh_url, { 368 body = tostring(st.stanza("body", { 369 sid = sid; 370 rid = "3"; 371 content = "text/xml; charset=utf-8"; 372 ["xml:lang"] = "en"; 373 xmlns = "http://jabber.org/protocol/httpbind"; 374 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 375 }):tag("iq", { xmlns = "jabber:client", type = "set", id = "bind" }) 376 :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }):up() 377 :up() 378 ) 379 }, function (response_body) 380 local resp = xml.parse(response_body); 381 print("RESP 3", resp:pretty_print()); 382 done3(); 383 -- The server should not respond to this request until 384 -- it has responded to rid 2 385 assert.is_true(rid2_response_received); 386 end); 387 388 print "SLEEPING" 389 sleep(2); 390 print "SLEPT" 391 392 print "WAIT 2" 393 wait2(); 394 print "WAIT 3" 395 wait3(); 396 print "QUIT" 397 end 398 run_async(test); 399 end); 400 401 it("should handle aborted pending requests", function () 402 local resp_1000_1, resp_1000_2 = "1", "2"; 403 404 local function test_bosh() 405 local sid; 406 407 -- Set up BOSH session 408 request(bosh_url, { 409 body = tostring(st.stanza("body", { 410 to = "localhost"; 411 from = "test@localhost"; 412 content = "text/xml; charset=utf-8"; 413 hold = "1"; 414 rid = "998"; 415 wait = "10"; 416 ["xml:lang"] = "en"; 417 ["xmpp:version"] = "1.0"; 418 xmlns = "http://jabber.org/protocol/httpbind"; 419 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 420 }) 421 :tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() 422 :tag("iq", { xmlns = "jabber:client", type = "set", id = "bind1" }) 423 :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }) 424 :tag("resource"):text("bosh-test1"):up() 425 :up() 426 :up() 427 ); 428 }, function (response_body) 429 local resp = xml.parse(response_body); 430 if not response_body:find("<jid>", 1, true) then 431 print("ERR", resp:pretty_print()); 432 error("Failed to set up BOSH session"); 433 end 434 sid = assert(resp.attr.sid); 435 print("SID", sid); 436 end); 437 438 -- Receive some additional post-login stuff 439 request(bosh_url, { 440 body = tostring(st.stanza("body", { 441 sid = sid; 442 rid = "999"; 443 content = "text/xml; charset=utf-8"; 444 ["xml:lang"] = "en"; 445 xmlns = "http://jabber.org/protocol/httpbind"; 446 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 447 }) 448 ) 449 }, function (response_body) 450 local resp = xml.parse(response_body); 451 print("RESP 999", resp:pretty_print()); 452 end); 453 454 -- Send first long poll 455 print "SEND 1000#1" 456 local wait1000_1 = request(bosh_url, { 457 body = tostring(st.stanza("body", { 458 sid = sid; 459 rid = "1000"; 460 content = "text/xml; charset=utf-8"; 461 ["xml:lang"] = "en"; 462 xmlns = "http://jabber.org/protocol/httpbind"; 463 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 464 })) 465 }, function (response_body) 466 local resp = xml.parse(response_body); 467 resp_1000_1 = resp; 468 assert.is_nil(resp.attr.type); 469 print("RESP 1000#1", resp:pretty_print()); 470 end, false); 471 472 -- Wait a couple of seconds 473 sleep(2) 474 475 -- Re-send rid 1000, we should eventually get a normal response (with no stanzas) 476 print "SEND 1000#2" 477 request(bosh_url, { 478 body = tostring(st.stanza("body", { 479 sid = sid; 480 rid = "1000"; 481 content = "text/xml; charset=utf-8"; 482 ["xml:lang"] = "en"; 483 xmlns = "http://jabber.org/protocol/httpbind"; 484 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 485 })) 486 }, function (response_body) 487 local resp = xml.parse(response_body); 488 resp_1000_2 = resp; 489 assert.is_nil(resp.attr.type); 490 print("RESP 1000#2", resp:pretty_print()); 491 end); 492 493 wait1000_1(); 494 print "DONE ALL" 495 end 496 run_async(test_bosh); 497 assert.truthy(resp_1000_1); 498 assert.same(resp_1000_1, resp_1000_2); 499 end); 500 501 it("should fail on requests beyond rid window", function () 502 local function test_bosh() 503 local sid; 504 505 -- Set up BOSH session 506 request(bosh_url, { 507 body = tostring(st.stanza("body", { 508 to = "localhost"; 509 from = "test@localhost"; 510 content = "text/xml; charset=utf-8"; 511 hold = "1"; 512 rid = "998"; 513 wait = "10"; 514 ["xml:lang"] = "en"; 515 ["xmpp:version"] = "1.0"; 516 xmlns = "http://jabber.org/protocol/httpbind"; 517 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 518 }) 519 :tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() 520 :tag("iq", { xmlns = "jabber:client", type = "set", id = "bind1" }) 521 :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }) 522 :tag("resource"):text("bosh-test1"):up() 523 :up() 524 :up() 525 ); 526 }, function (response_body) 527 local resp = xml.parse(response_body); 528 if not response_body:find("<jid>", 1, true) then 529 print("ERR", resp:pretty_print()); 530 error("Failed to set up BOSH session"); 531 end 532 sid = assert(resp.attr.sid); 533 print("SID", sid); 534 end); 535 536 -- Receive some additional post-login stuff 537 request(bosh_url, { 538 body = tostring(st.stanza("body", { 539 sid = sid; 540 rid = "999"; 541 content = "text/xml; charset=utf-8"; 542 ["xml:lang"] = "en"; 543 xmlns = "http://jabber.org/protocol/httpbind"; 544 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 545 }) 546 ) 547 }, function (response_body) 548 local resp = xml.parse(response_body); 549 print("RESP 999", resp:pretty_print()); 550 end); 551 552 -- Send poll with a rid that's too high (current + 2, where only current + 1 is allowed) 553 print "SEND 1002(!)" 554 request(bosh_url, { 555 body = tostring(st.stanza("body", { 556 sid = sid; 557 rid = "1002"; 558 content = "text/xml; charset=utf-8"; 559 ["xml:lang"] = "en"; 560 xmlns = "http://jabber.org/protocol/httpbind"; 561 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 562 })) 563 }, function (response_body) 564 local resp = xml.parse(response_body); 565 assert.equal("terminate", resp.attr.type); 566 print("RESP 1002(!)", resp:pretty_print()); 567 end); 568 569 print "DONE ALL" 570 end 571 run_async(test_bosh); 572 end); 573 574 it("should always succeed for requests within the rid window", function () 575 local function test() 576 local sid; 577 -- Set up BOSH session 578 request(bosh_url, { 579 body = tostring(st.stanza("body", { 580 to = "localhost"; 581 from = "test@localhost"; 582 content = "text/xml; charset=utf-8"; 583 hold = "1"; 584 rid = "1"; 585 wait = "10"; 586 ["xml:lang"] = "en"; 587 ["xmpp:version"] = "1.0"; 588 xmlns = "http://jabber.org/protocol/httpbind"; 589 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 590 })); 591 }, function (response_body) 592 local resp = xml.parse(response_body); 593 sid = assert(resp.attr.sid, "Failed to set up BOSH session"); 594 print("SID", sid); 595 end); 596 print "DONE 1" 597 598 request(bosh_url, { 599 body = tostring(st.stanza("body", { 600 sid = sid; 601 rid = "2"; 602 content = "text/xml; charset=utf-8"; 603 ["xml:lang"] = "en"; 604 xmlns = "http://jabber.org/protocol/httpbind"; 605 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 606 }):tag("auth", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl", mechanism = "ANONYMOUS" }):up() 607 ) 608 }, function (response_body) 609 local resp = xml.parse(response_body); 610 print("RESP 2", resp:pretty_print()); 611 end); 612 613 local resp3; 614 request(bosh_url, { 615 body = tostring(st.stanza("body", { 616 sid = sid; 617 rid = "3"; 618 content = "text/xml; charset=utf-8"; 619 ["xml:lang"] = "en"; 620 xmlns = "http://jabber.org/protocol/httpbind"; 621 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 622 }):tag("iq", { xmlns = "jabber:client", type = "set", id = "bind" }) 623 :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }):up() 624 :up() 625 ) 626 }, function (response_body) 627 local resp = xml.parse(response_body); 628 print("RESP 3#1", resp:pretty_print()); 629 resp3 = resp; 630 end); 631 632 633 request(bosh_url, { 634 body = tostring(st.stanza("body", { 635 sid = sid; 636 rid = "4"; 637 content = "text/xml; charset=utf-8"; 638 ["xml:lang"] = "en"; 639 xmlns = "http://jabber.org/protocol/httpbind"; 640 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 641 }):tag("iq", { xmlns = "jabber:client", type = "get", id = "ping1" }) 642 :tag("ping", { xmlns = "urn:xmpp:ping" }):up() 643 :up() 644 ) 645 }, function (response_body) 646 local resp = xml.parse(response_body); 647 print("RESP 4", resp:pretty_print()); 648 end); 649 650 request(bosh_url, { 651 body = tostring(st.stanza("body", { 652 sid = sid; 653 rid = "3"; 654 content = "text/xml; charset=utf-8"; 655 ["xml:lang"] = "en"; 656 xmlns = "http://jabber.org/protocol/httpbind"; 657 ["xmlns:xmpp"] = "urn:xmpp:xbosh"; 658 }):tag("iq", { xmlns = "jabber:client", type = "set", id = "bind" }) 659 :tag("bind", { xmlns = "urn:ietf:params:xml:ns:xmpp-bind" }):up() 660 :up() 661 ) 662 }, function (response_body) 663 local resp = xml.parse(response_body); 664 print("RESP 3#2", resp:pretty_print()); 665 assert.not_equal("terminate", resp.attr.type); 666 assert.same(resp3, resp); 667 end); 668 669 670 print "QUIT" 671 end 672 run_async(test); 673 end); 674end); 675