mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -05:00
feat: fetch() now handles redirects (#2561)
This commit is contained in:
parent
d82089ca35
commit
1d0d54247c
3 changed files with 110 additions and 24 deletions
100
js/fetch.ts
100
js/fetch.ts
|
@ -247,7 +247,7 @@ export class Response implements domTypes.Response {
|
|||
readonly url: string = "";
|
||||
statusText = "FIXME"; // TODO
|
||||
readonly type = "basic"; // TODO
|
||||
redirected = false; // TODO
|
||||
readonly redirected: boolean;
|
||||
headers: domTypes.Headers;
|
||||
readonly trailer: Promise<domTypes.Headers>;
|
||||
bodyUsed = false;
|
||||
|
@ -257,6 +257,7 @@ export class Response implements domTypes.Response {
|
|||
readonly status: number,
|
||||
headersList: Array<[string, string]>,
|
||||
rid: number,
|
||||
redirected_: boolean,
|
||||
body_: null | Body = null
|
||||
) {
|
||||
this.trailer = createResolvable();
|
||||
|
@ -268,6 +269,8 @@ export class Response implements domTypes.Response {
|
|||
} else {
|
||||
this.body = body_;
|
||||
}
|
||||
|
||||
this.redirected = redirected_;
|
||||
}
|
||||
|
||||
async arrayBuffer(): Promise<ArrayBuffer> {
|
||||
|
@ -308,7 +311,13 @@ export class Response implements domTypes.Response {
|
|||
headersList.push(header);
|
||||
}
|
||||
|
||||
return new Response(this.status, headersList, -1, this.body);
|
||||
return new Response(
|
||||
this.status,
|
||||
headersList,
|
||||
-1,
|
||||
this.redirected,
|
||||
this.body
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,6 +361,29 @@ function deserializeHeaderFields(m: msg.HttpHeader): Array<[string, string]> {
|
|||
return out;
|
||||
}
|
||||
|
||||
async function getFetchRes(
|
||||
url: string,
|
||||
method: string | null,
|
||||
headers: domTypes.Headers | null,
|
||||
body: ArrayBufferView | undefined
|
||||
): Promise<msg.FetchRes> {
|
||||
// Send Fetch message
|
||||
const builder = flatbuffers.createBuilder();
|
||||
const headerOff = msgHttpRequest(builder, url, method, headers);
|
||||
const resBase = await sendAsync(
|
||||
builder,
|
||||
msg.Any.Fetch,
|
||||
msg.Fetch.createFetch(builder, headerOff),
|
||||
body
|
||||
);
|
||||
|
||||
// Decode FetchRes
|
||||
assert(msg.Any.FetchRes === resBase.innerType());
|
||||
const inner = new msg.FetchRes();
|
||||
assert(resBase.inner(inner) != null);
|
||||
return inner;
|
||||
}
|
||||
|
||||
/** Fetch a resource from the network. */
|
||||
export async function fetch(
|
||||
input: domTypes.Request | string,
|
||||
|
@ -361,6 +393,8 @@ export async function fetch(
|
|||
let method: string | null = null;
|
||||
let headers: domTypes.Headers | null = null;
|
||||
let body: ArrayBufferView | undefined;
|
||||
let redirected = false;
|
||||
let remRedirectCount = 20; // TODO: use a better way to handle
|
||||
|
||||
if (typeof input === "string") {
|
||||
url = input;
|
||||
|
@ -414,28 +448,48 @@ export async function fetch(
|
|||
}
|
||||
}
|
||||
|
||||
// Send Fetch message
|
||||
const builder = flatbuffers.createBuilder();
|
||||
const headerOff = msgHttpRequest(builder, url, method, headers);
|
||||
const resBase = await sendAsync(
|
||||
builder,
|
||||
msg.Any.Fetch,
|
||||
msg.Fetch.createFetch(builder, headerOff),
|
||||
body
|
||||
);
|
||||
while (remRedirectCount) {
|
||||
const inner = await getFetchRes(url, method, headers, body);
|
||||
|
||||
// Decode FetchRes
|
||||
assert(msg.Any.FetchRes === resBase.innerType());
|
||||
const inner = new msg.FetchRes();
|
||||
assert(resBase.inner(inner) != null);
|
||||
const header = inner.header()!;
|
||||
const bodyRid = inner.bodyRid();
|
||||
assert(!header.isRequest());
|
||||
const status = header.status();
|
||||
|
||||
const header = inner.header()!;
|
||||
const bodyRid = inner.bodyRid();
|
||||
assert(!header.isRequest());
|
||||
const status = header.status();
|
||||
const headersList = deserializeHeaderFields(header);
|
||||
|
||||
const headersList = deserializeHeaderFields(header);
|
||||
|
||||
const response = new Response(status, headersList, bodyRid);
|
||||
return response;
|
||||
const response = new Response(status, headersList, bodyRid, redirected);
|
||||
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
||||
// We're in a redirect status
|
||||
switch ((init && init.redirect) || "follow") {
|
||||
case "error":
|
||||
throw notImplemented();
|
||||
case "manual":
|
||||
throw notImplemented();
|
||||
case "follow":
|
||||
default:
|
||||
let redirectUrl = response.headers.get("Location");
|
||||
if (redirectUrl == null) {
|
||||
return response; // Unspecified
|
||||
}
|
||||
if (
|
||||
!redirectUrl.startsWith("http://") &&
|
||||
!redirectUrl.startsWith("https://")
|
||||
) {
|
||||
redirectUrl =
|
||||
url.split("//")[0] +
|
||||
"//" +
|
||||
url.split("//")[1].split("/")[0] +
|
||||
redirectUrl; // TODO: handle relative redirection more gracefully
|
||||
}
|
||||
url = redirectUrl;
|
||||
redirected = true;
|
||||
remRedirectCount--;
|
||||
}
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
// Return a network error due to too many redirections
|
||||
throw notImplemented();
|
||||
}
|
||||
|
|
|
@ -99,6 +99,32 @@ testPerm(
|
|||
}
|
||||
);
|
||||
|
||||
testPerm({ net: true }, async function fetchWithRedirection(): Promise<void> {
|
||||
const response = await fetch("http://localhost:4546/"); // will redirect to http://localhost:4545/
|
||||
assertEquals(response.status, 200);
|
||||
const body = await response.text();
|
||||
assert(body.includes("<title>Directory listing for /</title>"));
|
||||
});
|
||||
|
||||
testPerm({ net: true }, async function fetchWithRelativeRedirection(): Promise<
|
||||
void
|
||||
> {
|
||||
const response = await fetch("http://localhost:4545/tests"); // will redirect to /tests/
|
||||
assertEquals(response.status, 200);
|
||||
const body = await response.text();
|
||||
assert(body.includes("<title>Directory listing for /tests/</title>"));
|
||||
});
|
||||
|
||||
// The feature below is not implemented, but the test should work after implementation
|
||||
/*
|
||||
testPerm({ net: true }, async function fetchWithInfRedirection(): Promise<
|
||||
void
|
||||
> {
|
||||
const response = await fetch("http://localhost:4549/tests"); // will redirect to the same place
|
||||
assertEquals(response.status, 0); // network error
|
||||
});
|
||||
*/
|
||||
|
||||
testPerm({ net: true }, async function fetchInitStringBody(): Promise<void> {
|
||||
const data = "Hello World";
|
||||
const response = await fetch("http://localhost:4545/echo_server", {
|
||||
|
|
|
@ -16,6 +16,7 @@ PORT = 4545
|
|||
REDIRECT_PORT = 4546
|
||||
ANOTHER_REDIRECT_PORT = 4547
|
||||
DOUBLE_REDIRECTS_PORT = 4548
|
||||
INF_REDIRECTS_PORT = 4549
|
||||
|
||||
QUIET = '-v' not in sys.argv and '--verbose' not in sys.argv
|
||||
|
||||
|
@ -153,6 +154,11 @@ def double_redirects_server():
|
|||
return base_redirect_server(DOUBLE_REDIRECTS_PORT, REDIRECT_PORT)
|
||||
|
||||
|
||||
# redirect server that points to itself
|
||||
def inf_redirects_server():
|
||||
return base_redirect_server(INF_REDIRECTS_PORT, INF_REDIRECTS_PORT)
|
||||
|
||||
|
||||
def start(s):
|
||||
thread = Thread(target=s.serve_forever, kwargs={"poll_interval": 0.05})
|
||||
thread.daemon = True
|
||||
|
@ -175,7 +181,7 @@ def spawn():
|
|||
|
||||
def main():
|
||||
servers = (server(), redirect_server(), another_redirect_server(),
|
||||
double_redirects_server())
|
||||
double_redirects_server(), inf_redirects_server())
|
||||
try:
|
||||
while all(s.thread.is_alive() for s in servers):
|
||||
sleep(10)
|
||||
|
|
Loading…
Reference in a new issue