1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

refactor: deno_fetch op crate (#7524)

This commit is contained in:
Bartek Iwańczuk 2020-09-18 15:20:55 +02:00 committed by GitHub
parent cead79f5b8
commit 7845740637
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 2544 additions and 2234 deletions

10
Cargo.lock generated
View file

@ -403,6 +403,7 @@ dependencies = [
"clap",
"deno_core",
"deno_doc",
"deno_fetch",
"deno_lint",
"deno_web",
"dissimilar",
@ -483,6 +484,15 @@ dependencies = [
"termcolor",
]
[[package]]
name = "deno_fetch"
version = "0.1.0"
dependencies = [
"deno_core",
"reqwest",
"serde",
]
[[package]]
name = "deno_lint"
version = "0.2.0"

View file

@ -4,6 +4,7 @@ members = [
"core",
"test_plugin",
"test_util",
"op_crates/fetch",
"op_crates/web",
]
exclude = [

View file

@ -22,6 +22,7 @@ path = "./bench/main.rs"
[build-dependencies]
deno_core = { path = "../core", version = "0.57.0" }
deno_web = { path = "../op_crates/web", version = "0.8.0" }
deno_fetch = { path = "../op_crates/fetch", version = "0.1.0" }
[target.'cfg(windows)'.build-dependencies]
winres = "0.1.11"
@ -32,6 +33,7 @@ deno_core = { path = "../core", version = "0.57.0" }
deno_doc = "0.1.9"
deno_lint = { version = "0.2.0", features = ["json"] }
deno_web = { path = "../op_crates/web", version = "0.8.0" }
deno_fetch = { path = "../op_crates/fetch", version = "0.1.0" }
atty = "0.2.14"
base64 = "0.12.3"

View file

@ -16,6 +16,7 @@ fn create_snapshot(
files: Vec<PathBuf>,
) {
deno_web::init(&mut isolate);
deno_fetch::init(&mut isolate);
// TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the
// workspace root.
let display_root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
@ -52,6 +53,10 @@ fn create_compiler_snapshot(
let mut custom_libs: HashMap<String, PathBuf> = HashMap::new();
custom_libs
.insert("lib.deno.web.d.ts".to_string(), deno_web::get_declaration());
custom_libs.insert(
"lib.deno.fetch.d.ts".to_string(),
deno_fetch::get_declaration(),
);
custom_libs.insert(
"lib.deno.window.d.ts".to_string(),
cwd.join("dts/lib.deno.window.d.ts"),
@ -112,6 +117,10 @@ fn main() {
"cargo:rustc-env=DENO_WEB_LIB_PATH={}",
deno_web::get_declaration().display()
);
println!(
"cargo:rustc-env=DENO_FETCH_LIB_PATH={}",
deno_fetch::get_declaration().display()
);
println!(
"cargo:rustc-env=TARGET={}",

View file

@ -5,6 +5,7 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="deno.web" />
/// <reference lib="deno.fetch" />
declare namespace WebAssembly {
interface CompileError {
@ -227,255 +228,6 @@ declare function removeEventListener(
options?: boolean | EventListenerOptions | undefined,
): void;
interface DomIterable<K, V> {
keys(): IterableIterator<K>;
values(): IterableIterator<V>;
entries(): IterableIterator<[K, V]>;
[Symbol.iterator](): IterableIterator<[K, V]>;
forEach(
callback: (value: V, key: K, parent: this) => void,
thisArg?: any,
): void;
}
interface ReadableStreamReadDoneResult<T> {
done: true;
value?: T;
}
interface ReadableStreamReadValueResult<T> {
done: false;
value: T;
}
type ReadableStreamReadResult<T> =
| ReadableStreamReadValueResult<T>
| ReadableStreamReadDoneResult<T>;
interface ReadableStreamDefaultReader<R = any> {
readonly closed: Promise<void>;
cancel(reason?: any): Promise<void>;
read(): Promise<ReadableStreamReadResult<R>>;
releaseLock(): void;
}
interface ReadableStreamReader<R = any> {
cancel(): Promise<void>;
read(): Promise<ReadableStreamReadResult<R>>;
releaseLock(): void;
}
interface ReadableByteStreamControllerCallback {
(controller: ReadableByteStreamController): void | PromiseLike<void>;
}
interface UnderlyingByteSource {
autoAllocateChunkSize?: number;
cancel?: ReadableStreamErrorCallback;
pull?: ReadableByteStreamControllerCallback;
start?: ReadableByteStreamControllerCallback;
type: "bytes";
}
interface UnderlyingSource<R = any> {
cancel?: ReadableStreamErrorCallback;
pull?: ReadableStreamDefaultControllerCallback<R>;
start?: ReadableStreamDefaultControllerCallback<R>;
type?: undefined;
}
interface ReadableStreamErrorCallback {
(reason: any): void | PromiseLike<void>;
}
interface ReadableStreamDefaultControllerCallback<R> {
(controller: ReadableStreamDefaultController<R>): void | PromiseLike<void>;
}
interface ReadableStreamDefaultController<R = any> {
readonly desiredSize: number | null;
close(): void;
enqueue(chunk: R): void;
error(error?: any): void;
}
interface ReadableByteStreamController {
readonly byobRequest: undefined;
readonly desiredSize: number | null;
close(): void;
enqueue(chunk: ArrayBufferView): void;
error(error?: any): void;
}
interface PipeOptions {
preventAbort?: boolean;
preventCancel?: boolean;
preventClose?: boolean;
signal?: AbortSignal;
}
interface QueuingStrategySizeCallback<T = any> {
(chunk: T): number;
}
interface QueuingStrategy<T = any> {
highWaterMark?: number;
size?: QueuingStrategySizeCallback<T>;
}
/** This Streams API interface provides a built-in byte length queuing strategy
* that can be used when constructing streams. */
declare class CountQueuingStrategy implements QueuingStrategy {
constructor(options: { highWaterMark: number });
highWaterMark: number;
size(chunk: any): 1;
}
declare class ByteLengthQueuingStrategy
implements QueuingStrategy<ArrayBufferView> {
constructor(options: { highWaterMark: number });
highWaterMark: number;
size(chunk: ArrayBufferView): number;
}
/** This Streams API interface represents a readable stream of byte data. The
* Fetch API offers a concrete instance of a ReadableStream through the body
* property of a Response object. */
interface ReadableStream<R = any> {
readonly locked: boolean;
cancel(reason?: any): Promise<void>;
getIterator(options?: { preventCancel?: boolean }): AsyncIterableIterator<R>;
// getReader(options: { mode: "byob" }): ReadableStreamBYOBReader;
getReader(): ReadableStreamDefaultReader<R>;
pipeThrough<T>(
{
writable,
readable,
}: {
writable: WritableStream<R>;
readable: ReadableStream<T>;
},
options?: PipeOptions,
): ReadableStream<T>;
pipeTo(dest: WritableStream<R>, options?: PipeOptions): Promise<void>;
tee(): [ReadableStream<R>, ReadableStream<R>];
[Symbol.asyncIterator](options?: {
preventCancel?: boolean;
}): AsyncIterableIterator<R>;
}
declare var ReadableStream: {
prototype: ReadableStream;
new (
underlyingSource: UnderlyingByteSource,
strategy?: { highWaterMark?: number; size?: undefined },
): ReadableStream<Uint8Array>;
new <R = any>(
underlyingSource?: UnderlyingSource<R>,
strategy?: QueuingStrategy<R>,
): ReadableStream<R>;
};
interface WritableStreamDefaultControllerCloseCallback {
(): void | PromiseLike<void>;
}
interface WritableStreamDefaultControllerStartCallback {
(controller: WritableStreamDefaultController): void | PromiseLike<void>;
}
interface WritableStreamDefaultControllerWriteCallback<W> {
(chunk: W, controller: WritableStreamDefaultController):
| void
| PromiseLike<
void
>;
}
interface WritableStreamErrorCallback {
(reason: any): void | PromiseLike<void>;
}
interface UnderlyingSink<W = any> {
abort?: WritableStreamErrorCallback;
close?: WritableStreamDefaultControllerCloseCallback;
start?: WritableStreamDefaultControllerStartCallback;
type?: undefined;
write?: WritableStreamDefaultControllerWriteCallback<W>;
}
/** This Streams API interface provides a standard abstraction for writing
* streaming data to a destination, known as a sink. This object comes with
* built-in backpressure and queuing. */
declare class WritableStream<W = any> {
constructor(
underlyingSink?: UnderlyingSink<W>,
strategy?: QueuingStrategy<W>,
);
readonly locked: boolean;
abort(reason?: any): Promise<void>;
close(): Promise<void>;
getWriter(): WritableStreamDefaultWriter<W>;
}
/** This Streams API interface represents a controller allowing control of a
* WritableStream's state. When constructing a WritableStream, the underlying
* sink is given a corresponding WritableStreamDefaultController instance to
* manipulate. */
interface WritableStreamDefaultController {
error(error?: any): void;
}
/** This Streams API interface is the object returned by
* WritableStream.getWriter() and once created locks the < writer to the
* WritableStream ensuring that no other streams can write to the underlying
* sink. */
interface WritableStreamDefaultWriter<W = any> {
readonly closed: Promise<void>;
readonly desiredSize: number | null;
readonly ready: Promise<void>;
abort(reason?: any): Promise<void>;
close(): Promise<void>;
releaseLock(): void;
write(chunk: W): Promise<void>;
}
declare class TransformStream<I = any, O = any> {
constructor(
transformer?: Transformer<I, O>,
writableStrategy?: QueuingStrategy<I>,
readableStrategy?: QueuingStrategy<O>,
);
readonly readable: ReadableStream<O>;
readonly writable: WritableStream<I>;
}
interface TransformStreamDefaultController<O = any> {
readonly desiredSize: number | null;
enqueue(chunk: O): void;
error(reason?: any): void;
terminate(): void;
}
interface Transformer<I = any, O = any> {
flush?: TransformStreamDefaultControllerCallback<O>;
readableType?: undefined;
start?: TransformStreamDefaultControllerCallback<O>;
transform?: TransformStreamDefaultControllerTransformCallback<I, O>;
writableType?: undefined;
}
interface TransformStreamDefaultControllerCallback<O> {
(controller: TransformStreamDefaultController<O>): void | PromiseLike<void>;
}
interface TransformStreamDefaultControllerTransformCallback<I, O> {
(
chunk: I,
controller: TransformStreamDefaultController<O>,
): void | PromiseLike<void>;
}
interface DOMStringList {
/** Returns the number of strings in strings. */
readonly length: number;
@ -487,43 +239,6 @@ interface DOMStringList {
}
type BufferSource = ArrayBufferView | ArrayBuffer;
type BlobPart = BufferSource | Blob | string;
interface BlobPropertyBag {
type?: string;
ending?: "transparent" | "native";
}
/** A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */
interface Blob {
readonly size: number;
readonly type: string;
arrayBuffer(): Promise<ArrayBuffer>;
slice(start?: number, end?: number, contentType?: string): Blob;
stream(): ReadableStream;
text(): Promise<string>;
}
declare const Blob: {
prototype: Blob;
new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
};
interface FilePropertyBag extends BlobPropertyBag {
lastModified?: number;
}
/** Provides information about files and allows JavaScript in a web page to
* access their content. */
interface File extends Blob {
readonly lastModified: number;
readonly name: string;
}
declare const File: {
prototype: File;
new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File;
};
interface FileReaderEventMap {
"abort": ProgressEvent<FileReader>;
@ -653,328 +368,6 @@ declare interface Crypto {
): T;
}
type FormDataEntryValue = File | string;
/** Provides a way to easily construct a set of key/value pairs representing
* form fields and their values, which can then be easily sent using the
* XMLHttpRequest.send() method. It uses the same format a form would use if the
* encoding type were set to "multipart/form-data". */
interface FormData extends DomIterable<string, FormDataEntryValue> {
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;
}
declare const FormData: {
prototype: FormData;
// TODO(ry) FormData constructor is non-standard.
// new(form?: HTMLFormElement): FormData;
new (): FormData;
};
interface Body {
/** A simple getter used to expose a `ReadableStream` of the body contents. */
readonly body: ReadableStream<Uint8Array> | null;
/** Stores a `Boolean` that declares whether the body has been used in a
* response yet.
*/
readonly bodyUsed: boolean;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with an `ArrayBuffer`.
*/
arrayBuffer(): Promise<ArrayBuffer>;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with a `Blob`.
*/
blob(): Promise<Blob>;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with a `FormData` object.
*/
formData(): Promise<FormData>;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with the result of parsing the body text as JSON.
*/
json(): Promise<any>;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with a `USVString` (text).
*/
text(): Promise<string>;
}
type HeadersInit = Headers | string[][] | Record<string, string>;
/** This Fetch API interface allows you to perform various actions on HTTP
* request and response headers. These actions include retrieving, setting,
* adding to, and removing. A Headers object has an associated header list,
* which is initially empty and consists of zero or more name and value pairs.
*  You can add to this using methods like append() (see Examples.) In all
* methods of this interface, header names are matched by case-insensitive byte
* sequence. */
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;
}
interface Headers extends DomIterable<string, string> {
/** Appends a new value onto an existing header inside a `Headers` object, or
* adds the header if it does not already exist.
*/
append(name: string, value: string): void;
/** Deletes a header from a `Headers` object. */
delete(name: string): void;
/** Returns an iterator allowing to go through all key/value pairs
* contained in this Headers object. The both the key and value of each pairs
* are ByteString objects.
*/
entries(): IterableIterator<[string, string]>;
/** Returns a `ByteString` sequence of all the values of a header within a
* `Headers` object with a given name.
*/
get(name: string): string | null;
/** Returns a boolean stating whether a `Headers` object contains a certain
* header.
*/
has(name: string): boolean;
/** Returns an iterator allowing to go through all keys contained in
* this Headers object. The keys are ByteString objects.
*/
keys(): IterableIterator<string>;
/** Sets a new value for an existing header inside a Headers object, or adds
* the header if it does not already exist.
*/
set(name: string, value: string): void;
/** Returns an iterator allowing to go through all values contained in
* this Headers object. The values are ByteString objects.
*/
values(): IterableIterator<string>;
forEach(
callbackfn: (value: string, key: string, parent: this) => void,
thisArg?: any,
): void;
/** The Symbol.iterator well-known symbol specifies the default
* iterator for this Headers object
*/
[Symbol.iterator](): IterableIterator<[string, string]>;
}
declare const Headers: {
prototype: Headers;
new (init?: HeadersInit): Headers;
};
type RequestInfo = Request | string;
type RequestCache =
| "default"
| "force-cache"
| "no-cache"
| "no-store"
| "only-if-cached"
| "reload";
type RequestCredentials = "include" | "omit" | "same-origin";
type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin";
type RequestRedirect = "error" | "follow" | "manual";
type ReferrerPolicy =
| ""
| "no-referrer"
| "no-referrer-when-downgrade"
| "origin"
| "origin-when-cross-origin"
| "same-origin"
| "strict-origin"
| "strict-origin-when-cross-origin"
| "unsafe-url";
type BodyInit =
| Blob
| BufferSource
| FormData
| URLSearchParams
| ReadableStream<Uint8Array>
| string;
type RequestDestination =
| ""
| "audio"
| "audioworklet"
| "document"
| "embed"
| "font"
| "image"
| "manifest"
| "object"
| "paintworklet"
| "report"
| "script"
| "sharedworker"
| "style"
| "track"
| "video"
| "worker"
| "xslt";
interface RequestInit {
/**
* A BodyInit object or null to set request's body.
*/
body?: BodyInit | null;
/**
* A string indicating how the request will interact with the browser's cache
* to set request's cache.
*/
cache?: RequestCache;
/**
* A string indicating whether credentials will be sent with the request
* always, never, or only when sent to a same-origin URL. Sets request's
* credentials.
*/
credentials?: RequestCredentials;
/**
* A Headers object, an object literal, or an array of two-item arrays to set
* request's headers.
*/
headers?: HeadersInit;
/**
* A cryptographic hash of the resource to be fetched by request. Sets
* request's integrity.
*/
integrity?: string;
/**
* A boolean to set request's keepalive.
*/
keepalive?: boolean;
/**
* A string to set request's method.
*/
method?: string;
/**
* A string to indicate whether the request will use CORS, or will be
* restricted to same-origin URLs. Sets request's mode.
*/
mode?: RequestMode;
/**
* A string indicating whether request follows redirects, results in an error
* upon encountering a redirect, or returns the redirect (in an opaque
* fashion). Sets request's redirect.
*/
redirect?: RequestRedirect;
/**
* A string whose value is a same-origin URL, "about:client", or the empty
* string, to set request's referrer.
*/
referrer?: string;
/**
* A referrer policy to set request's referrerPolicy.
*/
referrerPolicy?: ReferrerPolicy;
/**
* An AbortSignal to set request's signal.
*/
signal?: AbortSignal | null;
/**
* Can only be null. Used to disassociate request from any Window.
*/
window?: any;
}
/** This Fetch API interface represents a resource request. */
interface Request extends Body {
/**
* Returns the cache mode associated with request, which is a string
* indicating how 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-forward 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 const Request: {
prototype: Request;
new (input: RequestInfo, init?: RequestInit): Request;
};
interface ResponseInit {
headers?: HeadersInit;
@ -1003,26 +396,6 @@ interface Response extends Body {
clone(): Response;
}
declare const Response: {
prototype: Response;
new (body?: BodyInit | null, init?: ResponseInit): Response;
error(): Response;
redirect(url: string, status?: number): Response;
};
/** Fetch a resource from the network. It returns a Promise that resolves to the
* Response to that request, whether it is successful or not.
*
* const response = await fetch("http://my.json.host/data.json");
* console.log(response.status); // e.g. 200
* console.log(response.statusText); // e.g. "OK"
* const jsonData = await response.json();
*/
declare function fetch(
input: Request | URL | string,
init?: RequestInit,
): Promise<Response>;
interface URLSearchParams {
/** Appends a specified key/value pair as a new search parameter.
*

View file

@ -10,6 +10,7 @@ pub static COMPILER_SNAPSHOT: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/COMPILER_SNAPSHOT.bin"));
pub static DENO_NS_LIB: &str = include_str!("dts/lib.deno.ns.d.ts");
pub static DENO_WEB_LIB: &str = include_str!(env!("DENO_WEB_LIB_PATH"));
pub static DENO_FETCH_LIB: &str = include_str!(env!("DENO_FETCH_LIB_PATH"));
pub static SHARED_GLOBALS_LIB: &str =
include_str!("dts/lib.deno.shared_globals.d.ts");
pub static WINDOW_LIB: &str = include_str!("dts/lib.deno.window.d.ts");

View file

@ -140,9 +140,10 @@ fn print_cache_info(
fn get_types(unstable: bool) -> String {
let mut types = format!(
"{}\n{}\n{}\n{}",
"{}\n{}\n{}\n{}\n{}",
crate::js::DENO_NS_LIB,
crate::js::DENO_WEB_LIB,
crate::js::DENO_FETCH_LIB,
crate::js::SHARED_GLOBALS_LIB,
crate::js::WINDOW_LIB,
);

View file

@ -1,191 +1,12 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::http_util::create_http_client;
use deno_core::error::bad_resource_id;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::url;
use deno_core::BufVec;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use http::header::HeaderName;
use http::header::HeaderValue;
use http::Method;
use reqwest::Client;
use reqwest::Response;
use serde::Deserialize;
use serde_json::Value;
use std::cell::RefCell;
use std::convert::From;
use std::path::PathBuf;
use std::rc::Rc;
use crate::state::CliState;
pub fn init(rt: &mut deno_core::JsRuntime) {
super::reg_json_async(rt, "op_fetch", op_fetch);
super::reg_json_async(rt, "op_fetch_read", op_fetch_read);
super::reg_json_sync(rt, "op_create_http_client", op_create_http_client);
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct FetchArgs {
method: Option<String>,
url: String,
headers: Vec<(String, String)>,
client_rid: Option<u32>,
}
async fn op_fetch(
state: Rc<RefCell<OpState>>,
args: Value,
data: BufVec,
) -> Result<Value, AnyError> {
let args: FetchArgs = serde_json::from_value(args)?;
let url = args.url;
let client = if let Some(rid) = args.client_rid {
let state = state.borrow();
let r = state
.resource_table
.get::<HttpClientResource>(rid)
.ok_or_else(bad_resource_id)?;
r.client.clone()
} else {
let state_ = state.borrow();
let client = state_.borrow::<reqwest::Client>();
client.clone()
};
let method = match args.method {
Some(method_str) => Method::from_bytes(method_str.as_bytes())?,
None => Method::GET,
};
let url_ = url::Url::parse(&url)?;
// Check scheme before asking for net permission
let scheme = url_.scheme();
if scheme != "http" && scheme != "https" {
return Err(type_error(format!("scheme '{}' not supported", scheme)));
}
super::cli_state2(&state).check_net_url(&url_)?;
let mut request = client.request(method, url_);
match data.len() {
0 => {}
1 => request = request.body(Vec::from(&*data[0])),
_ => panic!("Invalid number of arguments"),
}
for (key, value) in args.headers {
let name = HeaderName::from_bytes(key.as_bytes()).unwrap();
let v = HeaderValue::from_str(&value).unwrap();
request = request.header(name, v);
}
debug!("Before fetch {}", url);
let res = request.send().await?;
debug!("Fetch response {}", url);
let status = res.status();
let mut res_headers = Vec::new();
for (key, val) in res.headers().iter() {
res_headers.push((key.to_string(), val.to_str().unwrap().to_owned()));
}
let rid = state
.borrow_mut()
.resource_table
.add("httpBody", Box::new(res));
Ok(json!({
"bodyRid": rid,
"status": status.as_u16(),
"statusText": status.canonical_reason().unwrap_or(""),
"headers": res_headers
}))
}
async fn op_fetch_read(
state: Rc<RefCell<OpState>>,
args: Value,
_data: BufVec,
) -> Result<Value, AnyError> {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct Args {
rid: u32,
}
let args: Args = serde_json::from_value(args)?;
let rid = args.rid;
use futures::future::poll_fn;
use futures::ready;
use futures::FutureExt;
let f = poll_fn(move |cx| {
let mut state = state.borrow_mut();
let response = state
.resource_table
.get_mut::<Response>(rid as u32)
.ok_or_else(bad_resource_id)?;
let mut chunk_fut = response.chunk().boxed_local();
let r = ready!(chunk_fut.poll_unpin(cx))?;
if let Some(chunk) = r {
Ok(json!({ "chunk": &*chunk })).into()
} else {
Ok(json!({ "chunk": null })).into()
}
});
f.await
/*
// I'm programming this as I want it to be programmed, even though it might be
// incorrect, normally we would use poll_fn here. We need to make this await pattern work.
let chunk = response.chunk().await?;
if let Some(chunk) = chunk {
// TODO(ry) This is terribly inefficient. Make this zero-copy.
Ok(json!({ "chunk": &*chunk }))
} else {
Ok(json!({ "chunk": null }))
}
*/
}
struct HttpClientResource {
client: Client,
}
impl HttpClientResource {
fn new(client: Client) -> Self {
Self { client }
}
}
#[derive(Deserialize, Default, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
struct CreateHttpClientOptions {
ca_file: Option<String>,
}
fn op_create_http_client(
state: &mut OpState,
args: Value,
_zero_copy: &mut [ZeroCopyBuf],
) -> Result<Value, AnyError> {
let args: CreateHttpClientOptions = serde_json::from_value(args)?;
if let Some(ca_file) = args.ca_file.clone() {
super::cli_state(state).check_read(&PathBuf::from(ca_file))?;
}
let client = create_http_client(args.ca_file.as_deref()).unwrap();
let rid = state
.resource_table
.add("httpClient", Box::new(HttpClientResource::new(client)));
Ok(json!(rid))
super::reg_json_async(rt, "op_fetch", deno_fetch::op_fetch::<CliState>);
super::reg_json_async(rt, "op_fetch_read", deno_fetch::op_fetch_read);
super::reg_json_sync(
rt,
"op_create_http_client",
deno_fetch::op_create_http_client::<CliState>,
);
}

View file

@ -1,10 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
function isTypedArray(x) {
return ArrayBuffer.isView(x) && !(x instanceof DataView);
}
function isInvalidDate(x) {
return isNaN(x.getTime());
}
@ -149,40 +145,12 @@
}
}
function getHeaderValueParams(value) {
const params = new Map();
// Forced to do so for some Map constructor param mismatch
value
.split(";")
.slice(1)
.map((s) => s.trim().split("="))
.filter((arr) => arr.length > 1)
.map(([k, v]) => [k, v.replace(/^"([^"]*)"$/, "$1")])
.forEach(([k, v]) => params.set(k, v));
return params;
}
function hasHeaderValueOf(s, value) {
return new RegExp(`^${value}[\t\s]*;?`).test(s);
}
/** An internal function which provides a function name for some generated
* functions, so stack traces are a bit more readable.
*/
function setFunctionName(fn, value) {
Object.defineProperty(fn, "name", { value, configurable: true });
}
window.__bootstrap.webUtil = {
isTypedArray,
isInvalidDate,
requiredArguments,
immutableDefine,
hasOwnProperty,
cloneValue,
defineEnumerableProps,
getHeaderValueParams,
hasHeaderValueOf,
setFunctionName,
};
})(this);

View file

@ -15,7 +15,6 @@
} = window.__bootstrap.colors;
const {
isTypedArray,
isInvalidDate,
hasOwnProperty,
} = window.__bootstrap.webUtil;
@ -23,6 +22,10 @@
// Copyright Joyent, Inc. and other Node contributors. MIT license.
// Forked from Node's lib/internal/cli_table.js
function isTypedArray(x) {
return ArrayBuffer.isView(x) && !(x instanceof DataView);
}
const tableChars = {
middleMiddle: "─",
rowMiddle: "┼",

View file

@ -1,223 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const { build } = window.__bootstrap.build;
const { ReadableStream } = window.__bootstrap.streams;
const bytesSymbol = Symbol("bytes");
function containsOnlyASCII(str) {
if (typeof str !== "string") {
return false;
}
return /^[\x00-\x7F]*$/.test(str);
}
function convertLineEndingsToNative(s) {
const nativeLineEnd = build.os == "windows" ? "\r\n" : "\n";
let position = 0;
let collectionResult = collectSequenceNotCRLF(s, position);
let token = collectionResult.collected;
position = collectionResult.newPosition;
let result = token;
while (position < s.length) {
const c = s.charAt(position);
if (c == "\r") {
result += nativeLineEnd;
position++;
if (position < s.length && s.charAt(position) == "\n") {
position++;
}
} else if (c == "\n") {
position++;
result += nativeLineEnd;
}
collectionResult = collectSequenceNotCRLF(s, position);
token = collectionResult.collected;
position = collectionResult.newPosition;
result += token;
}
return result;
}
function collectSequenceNotCRLF(
s,
position,
) {
const start = position;
for (
let c = s.charAt(position);
position < s.length && !(c == "\r" || c == "\n");
c = s.charAt(++position)
);
return { collected: s.slice(start, position), newPosition: position };
}
function toUint8Arrays(
blobParts,
doNormalizeLineEndingsToNative,
) {
const ret = [];
const enc = new TextEncoder();
for (const element of blobParts) {
if (typeof element === "string") {
let str = element;
if (doNormalizeLineEndingsToNative) {
str = convertLineEndingsToNative(element);
}
ret.push(enc.encode(str));
// eslint-disable-next-line @typescript-eslint/no-use-before-define
} else if (element instanceof Blob) {
ret.push(element[bytesSymbol]);
} else if (element instanceof Uint8Array) {
ret.push(element);
} else if (element instanceof Uint16Array) {
const uint8 = new Uint8Array(element.buffer);
ret.push(uint8);
} else if (element instanceof Uint32Array) {
const uint8 = new Uint8Array(element.buffer);
ret.push(uint8);
} else if (ArrayBuffer.isView(element)) {
// Convert view to Uint8Array.
const uint8 = new Uint8Array(element.buffer);
ret.push(uint8);
} else if (element instanceof ArrayBuffer) {
// Create a new Uint8Array view for the given ArrayBuffer.
const uint8 = new Uint8Array(element);
ret.push(uint8);
} else {
ret.push(enc.encode(String(element)));
}
}
return ret;
}
function processBlobParts(
blobParts,
options,
) {
const normalizeLineEndingsToNative = options.ending === "native";
// ArrayBuffer.transfer is not yet implemented in V8, so we just have to
// pre compute size of the array buffer and do some sort of static allocation
// instead of dynamic allocation.
const uint8Arrays = toUint8Arrays(blobParts, normalizeLineEndingsToNative);
const byteLength = uint8Arrays
.map((u8) => u8.byteLength)
.reduce((a, b) => a + b, 0);
const ab = new ArrayBuffer(byteLength);
const bytes = new Uint8Array(ab);
let courser = 0;
for (const u8 of uint8Arrays) {
bytes.set(u8, courser);
courser += u8.byteLength;
}
return bytes;
}
function getStream(blobBytes) {
// TODO: Align to spec https://fetch.spec.whatwg.org/#concept-construct-readablestream
return new ReadableStream({
type: "bytes",
start: (controller) => {
controller.enqueue(blobBytes);
controller.close();
},
});
}
async function readBytes(
reader,
) {
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (!done && value instanceof Uint8Array) {
chunks.push(value);
} else if (done) {
const size = chunks.reduce((p, i) => p + i.byteLength, 0);
const bytes = new Uint8Array(size);
let offs = 0;
for (const chunk of chunks) {
bytes.set(chunk, offs);
offs += chunk.byteLength;
}
return bytes.buffer;
} else {
throw new TypeError("Invalid reader result.");
}
}
}
// A WeakMap holding blob to byte array mapping.
// Ensures it does not impact garbage collection.
const blobBytesWeakMap = new WeakMap();
class Blob {
constructor(blobParts, options) {
if (arguments.length === 0) {
this[bytesSymbol] = new Uint8Array();
return;
}
const { ending = "transparent", type = "" } = options ?? {};
// Normalize options.type.
let normalizedType = type;
if (!containsOnlyASCII(type)) {
normalizedType = "";
} else {
if (type.length) {
for (let i = 0; i < type.length; ++i) {
const char = type[i];
if (char < "\u0020" || char > "\u007E") {
normalizedType = "";
break;
}
}
normalizedType = type.toLowerCase();
}
}
const bytes = processBlobParts(blobParts, { ending, type });
// Set Blob object's properties.
this[bytesSymbol] = bytes;
this.size = bytes.byteLength;
this.type = normalizedType;
}
slice(start, end, contentType) {
return new Blob([this[bytesSymbol].slice(start, end)], {
type: contentType || this.type,
});
}
stream() {
return getStream(this[bytesSymbol]);
}
async text() {
const reader = getStream(this[bytesSymbol]).getReader();
const decoder = new TextDecoder();
return decoder.decode(await readBytes(reader));
}
arrayBuffer() {
return readBytes(getStream(this[bytesSymbol]).getReader());
}
}
window.__bootstrap.blob = {
Blob,
bytesSymbol,
containsOnlyASCII,
blobBytesWeakMap,
};
})(this);

View file

@ -1,50 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const customInspect = Symbol.for("Deno.customInspect");
class CountQueuingStrategy {
constructor({ highWaterMark }) {
this.highWaterMark = highWaterMark;
}
size() {
return 1;
}
[customInspect]() {
return `${this.constructor.name} { highWaterMark: ${
String(this.highWaterMark)
}, size: f }`;
}
}
Object.defineProperty(CountQueuingStrategy.prototype, "size", {
enumerable: true,
});
class ByteLengthQueuingStrategy {
constructor({ highWaterMark }) {
this.highWaterMark = highWaterMark;
}
size(chunk) {
return chunk.byteLength;
}
[customInspect]() {
return `${this.constructor.name} { highWaterMark: ${
String(this.highWaterMark)
}, size: f }`;
}
}
Object.defineProperty(ByteLengthQueuingStrategy.prototype, "size", {
enumerable: true,
});
window.__bootstrap.queuingStrategy = {
CountQueuingStrategy,
ByteLengthQueuingStrategy,
};
})(this);

View file

@ -1,27 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const blob = window.__bootstrap.blob;
class DomFile extends blob.Blob {
constructor(
fileBits,
fileName,
options,
) {
const { lastModified = Date.now(), ...blobPropertyBag } = options ?? {};
super(fileBits, blobPropertyBag);
// 4.1.2.1 Replace any "/" character (U+002F SOLIDUS)
// with a ":" (U + 003A COLON)
this.name = String(fileName).replace(/\u002F/g, "\u003A");
// 4.1.3.3 If lastModified is not provided, set lastModified to the current
// date and time represented in number of milliseconds since the Unix Epoch.
this.lastModified = lastModified;
}
}
window.__bootstrap.domFile = {
DomFile,
};
})(this);

