1
0
Fork 0
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:
Long(Tony) Lian 2019-06-24 06:34:09 -07:00 committed by Ryan Dahl
parent d82089ca35
commit 1d0d54247c
3 changed files with 110 additions and 24 deletions

View file

@ -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();
} }

View file

@ -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", {

View file

@ -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)