1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -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.
// Public deno module.
/// <amd-module name="deno"/>
export {
env,
exit,
makeTempDirSync
} from "./os";
export { env, exit, makeTempDirSync } from "./os";
export { mkdirSync, mkdir } from "./mkdir";
export { removeSync, remove, removeAllSync, removeAll } from "./remove";
export { readFileSync, readFile } from "./read_file";

View file

@ -16,19 +16,75 @@ import {
Response,
Blob,
RequestInit,
HeadersInit,
FormData
} from "./fetch_types";
import { TextDecoder } from "./text_encoding";
class DenoHeaders implements Headers {
append(name: string, value: string): void {
assert(false, "Implement me");
interface Header {
name: string;
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 {
assert(false, "Implement me");
}
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;
}
has(name: string): boolean {
@ -54,15 +110,20 @@ class FetchResponse implements Response {
statusText = "FIXME"; // TODO
readonly type = "basic"; // TODO
redirected = false; // TODO
headers = new DenoHeaders();
headers: DenoHeaders;
readonly trailer: Promise<Headers>;
//private bodyChunks: Uint8Array[] = [];
private first = true;
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.trailer = createResolvable();
this.headers = new DenoHeaders(headersList);
setTimeout(() => {
this.bodyWaiter.resolve(body_);
}, 0);
@ -149,6 +210,14 @@ export async function fetch(
assert(bodyArray != null);
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;
}

View file

@ -18,3 +18,20 @@ test(async function fetchPerm() {
assertEqual(err.kind, deno.ErrorKind.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.
*******************************************************************************/
type HeadersInit = Headers | string[][] | Record<string, string>;
type HeadersInit = string[][] | Record<string, string>;
type BodyInit =
| Blob
| BufferSource

View file

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

View file

@ -4,8 +4,8 @@ import { flatbuffers } from "flatbuffers";
import * as dispatch from "./dispatch";
/**
* Synchronously renames (moves) oldpath to newpath. If newpath already exists
* and is not a directory, Rename replaces it. OS-specific restrictions may
* Synchronously renames (moves) oldpath to newpath. If newpath already exists
* and is not a directory, Rename replaces it. OS-specific restrictions may
* apply when oldpath and newpath are in different directories.
*
* import { renameSync } from "deno";
@ -16,8 +16,8 @@ export function renameSync(oldpath: string, newpath: string): void {
}
/**
* Renames (moves) oldpath to newpath. If newpath already exists
* and is not a directory, Rename replaces it. OS-specific restrictions may
* Renames (moves) oldpath to newpath. If newpath already exists
* and is not a directory, Rename replaces it. OS-specific restrictions may
* apply when oldpath and newpath are in different directories.
*
* import { rename } from "deno";

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 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 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.
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(
move |(status, body)| {
move |(status, body, headers)| {
let builder = &mut FlatBufferBuilder::new();
// Send the first message without a body. This is just to indicate
// what status code.
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(
builder,
&msg::FetchResArgs {
id,
status,
body: Some(body_off),
header_key: Some(header_keys_off),
header_value: Some(header_values_off),
..Default::default()
},
);
Ok(serialize_response(
cmd_id,
builder,

View file

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