0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

Add support for fetch() headers (#727)

This commit is contained in:
qti3e 2018-09-12 23:46:42 +04:30 committed by Ryan Dahl
parent cb6c78c6d2
commit 41c70b154f
8 changed files with 137 additions and 21 deletions

View file

@ -1,11 +1,7 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license. // Copyright 2018 the Deno authors. All rights reserved. MIT license.
// Public deno module. // Public deno module.
/// <amd-module name="deno"/> /// <amd-module name="deno"/>
export { export { env, exit, makeTempDirSync } from "./os";
env,
exit,
makeTempDirSync
} from "./os";
export { mkdirSync, mkdir } from "./mkdir"; export { mkdirSync, mkdir } from "./mkdir";
export { removeSync, remove, removeAllSync, removeAll } from "./remove"; export { removeSync, remove, removeAllSync, removeAll } from "./remove";
export { readFileSync, readFile } from "./read_file"; export { readFileSync, readFile } from "./read_file";

View file

@ -16,19 +16,75 @@ import {
Response, Response,
Blob, Blob,
RequestInit, RequestInit,
HeadersInit,
FormData FormData
} from "./fetch_types"; } from "./fetch_types";
import { TextDecoder } from "./text_encoding"; import { TextDecoder } from "./text_encoding";
class DenoHeaders implements Headers { interface Header {
append(name: string, value: string): void { name: string;
assert(false, "Implement me"); value: string;
} }
export class DenoHeaders implements Headers {
private readonly headerList: Header[] = [];
constructor(init?: HeadersInit) {
if (init) {
this._fill(init);
}
}
private _append(header: Header): void {
// TODO(qti3e) Check header based on the fetch spec.
this._appendToHeaderList(header);
}
private _appendToHeaderList(header: Header): void {
const lowerCaseName = header.name.toLowerCase();
for (let i = 0; i < this.headerList.length; ++i) {
if (this.headerList[i].name.toLowerCase() === lowerCaseName) {
header.name = this.headerList[i].name;
}
}
this.headerList.push(header);
}
private _fill(init: HeadersInit): void {
if (Array.isArray(init)) {
for (let i = 0; i < init.length; ++i) {
const header = init[i];
if (header.length !== 2) {
throw new TypeError("Failed to construct 'Headers': Invalid value");
}
this._append({
name: header[0],
value: header[1]
});
}
} else {
for (const key in init) {
this._append({
name: key,
value: init[key]
});
}
}
}
append(name: string, value: string): void {
this._appendToHeaderList({ name, value });
}
delete(name: string): void { delete(name: string): void {
assert(false, "Implement me"); assert(false, "Implement me");
} }
get(name: string): string | null { get(name: string): string | null {
assert(false, "Implement me"); for (const header of this.headerList) {
if (header.name.toLowerCase() === name.toLowerCase()) {
return header.value;
}
}
return null; return null;
} }
has(name: string): boolean { has(name: string): boolean {
@ -54,15 +110,20 @@ class FetchResponse implements Response {
statusText = "FIXME"; // TODO statusText = "FIXME"; // TODO
readonly type = "basic"; // TODO readonly type = "basic"; // TODO
redirected = false; // TODO redirected = false; // TODO
headers = new DenoHeaders(); headers: DenoHeaders;
readonly trailer: Promise<Headers>; readonly trailer: Promise<Headers>;
//private bodyChunks: Uint8Array[] = []; //private bodyChunks: Uint8Array[] = [];
private first = true; private first = true;
private bodyWaiter: Resolvable<ArrayBuffer>; private bodyWaiter: Resolvable<ArrayBuffer>;
constructor(readonly status: number, readonly body_: ArrayBuffer) { constructor(
readonly status: number,
readonly body_: ArrayBuffer,
headersList: Array<[string, string]>
) {
this.bodyWaiter = createResolvable(); this.bodyWaiter = createResolvable();
this.trailer = createResolvable(); this.trailer = createResolvable();
this.headers = new DenoHeaders(headersList);
setTimeout(() => { setTimeout(() => {
this.bodyWaiter.resolve(body_); this.bodyWaiter.resolve(body_);
}, 0); }, 0);
@ -149,6 +210,14 @@ export async function fetch(
assert(bodyArray != null); assert(bodyArray != null);
const body = typedArrayToArrayBuffer(bodyArray!); const body = typedArrayToArrayBuffer(bodyArray!);
const response = new FetchResponse(status, body); const headersList: Array<[string, string]> = [];
const len = msg.headerKeyLength();
for (let i = 0; i < len; ++i) {
const key = msg.headerKey(i);
const value = msg.headerValue(i);
headersList.push([key, value]);
}
const response = new FetchResponse(status, body, headersList);
return response; return response;
} }

View file

@ -18,3 +18,20 @@ test(async function fetchPerm() {
assertEqual(err.kind, deno.ErrorKind.PermissionDenied); assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
assertEqual(err.name, "PermissionDenied"); assertEqual(err.name, "PermissionDenied");
}); });
testPerm({ net: true }, async function fetchHeaders() {
const response = await fetch("http://localhost:4545/package.json");
const headers = response.headers;
assertEqual(headers.get("Content-Type"), "application/json");
assert(headers.get("Server").startsWith("SimpleHTTP"));
});
test(async function headersAppend() {
let err;
try {
const headers = new Headers([["foo", "bar", "baz"]]);
} catch (e) {
err = e;
}
assert(err instanceof TypeError);
});

2
js/fetch_types.d.ts vendored
View file

@ -13,7 +13,7 @@ See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License. and limitations under the License.
*******************************************************************************/ *******************************************************************************/
type HeadersInit = Headers | string[][] | Record<string, string>; type HeadersInit = string[][] | Record<string, string>;
type BodyInit = type BodyInit =
| Blob | Blob
| BufferSource | BufferSource

View file

@ -6,6 +6,7 @@ import * as textEncoding from "./text_encoding";
import * as fetch_ from "./fetch"; import * as fetch_ from "./fetch";
import { libdeno } from "./libdeno"; import { libdeno } from "./libdeno";
import { globalEval } from "./global-eval"; import { globalEval } from "./global-eval";
import { DenoHeaders } from "./fetch";
declare global { declare global {
interface Window { interface Window {
@ -23,6 +24,8 @@ declare global {
TextEncoder: typeof TextEncoder; TextEncoder: typeof TextEncoder;
TextDecoder: typeof TextDecoder; TextDecoder: typeof TextDecoder;
Headers: typeof Headers;
} }
const clearTimeout: typeof timers.clearTimer; const clearTimeout: typeof timers.clearTimer;
@ -38,6 +41,7 @@ declare global {
// tslint:disable:variable-name // tslint:disable:variable-name
const TextEncoder: typeof textEncoding.TextEncoder; const TextEncoder: typeof textEncoding.TextEncoder;
const TextDecoder: typeof textEncoding.TextDecoder; const TextDecoder: typeof textEncoding.TextDecoder;
const Headers: typeof DenoHeaders;
// tslint:enable:variable-name // tslint:enable:variable-name
} }
@ -57,3 +61,5 @@ window.TextEncoder = textEncoding.TextEncoder;
window.TextDecoder = textEncoding.TextDecoder; window.TextDecoder = textEncoding.TextDecoder;
window.fetch = fetch_.fetch; window.fetch = fetch_.fetch;
window.Headers = DenoHeaders;

View file

@ -323,27 +323,54 @@ fn handle_fetch_req(d: *const DenoC, base: &msg::Base) -> Box<Op> {
let url = url.parse::<hyper::Uri>().unwrap(); let url = url.parse::<hyper::Uri>().unwrap();
let client = Client::new(); let client = Client::new();
let future = client.get(url).and_then(|res| { let future = client.get(url).and_then(move |res| {
let status = res.status().as_u16() as i32; let status = res.status().as_u16() as i32;
let headers = {
let map = res.headers();
let keys = map
.keys()
.map(|s| s.as_str().to_string())
.collect::<Vec<_>>();
let values = map
.values()
.map(|s| s.to_str().unwrap().to_string())
.collect::<Vec<_>>();
(keys, values)
};
// TODO Handle streaming body. // TODO Handle streaming body.
res.into_body().concat2().map(move |body| (status, body)) res
.into_body()
.concat2()
.map(move |body| (status, body, headers))
}); });
let future = future.map_err(|err| -> DenoError { err.into() }).and_then( let future = future.map_err(|err| -> DenoError { err.into() }).and_then(
move |(status, body)| { move |(status, body, headers)| {
let builder = &mut FlatBufferBuilder::new(); let builder = &mut FlatBufferBuilder::new();
// Send the first message without a body. This is just to indicate // Send the first message without a body. This is just to indicate
// what status code. // what status code.
let body_off = builder.create_vector(body.as_ref()); let body_off = builder.create_vector(body.as_ref());
let header_keys: Vec<&str> = headers.0.iter().map(|s| &**s).collect();
let header_keys_off =
builder.create_vector_of_strings(header_keys.as_slice());
let header_values: Vec<&str> = headers.1.iter().map(|s| &**s).collect();
let header_values_off =
builder.create_vector_of_strings(header_values.as_slice());
let msg = msg::FetchRes::create( let msg = msg::FetchRes::create(
builder, builder,
&msg::FetchResArgs { &msg::FetchResArgs {
id, id,
status, status,
body: Some(body_off), body: Some(body_off),
header_key: Some(header_keys_off),
header_value: Some(header_values_off),
..Default::default() ..Default::default()
}, },
); );
Ok(serialize_response( Ok(serialize_response(
cmd_id, cmd_id,
builder, builder,

View file

@ -154,7 +154,8 @@ table FetchReq {
table FetchRes { table FetchRes {
id: uint; id: uint;
status: int; status: int;
header_line: [string]; header_key: [string];
header_value: [string];
body: [ubyte]; body: [ubyte];
} }