test_http2-proxy.js (27805B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /** 8 * This test checks following expectations when using HTTP/2 proxy: 9 * 10 * - when we request https access, we don't create different sessions for 11 * different origins, only new tunnels inside a single session 12 * - when the isolation key (`proxy_isolation`) is changed, new single session 13 * is created for new requests to same origins as before 14 * - error code returned from the tunnel (a proxy error - not end-server 15 * error!) doesn't kill the existing session 16 * - check we are seeing expected nsresult error codes on channels 17 * (nsIChannel.status) corresponding to different proxy status code 18 * responses (502, 504, 407, ...) 19 * - check we don't try to ask for credentials or otherwise authenticate to 20 * the proxy when 407 is returned and there is no Proxy-Authenticate 21 * response header sent 22 * - a stream reset for a connect stream to the proxy does not cause session to 23 * be closed and the request through the proxy will failed. 24 * - a "soft" stream error on a connection to the origin server will close the 25 * stream, but it will not close niether the HTTP/2 session to the proxy nor 26 * to the origin server. 27 * - a "hard" stream error on a connection to the origin server will close the 28 * HTTP/2 session to the origin server, but it will not close the HTTP/2 29 * session to the proxy. 30 */ 31 32 /* eslint-env node */ 33 /* global serverPort */ 34 35 "use strict"; 36 37 // We don't normally allow localhost channels to be proxied, but this 38 // is easier than updating all the certs and/or domains. 39 Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); 40 registerCleanupFunction(() => { 41 Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost"); 42 }); 43 44 const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(); 45 const { NodeServer } = ChromeUtils.importESModule( 46 "resource://testing-common/NodeServer.sys.mjs" 47 ); 48 49 let proxy_port; 50 let filter; 51 let proxy; 52 53 // See moz-http2 54 const proxy_auth = "authorization-token"; 55 let proxy_isolation; 56 57 class ProxyFilter { 58 constructor(type, host, port, flags) { 59 this._type = type; 60 this._host = host; 61 this._port = port; 62 this._flags = flags; 63 this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]); 64 } 65 applyFilter(uri, pi, cb) { 66 cb.onProxyFilterResult( 67 pps.newProxyInfo( 68 this._type, 69 this._host, 70 this._port, 71 proxy_auth, 72 proxy_isolation, 73 this._flags, 74 1000, 75 null 76 ) 77 ); 78 } 79 } 80 81 class UnxpectedAuthPrompt2 { 82 constructor(signal) { 83 this.signal = signal; 84 this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]); 85 } 86 asyncPromptAuth() { 87 this.signal.triggered = true; 88 throw Components.Exception("", Cr.ERROR_UNEXPECTED); 89 } 90 } 91 92 class SimpleAuthPrompt2 { 93 constructor(signal) { 94 this.signal = signal; 95 this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]); 96 } 97 asyncPromptAuth(channel, callback, context, encryptionLevel, authInfo) { 98 this.signal.triggered = true; 99 executeSoon(function () { 100 authInfo.username = "user"; 101 authInfo.password = "pass"; 102 callback.onAuthAvailable(context, authInfo); 103 }); 104 } 105 } 106 107 class AuthRequestor { 108 constructor(prompt) { 109 this.prompt = prompt; 110 this.QueryInterface = ChromeUtils.generateQI(["nsIInterfaceRequestor"]); 111 } 112 getInterface(iid) { 113 if (iid.equals(Ci.nsIAuthPrompt2)) { 114 return this.prompt(); 115 } 116 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 117 } 118 } 119 120 function createPrincipal(url) { 121 var ssm = Services.scriptSecurityManager; 122 try { 123 return ssm.createContentPrincipal(Services.io.newURI(url), {}); 124 } catch (e) { 125 return null; 126 } 127 } 128 129 function make_channel(url) { 130 return NetUtil.newChannel({ 131 uri: url, 132 loadingPrincipal: createPrincipal(url), 133 securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT, 134 // Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types 135 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, 136 }); 137 } 138 139 function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL, delay = 0) { 140 return new Promise(resolve => { 141 var listener = new ChannelListener( 142 (request, data) => { 143 request.QueryInterface(Ci.nsIHttpChannel); 144 const status = request.status; 145 const http_code = status ? undefined : request.responseStatus; 146 request.QueryInterface(Ci.nsIProxiedChannel); 147 const proxy_connect_response_code = 148 request.httpProxyConnectResponseCode; 149 resolve({ status, http_code, data, proxy_connect_response_code }); 150 }, 151 null, 152 flags 153 ); 154 if (delay > 0) { 155 do_timeout(delay, function () { 156 channel.asyncOpen(listener); 157 }); 158 } else { 159 channel.asyncOpen(listener); 160 } 161 }); 162 } 163 164 let initial_session_count = 0; 165 166 class http2ProxyCode { 167 static listen(server, envport) { 168 if (!server) { 169 return Promise.resolve(0); 170 } 171 172 let portSelection = 0; 173 if (envport !== undefined) { 174 try { 175 portSelection = parseInt(envport, 10); 176 } catch (e) { 177 portSelection = -1; 178 } 179 } 180 return new Promise(resolve => { 181 server.listen(portSelection, "0.0.0.0", 2000, () => { 182 resolve(server.address().port); 183 }); 184 }); 185 } 186 187 static startNewProxy() { 188 const fs = require("fs"); 189 const options = { 190 key: fs.readFileSync(__dirname + "/http2-cert.key"), 191 cert: fs.readFileSync(__dirname + "/http2-cert.pem"), 192 }; 193 const http2 = require("http2"); 194 global.proxy = http2.createSecureServer(options); 195 this.setupProxy(); 196 return http2ProxyCode.listen(proxy).then(port => { 197 return { port, success: true }; 198 }); 199 } 200 201 static closeProxy() { 202 proxy.closeSockets(); 203 return new Promise(resolve => { 204 proxy.close(resolve); 205 }); 206 } 207 208 static proxySessionCount() { 209 if (!proxy) { 210 return 0; 211 } 212 return proxy.proxy_session_count; 213 } 214 215 static proxySessionToOriginServersCount() { 216 if (!proxy) { 217 return 0; 218 } 219 return proxy.sessionToOriginServersCount; 220 } 221 222 static setupProxy() { 223 if (!proxy) { 224 throw new Error("proxy is null"); 225 } 226 proxy.proxy_session_count = 0; 227 proxy.sessionToOriginServersCount = 0; 228 proxy.on("session", () => { 229 ++proxy.proxy_session_count; 230 }); 231 232 // We need to track active connections so we can forcefully close keep-alive 233 // connections when shutting down the proxy. 234 proxy.socketIndex = 0; 235 proxy.socketMap = {}; 236 proxy.on("connection", function (socket) { 237 let index = proxy.socketIndex++; 238 proxy.socketMap[index] = socket; 239 socket.on("close", function () { 240 delete proxy.socketMap[index]; 241 }); 242 }); 243 proxy.closeSockets = function () { 244 for (let i in proxy.socketMap) { 245 proxy.socketMap[i].destroy(); 246 } 247 }; 248 249 proxy.on("stream", (stream, headers) => { 250 if (headers[":method"] !== "CONNECT") { 251 // Only accept CONNECT requests 252 stream.respond({ ":status": 405 }); 253 stream.end(); 254 return; 255 } 256 257 const target = headers[":authority"]; 258 259 const authorization_token = headers["proxy-authorization"]; 260 if (target == "407.example.com:443") { 261 stream.respond({ ":status": 407 }); 262 // Deliberately send no Proxy-Authenticate header 263 stream.end(); 264 return; 265 } 266 if (target == "407.basic.example.com:443") { 267 // we want to return a different response than 407 to not re-request 268 // credentials (and thus loop) but also not 200 to not let the channel 269 // attempt to waste time connecting a non-existing https server - hence 270 // 418 I'm a teapot :) 271 if ("Basic dXNlcjpwYXNz" == authorization_token) { 272 stream.respond({ ":status": 418 }); 273 stream.end(); 274 return; 275 } 276 stream.respond({ 277 ":status": 407, 278 "proxy-authenticate": "Basic realm='foo'", 279 }); 280 stream.end(); 281 return; 282 } 283 if (target == "404.example.com:443") { 284 // 404 Not Found, a response code that a proxy should return when the host can't be found 285 stream.respond({ ":status": 404 }); 286 stream.end(); 287 return; 288 } 289 if (target == "429.example.com:443") { 290 // 429 Too Many Requests, a response code that a proxy should return when receiving too many requests 291 stream.respond({ ":status": 429 }); 292 stream.end(); 293 return; 294 } 295 if (target == "502.example.com:443") { 296 // 502 Bad Gateway, a response code mostly resembling immediate connection error 297 stream.respond({ ":status": 502 }); 298 stream.end(); 299 return; 300 } 301 if (target == "504.example.com:443") { 302 // 504 Gateway Timeout, did not receive a timely response from an upstream server 303 stream.respond({ ":status": 504 }); 304 stream.end(); 305 return; 306 } 307 if (target == "reset.example.com:443") { 308 // always reset the stream. 309 stream.close(0x0); 310 return; 311 } 312 313 ++proxy.sessionToOriginServersCount; 314 const net = require("net"); 315 const socket = net.connect(serverPort, "127.0.0.1", () => { 316 try { 317 stream.respond({ ":status": 200 }); 318 socket.pipe(stream); 319 stream.pipe(socket); 320 } catch (exception) { 321 console.log(exception); 322 stream.close(); 323 } 324 }); 325 socket.on("error", error => { 326 throw new Error( 327 `Unexpected error when conneting the HTTP/2 server from the HTTP/2 proxy during CONNECT handling: '${error}'` 328 ); 329 }); 330 }); 331 } 332 } 333 334 async function proxy_session_counter() { 335 let data = await NodeServer.execute( 336 processId, 337 `http2ProxyCode.proxySessionCount()` 338 ); 339 return parseInt(data) - initial_session_count; 340 } 341 async function proxy_session_to_origin_server_counter() { 342 let data = await NodeServer.execute( 343 processId, 344 `http2ProxyCode.proxySessionToOriginServersCount()` 345 ); 346 return parseInt(data) - initial_session_count; 347 } 348 let processId; 349 add_task(async function setup() { 350 // Set to allow the cert presented by our H2 server 351 do_get_profile(); 352 353 // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem 354 // so add that cert to the trust list as a signing cert. 355 let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 356 Ci.nsIX509CertDB 357 ); 358 addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); 359 360 let server_port = Services.env.get("MOZHTTP2_PORT"); 361 Assert.notEqual(server_port, null); 362 processId = await NodeServer.fork(); 363 await NodeServer.execute(processId, `serverPort = ${server_port}`); 364 await NodeServer.execute(processId, http2ProxyCode); 365 let newProxy = await NodeServer.execute( 366 processId, 367 `http2ProxyCode.startNewProxy()` 368 ); 369 proxy_port = newProxy.port; 370 Assert.notEqual(proxy_port, null); 371 372 Services.prefs.setStringPref( 373 "services.settings.server", 374 `data:,#remote-settings-dummy/v1` 375 ); 376 377 Services.prefs.setBoolPref("network.http.http2.enabled", true); 378 379 // make all native resolve calls "secretly" resolve localhost instead 380 Services.prefs.setBoolPref("network.dns.native-is-localhost", true); 381 382 filter = new ProxyFilter("https", "localhost", proxy_port, 0); 383 pps.registerFilter(filter, 10); 384 385 initial_session_count = await proxy_session_counter(); 386 info(`Initial proxy session count = ${initial_session_count}`); 387 }); 388 389 registerCleanupFunction(async () => { 390 Services.prefs.clearUserPref("services.settings.server"); 391 Services.prefs.clearUserPref("network.http.http2.enabled"); 392 Services.prefs.clearUserPref("network.dns.native-is-localhost"); 393 394 pps.unregisterFilter(filter); 395 396 await NodeServer.execute(processId, `http2ProxyCode.closeProxy()`); 397 await NodeServer.kill(processId); 398 }); 399 400 /** 401 * Test series beginning. 402 */ 403 404 // Check we reach the h2 end server and keep only one session with the proxy for two different origin. 405 // Here we use the first isolation token. 406 add_task(async function proxy_success_one_session() { 407 proxy_isolation = "TOKEN1"; 408 409 const foo = await get_response( 410 make_channel(`https://foo.example.com/random-request-1`) 411 ); 412 const alt1 = await get_response( 413 make_channel(`https://alt1.example.com/random-request-2`) 414 ); 415 416 Assert.equal(foo.status, Cr.NS_OK); 417 Assert.equal(foo.proxy_connect_response_code, 200); 418 Assert.equal(foo.http_code, 200); 419 Assert.ok(foo.data.match("random-request-1")); 420 Assert.ok(foo.data.match("You Win!")); 421 Assert.equal(alt1.status, Cr.NS_OK); 422 Assert.equal(alt1.proxy_connect_response_code, 200); 423 Assert.equal(alt1.http_code, 200); 424 Assert.ok(alt1.data.match("random-request-2")); 425 Assert.ok(alt1.data.match("You Win!")); 426 Assert.equal( 427 await proxy_session_counter(), 428 1, 429 "Created just one session with the proxy" 430 ); 431 }); 432 433 // The proxy responses with 407 instead of 200 Connected, make sure we get a proper error 434 // code from the channel and not try to ask for any credentials. 435 add_task(async function proxy_auth_failure() { 436 const chan = make_channel(`https://407.example.com/`); 437 const auth_prompt = { triggered: false }; 438 chan.notificationCallbacks = new AuthRequestor( 439 () => new UnxpectedAuthPrompt2(auth_prompt) 440 ); 441 const { status, http_code, proxy_connect_response_code } = await get_response( 442 chan, 443 CL_EXPECT_FAILURE 444 ); 445 446 Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED); 447 Assert.equal(proxy_connect_response_code, 407); 448 Assert.equal(http_code, undefined); 449 Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger"); 450 Assert.equal( 451 await proxy_session_counter(), 452 1, 453 "No new session created by 407" 454 ); 455 }); 456 457 // The proxy responses with 407 with Proxy-Authenticate header presence. Make 458 // sure that we prompt the auth prompt to ask for credentials. 459 add_task(async function proxy_auth_basic() { 460 const chan = make_channel(`https://407.basic.example.com/`); 461 const auth_prompt = { triggered: false }; 462 chan.notificationCallbacks = new AuthRequestor( 463 () => new SimpleAuthPrompt2(auth_prompt) 464 ); 465 const { status, http_code, proxy_connect_response_code } = await get_response( 466 chan, 467 CL_EXPECT_FAILURE 468 ); 469 470 // 418 indicates we pass the basic authentication. 471 Assert.equal(status, Cr.NS_ERROR_PROXY_CONNECTION_REFUSED); 472 Assert.equal(proxy_connect_response_code, 418); 473 Assert.equal(http_code, undefined); 474 Assert.equal(auth_prompt.triggered, true, "Auth prompt should trigger"); 475 Assert.equal( 476 await proxy_session_counter(), 477 1, 478 "No new session created by 407" 479 ); 480 }); 481 482 // 502 Bad gateway code returned by the proxy, still one session only, proper different code 483 // from the channel. 484 add_task(async function proxy_bad_gateway_failure() { 485 const { status, http_code, proxy_connect_response_code } = await get_response( 486 make_channel(`https://502.example.com/`), 487 CL_EXPECT_FAILURE 488 ); 489 490 Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY); 491 Assert.equal(proxy_connect_response_code, 502); 492 Assert.equal(http_code, undefined); 493 Assert.equal( 494 await proxy_session_counter(), 495 1, 496 "No new session created by 502 after 407" 497 ); 498 }); 499 500 // Second 502 Bad gateway code returned by the proxy, still one session only with the proxy. 501 add_task(async function proxy_bad_gateway_failure_two() { 502 const { status, http_code, proxy_connect_response_code } = await get_response( 503 make_channel(`https://502.example.com/`), 504 CL_EXPECT_FAILURE 505 ); 506 507 Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY); 508 Assert.equal(proxy_connect_response_code, 502); 509 Assert.equal(http_code, undefined); 510 Assert.equal( 511 await proxy_session_counter(), 512 1, 513 "No new session created by second 502" 514 ); 515 }); 516 517 // 504 Gateway timeout code returned by the proxy, still one session only, proper different code 518 // from the channel. 519 add_task(async function proxy_gateway_timeout_failure() { 520 const { status, http_code, proxy_connect_response_code } = await get_response( 521 make_channel(`https://504.example.com/`), 522 CL_EXPECT_FAILURE 523 ); 524 525 Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT); 526 Assert.equal(proxy_connect_response_code, 504); 527 Assert.equal(http_code, undefined); 528 Assert.equal( 529 await proxy_session_counter(), 530 1, 531 "No new session created by 504 after 502" 532 ); 533 }); 534 535 // 404 Not Found means the proxy could not resolve the host. As for other error responses 536 // we still expect this not to close the existing session. 537 add_task(async function proxy_host_not_found_failure() { 538 const { status, http_code, proxy_connect_response_code } = await get_response( 539 make_channel(`https://404.example.com/`), 540 CL_EXPECT_FAILURE 541 ); 542 543 Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST); 544 Assert.equal(proxy_connect_response_code, 404); 545 Assert.equal(http_code, undefined); 546 Assert.equal( 547 await proxy_session_counter(), 548 1, 549 "No new session created by 404 after 504" 550 ); 551 }); 552 553 add_task(async function proxy_too_many_requests_failure() { 554 const { status, http_code, proxy_connect_response_code } = await get_response( 555 make_channel(`https://429.example.com/`), 556 CL_EXPECT_FAILURE 557 ); 558 559 Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS); 560 Assert.equal(proxy_connect_response_code, 429); 561 Assert.equal(http_code, undefined); 562 Assert.equal( 563 await proxy_session_counter(), 564 1, 565 "No new session created by 429 after 504" 566 ); 567 }); 568 569 add_task(async function proxy_stream_reset_failure() { 570 const { status, http_code, proxy_connect_response_code } = await get_response( 571 make_channel(`https://reset.example.com/`), 572 CL_EXPECT_FAILURE 573 ); 574 575 Assert.equal(status, Cr.NS_ERROR_NET_INTERRUPT); 576 Assert.equal(proxy_connect_response_code, 0); 577 Assert.equal(http_code, undefined); 578 Assert.equal( 579 await proxy_session_counter(), 580 1, 581 "No new session created by 429 after 504" 582 ); 583 }); 584 585 // The soft errors are not closing the session. 586 add_task(async function origin_server_stream_soft_failure() { 587 var current_num_sessions_to_origin_server = 588 await proxy_session_to_origin_server_counter(); 589 590 const { status, http_code, proxy_connect_response_code } = await get_response( 591 make_channel(`https://foo.example.com/illegalhpacksoft`), 592 CL_EXPECT_FAILURE 593 ); 594 595 Assert.equal(status, Cr.NS_ERROR_ILLEGAL_VALUE); 596 Assert.equal(proxy_connect_response_code, 200); 597 Assert.equal(http_code, undefined); 598 Assert.equal( 599 await proxy_session_counter(), 600 1, 601 "No session to the proxy closed by soft stream errors" 602 ); 603 Assert.equal( 604 await proxy_session_to_origin_server_counter(), 605 current_num_sessions_to_origin_server, 606 "No session to the origin server closed by soft stream errors" 607 ); 608 }); 609 610 // The soft errors are not closing the session. 611 add_task( 612 async function origin_server_stream_soft_failure_multiple_streams_not_affected() { 613 var current_num_sessions_to_origin_server = 614 await proxy_session_to_origin_server_counter(); 615 616 let should_succeed = get_response( 617 make_channel(`https://foo.example.com/750ms`) 618 ); 619 620 const failed = await get_response( 621 make_channel(`https://foo.example.com/illegalhpacksoft`), 622 CL_EXPECT_FAILURE, 623 20 624 ); 625 626 const succeeded = await should_succeed; 627 628 Assert.equal(failed.status, Cr.NS_ERROR_ILLEGAL_VALUE); 629 Assert.equal(failed.proxy_connect_response_code, 200); 630 Assert.equal(failed.http_code, undefined); 631 Assert.equal(succeeded.status, Cr.NS_OK); 632 Assert.equal(succeeded.proxy_connect_response_code, 200); 633 Assert.equal(succeeded.http_code, 200); 634 Assert.equal( 635 await proxy_session_counter(), 636 1, 637 "No session to the proxy closed by soft stream errors" 638 ); 639 Assert.equal( 640 await proxy_session_to_origin_server_counter(), 641 current_num_sessions_to_origin_server, 642 "No session to the origin server closed by soft stream errors" 643 ); 644 } 645 ); 646 647 // Make sure that the above error codes don't kill the session to the proxy. 648 add_task(async function proxy_success_still_one_session() { 649 const foo = await get_response( 650 make_channel(`https://foo.example.com/random-request-1`) 651 ); 652 const alt1 = await get_response( 653 make_channel(`https://alt1.example.com/random-request-2`) 654 ); 655 656 Assert.equal(foo.status, Cr.NS_OK); 657 Assert.equal(foo.http_code, 200); 658 Assert.equal(foo.proxy_connect_response_code, 200); 659 Assert.ok(foo.data.match("random-request-1")); 660 Assert.equal(alt1.status, Cr.NS_OK); 661 Assert.equal(alt1.proxy_connect_response_code, 200); 662 Assert.equal(alt1.http_code, 200); 663 Assert.ok(alt1.data.match("random-request-2")); 664 Assert.equal( 665 await proxy_session_counter(), 666 1, 667 "No new session to the proxy created after stream error codes" 668 ); 669 }); 670 671 // Have a new isolation key, this means we are expected to create a new, and again one only, 672 // session with the proxy to reach the end server. 673 add_task(async function proxy_success_isolated_session() { 674 Assert.notEqual(proxy_isolation, "TOKEN2"); 675 proxy_isolation = "TOKEN2"; 676 677 const foo = await get_response( 678 make_channel(`https://foo.example.com/random-request-1`) 679 ); 680 const alt1 = await get_response( 681 make_channel(`https://alt1.example.com/random-request-2`) 682 ); 683 const lh = await get_response( 684 make_channel(`https://localhost/random-request-3`) 685 ); 686 687 Assert.equal(foo.status, Cr.NS_OK); 688 Assert.equal(foo.proxy_connect_response_code, 200); 689 Assert.equal(foo.http_code, 200); 690 Assert.ok(foo.data.match("random-request-1")); 691 Assert.ok(foo.data.match("You Win!")); 692 Assert.equal(alt1.status, Cr.NS_OK); 693 Assert.equal(alt1.proxy_connect_response_code, 200); 694 Assert.equal(alt1.http_code, 200); 695 Assert.ok(alt1.data.match("random-request-2")); 696 Assert.ok(alt1.data.match("You Win!")); 697 Assert.equal(lh.status, Cr.NS_OK); 698 Assert.equal(lh.proxy_connect_response_code, 200); 699 Assert.equal(lh.http_code, 200); 700 Assert.ok(lh.data.match("random-request-3")); 701 Assert.ok(lh.data.match("You Win!")); 702 Assert.equal( 703 await proxy_session_counter(), 704 2, 705 "Just one new session seen after changing the isolation key" 706 ); 707 }); 708 709 // Check that error codes are still handled the same way with new isolation, just in case. 710 add_task(async function proxy_bad_gateway_failure_isolated() { 711 const failure1 = await get_response( 712 make_channel(`https://502.example.com/`), 713 CL_EXPECT_FAILURE 714 ); 715 const failure2 = await get_response( 716 make_channel(`https://502.example.com/`), 717 CL_EXPECT_FAILURE 718 ); 719 720 Assert.equal(failure1.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY); 721 Assert.equal(failure1.proxy_connect_response_code, 502); 722 Assert.equal(failure1.http_code, undefined); 723 Assert.equal(failure2.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY); 724 Assert.equal(failure2.proxy_connect_response_code, 502); 725 Assert.equal(failure2.http_code, undefined); 726 Assert.equal( 727 await proxy_session_counter(), 728 2, 729 "No new session created by 502" 730 ); 731 }); 732 733 add_task(async function proxy_success_check_number_of_session() { 734 const foo = await get_response( 735 make_channel(`https://foo.example.com/random-request-1`) 736 ); 737 const alt1 = await get_response( 738 make_channel(`https://alt1.example.com/random-request-2`) 739 ); 740 const lh = await get_response( 741 make_channel(`https://localhost/random-request-3`) 742 ); 743 744 Assert.equal(foo.status, Cr.NS_OK); 745 Assert.equal(foo.proxy_connect_response_code, 200); 746 Assert.equal(foo.http_code, 200); 747 Assert.ok(foo.data.match("random-request-1")); 748 Assert.ok(foo.data.match("You Win!")); 749 Assert.equal(alt1.status, Cr.NS_OK); 750 Assert.equal(alt1.proxy_connect_response_code, 200); 751 Assert.equal(alt1.http_code, 200); 752 Assert.ok(alt1.data.match("random-request-2")); 753 Assert.ok(alt1.data.match("You Win!")); 754 Assert.equal(lh.status, Cr.NS_OK); 755 Assert.equal(lh.proxy_connect_response_code, 200); 756 Assert.equal(lh.http_code, 200); 757 Assert.ok(lh.data.match("random-request-3")); 758 Assert.ok(lh.data.match("You Win!")); 759 Assert.equal( 760 await proxy_session_counter(), 761 2, 762 "The number of sessions has not changed" 763 ); 764 }); 765 766 // The hard errors are closing the session. 767 add_task(async function origin_server_stream_hard_failure() { 768 var current_num_sessions_to_origin_server = 769 await proxy_session_to_origin_server_counter(); 770 const { status, http_code, proxy_connect_response_code } = await get_response( 771 make_channel(`https://foo.example.com/illegalhpackhard`), 772 CL_EXPECT_FAILURE 773 ); 774 775 Assert.equal(status, 0x804b0053); 776 Assert.equal(proxy_connect_response_code, 200); 777 Assert.equal(http_code, undefined); 778 Assert.equal( 779 await proxy_session_counter(), 780 2, 781 "No new session to the proxy." 782 ); 783 Assert.equal( 784 await proxy_session_to_origin_server_counter(), 785 current_num_sessions_to_origin_server, 786 "No new session to the origin server yet." 787 ); 788 789 // Check the a new session ill be opened. 790 const foo = await get_response( 791 make_channel(`https://foo.example.com/random-request-1`) 792 ); 793 794 Assert.equal(foo.status, Cr.NS_OK); 795 Assert.equal(foo.proxy_connect_response_code, 200); 796 Assert.equal(foo.http_code, 200); 797 Assert.ok(foo.data.match("random-request-1")); 798 Assert.ok(foo.data.match("You Win!")); 799 800 Assert.equal( 801 await proxy_session_counter(), 802 2, 803 "No new session to the proxy is created after a hard stream failure on the session to the origin server." 804 ); 805 Assert.equal( 806 await proxy_session_to_origin_server_counter(), 807 current_num_sessions_to_origin_server + 1, 808 "A new session to the origin server after a hard stream error" 809 ); 810 }); 811 812 // The hard errors are closing the session. 813 add_task( 814 async function origin_server_stream_hard_failure_multiple_streams_affected() { 815 var current_num_sessions_to_origin_server = 816 await proxy_session_to_origin_server_counter(); 817 let should_fail = get_response( 818 make_channel(`https://foo.example.com/750msNoData`), 819 CL_EXPECT_FAILURE 820 ); 821 const failed1 = await get_response( 822 make_channel(`https://foo.example.com/illegalhpackhard`), 823 CL_EXPECT_FAILURE, 824 10 825 ); 826 827 const failed2 = await should_fail; 828 829 Assert.equal(failed1.status, 0x804b0053); 830 Assert.equal(failed1.proxy_connect_response_code, 200); 831 Assert.equal(failed1.http_code, undefined); 832 Assert.equal(failed2.status, 0x804b0053); 833 Assert.equal(failed2.proxy_connect_response_code, 200); 834 Assert.equal(failed2.http_code, undefined); 835 Assert.equal( 836 await proxy_session_counter(), 837 2, 838 "No new session to the proxy" 839 ); 840 Assert.equal( 841 await proxy_session_to_origin_server_counter(), 842 current_num_sessions_to_origin_server, 843 "No session to the origin server yet." 844 ); 845 // Check the a new session ill be opened. 846 const foo = await get_response( 847 make_channel(`https://foo.example.com/random-request-1`) 848 ); 849 850 Assert.equal(foo.status, Cr.NS_OK); 851 Assert.equal(foo.proxy_connect_response_code, 200); 852 Assert.equal(foo.http_code, 200); 853 Assert.ok(foo.data.match("random-request-1")); 854 Assert.ok(foo.data.match("You Win!")); 855 856 Assert.equal( 857 await proxy_session_counter(), 858 2, 859 "No new session to the proxy is created after a hard stream failure on the session to the origin server." 860 ); 861 862 Assert.equal( 863 await proxy_session_to_origin_server_counter(), 864 current_num_sessions_to_origin_server + 1, 865 "A new session to the origin server after a hard stream error" 866 ); 867 } 868 );