From 8d452d74fa6f63eb0bce0567083d375b50329de4 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Thu, 3 Jan 2019 06:41:20 -0500 Subject: [PATCH] Support more fetch init body types (#1449) --- js/blob.ts | 2 +- js/fetch.ts | 23 ++++++++++++++++++-- js/fetch_test.ts | 51 ++++++++++++++++++++++++++++++++++++++++++++ tools/http_server.py | 18 ++++++++++++++++ 4 files changed, 91 insertions(+), 3 deletions(-) diff --git a/js/blob.ts b/js/blob.ts index 96aae496e8..7510c9cef4 100644 --- a/js/blob.ts +++ b/js/blob.ts @@ -3,7 +3,7 @@ import * as domTypes from "./dom_types"; import { containsOnlyASCII } from "./util"; import { TextEncoder } from "./text_encoding"; -const bytesSymbol = Symbol("bytes"); +export const bytesSymbol = Symbol("bytes"); export class DenoBlob implements domTypes.Blob { private readonly [bytesSymbol]: Uint8Array; diff --git a/js/fetch.ts b/js/fetch.ts index 34afccd24a..fd6299c8f7 100644 --- a/js/fetch.ts +++ b/js/fetch.ts @@ -5,12 +5,13 @@ import { sendAsync } from "./dispatch"; import * as msg from "gen/msg_generated"; import * as domTypes from "./dom_types"; import { TextDecoder, TextEncoder } from "./text_encoding"; -import { DenoBlob } from "./blob"; +import { DenoBlob, bytesSymbol as blobBytesSymbol } from "./blob"; import { Headers } from "./headers"; import * as io from "./io"; import { read, close } from "./files"; import { Buffer } from "./buffer"; import { FormData } from "./form_data"; +import { URLSearchParams } from "./url_search_params"; function getHeaderValueParams(value: string): Map { const params = new Map(); @@ -165,7 +166,7 @@ class Body implements domTypes.Body, domTypes.ReadableStream, io.ReadCloser { // TODO: based on spec // https://xhr.spec.whatwg.org/#dom-formdata-append // https://xhr.spec.whatwg.org/#create-an-entry - // Currently it does not meantion how I could pass content-type + // Currently it does not mention how I could pass content-type // to the internally created file object... formData.append(dispositionName, blob, filename); } else { @@ -358,14 +359,32 @@ export async function fetch( 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 URLSearchParams) { + body = new TextEncoder().encode(init.body.toString()); + contentType = "application/x-www-form-urlencoded;charset=UTF-8"; + } else if (init.body instanceof DenoBlob) { + body = init.body[blobBytesSymbol]; + contentType = init.body.type; } else { + // TODO: FormData, ReadableStream notImplemented(); } + if (contentType && !headers.has("content-type")) { + headers.set("content-type", contentType); + } } } } else { diff --git a/js/fetch_test.ts b/js/fetch_test.ts index 773b2eebd6..582f92839e 100644 --- a/js/fetch_test.ts +++ b/js/fetch_test.ts @@ -84,6 +84,57 @@ testPerm({ net: true }, async function fetchURLEncodedFormDataSuccess() { assertEqual(formData.get("field_2").toString(), ""); }); +testPerm({ net: true }, async function fetchInitStringBody() { + const data = "Hello World"; + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: data + }); + const text = await response.text(); + assertEqual(text, data); + assert(response.headers.get("content-type").startsWith("text/plain")); +}); + +testPerm({ net: true }, async function fetchInitTypedArrayBody() { + const data = "Hello World"; + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: new TextEncoder().encode(data) + }); + const text = await response.text(); + assertEqual(text, data); +}); + +testPerm({ net: true }, async function fetchInitURLSearchParamsBody() { + const data = "param1=value1¶m2=value2"; + const params = new URLSearchParams(data); + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: params + }); + const text = await response.text(); + assertEqual(text, data); + assert( + response.headers + .get("content-type") + .startsWith("application/x-www-form-urlencoded") + ); +}); + +testPerm({ net: true }, async function fetchInitBlobBody() { + const data = "const a = 1"; + const blob = new Blob([data], { + type: "text/javascript" + }); + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: blob + }); + const text = await response.text(); + assertEqual(text, data); + assert(response.headers.get("content-type").startsWith("text/javascript")); +}); + // TODO(ry) The following tests work but are flaky. There's a race condition // somewhere. Here is what one of these flaky failures looks like: // diff --git a/tools/http_server.py b/tools/http_server.py index 7c4b1fe251..74402f5366 100755 --- a/tools/http_server.py +++ b/tools/http_server.py @@ -39,6 +39,24 @@ class ContentTypeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): return return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + def do_POST(self): + # Simple echo server for request reflection + if "echo_server" in self.path: + self.protocol_version = 'HTTP/1.1' + self.send_response(200, 'OK') + if self.headers.has_key('content-type'): + self.send_header('content-type', + self.headers.getheader('content-type')) + self.end_headers() + data_string = self.rfile.read(int(self.headers['Content-Length'])) + self.wfile.write(bytes(data_string)) + return + self.protocol_version = 'HTTP/1.1' + self.send_response(501) + self.send_header('content-type', 'text/plain') + self.end_headers() + self.wfile.write(bytes('Server does not support this operation')) + def guess_type(self, path): if ".t1." in path: return "text/typescript"