summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2018-11-14 17:36:34 -0800
committerGitHub <noreply@github.com>2018-11-14 17:36:34 -0800
commit3c8d2bde685d015721b1c0fd6f2a3ae54c156583 (patch)
tree0f770fccbe23bd5a410612678176b449651cd8ad
parent765863e87aea725301d5f528b6de15bfa6022d46 (diff)
Support request method and headers in fetch() (#1188)
Adds a general HttpHeader flatbuffer message for serializing requests and responses.
-rw-r--r--BUILD.gn1
-rw-r--r--Cargo.toml1
-rw-r--r--js/fetch.ts87
-rw-r--r--js/fetch_test.ts31
-rw-r--r--js/headers.ts9
-rw-r--r--src/main.rs1
-rw-r--r--src/msg.fbs24
-rw-r--r--src/msg_util.rs104
-rw-r--r--src/ops.rs93
9 files changed, 266 insertions, 85 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 198f68114..67d643c9c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -55,6 +55,7 @@ main_extern = [
"$rust_build:dirs",
"$rust_build:futures",
"$rust_build:getopts",
+ "$rust_build:http",
"$rust_build:hyper",
"$rust_build:hyper_rustls",
"$rust_build:lazy_static",
diff --git a/Cargo.toml b/Cargo.toml
index ddae4e447..6967f2b57 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ dirs = "1.0.4"
flatbuffers = { path = "third_party/flatbuffers/rust/flatbuffers/" }
futures = "0.1.25"
getopts = "0.2.18"
+http = "0.1.13"
hyper = "0.12.13"
# The current version of hyper-rustls, 0.14.0, depends on tokio-core, which is
# deprecated.
diff --git a/js/fetch.ts b/js/fetch.ts
index bd50cdfdc..636bbba10 100644
--- a/js/fetch.ts
+++ b/js/fetch.ts
@@ -1,5 +1,5 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-import { assert, log, createResolvable, notImplemented } from "./util";
+import { assert, createResolvable, notImplemented } from "./util";
import * as flatbuffers from "./flatbuffers";
import { sendAsync } from "./dispatch";
import * as msg from "gen/msg_generated";
@@ -162,19 +162,73 @@ class Response implements domTypes.Response {
}
}
+function msgHttpRequest(
+ builder: flatbuffers.Builder,
+ url: string,
+ method: null | string,
+ headers: null | domTypes.Headers
+): flatbuffers.Offset {
+ const methodOffset = !method ? -1 : builder.createString(method);
+ let fieldsOffset: flatbuffers.Offset = -1;
+ const urlOffset = builder.createString(url);
+ if (headers) {
+ const kvOffsets: flatbuffers.Offset[] = [];
+ for (const [key, val] of headers.entries()) {
+ const keyOffset = builder.createString(key);
+ const valOffset = builder.createString(val);
+ msg.KeyValue.startKeyValue(builder);
+ msg.KeyValue.addKey(builder, keyOffset);
+ msg.KeyValue.addValue(builder, valOffset);
+ kvOffsets.push(msg.KeyValue.endKeyValue(builder));
+ }
+ fieldsOffset = msg.HttpHeader.createFieldsVector(builder, kvOffsets);
+ } else {
+ }
+ msg.HttpHeader.startHttpHeader(builder);
+ msg.HttpHeader.addIsRequest(builder, true);
+ msg.HttpHeader.addUrl(builder, urlOffset);
+ if (methodOffset >= 0) {
+ msg.HttpHeader.addMethod(builder, methodOffset);
+ }
+ if (fieldsOffset >= 0) {
+ msg.HttpHeader.addFields(builder, fieldsOffset);
+ }
+ return msg.HttpHeader.endHttpHeader(builder);
+}
+
/** Fetch a resource from the network. */
export async function fetch(
- input?: domTypes.Request | string,
+ input: domTypes.Request | string,
init?: domTypes.RequestInit
): Promise<Response> {
- const url = input as string;
- log("dispatch FETCH_REQ", url);
+ let url: string;
+ let method: string | null = null;
+ let headers: domTypes.Headers | null = null;
+
+ if (typeof input === "string") {
+ url = input;
+ if (init != null) {
+ method = init.method || null;
+ if (init.headers) {
+ headers =
+ init.headers instanceof Headers
+ ? init.headers
+ : new Headers(init.headers);
+ } else {
+ headers = null;
+ }
+ }
+ } else {
+ url = input.url;
+ method = input.method;
+ headers = input.headers;
+ }
// Send Fetch message
const builder = flatbuffers.createBuilder();
- const url_ = builder.createString(url);
+ const headerOff = msgHttpRequest(builder, url, method, headers);
msg.Fetch.startFetch(builder);
- msg.Fetch.addUrl(builder, url_);
+ msg.Fetch.addHeader(builder, headerOff);
const resBase = await sendAsync(
builder,
msg.Any.Fetch,
@@ -186,17 +240,22 @@ export async function fetch(
const inner = new msg.FetchRes();
assert(resBase.inner(inner) != null);
- const status = inner.status();
+ const header = inner.header()!;
const bodyRid = inner.bodyRid();
+ assert(!header.isRequest());
+ const status = header.status();
- const headersList: Array<[string, string]> = [];
- const len = inner.headerKeyLength();
- for (let i = 0; i < len; ++i) {
- const key = inner.headerKey(i);
- const value = inner.headerValue(i);
- headersList.push([key, value]);
- }
+ const headersList = deserializeHeaderFields(header);
const response = new Response(status, headersList, bodyRid);
return response;
}
+
+function deserializeHeaderFields(m: msg.HttpHeader): Array<[string, string]> {
+ const out: Array<[string, string]> = [];
+ for (let i = 0; i < m.fieldsLength(); i++) {
+ const item = m.fields(i)!;
+ out.push([item.key()!, item.value()!]);
+ }
+ return out;
+}
diff --git a/js/fetch_test.ts b/js/fetch_test.ts
index 0c60efb9d..8fad8da75 100644
--- a/js/fetch_test.ts
+++ b/js/fetch_test.ts
@@ -46,3 +46,34 @@ testPerm({ net: true }, async function responseClone() {
assertEqual(ab[i], ab1[i]);
}
});
+
+testPerm({ net: true }, async function fetchRequest() {
+ const addr = "127.0.0.1:4501";
+ const listener = deno.listen("tcp", addr);
+ const buf = new deno.Buffer();
+ listener.accept().then(async conn => {
+ buf.readFrom(conn);
+ await conn.write(
+ new TextEncoder().encode(
+ "HTTP/1.0 404 Not Found\r\nContent-Length: 2\r\n\r\nNF"
+ )
+ );
+ conn.close();
+ });
+ const response = await fetch(`http://${addr}/blah`, {
+ method: "POST",
+ headers: [["Hello", "World"], ["Foo", "Bar"]]
+ });
+ listener.close();
+ assertEqual(response.status, 404);
+ assertEqual(response.headers.get("Content-Length"), "2");
+
+ const actual = new TextDecoder().decode(buf.bytes());
+ const expected = [
+ "POST /blah HTTP/1.1\r\n",
+ "hello: World\r\n",
+ "foo: Bar\r\n",
+ `host: ${addr}\r\n\r\n`
+ ].join("");
+ assertEqual(actual, expected);
+});
diff --git a/js/headers.ts b/js/headers.ts
index 6892b0ef2..1b47ac7d3 100644
--- a/js/headers.ts
+++ b/js/headers.ts
@@ -79,7 +79,8 @@ class HeadersBase {
// @internal
// tslint:disable-next-line:variable-name
-export const Headers = DomIterableMixin<string, string, typeof HeadersBase>(
- HeadersBase,
- headerMap
-);
+export class Headers extends DomIterableMixin<
+ string,
+ string,
+ typeof HeadersBase
+>(HeadersBase, headerMap) {}
diff --git a/src/main.rs b/src/main.rs
index 2c33583d0..6530c37fc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,6 +2,7 @@
extern crate dirs;
extern crate flatbuffers;
extern crate getopts;
+extern crate http;
extern crate hyper;
extern crate hyper_rustls;
extern crate libc;
diff --git a/src/msg.fbs b/src/msg.fbs
index 7fdc73946..2a9c013a3 100644
--- a/src/msg.fbs
+++ b/src/msg.fbs
@@ -194,18 +194,26 @@ table KeyValue {
value: string;
}
-table Fetch {
- id: uint;
+// Note this represents The WHOLE header of an http message, not just the key
+// value pairs. That means it includes method and url for Requests and status
+// for responses. This is why it is singular "Header" instead of "Headers".
+table HttpHeader {
+ is_request: bool;
+ // Request only:
+ method: string;
url: string;
- // TODO Supply request headers:
- // header_line: [string];
+ // Response only:
+ status: uint16;
+ // Both:
+ fields: [KeyValue];
+}
+
+table Fetch {
+ header: HttpHeader;
}
table FetchRes {
- id: uint;
- status: int;
- header_key: [string];
- header_value: [string];
+ header: HttpHeader;
body_rid: uint32;
}
diff --git a/src/msg_util.rs b/src/msg_util.rs
index d61be23f7..8634e2282 100644
--- a/src/msg_util.rs
+++ b/src/msg_util.rs
@@ -1,7 +1,18 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
// Helpers for serialization.
use flatbuffers;
+use http::header::HeaderName;
+use http::uri::Uri;
+use http::Method;
+use hyper::header::HeaderMap;
+use hyper::header::HeaderValue;
+use hyper::Body;
+use hyper::Request;
+use hyper::Response;
use msg;
+use std::str::FromStr;
+
+type Headers = HeaderMap<HeaderValue>;
pub fn serialize_key_value<'bldr>(
builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
@@ -15,6 +26,99 @@ pub fn serialize_key_value<'bldr>(
&msg::KeyValueArgs {
key: Some(key),
value: Some(value),
+ ..Default::default()
+ },
+ )
+}
+
+pub fn serialize_request_header<'bldr>(
+ builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
+ r: &Request<Body>,
+) -> flatbuffers::WIPOffset<msg::HttpHeader<'bldr>> {
+ let method = builder.create_string(r.method().as_str());
+ let url = builder.create_string(r.uri().to_string().as_ref());
+
+ let mut fields = Vec::new();
+ for (key, val) in r.headers().iter() {
+ let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap());
+ fields.push(kv);
+ }
+ let fields = builder.create_vector(fields.as_ref());
+
+ msg::HttpHeader::create(
+ builder,
+ &msg::HttpHeaderArgs {
+ is_request: true,
+ method: Some(method),
+ url: Some(url),
+ fields: Some(fields),
+ ..Default::default()
+ },
+ )
+}
+
+pub fn serialize_fields<'bldr>(
+ builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
+ headers: &Headers,
+) -> flatbuffers::WIPOffset<
+ flatbuffers::Vector<
+ 'bldr,
+ flatbuffers::ForwardsUOffset<msg::KeyValue<'bldr>>,
+ >,
+> {
+ let mut fields = Vec::new();
+ for (key, val) in headers.iter() {
+ let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap());
+ fields.push(kv);
+ }
+ return builder.create_vector(fields.as_ref());
+}
+
+// Not to be confused with serialize_response which has nothing to do with HTTP.
+pub fn serialize_http_response<'bldr>(
+ builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
+ r: &Response<Body>,
+) -> flatbuffers::WIPOffset<msg::HttpHeader<'bldr>> {
+ let status = r.status().as_u16();
+ let fields = serialize_fields(builder, r.headers());
+ msg::HttpHeader::create(
+ builder,
+ &msg::HttpHeaderArgs {
+ is_request: false,
+ status,
+ fields: Some(fields),
+ ..Default::default()
},
)
}
+
+pub fn deserialize_request(
+ header_msg: msg::HttpHeader,
+ body: Body,
+) -> Request<Body> {
+ let mut r = Request::new(body);
+
+ assert!(header_msg.is_request() == true);
+
+ let url = header_msg.url().unwrap();
+ let uri = Uri::from_str(url).unwrap();
+ *r.uri_mut() = uri;
+
+ if let Some(method) = header_msg.method() {
+ let method = Method::from_str(method).unwrap();
+ *r.method_mut() = method;
+ }
+
+ if let Some(fields) = header_msg.fields() {
+ let headers = r.headers_mut();
+ for i in 0..fields.len() {
+ let kv = fields.get(i);
+ let key = kv.key().unwrap();
+ let name = HeaderName::from_bytes(key.as_bytes()).unwrap();
+ let value = kv.value().unwrap();
+ let v = HeaderValue::from_str(value).unwrap();
+ headers.insert(name, v);
+ }
+ }
+ r
+}
diff --git a/src/ops.rs b/src/ops.rs
index ef03630f5..d920d25cb 100644
--- a/src/ops.rs
+++ b/src/ops.rs
@@ -389,74 +389,49 @@ fn op_fetch(
assert_eq!(data.len(), 0);
let inner = base.inner_as_fetch().unwrap();
let cmd_id = base.cmd_id();
- let id = inner.id();
- let url = inner.url().unwrap();
+
+ let header = inner.header().unwrap();
+ assert!(header.is_request());
+ let url = header.url().unwrap();
+
+ let body = hyper::Body::empty();
+ let req = msg_util::deserialize_request(header, body);
if let Err(e) = state.check_net(url) {
return odd_future(e);
}
- let url = url.parse::<hyper::Uri>().unwrap();
let client = http_util::get_client();
debug!("Before fetch {}", url);
- let future = client.get(url).and_then(move |res| {
- let status = i32::from(res.status().as_u16());
- debug!("fetch {}", status);
-
- let headers = {
- let map = res.headers();
- let keys = map
- .keys()
- .map(|s| s.as_str().to_string())
- .collect::<Vec<_>>();
- let values = map
- .values()
- .map(|s| s.to_str().unwrap().to_string())
- .collect::<Vec<_>>();
- (keys, values)
- };
-
- let body = res.into_body();
- let body_resource = resources::add_hyper_body(body);
- Ok((status, headers, body_resource))
- });
-
- let future = future.map_err(|err| -> DenoError { err.into() }).and_then(
- move |(status, headers, body_resource)| {
- debug!("fetch body ");
- let builder = &mut FlatBufferBuilder::new();
- // Send the first message without a body. This is just to indicate
- // what status code.
- let header_keys: Vec<&str> = headers.0.iter().map(|s| &**s).collect();
- let header_keys_off =
- builder.create_vector_of_strings(header_keys.as_slice());
- let header_values: Vec<&str> = headers.1.iter().map(|s| &**s).collect();
- let header_values_off =
- builder.create_vector_of_strings(header_values.as_slice());
-
- let inner = msg::FetchRes::create(
- builder,
- &msg::FetchResArgs {
- id,
- status,
- body_rid: body_resource.rid,
- header_key: Some(header_keys_off),
- header_value: Some(header_values_off),
- },
- );
+ let future =
+ client
+ .request(req)
+ .map_err(DenoError::from)
+ .and_then(move |res| {
+ let builder = &mut FlatBufferBuilder::new();
+ let header_off = msg_util::serialize_http_response(builder, &res);
+ let body = res.into_body();
+ let body_resource = resources::add_hyper_body(body);
+ let inner = msg::FetchRes::create(
+ builder,
+ &msg::FetchResArgs {
+ header: Some(header_off),
+ body_rid: body_resource.rid,
+ ..Default::default()
+ },
+ );
- Ok(serialize_response(
- cmd_id,
- builder,
- msg::BaseArgs {
- inner: Some(inner.as_union_value()),
- inner_type: msg::Any::FetchRes,
- ..Default::default()
- },
- ))
- },
- );
+ Ok(serialize_response(
+ cmd_id,
+ builder,
+ msg::BaseArgs {
+ inner: Some(inner.as_union_value()),
+ inner_type: msg::Any::FetchRes,
+ ..Default::default()
+ },
+ ))
+ });
Box::new(future)
}