Skip to content

Commit

Permalink
feat: Blob support for fetch API (#1070)
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford authored Dec 12, 2024
1 parent 5e2c682 commit 56aa96d
Show file tree
Hide file tree
Showing 15 changed files with 88 additions and 66 deletions.
1 change: 0 additions & 1 deletion integration-tests/js-compute/fixtures/app/tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -1096,7 +1096,6 @@
"GET /request/clone/valid": {},
"GET /request/clone/invalid": {},
"GET /response/blob": {
"environments": ["<disabled for now>"],
"downstream_response": {
"status": 200,
"headers": { "content-type": "text/html" },
Expand Down
46 changes: 20 additions & 26 deletions runtime/fastly/builtins/fetch/request-response.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "request-response.h"
#include "../../../StarlingMonkey/builtins/web/base64.h"
// #include "../../../StarlingMonkey/builtins/web/blob.h"
#include "../../../StarlingMonkey/builtins/web/blob.h"
#include "../../../StarlingMonkey/builtins/web/dom-exception.h"
#include "../../../StarlingMonkey/builtins/web/streams/native-stream-source.h"
#include "../../../StarlingMonkey/builtins/web/streams/transform-stream.h"
Expand Down Expand Up @@ -34,8 +34,8 @@
#pragma clang diagnostic pop

using builtins::web::base64::valueToJSByteString;
// using builtins::web::blob::Blob;
// using builtins::web::blob::BlobReader;
using builtins::web::blob::Blob;
using builtins::web::blob::BlobReader;
using builtins::web::dom_exception::DOMException;

// We use the StarlingMonkey Headers implementation, despite it supporting features that we do
Expand Down Expand Up @@ -360,9 +360,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,

host_api::HostString host_type_str;

// Blob support disabled pending bug fix in test
// /override-content-length/request/init/object-literal/true
/*if (body_obj && Blob::is_instance(body_obj)) {
if (Blob::is_instance(body_obj)) {
auto native_stream = NativeStreamSource::create(cx, body_obj, JS::UndefinedHandleValue,
Blob::stream_pull, Blob::stream_cancel);
if (!native_stream) {
Expand Down Expand Up @@ -396,8 +394,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self,
MOZ_ASSERT(host_type_str);
content_type = host_type_str.ptr.get();
}
} else */
if (body_obj && JS::IsReadableStream(body_obj)) {
} else if (body_obj && JS::IsReadableStream(body_obj)) {
if (RequestOrResponse::body_unusable(cx, body_obj)) {
JS_ReportErrorNumberLatin1(cx, FastlyGetErrorMessage, nullptr,
JSMSG_READABLE_STREAM_LOCKED_OR_DISTRUBED);
Expand Down Expand Up @@ -584,9 +581,7 @@ bool RequestOrResponse::parse_body(JSContext *cx, JS::HandleObject self, JS::Uni
}
static_cast<void>(buf.release());
result.setObject(*array_buffer);
}
// TODO: Blob support disabled pending bug fix
/* else if constexpr (result_type == RequestOrResponse::BodyReadResult::Blob) {
} else if constexpr (result_type == RequestOrResponse::BodyReadResult::Blob) {
JS::RootedString contentType(cx, JS_GetEmptyString(cx));
JS::RootedObject blob(cx, Blob::create(cx, std::move(buf), len, contentType));

Expand All @@ -595,8 +590,7 @@ bool RequestOrResponse::parse_body(JSContext *cx, JS::HandleObject self, JS::Uni
}

result.setObject(*blob);
} */
else {
} else {
JS::RootedString text(cx, JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(buf.get(), len)));
if (!text) {
return RejectPromiseWithPendingError(cx, result_promise);
Expand Down Expand Up @@ -1692,8 +1686,7 @@ const JSPropertySpec Request::static_properties[] = {
const JSFunctionSpec Request::methods[] = {
JS_FN("arrayBuffer", Request::bodyAll<RequestOrResponse::BodyReadResult::ArrayBuffer>, 0,
JSPROP_ENUMERATE),
// JS_FN("blob", Request::bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0,
// JSPROP_ENUMERATE),
JS_FN("blob", Request::bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0, JSPROP_ENUMERATE),
JS_FN("json", Request::bodyAll<RequestOrResponse::BodyReadResult::JSON>, 0, JSPROP_ENUMERATE),
JS_FN("text", Request::bodyAll<RequestOrResponse::BodyReadResult::Text>, 0, JSPROP_ENUMERATE),
JS_FN("setCacheOverride", Request::setCacheOverride, 3, JSPROP_ENUMERATE),
Expand Down Expand Up @@ -1910,9 +1903,10 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H
JS::RootedValue cache_override(cx);
JS::RootedValue cache_key(cx);
JS::RootedValue fastly_val(cx);
JS::RootedValue manualFramingHeaders(cx);
bool hasmanualFramingHeaders;
bool hasManualFramingHeaders = false;
bool setManualFramingHeaders = false;
if (init_val.isObject()) {
JS::RootedValue manualFramingHeaders(cx);
JS::RootedObject init(cx, init_val.toObjectOrNull());
if (!JS_GetProperty(cx, init, "method", &method_val) ||
!JS_GetProperty(cx, init, "headers", &headers_val) ||
Expand All @@ -1921,10 +1915,11 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H
!JS_GetProperty(cx, init, "cacheOverride", &cache_override) ||
!JS_GetProperty(cx, init, "cacheKey", &cache_key) ||
!JS_GetProperty(cx, init, "fastly", &fastly_val) ||
!JS_HasOwnProperty(cx, init, "manualFramingHeaders", &hasmanualFramingHeaders) ||
!JS_HasOwnProperty(cx, init, "manualFramingHeaders", &hasManualFramingHeaders) ||
!JS_GetProperty(cx, init, "manualFramingHeaders", &manualFramingHeaders)) {
return nullptr;
}
setManualFramingHeaders = manualFramingHeaders.isBoolean() && manualFramingHeaders.toBoolean();
} else if (!init_val.isNullOrUndefined()) {
JS_ReportErrorLatin1(cx, "Request constructor: |init| parameter can't be converted to "
"a dictionary");
Expand Down Expand Up @@ -2229,18 +2224,17 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H
JS::BooleanValue(false));
}

if (!hasmanualFramingHeaders) {
if (!hasManualFramingHeaders) {
if (input_request) {
manualFramingHeaders.set(
JS::GetReservedSlot(input_request, static_cast<uint32_t>(Slots::ManualFramingHeaders)));
} else {
manualFramingHeaders.setBoolean(false);
auto val =
JS::GetReservedSlot(input_request, static_cast<uint32_t>(Slots::ManualFramingHeaders));
setManualFramingHeaders = val.isBoolean() && val.toBoolean();
}
}
JS::SetReservedSlot(request, static_cast<uint32_t>(Slots::ManualFramingHeaders),
JS::BooleanValue(JS::ToBoolean(manualFramingHeaders)));
JS::BooleanValue(setManualFramingHeaders));

if (JS::ToBoolean(manualFramingHeaders)) {
if (setManualFramingHeaders) {
auto res =
request_handle.set_framing_headers_mode(host_api::FramingHeadersMode::ManuallyFromHeaders);
if (auto *err = res.to_err()) {
Expand Down Expand Up @@ -2947,7 +2941,7 @@ const JSPropertySpec Response::static_properties[] = {
const JSFunctionSpec Response::methods[] = {
JS_FN("arrayBuffer", bodyAll<RequestOrResponse::BodyReadResult::ArrayBuffer>, 0,
JSPROP_ENUMERATE),
// JS_FN("blob", bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0, JSPROP_ENUMERATE),
JS_FN("blob", bodyAll<RequestOrResponse::BodyReadResult::Blob>, 0, JSPROP_ENUMERATE),
JS_FN("json", bodyAll<RequestOrResponse::BodyReadResult::JSON>, 0, JSPROP_ENUMERATE),
JS_FN("text", bodyAll<RequestOrResponse::BodyReadResult::Text>, 0, JSPROP_ENUMERATE),
JS_FN("setManualFramingHeaders", Response::setManualFramingHeaders, 1, JSPROP_ENUMERATE),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"status": "PASS"
},
"Consume request's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume request's body as arrayBuffer": {
"status": "PASS"
Expand All @@ -21,13 +21,13 @@
"status": "FAIL"
},
"Consume empty blob request body as arrayBuffer": {
"status": "FAIL"
"status": "PASS"
},
"Consume empty text request body as arrayBuffer": {
"status": "PASS"
},
"Consume empty blob request body as text": {
"status": "FAIL"
"status": "PASS"
},
"Consume empty text request body as text": {
"status": "PASS"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"status": "PASS"
},
"Consume String request's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume String request's body as arrayBuffer": {
"status": "PASS"
Expand All @@ -18,7 +18,7 @@
"status": "PASS"
},
"Consume ArrayBuffer request's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume ArrayBuffer request's body as arrayBuffer": {
"status": "PASS"
Expand All @@ -33,7 +33,7 @@
"status": "PASS"
},
"Consume Uint8Array request's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume Uint8Array request's body as arrayBuffer": {
"status": "PASS"
Expand All @@ -48,7 +48,7 @@
"status": "PASS"
},
"Consume Int8Array request's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume Int8Array request's body as arrayBuffer": {
"status": "PASS"
Expand All @@ -63,7 +63,7 @@
"status": "PASS"
},
"Consume Float32Array request's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume Float32Array request's body as arrayBuffer": {
"status": "PASS"
Expand All @@ -78,7 +78,7 @@
"status": "PASS"
},
"Consume DataView request's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume DataView request's body as arrayBuffer": {
"status": "PASS"
Expand All @@ -93,22 +93,22 @@
"status": "FAIL"
},
"Consume blob response's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume blob response's body as text": {
"status": "FAIL"
"status": "PASS"
},
"Consume blob response's body as json": {
"status": "FAIL"
"status": "PASS"
},
"Consume blob response's body as arrayBuffer": {
"status": "FAIL"
"status": "PASS"
},
"Consume blob response's body as bytes": {
"status": "FAIL"
},
"Consume blob response's body as blob (empty blob as input)": {
"status": "FAIL"
"status": "PASS"
},
"Consume JSON from text: '\"null\"'": {
"status": "PASS"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"Request's body: initial state": {
"status": "FAIL"
},
"Request without body cannot be disturbed": {
"status": "PASS"
},
"Check cloning a disturbed request": {
"status": "FAIL"
},
"Check creating a new request from a disturbed request": {
"status": "FAIL"
},
"Check creating a new request with a new body from a disturbed request": {
"status": "FAIL"
},
"Input request used for creating new request became disturbed": {
"status": "FAIL"
},
"Input request used for creating new request became disturbed even if body is not used": {
"status": "FAIL"
},
"Check consuming a disturbed request": {
"status": "FAIL"
},
"Request construction failure should not set \"bodyUsed\"": {
"status": "FAIL"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
"status": "PASS"
},
"Testing empty Request Content-Type header": {
"status": "FAIL"
"status": "PASS"
},
"Test that Request.headers has the [SameObject] extended attribute": {
"status": "PASS"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"status": "PASS"
},
"Default Content-Type for Request with Blob body (no type set)": {
"status": "FAIL"
"status": "PASS"
},
"Default Content-Type for Request with Blob body (empty type)": {
"status": "FAIL"
"status": "PASS"
},
"Default Content-Type for Request with Blob body (set type)": {
"status": "FAIL"
"status": "PASS"
},
"Default Content-Type for Request with buffer source body": {
"status": "PASS"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"status": "PASS"
},
"Request has blob method": {
"status": "FAIL"
"status": "PASS"
},
"Request has formData method": {
"status": "FAIL"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"status": "PASS"
},
"Consume response's body as blob": {
"status": "FAIL"
"status": "PASS"
},
"Consume response's body as arrayBuffer": {
"status": "PASS"
Expand All @@ -21,13 +21,13 @@
"status": "FAIL"
},
"Consume empty blob response body as arrayBuffer": {
"status": "FAIL"
"status": "PASS"
},
"Consume empty text response body as arrayBuffer": {
"status": "PASS"
},
"Consume empty blob response body as text": {
"status": "FAIL"
"status": "PASS"
},
"Consume empty text response body as text": {
"status": "PASS"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"status": "PASS"
},
"ReadableStream start() Error propagates to Response.blob() Promise": {
"status": "FAIL"
"status": "PASS"
},
"ReadableStream start() Error propagates to Response.bytes() Promise": {
"status": "FAIL"
Expand All @@ -27,7 +27,7 @@
"status": "PASS"
},
"ReadableStream pull() Error propagates to Response.blob() Promise": {
"status": "FAIL"
"status": "PASS"
},
"ReadableStream pull() Error propagates to Response.bytes() Promise": {
"status": "FAIL"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"status": "PASS"
},
"Default Content-Type for Response with Blob body (no type set)": {
"status": "FAIL"
"status": "PASS"
},
"Default Content-Type for Response with Blob body (empty type)": {
"status": "FAIL"
"status": "PASS"
},
"Default Content-Type for Response with Blob body (set type)": {
"status": "FAIL"
"status": "PASS"
},
"Default Content-Type for Response with buffer source body": {
"status": "PASS"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Getting blob after getting the Response body - not disturbed, not locked (body source: fetch)": {
"status": "FAIL"
"status": "PASS"
},
"Getting text after getting the Response body - not disturbed, not locked (body source: fetch)": {
"status": "PASS"
Expand All @@ -12,7 +12,7 @@
"status": "PASS"
},
"Getting blob after getting the Response body - not disturbed, not locked (body source: stream)": {
"status": "FAIL"
"status": "PASS"
},
"Getting text after getting the Response body - not disturbed, not locked (body source: stream)": {
"status": "PASS"
Expand All @@ -24,7 +24,7 @@
"status": "PASS"
},
"Getting blob after getting the Response body - not disturbed, not locked (body source: string)": {
"status": "FAIL"
"status": "PASS"
},
"Getting text after getting the Response body - not disturbed, not locked (body source: string)": {
"status": "PASS"
Expand Down
Loading

0 comments on commit 56aa96d

Please sign in to comment.