View file

@ -1,116 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const blob = window.__bootstrap.blob;
const domFile = window.__bootstrap.domFile;
const { DomIterableMixin } = window.__bootstrap.domIterable;
const { requiredArguments } = window.__bootstrap.webUtil;
const dataSymbol = Symbol("data");
function parseFormDataValue(value, filename) {
if (value instanceof domFile.DomFile) {
return new domFile.DomFile([value], filename || value.name, {
type: value.type,
lastModified: value.lastModified,
});
} else if (value instanceof blob.Blob) {
return new domFile.DomFile([value], filename || "blob", {
type: value.type,
});
} else {
return String(value);
}
}
class FormDataBase {
[dataSymbol] = [];
append(name, value, filename) {
requiredArguments("FormData.append", arguments.length, 2);
name = String(name);
this[dataSymbol].push([name, parseFormDataValue(value, filename)]);
}
delete(name) {
requiredArguments("FormData.delete", arguments.length, 1);
name = String(name);
let i = 0;
while (i < this[dataSymbol].length) {
if (this[dataSymbol][i][0] === name) {
this[dataSymbol].splice(i, 1);
} else {
i++;
}
}
}
getAll(name) {
requiredArguments("FormData.getAll", arguments.length, 1);
name = String(name);
const values = [];
for (const entry of this[dataSymbol]) {
if (entry[0] === name) {
values.push(entry[1]);
}
}
return values;
}
get(name) {
requiredArguments("FormData.get", arguments.length, 1);
name = String(name);
for (const entry of this[dataSymbol]) {
if (entry[0] === name) {
return entry[1];
}
}
return null;
}
has(name) {
requiredArguments("FormData.has", arguments.length, 1);
name = String(name);
return this[dataSymbol].some((entry) => entry[0] === name);
}
set(name, value, filename) {
requiredArguments("FormData.set", arguments.length, 2);
name = String(name);
// If there are any entries in the context objects entry list whose name
// is name, replace the first such entry with entry and remove the others
let found = false;
let i = 0;
while (i < this[dataSymbol].length) {
if (this[dataSymbol][i][0] === name) {
if (!found) {
this[dataSymbol][i][1] = parseFormDataValue(value, filename);
found = true;
} else {
this[dataSymbol].splice(i, 1);
continue;
}
}
i++;
}
// Otherwise, append entry to the context objects entry list.
if (!found) {
this[dataSymbol].push([name, parseFormDataValue(value, filename)]);
}
}
get [Symbol.toStringTag]() {
return "FormData";
}
}
class FormData extends DomIterableMixin(FormDataBase, dataSymbol) {}
window.__bootstrap.formData = {
FormData,
};
})(this);

