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:
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.
|
||||
// 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";
|
||||
|
|
83
js/fetch.ts
83
js/fetch.ts
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
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.
|
||||
*******************************************************************************/
|
||||
|
||||
type HeadersInit = Headers | string[][] | Record<string, string>;
|
||||
type HeadersInit = string[][] | Record<string, string>;
|
||||
type BodyInit =
|
||||
| Blob
|
||||
| BufferSource
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -154,7 +154,8 @@ table FetchReq {
|
|||
table FetchRes {
|
||||
id: uint;
|
||||
status: int;
|
||||
header_line: [string];
|
||||
header_key: [string];
|
||||
header_value: [string];
|
||||
body: [ubyte];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue