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 = "";
|
readonly url: string = "";
|
||||||
statusText = "FIXME"; // TODO
|
statusText = "FIXME"; // TODO
|
||||||
readonly type = "basic"; // TODO
|
readonly type = "basic"; // TODO
|
||||||
redirected = false; // TODO
|
readonly redirected: boolean;
|
||||||
headers: domTypes.Headers;
|
headers: domTypes.Headers;
|
||||||
readonly trailer: Promise<domTypes.Headers>;
|
readonly trailer: Promise<domTypes.Headers>;
|
||||||
bodyUsed = false;
|
bodyUsed = false;
|
||||||
|
@ -257,6 +257,7 @@ export class Response implements domTypes.Response {
|
||||||
readonly status: number,
|
readonly status: number,
|
||||||
headersList: Array<[string, string]>,
|
headersList: Array<[string, string]>,
|
||||||
rid: number,
|
rid: number,
|
||||||
|
redirected_: boolean,
|
||||||
body_: null | Body = null
|
body_: null | Body = null
|
||||||
) {
|
) {
|
||||||
this.trailer = createResolvable();
|
this.trailer = createResolvable();
|
||||||
|
@ -268,6 +269,8 @@ export class Response implements domTypes.Response {
|
||||||
} else {
|
} else {
|
||||||
this.body = body_;
|
this.body = body_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.redirected = redirected_;
|
||||||
}
|
}
|
||||||
|
|
||||||
async arrayBuffer(): Promise<ArrayBuffer> {
|
async arrayBuffer(): Promise<ArrayBuffer> {
|
||||||
|
@ -308,7 +311,13 @@ export class Response implements domTypes.Response {
|
||||||
headersList.push(header);
|
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;
|
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. */
|
/** Fetch a resource from the network. */
|
||||||
export async function fetch(
|
export async function fetch(
|
||||||
input: domTypes.Request | string,
|
input: domTypes.Request | string,
|
||||||
|
@ -361,6 +393,8 @@ export async function fetch(
|
||||||
let method: string | null = null;
|
let method: string | null = null;
|
||||||
let headers: domTypes.Headers | null = null;
|
let headers: domTypes.Headers | null = null;
|
||||||
let body: ArrayBufferView | undefined;
|
let body: ArrayBufferView | undefined;
|
||||||
|
let redirected = false;
|
||||||
|
let remRedirectCount = 20; // TODO: use a better way to handle
|
||||||
|
|
||||||
if (typeof input === "string") {
|
if (typeof input === "string") {
|
||||||
url = input;
|
url = input;
|
||||||
|
@ -414,28 +448,48 @@ export async function fetch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send Fetch message
|
while (remRedirectCount) {
|
||||||
const builder = flatbuffers.createBuilder();
|
const inner = await getFetchRes(url, method, headers, body);
|
||||||
const headerOff = msgHttpRequest(builder, url, method, headers);
|
|
||||||
const resBase = await sendAsync(
|
|
||||||
builder,
|
|
||||||
msg.Any.Fetch,
|
|
||||||
msg.Fetch.createFetch(builder, headerOff),
|
|
||||||
body
|
|
||||||
);
|
|
||||||
|
|
||||||
// Decode FetchRes
|
const header = inner.header()!;
|
||||||
assert(msg.Any.FetchRes === resBase.innerType());
|
const bodyRid = inner.bodyRid();
|
||||||
const inner = new msg.FetchRes();
|
assert(!header.isRequest());
|
||||||
assert(resBase.inner(inner) != null);
|
const status = header.status();
|
||||||
|
|
||||||
const header = inner.header()!;
|
const headersList = deserializeHeaderFields(header);
|
||||||
const bodyRid = inner.bodyRid();
|
|
||||||
assert(!header.isRequest());
|
|
||||||
const status = header.status();
|
|
||||||
|
|
||||||
const headersList = deserializeHeaderFields(header);
|
const response = new Response(status, headersList, bodyRid, redirected);
|
||||||
|
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
||||||
const response = new Response(status, headersList, bodyRid);
|
// We're in a redirect status
|
||||||
return response;
|
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> {
|
testPerm({ net: true }, async function fetchInitStringBody(): Promise<void> {
|
||||||
const data = "Hello World";
|
const data = "Hello World";
|
||||||
const response = await fetch("http://localhost:4545/echo_server", {
|
const response = await fetch("http://localhost:4545/echo_server", {
|
||||||
|
|
|
@ -16,6 +16,7 @@ PORT = 4545
|
||||||
REDIRECT_PORT = 4546
|
REDIRECT_PORT = 4546
|
||||||
ANOTHER_REDIRECT_PORT = 4547
|
ANOTHER_REDIRECT_PORT = 4547
|
||||||
DOUBLE_REDIRECTS_PORT = 4548
|
DOUBLE_REDIRECTS_PORT = 4548
|
||||||
|
INF_REDIRECTS_PORT = 4549
|
||||||
|
|
||||||
QUIET = '-v' not in sys.argv and '--verbose' not in sys.argv
|
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)
|
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):
|
def start(s):
|
||||||
thread = Thread(target=s.serve_forever, kwargs={"poll_interval": 0.05})
|
thread = Thread(target=s.serve_forever, kwargs={"poll_interval": 0.05})
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
|
@ -175,7 +181,7 @@ def spawn():
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
servers = (server(), redirect_server(), another_redirect_server(),
|
servers = (server(), redirect_server(), another_redirect_server(),
|
||||||
double_redirects_server())
|
double_redirects_server(), inf_redirects_server())
|
||||||
try:
|
try:
|
||||||
while all(s.thread.is_alive() for s in servers):
|
while all(s.thread.is_alive() for s in servers):
|
||||||
sleep(10)
|
sleep(10)
|
||||||
|
|
Loading…
Reference in a new issue