View file

@ -1,199 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const { Buffer } = window.__bootstrap.buffer;
const { bytesSymbol, Blob } = window.__bootstrap.blob;
const { DomFile } = window.__bootstrap.domFile;
const { getHeaderValueParams } = window.__bootstrap.webUtil;
const decoder = new TextDecoder();
const encoder = new TextEncoder();
const CR = "\r".charCodeAt(0);
const LF = "\n".charCodeAt(0);
class MultipartBuilder {
constructor(formData, boundary) {
this.formData = formData;
this.boundary = boundary ?? this.#createBoundary();
this.writer = new Buffer();
}
getContentType() {
return `multipart/form-data; boundary=${this.boundary}`;
}
getBody() {
for (const [fieldName, fieldValue] of this.formData.entries()) {
if (fieldValue instanceof DomFile) {
this.#writeFile(fieldName, fieldValue);
} else this.#writeField(fieldName, fieldValue);
}
this.writer.writeSync(encoder.encode(`\r\n--${this.boundary}--`));
return this.writer.bytes();
}
#createBoundary = () => {
return (
"----------" +
Array.from(Array(32))
.map(() => Math.random().toString(36)[2] || 0)
.join("")
);
};
#writeHeaders = (headers) => {
let buf = this.writer.empty() ? "" : "\r\n";
buf += `--${this.boundary}\r\n`;
for (const [key, value] of headers) {
buf += `${key}: ${value}\r\n`;
}
buf += `\r\n`;
this.writer.writeSync(encoder.encode(buf));
};
#writeFileHeaders = (
field,
filename,
type,
) => {
const headers = [
[
"Content-Disposition",
`form-data; name="${field}"; filename="${filename}"`,
],
["Content-Type", type || "application/octet-stream"],
];
return this.#writeHeaders(headers);
};
#writeFieldHeaders = (field) => {
const headers = [["Content-Disposition", `form-data; name="${field}"`]];
return this.#writeHeaders(headers);
};
#writeField = (field, value) => {
this.#writeFieldHeaders(field);
this.writer.writeSync(encoder.encode(value));
};
#writeFile = (field, value) => {
this.#writeFileHeaders(field, value.name, value.type);
this.writer.writeSync(value[bytesSymbol]);
};
}
class MultipartParser {
constructor(body, boundary) {
if (!boundary) {
throw new TypeError("multipart/form-data must provide a boundary");
}
this.boundary = `--${boundary}`;
this.body = body;
this.boundaryChars = encoder.encode(this.boundary);
}
#parseHeaders = (headersText) => {
const headers = new Headers();
const rawHeaders = headersText.split("\r\n");
for (const rawHeader of rawHeaders) {
const sepIndex = rawHeader.indexOf(":");
if (sepIndex < 0) {
continue; // Skip this header
}
const key = rawHeader.slice(0, sepIndex);
const value = rawHeader.slice(sepIndex + 1);
headers.set(key, value);
}
return {
headers,
disposition: getHeaderValueParams(
headers.get("Content-Disposition") ?? "",
),
};
};
parse() {
const formData = new FormData();
let headerText = "";
let boundaryIndex = 0;
let state = 0;
let fileStart = 0;
for (let i = 0; i < this.body.length; i++) {
const byte = this.body[i];
const prevByte = this.body[i - 1];
const isNewLine = byte === LF && prevByte === CR;
if (state === 1 || state === 2 || state == 3) {
headerText += String.fromCharCode(byte);
}
if (state === 0 && isNewLine) {
state = 1;
} else if (state === 1 && isNewLine) {
state = 2;
const headersDone = this.body[i + 1] === CR &&
this.body[i + 2] === LF;
if (headersDone) {
state = 3;
}
} else if (state === 2 && isNewLine) {
state = 3;
} else if (state === 3 && isNewLine) {
state = 4;
fileStart = i + 1;
} else if (state === 4) {
if (this.boundaryChars[boundaryIndex] !== byte) {
boundaryIndex = 0;
} else {
boundaryIndex++;
}
if (boundaryIndex >= this.boundary.length) {
const { headers, disposition } = this.#parseHeaders(headerText);
const content = this.body.subarray(
fileStart,
i - boundaryIndex - 1,
);
// https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata
const filename = disposition.get("filename");
const name = disposition.get("name");
state = 5;
// Reset
boundaryIndex = 0;
headerText = "";
if (!name) {
continue; // Skip, unknown name
}
if (filename) {
const blob = new Blob([content], {
type: headers.get("Content-Type") || "application/octet-stream",
});
formData.append(name, blob, filename);
} else {
formData.append(name, decoder.decode(content));
}
}
} else if (state === 5 && isNewLine) {
state = 1;
}
}
return formData;
}
}
window.__bootstrap.multipart = {
MultipartBuilder,
MultipartParser,
};
})(this);

