1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
HttpServer: "resource://testing-common/httpd.sys.mjs",
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
});
const PingServer = {
_httpServer: null,
_started: false,
_defers: [Promise.withResolvers()],
_currentDeferred: 0,
get port() {
return this._httpServer.identity.primaryPort;
},
get host() {
return this._httpServer.identity.primaryHost;
},
get started() {
return this._started;
},
registerPingHandler(handler) {
this._httpServer.registerPrefixHandler("/submit/", handler);
},
resetPingHandler() {
this.registerPingHandler(request => {
let r = request;
console.trace(
`defaultPingHandler() - ${r.method} ${r.scheme}://${r.host}:${r.port}${r.path}`
);
let deferred = this._defers[this._defers.length - 1];
this._defers.push(Promise.withResolvers());
deferred.resolve(request);
});
},
start() {
this._httpServer = new HttpServer();
this._httpServer.start(-1);
this._started = true;
this.clearRequests();
this.resetPingHandler();
},
stop() {
return new Promise(resolve => {
this._httpServer.stop(resolve);
this._started = false;
});
},
clearRequests() {
this._defers = [Promise.withResolvers()];
this._currentDeferred = 0;
},
promiseNextRequest() {
const deferred = this._defers[this._currentDeferred++];
// Send the ping to the consumer on the next tick, so that the completion gets
// signaled to Telemetry.
return new Promise(r =>
Services.tm.dispatchToMainThread(() => r(deferred.promise))
);
},
promiseNextPing() {
return this.promiseNextRequest().then(request =>
decodeRequestPayload(request)
);
},
async promiseNextRequests(count) {
let results = [];
for (let i = 0; i < count; ++i) {
results.push(await this.promiseNextRequest());
}
return results;
},
promiseNextPings(count) {
return this.promiseNextRequests(count).then(requests => {
return Array.from(requests, decodeRequestPayload);
});
},
};
/**
* Decode the payload of an HTTP request into a ping.
*
* @param {object} request The data representing an HTTP request (nsIHttpRequest).
* @returns {object} The decoded ping payload.
*/
function decodeRequestPayload(request) {
let s = request.bodyInputStream;
let payload = null;
if (
request.hasHeader("content-encoding") &&
request.getHeader("content-encoding") == "gzip"
) {
let observer = {
buffer: "",
onStreamComplete(loader, context, status, length, result) {
// String.fromCharCode can only deal with 500,000 characters
// at a time, so chunk the result into parts of that size.
const chunkSize = 500000;
for (let offset = 0; offset < result.length; offset += chunkSize) {
this.buffer += String.fromCharCode.apply(
String,
result.slice(offset, offset + chunkSize)
);
}
},
};
let scs = Cc["@mozilla.org/streamConverters;1"].getService(
Ci.nsIStreamConverterService
);
let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
Ci.nsIStreamLoader
);
listener.init(observer);
let converter = scs.asyncConvertData(
"gzip",
"uncompressed",
listener,
null
);
converter.onStartRequest(null, null);
converter.onDataAvailable(null, s, 0, s.available());
converter.onStopRequest(null, null, null);
// TODO: nsIScriptableUnicodeConverter is deprecated
// But I can't figure out how else to ungzip bodyInputStream.
let unicodeConverter = Cc[
"@mozilla.org/intl/scriptableunicodeconverter"
].createInstance(Ci.nsIScriptableUnicodeConverter);
unicodeConverter.charset = "UTF-8";
let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer);
utf8string += unicodeConverter.Finish();
payload = JSON.parse(utf8string);
} else {
let bytes = NetUtil.readInputStream(s, s.available());
payload = JSON.parse(new TextDecoder().decode(bytes));
}
return payload;
}
|