Compare commits

...

3 Commits

Author SHA1 Message Date
deepak1556
385f3eacd1 chore: update docs 2025-01-22 20:16:57 +09:00
deepak1556
cc8aefb554 chore: update spec 2025-01-22 20:13:29 +09:00
deepak1556
861fe4fc36 fix: support response.url in net.fetch 2025-01-22 20:13:29 +09:00
11 changed files with 149 additions and 19 deletions

View File

@@ -102,3 +102,8 @@ duplicates are not merged.
// '*/*' ]
console.log(response.rawHeaders)
```
#### `response.urlList`
A `string[]` containing the chain of urls traversed by this request.
If the request had no redirects, this array will contain one element.

View File

@@ -97,8 +97,7 @@ Limitations:
* `net.fetch()` does not support the `data:` or `blob:` schemes.
* The value of the `integrity` option is ignored.
* The `.type` and `.url` values of the returned `Response` object are
incorrect.
* The `.type` value of the returned `Response` object is incorrect.
By default, requests made with `net.fetch` can be made to [custom protocols](protocol.md)
as well as `file:`, and will trigger [webRequest](web-request.md) handlers if present.

View File

@@ -106,11 +106,21 @@ export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypa
}
const nullBodyStatus = [101, 204, 205, 304];
const body = nullBodyStatus.includes(resp.statusCode) || req.method === 'HEAD' ? null : Readable.toWeb(resp as unknown as Readable) as ReadableStream;
type ResponseInitWithUrlList = ResponseInit & { urlList?: URL[] };
const urlList = resp.urlList.map((url) => {
let parsedURL;
try {
parsedURL = new URL(url);
} catch {
}
return parsedURL;
});
const rResp = new Response(body, {
headers,
status: resp.statusCode,
statusText: resp.statusMessage
});
statusText: resp.statusMessage,
urlList
} as ResponseInitWithUrlList);
(rResp as any).__original_resp = resp;
p.resolve(rResp);
});

View File

@@ -98,6 +98,10 @@ class IncomingMessage extends Readable {
throw new Error('HTTP trailers are not supported');
}
get urlList () {
return this._responseHead.urlList;
}
_storeInternalData (chunk: Buffer | null, resume: (() => void) | null) {
// save the network callback for use in _pushInternalData
this._resume = resume;
@@ -460,7 +464,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
}
});
this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
this._urlLoader.on('redirect', (event, redirectInfo, responseHead) => {
const { statusCode, newMethod, newUrl } = redirectInfo;
if (this._redirectPolicy === 'error') {
this._die(new Error('Attempted to redirect, but redirect policy was \'error\''));
@@ -468,7 +472,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
let _followRedirect = false;
this._followRedirectCb = () => { _followRedirect = true; };
try {
this.emit('redirect', statusCode, newMethod, newUrl, headers);
this.emit('redirect', statusCode, newMethod, newUrl, responseHead.headers);
} finally {
this._followRedirectCb = undefined;
if (!_followRedirect && !this._aborted) {
@@ -481,7 +485,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
// though...? Since the redirect will happen regardless.)
try {
this._followRedirectCb = () => {};
this.emit('redirect', statusCode, newMethod, newUrl, headers);
this.emit('redirect', statusCode, newMethod, newUrl, responseHead.headers);
} finally {
this._followRedirectCb = undefined;
}

View File

@@ -140,3 +140,4 @@ build_disable_thin_lto_mac.patch
build_add_public_config_simdutf_config.patch
revert_code_health_clean_up_stale_macwebcontentsocclusion.patch
check_for_unit_to_activate_before_notifying_about_success.patch
feat_expose_url_chain_from_urlloader.patch

View File

@@ -0,0 +1,38 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: deepak1556 <hop2deep@gmail.com>
Date: Tue, 19 Nov 2024 17:52:08 +0900
Subject: feat: expose url chain from urlloader
The patch adds the url chain to the URLResponseHead struct. This is
used to support the Response.url property in the Fetch API.
Should be upstreamed.
diff --git a/services/network/public/mojom/url_response_head.mojom b/services/network/public/mojom/url_response_head.mojom
index e7390e01f113755613f42d592b36108b703960dc..123636abfd4e8497668356202353cb08a48e99fc 100644
--- a/services/network/public/mojom/url_response_head.mojom
+++ b/services/network/public/mojom/url_response_head.mojom
@@ -53,6 +53,10 @@ struct URLResponseHead {
// Actual response headers, as obtained from the network stack.
array<HttpRawHeaderPair> raw_response_headers;
+ // The chain of urls traversed by this request. If the request had no
+ // redirects, this vector will contain one element.
+ array<url.mojom.Url> url_list;
+
// The mime type of the response. This may be a derived value.
string mime_type;
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index beb3fe75a654510b60b62ab29380c7ff243bb095..fdf3fc5d7edc902ce7cfd23e5cdd1fa97d744120 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -1537,6 +1537,8 @@ mojom::URLResponseHeadPtr URLLoader::BuildResponseHead() const {
response->load_with_storage_access = ShouldSetLoadWithStorageAccess();
+ response->url_list = url_request_->url_chain();
+
return response;
}

View File

@@ -47,3 +47,4 @@ build_allow_unbundling_of_node_js_dependencies.patch
test_use_static_method_names_in_call_stacks.patch
build_use_third_party_simdutf.patch
fix_remove_fastapitypedarray_usage.patch
feat_undici_support_urllist_initialization_in_response_ctor.patch

View File

@@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: deepak1556 <hop2deep@gmail.com>
Date: Tue, 19 Nov 2024 17:54:33 +0900
Subject: feat(undici): support urlList initialization in Response ctor
Extends the Response constructor to initialize with urlList array,
which will be used to support the url getter.
Should be upstreamed.
diff --git a/deps/undici/undici.js b/deps/undici/undici.js
index acbece9168787204c1b5d27aa05c6709899d0847..eb4dc9497acb880a2d00fe319d56129d65084719 100644
--- a/deps/undici/undici.js
+++ b/deps/undici/undici.js
@@ -9050,6 +9050,9 @@ var require_response = __commonJS({
if ("headers" in init && init.headers != null) {
fill(response[kHeaders], init.headers);
}
+ if ("urlList" in init && init.urlList != null) {
+ response[kState].urlList = [...init.urlList];
+ }
if (body) {
if (nullBodyStatus.includes(response.status)) {
throw webidl.errors.exception({
@@ -9126,6 +9129,11 @@ var require_response = __commonJS({
{
key: "headers",
converter: webidl.converters.HeadersInit
+ },
+ {
+ key: "urlList",
+ converter: webidl.converters.any,
+ defaultValue: () => []
}
]);
module2.exports = {

View File

@@ -125,6 +125,23 @@ struct Converter<net::ReferrerPolicy> {
return FromV8WithLowerLookup(isolate, val, Lookup, out);
}
};
template <>
struct Converter<network::mojom::URLResponseHead> {
static v8::Local<v8::Value> ToV8(
v8::Isolate* isolate,
const network::mojom::URLResponseHead& response_head) {
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
dict.Set("statusCode", response_head.headers->response_code());
dict.Set("statusMessage", response_head.headers->GetStatusText());
dict.Set("httpVersion", response_head.headers->GetHttpVersion());
dict.Set("headers", response_head.headers.get());
dict.Set("rawHeaders", response_head.raw_response_headers);
dict.Set("mimeType", response_head.mime_type);
dict.Set("urlList", response_head.url_list);
return dict.GetHandle();
}
};
} // namespace gin
namespace electron::api {
@@ -739,16 +756,7 @@ void SimpleURLLoaderWrapper::OnComplete(bool success) {
void SimpleURLLoaderWrapper::OnResponseStarted(
const GURL& final_url,
const network::mojom::URLResponseHead& response_head) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
auto dict = gin::Dictionary::CreateEmpty(isolate);
dict.Set("statusCode", response_head.headers->response_code());
dict.Set("statusMessage", response_head.headers->GetStatusText());
dict.Set("httpVersion", response_head.headers->GetHttpVersion());
dict.Set("headers", response_head.headers.get());
dict.Set("rawHeaders", response_head.raw_response_headers);
dict.Set("mimeType", response_head.mime_type);
Emit("response-started", final_url, dict);
Emit("response-started", final_url, response_head);
}
void SimpleURLLoaderWrapper::OnRedirect(
@@ -756,7 +764,7 @@ void SimpleURLLoaderWrapper::OnRedirect(
const net::RedirectInfo& redirect_info,
const network::mojom::URLResponseHead& response_head,
std::vector<std::string>* removed_headers) {
Emit("redirect", redirect_info, response_head.headers.get());
Emit("redirect", redirect_info, response_head);
if (!loader_)
// The redirect was aborted by JS.

View File

@@ -1524,6 +1524,7 @@ describe('net module', () => {
});
const resp = await net.fetch(serverUrl);
expect(resp.ok).to.be.true();
expect(resp.url).to.equal(serverUrl);
expect(await resp.text()).to.equal('test');
});
@@ -1536,6 +1537,7 @@ describe('net module', () => {
method: 'POST',
body: 'anchovies'
});
expect(resp.url).to.equal(serverUrl);
expect(await resp.text()).to.equal('anchovies');
});
@@ -1548,6 +1550,7 @@ describe('net module', () => {
method: 'POST',
body: 'anchovies'
});
expect(resp.url).to.equal(serverUrl);
expect(new TextDecoder().decode(new Uint8Array(await resp.arrayBuffer()))).to.equal('anchovies');
});
@@ -1557,6 +1560,7 @@ describe('net module', () => {
response.end('foo=bar');
});
const resp = await net.fetch(serverUrl);
expect(resp.url).to.equal(serverUrl);
const result = await resp.formData();
expect(result.get('foo')).to.equal('bar');
});
@@ -1573,8 +1577,31 @@ describe('net module', () => {
});
const r = await net.fetch(serverUrl);
expect(r.status).to.equal(200);
expect(r.url).to.equal(serverUrl);
await expect(r.text()).to.be.rejectedWith(/ERR_INCOMPLETE_CHUNKED_ENCODING/);
});
test('should follow redirect when mode is follow', async () => {
const serverUrl = await respondOnce.toRoutes({
'/redirectChain': (request, response) => {
response.statusCode = 302;
response.setHeader('Location', '/302');
response.end();
},
'/302': (request, response) => {
response.statusCode = 302;
response.setHeader('Location', '/200');
response.end();
},
'/200': (request, response) => {
response.statusCode = 200;
response.end();
}
});
const resp = await net.fetch(`${serverUrl}/redirectChain`);
expect(resp.status).to.equal(200);
expect(resp.url).to.equal(`${serverUrl}/200`);
});
});
});

View File

@@ -175,6 +175,7 @@ declare namespace NodeJS {
httpVersion: { major: number, minor: number };
rawHeaders: { key: string, value: string }[];
headers: Record<string, string[]>;
urlList: string[];
};
type RedirectInfo = {
@@ -194,7 +195,7 @@ declare namespace NodeJS {
on(eventName: 'complete', listener: (event: any) => void): this;
on(eventName: 'error', listener: (event: any, netErrorString: string) => void): this;
on(eventName: 'login', listener: (event: any, authInfo: Electron.AuthInfo, callback: (username?: string, password?: string) => void) => void): this;
on(eventName: 'redirect', listener: (event: any, redirectInfo: RedirectInfo, headers: Record<string, string>) => void): this;
on(eventName: 'redirect', listener: (event: any, redirectInfo: RedirectInfo, responseHead: ResponseHead) => void): this;
on(eventName: 'upload-progress', listener: (event: any, position: number, total: number) => void): this;
on(eventName: 'download-progress', listener: (event: any, current: number) => void): this;
}