View file

@ -1,220 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const { Blob } = window.__bootstrap.blob;
const { ReadableStream, isReadableStreamDisturbed } =
window.__bootstrap.streams;
const { Buffer } = window.__bootstrap.buffer;
const {
getHeaderValueParams,
hasHeaderValueOf,
isTypedArray,
} = window.__bootstrap.webUtil;
const { MultipartParser } = window.__bootstrap.multipart;
function validateBodyType(owner, bodySource) {
if (isTypedArray(bodySource)) {
return true;
} else if (bodySource instanceof ArrayBuffer) {
return true;
} else if (typeof bodySource === "string") {
return true;
} else if (bodySource instanceof ReadableStream) {
return true;
} else if (bodySource instanceof FormData) {
return true;
} else if (bodySource instanceof URLSearchParams) {
return true;
} else if (!bodySource) {
return true; // null body is fine
}
throw new Error(
`Bad ${owner.constructor.name} body type: ${bodySource.constructor.name}`,
);
}
async function bufferFromStream(
stream,
size,
) {
const encoder = new TextEncoder();
const buffer = new Buffer();
if (size) {
// grow to avoid unnecessary allocations & copies
buffer.grow(size);
}
while (true) {
const { done, value } = await stream.read();
if (done) break;
if (typeof value === "string") {
buffer.writeSync(encoder.encode(value));
} else if (value instanceof ArrayBuffer) {
buffer.writeSync(new Uint8Array(value));
} else if (value instanceof Uint8Array) {
buffer.writeSync(value);
} else if (!value) {
// noop for undefined
} else {
throw new Error("unhandled type on stream read");
}
}
return buffer.bytes().buffer;
}
function bodyToArrayBuffer(bodySource) {
if (isTypedArray(bodySource)) {
return bodySource.buffer;
} else if (bodySource instanceof ArrayBuffer) {
return bodySource;
} else if (typeof bodySource === "string") {
const enc = new TextEncoder();
return enc.encode(bodySource).buffer;
} else if (bodySource instanceof ReadableStream) {
throw new Error(
`Can't convert stream to ArrayBuffer (try bufferFromStream)`,
);
} else if (
bodySource instanceof FormData ||
bodySource instanceof URLSearchParams
) {
const enc = new TextEncoder();
return enc.encode(bodySource.toString()).buffer;
} else if (!bodySource) {
return new ArrayBuffer(0);
}
throw new Error(
`Body type not implemented: ${bodySource.constructor.name}`,
);
}
const BodyUsedError =
"Failed to execute 'clone' on 'Body': body is already used";
class Body {
#contentType = "";
#size = undefined;
constructor(_bodySource, meta) {
validateBodyType(this, _bodySource);
this._bodySource = _bodySource;
this.#contentType = meta.contentType;
this.#size = meta.size;
this._stream = null;
}
get body() {
if (this._stream) {
return this._stream;
}
if (!this._bodySource) {
return null;
} else if (this._bodySource instanceof ReadableStream) {
this._stream = this._bodySource;
} else {
const buf = bodyToArrayBuffer(this._bodySource);
if (!(buf instanceof ArrayBuffer)) {
throw new Error(
`Expected ArrayBuffer from body`,
);
}
this._stream = new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});
}
return this._stream;
}
get bodyUsed() {
if (this.body && isReadableStreamDisturbed(this.body)) {
return true;
}
return false;
}
async blob() {
return new Blob([await this.arrayBuffer()], {
type: this.#contentType,
});
}
// ref: https://fetch.spec.whatwg.org/#body-mixin
async formData() {
const formData = new FormData();
if (hasHeaderValueOf(this.#contentType, "multipart/form-data")) {
const params = getHeaderValueParams(this.#contentType);
// ref: https://tools.ietf.org/html/rfc2046#section-5.1
const boundary = params.get("boundary");
const body = new Uint8Array(await this.arrayBuffer());
const multipartParser = new MultipartParser(body, boundary);
return multipartParser.parse();
} else if (
hasHeaderValueOf(this.#contentType, "application/x-www-form-urlencoded")
) {
// From https://github.com/github/fetch/blob/master/fetch.js
// Copyright (c) 2014-2016 GitHub, Inc. MIT License
const body = await this.text();
try {
body
.trim()
.split("&")
.forEach((bytes) => {
if (bytes) {
const split = bytes.split("=");
const name = split.shift().replace(/\+/g, " ");
const value = split.join("=").replace(/\+/g, " ");
formData.append(
decodeURIComponent(name),
decodeURIComponent(value),
);
}
});
} catch (e) {
throw new TypeError("Invalid form urlencoded format");
}
return formData;
} else {
throw new TypeError("Invalid form data");
}
}
async text() {
if (typeof this._bodySource === "string") {
return this._bodySource;
}
const ab = await this.arrayBuffer();
const decoder = new TextDecoder("utf-8");
return decoder.decode(ab);
}
async json() {
const raw = await this.text();
return JSON.parse(raw);
}
arrayBuffer() {
if (this._bodySource instanceof ReadableStream) {
return bufferFromStream(this._bodySource.getReader(), this.#size);
}
return bodyToArrayBuffer(this._bodySource);
}
}
window.__bootstrap.body = {
Body,
BodyUsedError,
};
})(this);

View file

@ -1,139 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const body = window.__bootstrap.body;
const { ReadableStream } = window.__bootstrap.streams;
function byteUpperCase(s) {
return String(s).replace(/[a-z]/g, function byteUpperCaseReplace(c) {
return c.toUpperCase();
});
}
function normalizeMethod(m) {
const u = byteUpperCase(m);
if (
u === "DELETE" ||
u === "GET" ||
u === "HEAD" ||
u === "OPTIONS" ||
u === "POST" ||
u === "PUT"
) {
return u;
}
return m;
}
class Request extends body.Body {
constructor(input, init) {
if (arguments.length < 1) {
throw TypeError("Not enough arguments");
}
if (!init) {
init = {};
}
let b;
// prefer body from init
if (init.body) {
b = init.body;
} else if (input instanceof Request && input._bodySource) {
if (input.bodyUsed) {
throw TypeError(body.BodyUsedError);
}
b = input._bodySource;
} else if (typeof input === "object" && "body" in input && input.body) {
if (input.bodyUsed) {
throw TypeError(body.BodyUsedError);
}
b = input.body;
} else {
b = "";
}
let headers;
// prefer headers from init
if (init.headers) {
headers = new Headers(init.headers);
} else if (input instanceof Request) {
headers = input.headers;
} else {
headers = new Headers();
}
const contentType = headers.get("content-type") || "";
super(b, { contentType });
this.headers = headers;
// readonly attribute ByteString method;
this.method = "GET";
// readonly attribute USVString url;
this.url = "";
// readonly attribute RequestCredentials credentials;
this.credentials = "omit";
if (input instanceof Request) {
if (input.bodyUsed) {
throw TypeError(body.BodyUsedError);
}
this.method = input.method;
this.url = input.url;
this.headers = new Headers(input.headers);
this.credentials = input.credentials;
this._stream = input._stream;
} else if (typeof input === "string") {
this.url = input;
}
if (init && "method" in init) {
this.method = normalizeMethod(init.method);
}
if (
init &&
"credentials" in init &&
init.credentials &&
["omit", "same-origin", "include"].indexOf(init.credentials) !== -1
) {
this.credentials = init.credentials;
}
}
clone() {
if (this.bodyUsed) {
throw TypeError(body.BodyUsedError);
}
const iterators = this.headers.entries();
const headersList = [];
for (const header of iterators) {
headersList.push(header);
}
let body2 = this._bodySource;
if (this._bodySource instanceof ReadableStream) {
const tees = this._bodySource.tee();
this._stream = this._bodySource = tees[0];
body2 = tees[1];
}
return new Request(this.url, {
body: body2,
method: this.method,
headers: new Headers(headersList),
credentials: this.credentials,
});
}
}
window.__bootstrap.request = {
Request,
};
})(this);

View file

@ -1,391 +0,0 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const core = window.Deno.core;
const { notImplemented } = window.__bootstrap.util;
const { getHeaderValueParams, isTypedArray } = window.__bootstrap.webUtil;
const { Blob, bytesSymbol: blobBytesSymbol } = window.__bootstrap.blob;
const Body = window.__bootstrap.body;
const { ReadableStream } = window.__bootstrap.streams;
const { MultipartBuilder } = window.__bootstrap.multipart;
const { Headers } = window.__bootstrap.headers;
function createHttpClient(options) {
return new HttpClient(opCreateHttpClient(options));
}
function opCreateHttpClient(args) {
return core.jsonOpSync("op_create_http_client", args);
}
class HttpClient {
constructor(rid) {
this.rid = rid;
}
close() {
core.close(this.rid);
}
}
function opFetch(args, body) {
let zeroCopy;
if (body != null) {
zeroCopy = new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
}
return core.jsonOpAsync("op_fetch", args, ...(zeroCopy ? [zeroCopy] : []));
}
const NULL_BODY_STATUS = [101, 204, 205, 304];
const REDIRECT_STATUS = [301, 302, 303, 307, 308];
const responseData = new WeakMap();
class Response extends Body.Body {
constructor(body = null, init) {
init = init ?? {};
if (typeof init !== "object") {
throw new TypeError(`'init' is not an object`);
}
const extraInit = responseData.get(init) || {};
let { type = "default", url = "" } = extraInit;
let status = init.status === undefined ? 200 : Number(init.status || 0);
let statusText = init.statusText ?? "";
let headers = init.headers instanceof Headers
? init.headers
: new Headers(init.headers);
if (init.status !== undefined && (status < 200 || status > 599)) {
throw new RangeError(
`The status provided (${init.status}) is outside the range [200, 599]`,
);
}
// null body status
if (body && NULL_BODY_STATUS.includes(status)) {
throw new TypeError("Response with null body status cannot have body");
}
if (!type) {
type = "default";
} else {
if (type == "error") {
// spec: https://fetch.spec.whatwg.org/#concept-network-error
status = 0;
statusText = "";
headers = new Headers();
body = null;
/* spec for other Response types:
https://fetch.spec.whatwg.org/#concept-filtered-response-basic
Please note that type "basic" is not the same thing as "default".*/
} else if (type == "basic") {
for (const h of headers) {
/* Forbidden Response-Header Names:
https://fetch.spec.whatwg.org/#forbidden-response-header-name */
if (["set-cookie", "set-cookie2"].includes(h[0].toLowerCase())) {
headers.delete(h[0]);
}
}
} else if (type == "cors") {
/* CORS-safelisted Response-Header Names:
https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name */
const allowedHeaders = [
"Cache-Control",
"Content-Language",
"Content-Length",
"Content-Type",
"Expires",
"Last-Modified",
"Pragma",
].map((c) => c.toLowerCase());
for (const h of headers) {
/* Technically this is still not standards compliant because we are
supposed to allow headers allowed in the
'Access-Control-Expose-Headers' header in the 'internal response'
However, this implementation of response doesn't seem to have an
easy way to access the internal response, so we ignore that
header.
TODO(serverhiccups): change how internal responses are handled
so we can do this properly. */
if (!allowedHeaders.includes(h[0].toLowerCase())) {
headers.delete(h[0]);
}
}
/* TODO(serverhiccups): Once I fix the 'internal response' thing,
these actually need to treat the internal response differently */
} else if (type == "opaque" || type == "opaqueredirect") {
url = "";
status = 0;
statusText = "";
headers = new Headers();
body = null;
}
}
const contentType = headers.get("content-type") || "";
const size = Number(headers.get("content-length")) || undefined;
super(body, { contentType, size });
this.url = url;
this.statusText = statusText;
this.status = extraInit.status || status;
this.headers = headers;
this.redirected = extraInit.redirected || false;
this.type = type;
}
get ok() {
return 200 <= this.status && this.status < 300;
}
clone() {
if (this.bodyUsed) {
throw TypeError(Body.BodyUsedError);
}
const iterators = this.headers.entries();
const headersList = [];
for (const header of iterators) {
headersList.push(header);
}
let resBody = this._bodySource;
if (this._bodySource instanceof ReadableStream) {
const tees = this._bodySource.tee();
this._stream = this._bodySource = tees[0];
resBody = tees[1];
}
return new Response(resBody, {
status: this.status,
statusText: this.statusText,
headers: new Headers(headersList),
});
}
static redirect(url, status) {
if (![301, 302, 303, 307, 308].includes(status)) {
throw new RangeError(
"The redirection status must be one of 301, 302, 303, 307 and 308.",
);
}
return new Response(null, {
status,
statusText: "",
headers: [["Location", typeof url === "string" ? url : url.toString()]],
});
}
}
function sendFetchReq(url, method, headers, body, clientRid) {
let headerArray = [];
if (headers) {
headerArray = Array.from(headers.entries());
}
const args = {
method,
url,
headers: headerArray,
clientRid,
};
return opFetch(args, body);
}
async function fetch(input, init) {
let url;
let method = null;
let headers = null;
let body;
let clientRid = null;
let redirected = false;
let remRedirectCount = 20; // TODO: use a better way to handle
if (typeof input === "string" || input instanceof URL) {
url = typeof input === "string" ? input : input.href;
if (init != null) {
method = init.method || null;
if (init.headers) {
headers = init.headers instanceof Headers
? init.headers
: new Headers(init.headers);
} else {
headers = null;
}
// ref: https://fetch.spec.whatwg.org/#body-mixin
// Body should have been a mixin
// but we are treating it as a separate class
if (init.body) {
if (!headers) {
headers = new Headers();
}
let contentType = "";
if (typeof init.body === "string") {
body = new TextEncoder().encode(init.body);
contentType = "text/plain;charset=UTF-8";
} else if (isTypedArray(init.body)) {
body = init.body;
} else if (init.body instanceof ArrayBuffer) {
body = new Uint8Array(init.body);
} else if (init.body instanceof URLSearchParams) {
body = new TextEncoder().encode(init.body.toString());
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
} else if (init.body instanceof Blob) {
body = init.body[blobBytesSymbol];
contentType = init.body.type;
} else if (init.body instanceof FormData) {
let boundary;
if (headers.has("content-type")) {
const params = getHeaderValueParams("content-type");
boundary = params.get("boundary");
}
const multipartBuilder = new MultipartBuilder(init.body, boundary);
body = multipartBuilder.getBody();
contentType = multipartBuilder.getContentType();
} else {
// TODO: ReadableStream
notImplemented();
}
if (contentType && !headers.has("content-type")) {
headers.set("content-type", contentType);
}
}
if (init.client instanceof HttpClient) {
clientRid = init.client.rid;
}
}
} else {
url = input.url;
method = input.method;
headers = input.headers;
if (input._bodySource) {
body = new DataView(await input.arrayBuffer());
}
}
let responseBody;
let responseInit = {};
while (remRedirectCount) {
const fetchResponse = await sendFetchReq(
url,
method,
headers,
body,
clientRid,
);
const rid = fetchResponse.bodyRid;
if (
NULL_BODY_STATUS.includes(fetchResponse.status) ||
REDIRECT_STATUS.includes(fetchResponse.status)
) {
// We won't use body of received response, so close it now
// otherwise it will be kept in resource table.
core.close(fetchResponse.bodyRid);
responseBody = null;
} else {
responseBody = new ReadableStream({
type: "bytes",
async pull(controller) {
try {
const result = await core.jsonOpAsync("op_fetch_read", { rid });
if (!result || !result.chunk) {
controller.close();
core.close(rid);
} else {
// TODO(ry) This is terribly inefficient. Make this zero-copy.
const chunk = new Uint8Array(result.chunk);
controller.enqueue(chunk);
}
} catch (e) {
controller.error(e);
controller.close();
core.close(rid);
}
},
cancel() {
// When reader.cancel() is called
core.close(rid);
},
});
}
responseInit = {
status: 200,
statusText: fetchResponse.statusText,
headers: fetchResponse.headers,
};
responseData.set(responseInit, {
redirected,
rid: fetchResponse.bodyRid,
status: fetchResponse.status,
url,
});
const response = new Response(responseBody, responseInit);
if (REDIRECT_STATUS.includes(fetchResponse.status)) {
// We're in a redirect status
switch ((init && init.redirect) || "follow") {
case "error":
responseInit = {};
responseData.set(responseInit, {
type: "error",
redirected: false,
url: "",
});
return new Response(null, responseInit);
case "manual":
responseInit = {};
responseData.set(responseInit, {
type: "opaqueredirect",
redirected: false,
url: "",
});
return new Response(null, responseInit);
case "follow":
default:
let redirectUrl = response.headers.get("Location");
if (redirectUrl == null) {
return response; // Unspecified
}
if (
!redirectUrl.startsWith("http://") &&
!redirectUrl.startsWith("https://")
) {
redirectUrl = new URL(redirectUrl, url).href;
}
url = redirectUrl;
redirected = true;
remRedirectCount--;
}
} else {
return response;
}
}
responseData.set(responseInit, {
type: "error",
redirected: false,
url: "",
});
return new Response(null, responseInit);
}
window.__bootstrap.fetch = {
fetch,
Response,
HttpClient,
createHttpClient,
};
})(this);

