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:
parent
cb6c78c6d2
commit
41c70b154f
8 changed files with 137 additions and 21 deletions
|
@ -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";
|
||||||
|
|
83
js/fetch.ts
83
js/fetch.ts
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
2
js/fetch_types.d.ts
vendored
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue