summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BUILD.gn2
-rw-r--r--js/assets.ts7
-rw-r--r--js/console.ts3
-rw-r--r--js/fetch.ts120
-rw-r--r--js/fetch_types.d.ts441
-rw-r--r--js/globals.ts6
-rw-r--r--js/main.ts7
-rw-r--r--js/unit_tests.ts2
-rw-r--r--js/util.ts2
-rw-r--r--src/handlers.rs96
-rw-r--r--src/main.rs1
-rw-r--r--tests/fetch_deps.ts14
12 files changed, 660 insertions, 41 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 95640c7c8..38b194b9a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -197,6 +197,8 @@ run_node("bundle") {
sources = [
"js/assets.ts",
"js/console.ts",
+ "js/fetch.ts",
+ "js/fetch_types.d.ts",
"js/globals.ts",
"js/lib.globals.d.ts",
"js/main.ts",
diff --git a/js/assets.ts b/js/assets.ts
index 6261f4ad3..425ef3fc2 100644
--- a/js/assets.ts
+++ b/js/assets.ts
@@ -11,6 +11,7 @@ import consoleDts from "gen/js/console.d.ts!string";
import denoDts from "gen/js/deno.d.ts!string";
import globalsDts from "gen/js/globals.d.ts!string";
import osDts from "gen/js/os.d.ts!string";
+import fetchDts from "gen/js/fetch.d.ts!string";
import timersDts from "gen/js/timers.d.ts!string";
import utilDts from "gen/js/util.d.ts!string";
@@ -48,6 +49,7 @@ import libGlobalsDts from "/js/lib.globals.d.ts!string";
// Static definitions
import typescriptDts from "/third_party/node_modules/typescript/lib/typescript.d.ts!string";
import typesDts from "/js/types.d.ts!string";
+import fetchTypesDts from "/js/fetch_types.d.ts!string";
// tslint:enable:max-line-length
// prettier-ignore
@@ -57,6 +59,8 @@ export const assetSourceCode: { [key: string]: string } = {
"deno.d.ts": denoDts,
"globals.d.ts": globalsDts,
"os.d.ts": osDts,
+ "fetch.d.ts": fetchDts,
+ "fetch_types.d.ts": fetchTypesDts,
"timers.d.ts": timersDts,
"util.d.ts": utilDts,
@@ -94,4 +98,7 @@ export const assetSourceCode: { [key: string]: string } = {
// Static definitions
"typescript.d.ts": typescriptDts,
"types.d.ts": typesDts,
+
+ // TODO Remove.
+ "msg_generated.d.ts": "",
};
diff --git a/js/console.ts b/js/console.ts
index 48ecc6e7e..2eb5e27c0 100644
--- a/js/console.ts
+++ b/js/console.ts
@@ -112,7 +112,8 @@ export class Console {
// tslint:disable-next-line:no-any
warn(...args: any[]): void {
- this.printFunc(`ERROR: ${stringifyArgs(args)}`);
+ // TODO Log to stderr.
+ this.printFunc(stringifyArgs(args));
}
error = this.warn;
diff --git a/js/fetch.ts b/js/fetch.ts
index 48696778e..599d02baf 100644
--- a/js/fetch.ts
+++ b/js/fetch.ts
@@ -7,42 +7,77 @@ import {
typedArrayToArrayBuffer,
notImplemented,
} from "./util";
-import { pubInternal, sub } from "./dispatch";
-import { deno as pb } from "./msg.pb";
-
-export function initFetch() {
- sub("fetch", (payload: Uint8Array) => {
- const msg = pb.Msg.decode(payload);
- assert(msg.command === pb.Msg.Command.FETCH_RES);
- const id = msg.fetchResId;
- const f = fetchRequests.get(id);
- assert(f != null, `Couldn't find FetchRequest id ${id}`);
-
- f.onMsg(msg);
- });
+import { flatbuffers } from "flatbuffers";
+import { libdeno } from "./globals";
+import { deno as fbs } from "gen/msg_generated";
+import {
+ Headers,
+ Request,
+ Response,
+ Blob,
+ RequestInit,
+ FormData
+} from "./fetch_types";
+import { TextDecoder } from "./text_encoding";
+
+/** @internal */
+export function onFetchRes(base: fbs.Base, msg: fbs.FetchRes) {
+ const id = msg.id();
+ const req = fetchRequests.get(id);
+ assert(req != null, `Couldn't find FetchRequest id ${id}`);
+ req!.onMsg(base, msg);
}
const fetchRequests = new Map<number, FetchRequest>();
+class DenoHeaders implements Headers {
+ append(name: string, value: string): void {
+ assert(false, "Implement me");
+ }
+ delete(name: string): void {
+ assert(false, "Implement me");
+ }
+ get(name: string): string | null {
+ assert(false, "Implement me");
+ return null;
+ }
+ has(name: string): boolean {
+ assert(false, "Implement me");
+ return false;
+ }
+ set(name: string, value: string): void {
+ assert(false, "Implement me");
+ }
+ forEach(
+ callbackfn: (value: string, key: string, parent: Headers) => void,
+ // tslint:disable-next-line:no-any
+ thisArg?: any
+ ): void {
+ assert(false, "Implement me");
+ }
+}
+
class FetchResponse implements Response {
readonly url: string;
body: null;
bodyUsed = false; // TODO
- status: number;
+ status = 0;
statusText = "FIXME"; // TODO
readonly type = "basic"; // TODO
redirected = false; // TODO
- headers: null; // TODO
+ headers = new DenoHeaders();
+ readonly trailer: Promise<Headers>;
//private bodyChunks: Uint8Array[] = [];
private first = true;
+ private bodyWaiter: Resolvable<ArrayBuffer>;
constructor(readonly req: FetchRequest) {
this.url = req.url;
+ this.bodyWaiter = createResolvable();
+ this.trailer = createResolvable();
}
- bodyWaiter: Resolvable<ArrayBuffer>;
arrayBuffer(): Promise<ArrayBuffer> {
- this.bodyWaiter = createResolvable();
return this.bodyWaiter;
}
@@ -73,23 +108,27 @@ class FetchResponse implements Response {
notImplemented();
}
- onHeader: (res: Response) => void;
- onError: (error: Error) => void;
+ onHeader?: (res: FetchResponse) => void;
+ onError?: (error: Error) => void;
- onMsg(msg: pb.Msg) {
- if (msg.error !== null && msg.error !== "") {
- //throw new Error(msg.error)
- this.onError(new Error(msg.error));
+ onMsg(base: fbs.Base, msg: fbs.FetchRes) {
+ const error = base.error();
+ if (error != null) {
+ assert(this.onError != null);
+ this.onError!(new Error(error));
return;
}
if (this.first) {
this.first = false;
- this.status = msg.fetchResStatus;
- this.onHeader(this);
+ this.status = msg.status();
+ assert(this.onHeader != null);
+ this.onHeader!(this);
} else {
// Body message. Assuming it all comes in one message now.
- const ab = typedArrayToArrayBuffer(msg.fetchResBody);
+ const bodyArray = msg.bodyArray();
+ assert(bodyArray != null);
+ const ab = typedArrayToArrayBuffer(bodyArray!);
this.bodyWaiter.resolve(ab);
}
}
@@ -106,8 +145,8 @@ class FetchRequest {
this.response = new FetchResponse(this);
}
- onMsg(msg: pb.Msg) {
- this.response.onMsg(msg);
+ onMsg(base: fbs.Base, msg: fbs.FetchRes) {
+ this.response.onMsg(base, msg);
}
destroy() {
@@ -116,12 +155,22 @@ class FetchRequest {
start() {
log("dispatch FETCH_REQ", this.id, this.url);
- const res = pubInternal("fetch", {
- command: pb.Msg.Command.FETCH_REQ,
- fetchReqId: this.id,
- fetchReqUrl: this.url
- });
- assert(res == null);
+
+ // Send FetchReq message
+ const builder = new flatbuffers.Builder();
+ const url = builder.createString(this.url);
+ fbs.FetchReq.startFetchReq(builder);
+ fbs.FetchReq.addId(builder, this.id);
+ fbs.FetchReq.addUrl(builder, url);
+ const msg = fbs.FetchReq.endFetchReq(builder);
+ fbs.Base.startBase(builder);
+ fbs.Base.addMsg(builder, msg);
+ fbs.Base.addMsgType(builder, fbs.Any.FetchReq);
+ builder.finish(fbs.Base.endBase(builder));
+ const resBuf = libdeno.send(builder.asUint8Array());
+ assert(resBuf == null);
+
+ //console.log("FetchReq sent", builder);
}
}
@@ -132,8 +181,7 @@ export function fetch(
const fetchReq = new FetchRequest(input as string);
const response = fetchReq.response;
return new Promise((resolve, reject) => {
- // tslint:disable-next-line:no-any
- response.onHeader = (response: any) => {
+ response.onHeader = (response: FetchResponse) => {
log("onHeader");
resolve(response);
};
diff --git a/js/fetch_types.d.ts b/js/fetch_types.d.ts
new file mode 100644
index 000000000..644cb76ee
--- /dev/null
+++ b/js/fetch_types.d.ts
@@ -0,0 +1,441 @@
+/*! ****************************************************************************
+Copyright (c) Microsoft Corporation. All rights reserved.
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at http://www.apache.org/licenses/LICENSE-2.0
+
+THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
+WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
+MERCHANTABLITY OR NON-INFRINGEMENT.
+
+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 BodyInit =
+ | Blob
+ | BufferSource
+ | FormData
+ | URLSearchParams
+ | ReadableStream
+ | string;
+type RequestInfo = Request | string;
+type ReferrerPolicy =
+ | ""
+ | "no-referrer"
+ | "no-referrer-when-downgrade"
+ | "origin-only"
+ | "origin-when-cross-origin"
+ | "unsafe-url";
+type BlobPart = BufferSource | Blob | string;
+declare type EventListenerOrEventListenerObject =
+ | EventListener
+ | EventListenerObject;
+
+interface Element {
+ // TODO
+}
+
+interface HTMLFormElement {
+ // TODO
+}
+
+interface FormDataEntryValue {
+ // TODO
+}
+
+interface BlobPropertyBag {
+ type?: string;
+}
+
+interface AbortSignalEventMap {
+ abort: ProgressEvent;
+}
+
+interface EventTarget {
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject | null,
+ options?: boolean | AddEventListenerOptions
+ ): void;
+ dispatchEvent(evt: Event): boolean;
+ removeEventListener(
+ type: string,
+ listener?: EventListenerOrEventListenerObject | null,
+ options?: EventListenerOptions | boolean
+ ): void;
+}
+
+interface ProgressEventInit extends EventInit {
+ lengthComputable?: boolean;
+ loaded?: number;
+ total?: number;
+}
+
+interface URLSearchParams {
+ /**
+ * Appends a specified key/value pair as a new search parameter.
+ */
+ append(name: string, value: string): void;
+ /**
+ * Deletes the given search parameter, and its associated value, from the list of all search parameters.
+ */
+ delete(name: string): void;
+ /**
+ * Returns the first value associated to the given search parameter.
+ */
+ get(name: string): string | null;
+ /**
+ * Returns all the values association with a given search parameter.
+ */
+ getAll(name: string): string[];
+ /**
+ * Returns a Boolean indicating if such a search parameter exists.
+ */
+ has(name: string): boolean;
+ /**
+ * Sets the value associated to a given search parameter to the given value. If there were several values, delete the others.
+ */
+ set(name: string, value: string): void;
+ sort(): void;
+ forEach(
+ callbackfn: (value: string, key: string, parent: URLSearchParams) => void,
+ thisArg?: any
+ ): void;
+}
+
+interface EventListener {
+ (evt: Event): void;
+}
+
+interface EventInit {
+ bubbles?: boolean;
+ cancelable?: boolean;
+ composed?: boolean;
+}
+
+interface Event {
+ readonly bubbles: boolean;
+ cancelBubble: boolean;
+ readonly cancelable: boolean;
+ readonly composed: boolean;
+ readonly currentTarget: EventTarget | null;
+ readonly defaultPrevented: boolean;
+ readonly eventPhase: number;
+ readonly isTrusted: boolean;
+ returnValue: boolean;
+ readonly srcElement: Element | null;
+ readonly target: EventTarget | null;
+ readonly timeStamp: number;
+ readonly type: string;
+ deepPath(): EventTarget[];
+ initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void;
+ preventDefault(): void;
+ stopImmediatePropagation(): void;
+ stopPropagation(): void;
+ readonly AT_TARGET: number;
+ readonly BUBBLING_PHASE: number;
+ readonly CAPTURING_PHASE: number;
+ readonly NONE: number;
+}
+
+interface ProgressEvent extends Event {
+ readonly lengthComputable: boolean;
+ readonly loaded: number;
+ readonly total: number;
+}
+
+declare var ProgressEvent: {
+ prototype: ProgressEvent;
+ new (type: string, eventInitDict?: ProgressEventInit): ProgressEvent;
+};
+
+interface EventListenerOptions {
+ capture?: boolean;
+}
+
+interface AddEventListenerOptions extends EventListenerOptions {
+ once?: boolean;
+ passive?: boolean;
+}
+
+interface AbortSignal extends EventTarget {
+ readonly aborted: boolean;
+ onabort: ((this: AbortSignal, ev: ProgressEvent) => any) | null;
+ addEventListener<K extends keyof AbortSignalEventMap>(
+ type: K,
+ listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any,
+ options?: boolean | AddEventListenerOptions
+ ): void;
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions
+ ): void;
+ removeEventListener<K extends keyof AbortSignalEventMap>(
+ type: K,
+ listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any,
+ options?: boolean | EventListenerOptions
+ ): void;
+ removeEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | EventListenerOptions
+ ): void;
+}
+
+declare var AbortSignal: {
+ prototype: AbortSignal;
+ new (): AbortSignal;
+};
+
+interface ReadableStream {
+ readonly locked: boolean;
+ cancel(): Promise<void>;
+ getReader(): ReadableStreamReader;
+}
+
+declare var ReadableStream: {
+ prototype: ReadableStream;
+ new (): ReadableStream;
+};
+
+interface EventListenerObject {
+ handleEvent(evt: Event): void;
+}
+
+interface ReadableStreamReader {
+ cancel(): Promise<void>;
+ read(): Promise<any>;
+ releaseLock(): void;
+}
+
+declare var ReadableStreamReader: {
+ prototype: ReadableStreamReader;
+ new (): ReadableStreamReader;
+};
+
+interface FormData {
+ append(name: string, value: string | Blob, fileName?: string): void;
+ delete(name: string): void;
+ get(name: string): FormDataEntryValue | null;
+ getAll(name: string): FormDataEntryValue[];
+ has(name: string): boolean;
+ set(name: string, value: string | Blob, fileName?: string): void;
+ forEach(
+ callbackfn: (
+ value: FormDataEntryValue,
+ key: string,
+ parent: FormData
+ ) => void,
+ thisArg?: any
+ ): void;
+}
+
+declare var FormData: {
+ prototype: FormData;
+ new (form?: HTMLFormElement): FormData;
+};
+
+export interface Blob {
+ readonly size: number;
+ readonly type: string;
+ slice(start?: number, end?: number, contentType?: string): Blob;
+}
+
+declare var Blob: {
+ prototype: Blob;
+ new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
+};
+
+interface Body {
+ readonly body: ReadableStream | null;
+ readonly bodyUsed: boolean;
+ arrayBuffer(): Promise<ArrayBuffer>;
+ blob(): Promise<Blob>;
+ formData(): Promise<FormData>;
+ json(): Promise<any>;
+ text(): Promise<string>;
+}
+
+interface Headers {
+ append(name: string, value: string): void;
+ delete(name: string): void;
+ get(name: string): string | null;
+ has(name: string): boolean;
+ set(name: string, value: string): void;
+ forEach(
+ callbackfn: (value: string, key: string, parent: Headers) => void,
+ thisArg?: any
+ ): void;
+}
+
+declare var Headers: {
+ prototype: Headers;
+ new (init?: HeadersInit): Headers;
+};
+
+type RequestCache =
+ | "default"
+ | "no-store"
+ | "reload"
+ | "no-cache"
+ | "force-cache"
+ | "only-if-cached";
+type RequestCredentials = "omit" | "same-origin" | "include";
+type RequestDestination =
+ | ""
+ | "audio"
+ | "audioworklet"
+ | "document"
+ | "embed"
+ | "font"
+ | "image"
+ | "manifest"
+ | "object"
+ | "paintworklet"
+ | "report"
+ | "script"
+ | "sharedworker"
+ | "style"
+ | "track"
+ | "video"
+ | "worker"
+ | "xslt";
+type RequestMode = "navigate" | "same-origin" | "no-cors" | "cors";
+type RequestRedirect = "follow" | "error" | "manual";
+type ResponseType =
+ | "basic"
+ | "cors"
+ | "default"
+ | "error"
+ | "opaque"
+ | "opaqueredirect";
+
+export interface RequestInit {
+ body?: BodyInit | null;
+ cache?: RequestCache;
+ credentials?: RequestCredentials;
+ headers?: HeadersInit;
+ integrity?: string;
+ keepalive?: boolean;
+ method?: string;
+ mode?: RequestMode;
+ redirect?: RequestRedirect;
+ referrer?: string;
+ referrerPolicy?: ReferrerPolicy;
+ signal?: AbortSignal | null;
+ window?: any;
+}
+
+export interface ResponseInit {
+ headers?: HeadersInit;
+ status?: number;
+ statusText?: string;
+}
+
+export interface Request extends Body {
+ /**
+ * Returns the cache mode associated with request, which is a string indicating
+ * how the the request will interact with the browser's cache when fetching.
+ */
+ readonly cache: RequestCache;
+ /**
+ * Returns the credentials mode associated with request, which is a string
+ * indicating whether credentials will be sent with the request always, never, or only when sent to a
+ * same-origin URL.
+ */
+ readonly credentials: RequestCredentials;
+ /**
+ * Returns the kind of resource requested by request, e.g., "document" or
+ * "script".
+ */
+ readonly destination: RequestDestination;
+ /**
+ * Returns a Headers object consisting of the headers associated with request.
+ * Note that headers added in the network layer by the user agent will not be accounted for in this
+ * object, e.g., the "Host" header.
+ */
+ readonly headers: Headers;
+ /**
+ * Returns request's subresource integrity metadata, which is a cryptographic hash of
+ * the resource being fetched. Its value consists of multiple hashes separated by whitespace. [SRI]
+ */
+ readonly integrity: string;
+ /**
+ * Returns a boolean indicating whether or not request is for a history
+ * navigation (a.k.a. back-foward navigation).
+ */
+ readonly isHistoryNavigation: boolean;
+ /**
+ * Returns a boolean indicating whether or not request is for a reload navigation.
+ */
+ readonly isReloadNavigation: boolean;
+ /**
+ * Returns a boolean indicating whether or not request can outlive the global in which
+ * it was created.
+ */
+ readonly keepalive: boolean;
+ /**
+ * Returns request's HTTP method, which is "GET" by default.
+ */
+ readonly method: string;
+ /**
+ * Returns the mode associated with request, which is a string indicating
+ * whether the request will use CORS, or will be restricted to same-origin URLs.
+ */
+ readonly mode: RequestMode;
+ /**
+ * Returns the redirect mode associated with request, which is a string
+ * indicating how redirects for the request will be handled during fetching. A request will follow redirects by default.
+ */
+ readonly redirect: RequestRedirect;
+ /**
+ * Returns the referrer of request. Its value can be a same-origin URL if
+ * explicitly set in init, the empty string to indicate no referrer, and
+ * "about:client" when defaulting to the global's default. This is used during
+ * fetching to determine the value of the `Referer` header of the request being made.
+ */
+ readonly referrer: string;
+ /**
+ * Returns the referrer policy associated with request. This is used during
+ * fetching to compute the value of the request's referrer.
+ */
+ readonly referrerPolicy: ReferrerPolicy;
+ /**
+ * Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort
+ * event handler.
+ */
+ readonly signal: AbortSignal;
+ /**
+ * Returns the URL of request as a string.
+ */
+ readonly url: string;
+ clone(): Request;
+}
+
+declare var Request: {
+ prototype: Request;
+ new (input: RequestInfo, init?: RequestInit): Request;
+};
+
+export interface Response extends Body {
+ readonly headers: Headers;
+ readonly ok: boolean;
+ readonly redirected: boolean;
+ readonly status: number;
+ readonly statusText: string;
+ readonly trailer: Promise<Headers>;
+ readonly type: ResponseType;
+ readonly url: string;
+ clone(): Response;
+}
+
+declare var Response: {
+ prototype: Response;
+ new (body?: BodyInit | null, init?: ResponseInit): Response;
+ error(): Response;
+ redirect(url: string, status?: number): Response;
+};
diff --git a/js/globals.ts b/js/globals.ts
index a6f1b0927..ebfd3d265 100644
--- a/js/globals.ts
+++ b/js/globals.ts
@@ -4,6 +4,7 @@ import { Console } from "./console";
import { RawSourceMap } from "./types";
import * as timers from "./timers";
import { TextEncoder, TextDecoder } from "./text_encoding";
+import * as fetch_ from "./fetch";
declare global {
interface Window {
@@ -18,6 +19,8 @@ declare global {
const console: Console;
const window: Window;
+ const fetch: typeof fetch_.fetch;
+
// tslint:disable:variable-name
let TextEncoder: TextEncoder;
let TextDecoder: TextDecoder;
@@ -58,5 +61,4 @@ window.console = new Console(libdeno.print);
window.TextEncoder = TextEncoder;
window.TextDecoder = TextDecoder;
-// import { fetch } from "./fetch";
-// window["fetch"] = fetch;
+window.fetch = fetch_.fetch;
diff --git a/js/main.ts b/js/main.ts
index c712f7e08..d035f9ba6 100644
--- a/js/main.ts
+++ b/js/main.ts
@@ -7,6 +7,7 @@ import * as os from "./os";
import * as runtime from "./runtime";
import { libdeno } from "./globals";
import * as timers from "./timers";
+import { onFetchRes } from "./fetch";
function startMsg(cmdId: number): Uint8Array {
const builder = new flatbuffers.Builder();
@@ -24,6 +25,12 @@ function onMessage(ui8: Uint8Array) {
const bb = new flatbuffers.ByteBuffer(ui8);
const base = fbs.Base.getRootAsBase(bb);
switch (base.msgType()) {
+ case fbs.Any.FetchRes: {
+ const msg = new fbs.FetchRes();
+ assert(base.msg(msg) != null);
+ onFetchRes(base, msg);
+ break;
+ }
case fbs.Any.TimerReady: {
const msg = new fbs.TimerReady();
assert(base.msg(msg) != null);
diff --git a/js/unit_tests.ts b/js/unit_tests.ts
index 7caae5283..96341b4a2 100644
--- a/js/unit_tests.ts
+++ b/js/unit_tests.ts
@@ -92,13 +92,13 @@ test(async function tests_readFileSync() {
assertEqual(pkg.name, "deno");
});
-/*
test(async function tests_fetch() {
const response = await fetch("http://localhost:4545/package.json");
const json = await response.json();
assertEqual(json.name, "deno");
});
+/*
test(async function tests_writeFileSync() {
const enc = new TextEncoder();
const data = enc.encode("Hello");
diff --git a/js/util.ts b/js/util.ts
index 3eaa98a6b..1754dc663 100644
--- a/js/util.ts
+++ b/js/util.ts
@@ -56,7 +56,7 @@ export interface ResolvableMethods<T> {
reject: (reason?: any) => void;
}
-type Resolvable<T> = Promise<T> & ResolvableMethods<T>;
+export type Resolvable<T> = Promise<T> & ResolvableMethods<T>;
export function createResolvable<T>(): Resolvable<T> {
let methods: ResolvableMethods<T>;
diff --git a/src/handlers.rs b/src/handlers.rs
index 21e5b2baa..b40bbc67b 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -6,9 +6,13 @@ use from_c;
use fs;
use futures;
use futures::sync::oneshot;
+use hyper;
+use hyper::rt::{Future, Stream};
+use hyper::Client;
use msg_generated::deno as msg;
use std;
use std::path::Path;
+use tokio::prelude::future;
pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) {
let bytes = unsafe { std::slice::from_raw_parts(buf.data_ptr, buf.data_len) };
@@ -33,6 +37,12 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) {
let output_code = msg.output_code().unwrap();
handle_code_cache(d, filename, source_code, output_code);
}
+ msg::Any::FetchReq => {
+ // TODO base.msg_as_FetchReq();
+ let msg = msg::FetchReq::init_from_table(base.msg().unwrap());
+ let url = msg.url().unwrap();
+ handle_fetch_req(d, msg.id(), url);
+ }
msg::Any::TimerStart => {
// TODO base.msg_as_TimerStart();
let msg = msg::TimerStart::init_from_table(base.msg().unwrap());
@@ -210,6 +220,92 @@ fn handle_code_cache(
// null response indicates success.
}
+fn handle_fetch_req(d: *const DenoC, id: u32, url: &str) {
+ let deno = from_c(d);
+ let url = url.parse::<hyper::Uri>().unwrap();
+ let client = Client::new();
+
+ deno.rt.spawn(
+ client
+ .get(url)
+ .map(move |res| {
+ let status = res.status().as_u16() as i32;
+
+ // Send the first message without a body. This is just to indicate
+ // what status code.
+ let mut builder = flatbuffers::FlatBufferBuilder::new();
+ let msg = msg::FetchRes::create(
+ &mut builder,
+ &msg::FetchResArgs {
+ id,
+ status,
+ ..Default::default()
+ },
+ );
+ send_base(
+ d,
+ &mut builder,
+ &msg::BaseArgs {
+ msg: Some(flatbuffers::Offset::new(msg.value())),
+ msg_type: msg::Any::FetchRes,
+ ..Default::default()
+ },
+ );
+ res
+ })
+ .and_then(move |res| {
+ // Send the body as a FetchRes message.
+ res.into_body().concat2().map(move |body_buffer| {
+ let mut builder = flatbuffers::FlatBufferBuilder::new();
+ let data_off = builder.create_byte_vector(body_buffer.as_ref());
+ let msg = msg::FetchRes::create(
+ &mut builder,
+ &msg::FetchResArgs {
+ id,
+ body: Some(data_off),
+ ..Default::default()
+ },
+ );
+ send_base(
+ d,
+ &mut builder,
+ &msg::BaseArgs {
+ msg: Some(flatbuffers::Offset::new(msg.value())),
+ msg_type: msg::Any::FetchRes,
+ ..Default::default()
+ },
+ );
+ })
+ })
+ .map_err(move |err| {
+ let errmsg = format!("{}", err);
+
+ // TODO This is obviously a lot of duplicated code from the success case.
+ // Leaving it here now jsut to get a first pass implementation, but this
+ // needs to be cleaned up.
+ let mut builder = flatbuffers::FlatBufferBuilder::new();
+ let err_off = builder.create_string(errmsg.as_str());
+ let msg = msg::FetchRes::create(
+ &mut builder,
+ &msg::FetchResArgs {
+ id,
+ ..Default::default()
+ },
+ );
+ send_base(
+ d,
+ &mut builder,
+ &msg::BaseArgs {
+ msg: Some(flatbuffers::Offset::new(msg.value())),
+ msg_type: msg::Any::FetchRes,
+ error: Some(err_off),
+ ..Default::default()
+ },
+ );
+ }),
+ );
+}
+
fn set_timeout<F>(
cb: F,
delay: u32,
diff --git a/src/main.rs b/src/main.rs
index b5eab9e7c..2d83b8259 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
extern crate flatbuffers;
extern crate futures;
+extern crate hyper;
extern crate libc;
extern crate msg_rs as msg_generated;
extern crate sha1;
diff --git a/tests/fetch_deps.ts b/tests/fetch_deps.ts
new file mode 100644
index 000000000..d2690e01c
--- /dev/null
+++ b/tests/fetch_deps.ts
@@ -0,0 +1,14 @@
+// Run ./tools/http_server.py too in order for this test to run.
+import { assert } from "../js/testing/util.ts";
+
+// TODO Top level await https://github.com/denoland/deno/issues/471
+async function main() {
+ const response = await fetch("http://localhost:4545/package.json");
+ const json = await response.json();
+ const deps = Object.keys(json.devDependencies);
+ console.log("Deno JS Deps");
+ console.log(deps.map(d => `* ${d}`).join("\n"));
+ assert(deps.includes("typescript"));
+}
+
+main();