View file

@ -22,15 +22,10 @@ delete Object.prototype.__proto__;
const crypto = window.__bootstrap.crypto;
const url = window.__bootstrap.url;
const headers = window.__bootstrap.headers;
const queuingStrategy = window.__bootstrap.queuingStrategy;
const streams = window.__bootstrap.streams;
const blob = window.__bootstrap.blob;
const domFile = window.__bootstrap.domFile;
const progressEvent = window.__bootstrap.progressEvent;
const fileReader = window.__bootstrap.fileReader;
const formData = window.__bootstrap.formData;
const webSocket = window.__bootstrap.webSocket;
const request = window.__bootstrap.request;
const fetch = window.__bootstrap.fetch;
const denoNs = window.__bootstrap.denoNs;
const denoNsUnstable = window.__bootstrap.denoNsUnstable;
@ -198,22 +193,22 @@ delete Object.prototype.__proto__;
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
const windowOrWorkerGlobalScope = {
Blob: util.nonEnumerable(blob.Blob),
Blob: util.nonEnumerable(fetch.Blob),
ByteLengthQueuingStrategy: util.nonEnumerable(
queuingStrategy.ByteLengthQueuingStrategy,
streams.ByteLengthQueuingStrategy,
),
CloseEvent: util.nonEnumerable(CloseEvent),
CountQueuingStrategy: util.nonEnumerable(
queuingStrategy.CountQueuingStrategy,
streams.CountQueuingStrategy,
),
CustomEvent: util.nonEnumerable(CustomEvent),
DOMException: util.nonEnumerable(DOMException),
ErrorEvent: util.nonEnumerable(ErrorEvent),
Event: util.nonEnumerable(Event),
EventTarget: util.nonEnumerable(EventTarget),
File: util.nonEnumerable(domFile.DomFile),
File: util.nonEnumerable(fetch.DomFile),
FileReader: util.nonEnumerable(fileReader.FileReader),
FormData: util.nonEnumerable(formData.FormData),
FormData: util.nonEnumerable(fetch.FormData),
Headers: util.nonEnumerable(headers.Headers),
MessageEvent: util.nonEnumerable(MessageEvent),
Performance: util.nonEnumerable(performance.Performance),
@ -222,7 +217,7 @@ delete Object.prototype.__proto__;
PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure),
ProgressEvent: util.nonEnumerable(progressEvent.ProgressEvent),
ReadableStream: util.nonEnumerable(streams.ReadableStream),
Request: util.nonEnumerable(request.Request),
Request: util.nonEnumerable(fetch.Request),
Response: util.nonEnumerable(fetch.Response),
TextDecoder: util.nonEnumerable(TextDecoder),
TextEncoder: util.nonEnumerable(TextEncoder),

View file

@ -21,6 +21,7 @@ use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::pin::Pin;
use std::rc::Rc;
use std::str;
@ -315,3 +316,13 @@ impl CliState {
}
}
}
impl deno_fetch::FetchPermissions for CliState {
fn check_net_url(&self, url: &url::Url) -> Result<(), AnyError> {
CliState::check_net_url(self, url)
}
fn check_read(&self, p: &PathBuf) -> Result<(), AnyError> {
CliState::check_read(self, p)
}
}

View file

@ -1,5 +1,5 @@
[WILDCARD]
Files: 45
Files: 46
Nodes: [WILDCARD]
Identifiers: [WILDCARD]
Symbols: [WILDCARD]

View file

@ -61,6 +61,7 @@ unitTest(function blobShouldNotThrowError(): void {
assertEquals(hasThrown, false);
});
/* TODO https://github.com/denoland/deno/issues/7540
unitTest(function nativeEndLine(): void {
const options = {
ending: "native",
@ -69,6 +70,7 @@ unitTest(function nativeEndLine(): void {
assertEquals(blob.size, Deno.build.os === "windows" ? 12 : 11);
});
*/
unitTest(async function blobText(): Promise<void> {
const blob = new Blob(["Hello World"]);

View file

@ -1,4 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
/* TODO https://github.com/denoland/deno/issues/7540
import { unitTest, assert, assertEquals } from "./test_util.ts";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@ -25,6 +27,7 @@ function setup() {
};
}
unitTest(function testDomIterable(): void {
const { DomIterable, Base } = setup();
@ -85,3 +88,4 @@ unitTest(function testDomIterableScope(): void {
checkScope(null, window);
checkScope(undefined, window);
});
*/

View file

@ -435,6 +435,7 @@ delete Object.prototype.__proto__;
ts.libs.push("deno.ns", "deno.window", "deno.worker", "deno.shared_globals");
ts.libMap.set("deno.ns", "lib.deno.ns.d.ts");
ts.libMap.set("deno.web", "lib.deno.web.d.ts");
ts.libMap.set("deno.fetch", "lib.deno.fetch.d.ts");
ts.libMap.set("deno.window", "lib.deno.window.d.ts");
ts.libMap.set("deno.worker", "lib.deno.worker.d.ts");
ts.libMap.set("deno.shared_globals", "lib.deno.shared_globals.d.ts");
@ -450,6 +451,10 @@ delete Object.prototype.__proto__;
`${ASSETS}/lib.deno.web.d.ts`,
ts.ScriptTarget.ESNext,
);
SNAPSHOT_HOST.getSourceFile(
`${ASSETS}/lib.deno.fetch.d.ts`,
ts.ScriptTarget.ESNext,
);
SNAPSHOT_HOST.getSourceFile(
`${ASSETS}/lib.deno.window.d.ts`,
ts.ScriptTarget.ESNext,

View file

@ -0,0 +1,20 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
function requiredArguments(
name,
length,
required,
) {
if (length < required) {
const errMsg = `${name} requires at least ${required} argument${
required === 1 ? "" : "s"
}, but only ${length} present`;
throw new TypeError(errMsg);
}
}
window.__bootstrap.fetchUtil = {
requiredArguments,
};
})(this);

View file

