mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
Support more fetch init body types (#1449)
This commit is contained in:
parent
5b9c488921
commit
8d452d74fa
4 changed files with 91 additions and 3 deletions
|
@ -3,7 +3,7 @@ import * as domTypes from "./dom_types";
|
||||||
import { containsOnlyASCII } from "./util";
|
import { containsOnlyASCII } from "./util";
|
||||||
import { TextEncoder } from "./text_encoding";
|
import { TextEncoder } from "./text_encoding";
|
||||||
|
|
||||||
const bytesSymbol = Symbol("bytes");
|
export const bytesSymbol = Symbol("bytes");
|
||||||
|
|
||||||
export class DenoBlob implements domTypes.Blob {
|
export class DenoBlob implements domTypes.Blob {
|
||||||
private readonly [bytesSymbol]: Uint8Array;
|
private readonly [bytesSymbol]: Uint8Array;
|
||||||
|
|
23
js/fetch.ts
23
js/fetch.ts
|
@ -5,12 +5,13 @@ import { sendAsync } from "./dispatch";
|
||||||
import * as msg from "gen/msg_generated";
|
import * as msg from "gen/msg_generated";
|
||||||
import * as domTypes from "./dom_types";
|
import * as domTypes from "./dom_types";
|
||||||
import { TextDecoder, TextEncoder } from "./text_encoding";
|
import { TextDecoder, TextEncoder } from "./text_encoding";
|
||||||
import { DenoBlob } from "./blob";
|
import { DenoBlob, bytesSymbol as blobBytesSymbol } from "./blob";
|
||||||
import { Headers } from "./headers";
|
import { Headers } from "./headers";
|
||||||
import * as io from "./io";
|
import * as io from "./io";
|
||||||
import { read, close } from "./files";
|
import { read, close } from "./files";
|
||||||
import { Buffer } from "./buffer";
|
import { Buffer } from "./buffer";
|
||||||
import { FormData } from "./form_data";
|
import { FormData } from "./form_data";
|
||||||
|
import { URLSearchParams } from "./url_search_params";
|
||||||
|
|
||||||
function getHeaderValueParams(value: string): Map<string, string> {
|
function getHeaderValueParams(value: string): Map<string, string> {
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
|
@ -165,7 +166,7 @@ class Body implements domTypes.Body, domTypes.ReadableStream, io.ReadCloser {
|
||||||
// TODO: based on spec
|
// TODO: based on spec
|
||||||
// https://xhr.spec.whatwg.org/#dom-formdata-append
|
// https://xhr.spec.whatwg.org/#dom-formdata-append
|
||||||
// https://xhr.spec.whatwg.org/#create-an-entry
|
// 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...
|
// to the internally created file object...
|
||||||
formData.append(dispositionName, blob, filename);
|
formData.append(dispositionName, blob, filename);
|
||||||
} else {
|
} else {
|
||||||
|
@ -358,14 +359,32 @@ export async function fetch(
|
||||||
headers = null;
|
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 (init.body) {
|
||||||
|
if (!headers) {
|
||||||
|
headers = new Headers();
|
||||||
|
}
|
||||||
|
let contentType = "";
|
||||||
if (typeof init.body === "string") {
|
if (typeof init.body === "string") {
|
||||||
body = new TextEncoder().encode(init.body);
|
body = new TextEncoder().encode(init.body);
|
||||||
|
contentType = "text/plain;charset=UTF-8";
|
||||||
} else if (isTypedArray(init.body)) {
|
} else if (isTypedArray(init.body)) {
|
||||||
body = 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 {
|
} else {
|
||||||
|
// TODO: FormData, ReadableStream
|
||||||
notImplemented();
|
notImplemented();
|
||||||
}
|
}
|
||||||
|
if (contentType && !headers.has("content-type")) {
|
||||||
|
headers.set("content-type", contentType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -84,6 +84,57 @@ testPerm({ net: true }, async function fetchURLEncodedFormDataSuccess() {
|
||||||
assertEqual(formData.get("field_2").toString(), "<Deno>");
|
assertEqual(formData.get("field_2").toString(), "<Deno>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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
|
// 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:
|
// somewhere. Here is what one of these flaky failures looks like:
|
||||||
//
|
//
|
||||||
|
|
|
@ -39,6 +39,24 @@ class ContentTypeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||||
return
|
return
|
||||||
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
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):
|
def guess_type(self, path):
|
||||||
if ".t1." in path:
|
if ".t1." in path:
|
||||||
return "text/typescript"
|
return "text/typescript"
|
||||||
|
|
Loading…
Reference in a new issue