From 9cfb8247c96ff927bf0ae4d4b97d7216712dfd90 Mon Sep 17 00:00:00 2001 From: Tom Tang Date: Tue, 9 Jul 2024 06:07:51 +0000 Subject: [PATCH 1/3] feat(XHR): print debug logs with a unique connection id to identify each XHR connection --- .../XMLHttpRequest-internal.d.ts | 3 ++ .../XMLHttpRequest-internal.py | 3 +- .../builtin_modules/XMLHttpRequest.js | 42 +++++++++++++------ 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.d.ts b/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.d.ts index bdb159b0..2d8c4495 100644 --- a/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.d.ts +++ b/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.d.ts @@ -46,6 +46,9 @@ export declare function request( // callbacks for known exceptions onTimeoutError: (err: Error) => void, onNetworkError: (err: Error) => void, + // the debug logging function + /** See `pm.bootstrap.require("debug")` */ + debug: (selector: string) => ((...args: string[]) => void), ): Promise; /** diff --git a/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.py b/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.py index e3ea1824..ca716d88 100644 --- a/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.py +++ b/python/pythonmonkey/builtin_modules/XMLHttpRequest-internal.py @@ -44,9 +44,10 @@ async def request( # callbacks for known exceptions onTimeoutError: Callable[[asyncio.TimeoutError], None], onNetworkError: Callable[[aiohttp.ClientError], None], + # the debug logging function, see `pm.bootstrap.require("debug")` + debug: Callable[[str], Callable[..., None]], / ): - debug = pm.bootstrap.require("debug") # to support HTTP-Keep-Alive global keepAliveConnector diff --git a/python/pythonmonkey/builtin_modules/XMLHttpRequest.js b/python/pythonmonkey/builtin_modules/XMLHttpRequest.js index 6db9bc04..a3cdf133 100644 --- a/python/pythonmonkey/builtin_modules/XMLHttpRequest.js +++ b/python/pythonmonkey/builtin_modules/XMLHttpRequest.js @@ -20,7 +20,7 @@ const debug = globalThis.python.eval('__import__("pythonmonkey").bootstrap.requi * @param {any} what The thing to truncate; must have a slice method and index property. * Works with string, array, typedarray, etc. * @param {number} maxlen The maximum length for truncation - * @param {boolean} coerce Not false = coerce to printable character codes + * @param {boolean=} coerce Not false = coerce to printable character codes * @returns {string} */ function trunc(what, maxlen, coerce) @@ -104,6 +104,21 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget /** @type {EventListenerFn} */ onreadystatechange = null; + // + // debugging + // + /** The unique connection id to identify each XHR connection when debugging */ + #connectionId = Math.random().toString(16).slice(2, 9); // random 7-character hex string + + /** + * Wrapper to print debug logs with connection id information + * @param {string} selector + */ + #debug(selector) + { + return (...args) => debug(selector)(`Conn<${this.#connectionId}>:`, ...args); + } + // // states // @@ -142,7 +157,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget */ open(method, url, async = true, username = null, password = null) { - debug('xhr:open')('open start, method=' + method); + this.#debug('xhr:open')('open start, method=' + method); // Normalize the method. // @ts-expect-error method = method.toString().toUpperCase(); @@ -156,7 +171,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget parsedURL.username = username; if (password) parsedURL.password = password; - debug('xhr:open')('url is ' + parsedURL.href); + this.#debug('xhr:open')('url is ' + parsedURL.href); // step 11 this.#sendFlag = false; @@ -176,7 +191,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget this.#state = XMLHttpRequest.OPENED; this.dispatchEvent(new Event('readystatechange')); } - debug('xhr:open')('finished open, state is ' + this.#state); + this.#debug('xhr:open')('finished open, state is ' + this.#state); } /** @@ -186,7 +201,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget */ setRequestHeader(name, value) { - debug('xhr:headers')(`set header ${name}=${value}`); + this.#debug('xhr:headers')(`set header ${name}=${value}`); if (this.#state !== XMLHttpRequest.OPENED) throw new DOMException('setRequestHeader can only be called when state is OPEN', 'InvalidStateError'); if (this.#sendFlag) @@ -252,7 +267,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget */ send(body = null) { - debug('xhr:send')(`sending; body length=${body?.length}`); + this.#debug('xhr:send')(`sending; body length=${body?.length}`); if (this.#state !== XMLHttpRequest.OPENED) // step 1 throw new DOMException('connection must be opened before send() is called', 'InvalidStateError'); if (this.#sendFlag) // step 2 @@ -285,7 +300,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget if (!originalAuthorContentType && extractedContentType) this.#requestHeaders['content-type'] = extractedContentType; } - debug('xhr:send')(`content-type=${this.#requestHeaders['content-type']}`); + this.#debug('xhr:send')(`content-type=${this.#requestHeaders['content-type']}`); // step 5 if (this.#uploadObject._hasAnyListeners()) @@ -310,7 +325,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget */ #sendAsync() { - debug('xhr:send')('sending in async mode'); + this.#debug('xhr:send')('sending in async mode'); this.dispatchEvent(new ProgressEvent('loadstart', { loaded:0, total:0 })); // step 11.1 let requestBodyTransmitted = 0; // step 11.2 @@ -343,7 +358,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget let responseLength = 0; const processResponse = (response) => { - debug('xhr:response')(`response headers ----\n${response.getAllResponseHeaders()}`); + this.#debug('xhr:response')(`response headers ----\n${response.getAllResponseHeaders()}`); this.#response = response; // step 11.9.1 this.#state = XMLHttpRequest.HEADERS_RECEIVED; // step 11.9.4 this.dispatchEvent(new Event('readystatechange')); // step 11.9.5 @@ -354,7 +369,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget const processBodyChunk = (/** @type {Uint8Array} */ bytes) => { - debug('xhr:response')(`recv chunk, ${bytes.length} bytes (${trunc(bytes, 100)})`); + this.#debug('xhr:response')(`recv chunk, ${bytes.length} bytes (${trunc(bytes, 100)})`); this.#receivedBytes.push(bytes); if (this.#state === XMLHttpRequest.HEADERS_RECEIVED) this.#state = XMLHttpRequest.LOADING; @@ -367,7 +382,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget */ const processEndOfBody = () => { - debug('xhr:response')(`end of body, received ${this.#receivedLength} bytes`); + this.#debug('xhr:response')(`end of body, received ${this.#receivedLength} bytes`); const transmitted = this.#receivedLength; // step 3 const length = responseLength || 0; // step 4 @@ -380,8 +395,8 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget this.dispatchEvent(new ProgressEvent(eventType, { loaded:transmitted, total:length })); }; - debug('xhr:send')(`${this.#requestMethod} ${this.#requestURL.href}`); - debug('xhr:headers')('headers=' + Object.entries(this.#requestHeaders)); + this.#debug('xhr:send')(`${this.#requestMethod} ${this.#requestURL.href}`); + this.#debug('xhr:headers')('headers=' + Object.entries(this.#requestHeaders)); // send() step 6 request( @@ -397,6 +412,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget processEndOfBody, () => (this.#timedOutFlag = true), // onTimeoutError () => (this.#response = null /* network error */), // onNetworkError + this.#debug.bind(this), ).catch((e) => this.#handleErrors(e)); } From d6639cd8927a4bf19ef1081401e305b9191b10ca Mon Sep 17 00:00:00 2001 From: Tom Tang Date: Tue, 9 Jul 2024 06:45:58 +0000 Subject: [PATCH 2/3] feat(XHR): print the request body contents in debug logs --- python/pythonmonkey/builtin_modules/XMLHttpRequest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/pythonmonkey/builtin_modules/XMLHttpRequest.js b/python/pythonmonkey/builtin_modules/XMLHttpRequest.js index a3cdf133..26de00c1 100644 --- a/python/pythonmonkey/builtin_modules/XMLHttpRequest.js +++ b/python/pythonmonkey/builtin_modules/XMLHttpRequest.js @@ -267,7 +267,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget */ send(body = null) { - this.#debug('xhr:send')(`sending; body length=${body?.length}`); + this.#debug('xhr:send')(`sending; body length=${body?.length} «${body ? trunc(body, 100) : ''}»`); if (this.#state !== XMLHttpRequest.OPENED) // step 1 throw new DOMException('connection must be opened before send() is called', 'InvalidStateError'); if (this.#sendFlag) // step 2 @@ -369,7 +369,7 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget const processBodyChunk = (/** @type {Uint8Array} */ bytes) => { - this.#debug('xhr:response')(`recv chunk, ${bytes.length} bytes (${trunc(bytes, 100)})`); + this.#debug('xhr:response')(`recv chunk, ${bytes.length} bytes «${trunc(bytes, 100)}»`); this.#receivedBytes.push(bytes); if (this.#state === XMLHttpRequest.HEADERS_RECEIVED) this.#state = XMLHttpRequest.LOADING; From 1c09b2b964be779cb1992930f9ff70639a9aac0a Mon Sep 17 00:00:00 2001 From: Tom Tang Date: Tue, 9 Jul 2024 08:10:51 +0000 Subject: [PATCH 3/3] feat(XHR): add `_requestMetadata` accessor to allow others to inspect the internal properties --- .../pythonmonkey/builtin_modules/XMLHttpRequest.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/python/pythonmonkey/builtin_modules/XMLHttpRequest.js b/python/pythonmonkey/builtin_modules/XMLHttpRequest.js index 26de00c1..3cbacfc1 100644 --- a/python/pythonmonkey/builtin_modules/XMLHttpRequest.js +++ b/python/pythonmonkey/builtin_modules/XMLHttpRequest.js @@ -119,6 +119,19 @@ class XMLHttpRequest extends XMLHttpRequestEventTarget return (...args) => debug(selector)(`Conn<${this.#connectionId}>:`, ...args); } + /** + * Allowing others to inspect the internal properties + */ + get _requestMetadata() + { + return { + method: this.#requestMethod, + url: this.#requestURL.toString(), + headers: this.#requestHeaders, + body: this.#requestBody, + }; + } + // // states //