@ -1,8 +1,8 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
((window) => {
const { requiredArguments } = window.__bootstrap.webUtil;
const { exposeForTest } = window.__bootstrap.internals;
const { requiredArguments } = window.__bootstrap.fetchUtil;
// const { exposeForTest } = window.__bootstrap.internals;
function DomIterableMixin(
Base,
@ -69,7 +69,7 @@
return DomIterable;
}
exposeForTest("DomIterableMixin", DomIterableMixin);
// exposeForTest("DomIterableMixin", DomIterableMixin);
window.__bootstrap.domIterable = {
DomIterableMixin,

View file

@ -9,11 +9,107 @@
((window) => {
/* eslint-disable @typescript-eslint/no-explicit-any,require-await */
const { cloneValue, setFunctionName } = window.__bootstrap.webUtil;
const { assert, AssertionError } = window.__bootstrap.util;
const customInspect = Symbol.for("Deno.customInspect");
/** Clone a value in a similar way to structured cloning. It is similar to a
* StructureDeserialize(StructuredSerialize(...)). */
function cloneValue(value) {
switch (typeof value) {
case "number":
case "string":
case "boolean":
case "undefined":
case "bigint":
return value;
case "object": {
if (objectCloneMemo.has(value)) {
return objectCloneMemo.get(value);
}
if (value === null) {
return value;
}
if (value instanceof Date) {
return new Date(value.valueOf());
}
if (value instanceof RegExp) {
return new RegExp(value);
}
if (value instanceof SharedArrayBuffer) {
return value;
}
if (value instanceof ArrayBuffer) {
const cloned = cloneArrayBuffer(
value,
0,
value.byteLength,
ArrayBuffer,
);
objectCloneMemo.set(value, cloned);
return cloned;
}
if (ArrayBuffer.isView(value)) {
const clonedBuffer = cloneValue(value.buffer);
// Use DataViewConstructor type purely for type-checking, can be a
// DataView or TypedArray. They use the same constructor signature,
// only DataView has a length in bytes and TypedArrays use a length in
// terms of elements, so we adjust for that.
let length;
if (value instanceof DataView) {
length = value.byteLength;
} else {
length = value.length;
}
return new (value.constructor)(
clonedBuffer,
value.byteOffset,
length,
);
}
if (value instanceof Map) {
const clonedMap = new Map();
objectCloneMemo.set(value, clonedMap);
value.forEach((v, k) => clonedMap.set(k, cloneValue(v)));
return clonedMap;
}
if (value instanceof Set) {
const clonedSet = new Map();
objectCloneMemo.set(value, clonedSet);
value.forEach((v, k) => clonedSet.set(k, cloneValue(v)));
return clonedSet;
}
const clonedObj = {};
objectCloneMemo.set(value, clonedObj);
const sourceKeys = Object.getOwnPropertyNames(value);
for (const key of sourceKeys) {
clonedObj[key] = cloneValue(value[key]);
}
return clonedObj;
}
case "symbol":
case "function":
default:
throw new DOMException("Uncloneable value in stream", "DataCloneError");
}
}
function setFunctionName(fn, value) {
Object.defineProperty(fn, "name", { value, configurable: true });
}
class AssertionError extends Error {
constructor(msg) {
super(msg);
this.name = "AssertionError";
}
}
function assert(cond, msg = "Assertion failed.") {
if (!cond) {
throw new AssertionError(msg);
}
}
const sym = {
abortAlgorithm: Symbol("abortAlgorithm"),
abortSteps: Symbol("abortSteps"),
@ -3271,10 +3367,52 @@
}
/* eslint-enable */
class CountQueuingStrategy {
constructor({ highWaterMark }) {
this.highWaterMark = highWaterMark;
}
size() {
return 1;
}
[customInspect]() {
return `${this.constructor.name} { highWaterMark: ${
String(this.highWaterMark)
}, size: f }`;
}
}
Object.defineProperty(CountQueuingStrategy.prototype, "size", {
enumerable: true,
});
class ByteLengthQueuingStrategy {
constructor({ highWaterMark }) {
this.highWaterMark = highWaterMark;
}
size(chunk) {
return chunk.byteLength;
}
[customInspect]() {
return `${this.constructor.name} { highWaterMark: ${
String(this.highWaterMark)
}, size: f }`;
}
}
Object.defineProperty(ByteLengthQueuingStrategy.prototype, "size", {
enumerable: true,
});
window.__bootstrap.streams = {
ReadableStream,
TransformStream,
WritableStream,
isReadableStreamDisturbed,
CountQueuingStrategy,
ByteLengthQueuingStrategy,
};
})(this);

View file

@ -2,7 +2,7 @@
((window) => {
const { DomIterableMixin } = window.__bootstrap.domIterable;
const { requiredArguments } = window.__bootstrap.webUtil;
const { requiredArguments } = window.__bootstrap.fetchUtil;
// From node-fetch
// Copyright (c) 2016 David Frank. MIT License.

1390
op_crates/fetch/26_fetch.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
[package]
name = "deno_fetch"
version = "0.1.0"
edition = "2018"
description = "fetch Web API"
authors = ["the Deno authors"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/denoland/deno"
[lib]
path = "lib.rs"
[dependencies]
deno_core = { version = "0.57.0", path = "../../core" }
reqwest = { version = "0.10.8", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli"] }
serde = { version = "1.0.116", features = ["derive"] }

636
op_crates/fetch/lib.deno_fetch.d.ts vendored Normal file
View file

@ -0,0 +1,636 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, no-var */
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
interface DomIterable<K, V> {
keys(): IterableIterator<K>;
values(): IterableIterator<V>;
entries(): IterableIterator<[K, V]>;
[Symbol.iterator](): IterableIterator<[K, V]>;
forEach(
callback: (value: V, key: K, parent: this) => void,
thisArg?: any,
): void;
}
interface ReadableStreamReadDoneResult<T> {
done: true;
value?: T;
}
interface ReadableStreamReadValueResult<T> {
done: false;
value: T;
}
type ReadableStreamReadResult<T> =
| ReadableStreamReadValueResult<T>
| ReadableStreamReadDoneResult<T>;
interface ReadableStreamDefaultReader<R = any> {
readonly closed: Promise<void>;
cancel(reason?: any): Promise<void>;
read(): Promise<ReadableStreamReadResult<R>>;
releaseLock(): void;
}
interface ReadableStreamReader<R = any> {
cancel(): Promise<void>;
read(): Promise<ReadableStreamReadResult<R>>;
releaseLock(): void;
}
interface ReadableByteStreamControllerCallback {
(controller: ReadableByteStreamController): void | PromiseLike<void>;
}
interface UnderlyingByteSource {
autoAllocateChunkSize?: number;
cancel?: ReadableStreamErrorCallback;
pull?: ReadableByteStreamControllerCallback;
start?: ReadableByteStreamControllerCallback;
type: "bytes";
}
interface UnderlyingSource<R = any> {
cancel?: ReadableStreamErrorCallback;
pull?: ReadableStreamDefaultControllerCallback<R>;
start?: ReadableStreamDefaultControllerCallback<R>;
type?: undefined;
}
interface ReadableStreamErrorCallback {
(reason: any): void | PromiseLike<void>;
}
interface ReadableStreamDefaultControllerCallback<R> {
(controller: ReadableStreamDefaultController<R>): void | PromiseLike<void>;
}
interface ReadableStreamDefaultController<R = any> {
readonly desiredSize: number | null;
close(): void;
enqueue(chunk: R): void;
error(error?: any): void;
}
interface ReadableByteStreamController {
readonly byobRequest: undefined;
readonly desiredSize: number | null;
close(): void;
enqueue(chunk: ArrayBufferView): void;
error(error?: any): void;
}
interface PipeOptions {
preventAbort?: boolean;
preventCancel?: boolean;
preventClose?: boolean;
signal?: AbortSignal;
}
interface QueuingStrategySizeCallback<T = any> {
(chunk: T): number;
}
interface QueuingStrategy<T = any> {
highWaterMark?: number;
size?: QueuingStrategySizeCallback<T>;
}
/** This Streams API interface provides a built-in byte length queuing strategy
* that can be used when constructing streams. */
declare class CountQueuingStrategy implements QueuingStrategy {
constructor(options: { highWaterMark: number });
highWaterMark: number;
size(chunk: any): 1;
}
declare class ByteLengthQueuingStrategy
implements QueuingStrategy<ArrayBufferView> {
constructor(options: { highWaterMark: number });
highWaterMark: number;
size(chunk: ArrayBufferView): number;
}
/** This Streams API interface represents a readable stream of byte data. The
* Fetch API offers a concrete instance of a ReadableStream through the body
* property of a Response object. */
interface ReadableStream<R = any> {
readonly locked: boolean;
cancel(reason?: any): Promise<void>;
getIterator(options?: { preventCancel?: boolean }): AsyncIterableIterator<R>;
// getReader(options: { mode: "byob" }): ReadableStreamBYOBReader;
getReader(): ReadableStreamDefaultReader<R>;
pipeThrough<T>(
{
writable,
readable,
}: {
writable: WritableStream<R>;
readable: ReadableStream<T>;
},
options?: PipeOptions,
): ReadableStream<T>;
pipeTo(dest: WritableStream<R>, options?: PipeOptions): Promise<void>;
tee(): [ReadableStream<R>, ReadableStream<R>];
[Symbol.asyncIterator](options?: {
preventCancel?: boolean;
}): AsyncIterableIterator<R>;
}
declare var ReadableStream: {
prototype: ReadableStream;
new (
underlyingSource: UnderlyingByteSource,
strategy?: { highWaterMark?: number; size?: undefined },
): ReadableStream<Uint8Array>;
new <R = any>(
underlyingSource?: UnderlyingSource<R>,
strategy?: QueuingStrategy<R>,
): ReadableStream<R>;
};
interface WritableStreamDefaultControllerCloseCallback {
(): void | PromiseLike<void>;
}
interface WritableStreamDefaultControllerStartCallback {
(controller: WritableStreamDefaultController): void | PromiseLike<void>;
}
interface WritableStreamDefaultControllerWriteCallback<W> {
(chunk: W, controller: WritableStreamDefaultController):
| void
| PromiseLike<
void
>;
}
interface WritableStreamErrorCallback {
(reason: any): void | PromiseLike<void>;
}
interface UnderlyingSink<W = any> {
abort?: WritableStreamErrorCallback;
close?: WritableStreamDefaultControllerCloseCallback;
start?: WritableStreamDefaultControllerStartCallback;
type?: undefined;
write?: WritableStreamDefaultControllerWriteCallback<W>;
}
/** This Streams API interface provides a standard abstraction for writing
* streaming data to a destination, known as a sink. This object comes with
* built-in backpressure and queuing. */
declare class WritableStream<W = any> {
constructor(
underlyingSink?: UnderlyingSink<W>,
strategy?: QueuingStrategy<W>,
);
readonly locked: boolean;
abort(reason?: any): Promise<void>;
close(): Promise<void>;
getWriter(): WritableStreamDefaultWriter<W>;
}
/** This Streams API interface represents a controller allowing control of a
* WritableStream's state. When constructing a WritableStream, the underlying
* sink is given a corresponding WritableStreamDefaultController instance to
* manipulate. */
interface WritableStreamDefaultController {
error(error?: any): void;
}
/** This Streams API interface is the object returned by
* WritableStream.getWriter() and once created locks the < writer to the
* WritableStream ensuring that no other streams can write to the underlying
* sink. */
interface WritableStreamDefaultWriter<W = any> {
readonly closed: Promise<void>;
readonly desiredSize: number | null;
readonly ready: Promise<void>;
abort(reason?: any): Promise<void>;
close(): Promise<void>;
releaseLock(): void;
write(chunk: W): Promise<void>;
}
declare class TransformStream<I = any, O = any> {
constructor(
transformer?: Transformer<I, O>,
writableStrategy?: QueuingStrategy<I>,
readableStrategy?: QueuingStrategy<O>,
);
readonly readable: ReadableStream<O>;
readonly writable: WritableStream<I>;
}
interface TransformStreamDefaultController<O = any> {
readonly desiredSize: number | null;
enqueue(chunk: O): void;
error(reason?: any): void;
terminate(): void;
}
interface Transformer<I = any, O = any> {
flush?: TransformStreamDefaultControllerCallback<O>;
readableType?: undefined;
start?: TransformStreamDefaultControllerCallback<O>;
transform?: TransformStreamDefaultControllerTransformCallback<I, O>;
writableType?: undefined;
}
interface TransformStreamDefaultControllerCallback<O> {
(controller: TransformStreamDefaultController<O>): void | PromiseLike<void>;
}
interface TransformStreamDefaultControllerTransformCallback<I, O> {
(
chunk: I,
controller: TransformStreamDefaultController<O>,
): void | PromiseLike<void>;
}
type BlobPart = BufferSource | Blob | string;
interface BlobPropertyBag {
type?: string;
ending?: "transparent" | "native";
}
/** A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */
interface Blob {
readonly size: number;
readonly type: string;
arrayBuffer(): Promise<ArrayBuffer>;
slice(start?: number, end?: number, contentType?: string): Blob;
stream(): ReadableStream;
text(): Promise<string>;
}
declare const Blob: {
prototype: Blob;
new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
};
interface FilePropertyBag extends BlobPropertyBag {
lastModified?: number;
}
/** Provides information about files and allows JavaScript in a web page to
* access their content. */
interface File extends Blob {
readonly lastModified: number;
readonly name: string;
}
declare const File: {
prototype: File;
new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File;
};
type FormDataEntryValue = File | string;
/** Provides a way to easily construct a set of key/value pairs representing
* form fields and their values, which can then be easily sent using the
* XMLHttpRequest.send() method. It uses the same format a form would use if the
* encoding type were set to "multipart/form-data". */
interface FormData extends DomIterable<string, FormDataEntryValue> {
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;
}
declare const FormData: {
prototype: FormData;
// TODO(ry) FormData constructor is non-standard.
// new(form?: HTMLFormElement): FormData;
new (): FormData;
};
interface Body {
/** A simple getter used to expose a `ReadableStream` of the body contents. */
readonly body: ReadableStream<Uint8Array> | null;
/** Stores a `Boolean` that declares whether the body has been used in a
* response yet.
*/
readonly bodyUsed: boolean;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with an `ArrayBuffer`.
*/
arrayBuffer(): Promise<ArrayBuffer>;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with a `Blob`.
*/
blob(): Promise<Blob>;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with a `FormData` object.
*/
formData(): Promise<FormData>;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with the result of parsing the body text as JSON.
*/
json(): Promise<any>;
/** Takes a `Response` stream and reads it to completion. It returns a promise
* that resolves with a `USVString` (text).
*/
text(): Promise<string>;
}
type HeadersInit = Headers | string[][] | Record<string, string>;
/** This Fetch API interface allows you to perform various actions on HTTP
* request and response headers. These actions include retrieving, setting,
* adding to, and removing. A Headers object has an associated header list,
* which is initially empty and consists of zero or more name and value pairs.
*  You can add to this using methods like append() (see Examples.) In all
* methods of this interface, header names are matched by case-insensitive byte
* sequence. */
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;
}
interface Headers extends DomIterable<string, string> {
/** Appends a new value onto an existing header inside a `Headers` object, or
* adds the header if it does not already exist.
*/
append(name: string, value: string): void;
/** Deletes a header from a `Headers` object. */
delete(name: string): void;
/** Returns an iterator allowing to go through all key/value pairs
* contained in this Headers object. The both the key and value of each pairs
* are ByteString objects.
*/
entries(): IterableIterator<[string, string]>;
/** Returns a `ByteString` sequence of all the values of a header within a
* `Headers` object with a given name.
*/
get(name: string): string | null;
/** Returns a boolean stating whether a `Headers` object contains a certain
* header.
*/
has(name: string): boolean;
/** Returns an iterator allowing to go through all keys contained in
* this Headers object. The keys are ByteString objects.
*/
keys(): IterableIterator<string>;
/** Sets a new value for an existing header inside a Headers object, or adds
* the header if it does not already exist.
*/
set(name: string, value: string): void;
/** Returns an iterator allowing to go through all values contained in
* this Headers object. The values are ByteString objects.
*/
values(): IterableIterator<string>;
forEach(
callbackfn: (value: string, key: string, parent: this) => void,
thisArg?: any,
): void;
/** The Symbol.iterator well-known symbol specifies the default
* iterator for this Headers object
*/
[Symbol.iterator](): IterableIterator<[string, string]>;
}
declare const Headers: {
prototype: Headers;
new (init?: HeadersInit): Headers;
};
type RequestInfo = Request | string;
type RequestCache =
| "default"
| "force-cache"
| "no-cache"
| "no-store"
| "only-if-cached"
| "reload";
type RequestCredentials = "include" | "omit" | "same-origin";
type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin";
type RequestRedirect = "error" | "follow" | "manual";
type ReferrerPolicy =
| ""
| "no-referrer"
| "no-referrer-when-downgrade"
| "origin"
| "origin-when-cross-origin"
| "same-origin"
| "strict-origin"
| "strict-origin-when-cross-origin"
| "unsafe-url";
type BodyInit =
| Blob
| BufferSource
| FormData
| URLSearchParams
| ReadableStream<Uint8Array>
| string;
type RequestDestination =
| ""
| "audio"
| "audioworklet"
| "document"
| "embed"
| "font"
| "image"
| "manifest"
| "object"
| "paintworklet"
| "report"
| "script"
| "sharedworker"
| "style"
| "track"
| "video"
| "worker"
| "xslt";
interface RequestInit {
/**
* A BodyInit object or null to set request's body.
*/
body?: BodyInit | null;
/**
* A string indicating how the request will interact with the browser's cache
* to set request's cache.
*/
cache?: RequestCache;
/**
* A string indicating whether credentials will be sent with the request
* always, never, or only when sent to a same-origin URL. Sets request's
* credentials.
*/
credentials?: RequestCredentials;
/**
* A Headers object, an object literal, or an array of two-item arrays to set
* request's headers.
*/
headers?: HeadersInit;
/**
* A cryptographic hash of the resource to be fetched by request. Sets
* request's integrity.
*/
integrity?: string;
/**
* A boolean to set request's keepalive.
*/
keepalive?: boolean;
/**
* A string to set request's method.
*/
method?: string;
/**
* A string to indicate whether the request will use CORS, or will be
* restricted to same-origin URLs. Sets request's mode.
*/
mode?: RequestMode;
/**
* A string indicating whether request follows redirects, results in an error
* upon encountering a redirect, or returns the redirect (in an opaque
* fashion). Sets request's redirect.
*/
redirect?: RequestRedirect;
/**
* A string whose value is a same-origin URL, "about:client", or the empty
* string, to set request's referrer.
*/
referrer?: string;
/**
* A referrer policy to set request's referrerPolicy.
*/
referrerPolicy?: ReferrerPolicy;
/**
* An AbortSignal to set request's signal.
*/
signal?: AbortSignal | null;
/**
* Can only be null. Used to disassociate request from any Window.
*/
window?: any;
}
/** This Fetch API interface represents a resource request. */
interface Request extends Body {
/**
* Returns the cache mode associated with request, which is a string
* indicating how 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-forward 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 const Request: {
prototype: Request;
new (input: RequestInfo, init?: RequestInit): Request;
};
declare const Response: {
prototype: Response;
new (body?: BodyInit | null, init?: ResponseInit): Response;
error(): Response;
redirect(url: string, status?: number): Response;
};
/** Fetch a resource from the network. It returns a Promise that resolves to the
* Response to that request, whether it is successful or not.
*
* const response = await fetch("http://my.json.host/data.json");
* console.log(response.status); // e.g. 200
* console.log(response.statusText); // e.g. "OK"
* const jsonData = await response.json();
*/
declare function fetch(
input: Request | URL | string,
init?: RequestInit,
): Promise<Response>;

266
op_crates/fetch/lib.rs Normal file
View file

@ -0,0 +1,266 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use deno_core::error::bad_resource_id;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures;
use deno_core::js_check;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::url;
use deno_core::url::Url;
use deno_core::BufVec;
use deno_core::JsRuntime;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use reqwest::header::HeaderMap;
use reqwest::header::HeaderName;
use reqwest::header::HeaderValue;
use reqwest::header::USER_AGENT;
use reqwest::redirect::Policy;
use reqwest::Client;
use reqwest::Method;
use reqwest::Response;
use serde::Deserialize;
use std::cell::RefCell;
use std::convert::From;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
pub fn init(isolate: &mut JsRuntime) {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let files = vec![
manifest_dir.join("01_fetch_util.js"),
manifest_dir.join("03_dom_iterable.js"),
manifest_dir.join("11_streams.js"),
manifest_dir.join("20_headers.js"),
manifest_dir.join("26_fetch.js"),
];
// TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the
// workspace root.
let display_root = manifest_dir.parent().unwrap().parent().unwrap();
for file in files {
println!("cargo:rerun-if-changed={}", file.display());
let display_path = file.strip_prefix(display_root).unwrap();
let display_path_str = display_path.display().to_string();
js_check(isolate.execute(
&("deno:".to_string() + &display_path_str.replace('\\', "/")),
&std::fs::read_to_string(&file).unwrap(),
));
}
}
pub trait FetchPermissions {
fn check_net_url(&self, url: &Url) -> Result<(), AnyError>;
fn check_read(&self, p: &PathBuf) -> Result<(), AnyError>;
}
pub fn get_declaration() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_fetch.d.ts")
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct FetchArgs {
method: Option<String>,
url: String,
headers: Vec<(String, String)>,
client_rid: Option<u32>,
}
pub async fn op_fetch<FP>(
state: Rc<RefCell<OpState>>,
args: Value,
data: BufVec,
) -> Result<Value, AnyError>
where
FP: FetchPermissions + 'static,
{
let args: FetchArgs = serde_json::from_value(args)?;
let url = args.url;
let client = if let Some(rid) = args.client_rid {
let state_ = state.borrow();
let r = state_
.resource_table
.get::<HttpClientResource>(rid)
.ok_or_else(bad_resource_id)?;
r.client.clone()
} else {
let state_ = state.borrow();
let client = state_.borrow::<reqwest::Client>();
client.clone()
};
let method = match args.method {
Some(method_str) => Method::from_bytes(method_str.as_bytes())?,
None => Method::GET,
};
let url_ = url::Url::parse(&url)?;
// Check scheme before asking for net permission
let scheme = url_.scheme();
if scheme != "http" && scheme != "https" {
return Err(type_error(format!("scheme '{}' not supported", scheme)));
}
{
let state_ = state.borrow();
// TODO(ry) The Rc below is a hack because we store Rc<CliState> in OpState.
// Ideally it could be removed.
let permissions = state_.borrow::<Rc<FP>>();
permissions.check_net_url(&url_)?;
}
let mut request = client.request(method, url_);
match data.len() {
0 => {}
1 => request = request.body(Vec::from(&*data[0])),
_ => panic!("Invalid number of arguments"),
}
for (key, value) in args.headers {
let name = HeaderName::from_bytes(key.as_bytes()).unwrap();
let v = HeaderValue::from_str(&value).unwrap();
request = request.header(name, v);
}
//debug!("Before fetch {}", url);
let res = request.send().await?;
//debug!("Fetch response {}", url);
let status = res.status();
let mut res_headers = Vec::new();
for (key, val) in res.headers().iter() {
res_headers.push((key.to_string(), val.to_str().unwrap().to_owned()));
}
let rid = state
.borrow_mut()
.resource_table
.add("httpBody", Box::new(res));
Ok(json!({
"bodyRid": rid,
"status": status.as_u16(),
"statusText": status.canonical_reason().unwrap_or(""),
"headers": res_headers
}))
}
pub async fn op_fetch_read(
state: Rc<RefCell<OpState>>,
args: Value,
_data: BufVec,
) -> Result<Value, AnyError> {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct Args {
rid: u32,
}
let args: Args = serde_json::from_value(args)?;
let rid = args.rid;
use futures::future::poll_fn;
use futures::ready;
use futures::FutureExt;
let f = poll_fn(move |cx| {
let mut state = state.borrow_mut();
let response = state
.resource_table
.get_mut::<Response>(rid as u32)
.ok_or_else(bad_resource_id)?;
let mut chunk_fut = response.chunk().boxed_local();
let r = ready!(chunk_fut.poll_unpin(cx))?;
if let Some(chunk) = r {
Ok(json!({ "chunk": &*chunk })).into()
} else {
Ok(json!({ "chunk": null })).into()
}
});
f.await
/*
// I'm programming this as I want it to be programmed, even though it might be
// incorrect, normally we would use poll_fn here. We need to make this await pattern work.
let chunk = response.chunk().await?;
if let Some(chunk) = chunk {
// TODO(ry) This is terribly inefficient. Make this zero-copy.
Ok(json!({ "chunk": &*chunk }))
} else {
Ok(json!({ "chunk": null }))
}
*/
}
struct HttpClientResource {
client: Client,
}
impl HttpClientResource {
fn new(client: Client) -> Self {
Self { client }
}
}
pub fn op_create_http_client<FP>(
state: &mut OpState,
args: Value,
_zero_copy: &mut [ZeroCopyBuf],
) -> Result<Value, AnyError>
where
FP: FetchPermissions + 'static,
{
#[derive(Deserialize, Default, Debug)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
struct CreateHttpClientOptions {
ca_file: Option<String>,
}
let args: CreateHttpClientOptions = serde_json::from_value(args)?;
if let Some(ca_file) = args.ca_file.clone() {
// TODO(ry) The Rc below is a hack because we store Rc<CliState> in OpState.
// Ideally it could be removed.
let permissions = state.borrow::<Rc<FP>>();
permissions.check_read(&PathBuf::from(ca_file))?;
}
let client = create_http_client(args.ca_file.as_deref()).unwrap();
let rid = state
.resource_table
.add("httpClient", Box::new(HttpClientResource::new(client)));
Ok(json!(rid))
}
/// Create new instance of async reqwest::Client. This client supports
/// proxies and doesn't follow redirects.
fn create_http_client(ca_file: Option<&str>) -> Result<Client, AnyError> {
let mut headers = HeaderMap::new();
// TODO(ry) set the verison correctly.
headers.insert(USER_AGENT, format!("Deno/{}", "x.x.x").parse().unwrap());
let mut builder = Client::builder()
.redirect(Policy::none())
.default_headers(headers)
.use_rustls_tls();
if let Some(ca_file) = ca_file {
let mut buf = Vec::new();
File::open(ca_file)?.read_to_end(&mut buf)?;
let cert = reqwest::Certificate::from_pem(&buf)?;
builder = builder.add_root_certificate(cert);
}
builder
.build()
.map_err(|_| deno_core::error::generic_error("Unable to build http client"))
}

View file

@ -197,7 +197,7 @@ Deno.test("contentType", async () => {
(response.body as Deno.File).close();
});
/*
/* TODO https://github.com/denoland/deno/issues/7540
Deno.test("file_server running as library", async function (): Promise<void> {
await startFileServerAsLibrary();
try {