From 3ccefb2c4c5d57d0a1322e4818c70ff4168c7ec8 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 30 Apr 2024 11:24:33 -0400 Subject: [PATCH 01/52] fix fetch attempt --- CMakeLists.txt | 1 + builtins/web/fetch/fetch_event.cpp | 1 + builtins/web/fetch/request-response.cpp | 14 ++++++++--- builtins/web/fetch/request-response.h | 1 + builtins/web/streams/native-stream-source.cpp | 19 ++++++++++++++ builtins/web/streams/native-stream-source.h | 1 + tests/e2e/fetch/expect_serve_body.txt | 1 + tests/e2e/fetch/expect_serve_stderr.txt | 0 tests/e2e/fetch/expect_serve_stdout.txt | 2 ++ tests/e2e/fetch/fetch.js | 25 +++++++++++++++++++ tests/tests.cmake | 5 +--- 11 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 tests/e2e/fetch/expect_serve_body.txt create mode 100644 tests/e2e/fetch/expect_serve_stderr.txt create mode 100644 tests/e2e/fetch/expect_serve_stdout.txt create mode 100644 tests/e2e/fetch/fetch.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 92184809..2c16b500 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,5 +102,6 @@ function(componentize OUTPUT) endfunction() componentize(smoke-test SOURCES tests/cases/smoke/smoke.js) +componentize(fetch-test SOURCES tests/cases/fetch/fetch.js) include("tests/tests.cmake") diff --git a/builtins/web/fetch/fetch_event.cpp b/builtins/web/fetch/fetch_event.cpp index 593a386f..5d939519 100644 --- a/builtins/web/fetch/fetch_event.cpp +++ b/builtins/web/fetch/fetch_event.cpp @@ -144,6 +144,7 @@ bool send_response(host_api::HttpOutgoingResponse *response, JS::HandleObject se FetchEvent::State new_state) { MOZ_ASSERT(FetchEvent::state(self) == FetchEvent::State::unhandled || FetchEvent::state(self) == FetchEvent::State::waitToRespond); + // This isn't always firing. auto result = response->send(RESPONSE_OUT); FetchEvent::set_state(self, new_state); diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 765e2b9f..8726a403 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -334,6 +334,13 @@ bool RequestOrResponse::body_unusable(JSContext *cx, JS::HandleObject body) { return disturbed || locked; } +bool RequestOrResponse::body_disturbed(JSContext *cx, JS::HandleObject body) { + MOZ_ASSERT(JS::IsReadableStream(body)); + bool disturbed; + MOZ_RELEASE_ASSERT(JS::ReadableStreamIsDisturbed(cx, body, &disturbed)); + return disturbed; +} + /** * Implementation of the `extract a body` algorithm at * https://fetch.spec.whatwg.org/#concept-bodyinit-extract @@ -757,7 +764,7 @@ bool RequestOrResponse::consume_content_stream_for_bodyAll(JSContext *cx, JS::Ha return false; } MOZ_ASSERT(JS::IsReadableStream(stream)); - if (RequestOrResponse::body_unusable(cx, stream)) { + if (RequestOrResponse::body_disturbed(cx, stream)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RESPONSE_BODY_DISTURBED_OR_LOCKED); JS::RootedObject result_promise(cx); @@ -766,8 +773,9 @@ bool RequestOrResponse::consume_content_stream_for_bodyAll(JSContext *cx, JS::Ha JS::SetReservedSlot(self, static_cast(Slots::BodyAllPromise), JS::UndefinedValue()); return RejectPromiseWithPendingError(cx, result_promise); } - JS::Rooted unwrappedReader( - cx, JS::ReadableStreamGetReader(cx, stream, JS::ReadableStreamReaderMode::Default)); + + JS::RootedObject unwrappedReader(cx, streams::NativeStreamSource::get_locked_by_internal_reader(cx, stream)); + if (!unwrappedReader) { return false; } diff --git a/builtins/web/fetch/request-response.h b/builtins/web/fetch/request-response.h index e1b2f1d5..f911762d 100644 --- a/builtins/web/fetch/request-response.h +++ b/builtins/web/fetch/request-response.h @@ -42,6 +42,7 @@ class RequestOrResponse final { static JS::Value url(JSObject *obj); static void set_url(JSObject *obj, JS::Value url); static bool body_unusable(JSContext *cx, JS::HandleObject body); + static bool body_disturbed(JSContext *cx, JS::HandleObject body); static bool extract_body(JSContext *cx, JS::HandleObject self, JS::HandleValue body_val); /** diff --git a/builtins/web/streams/native-stream-source.cpp b/builtins/web/streams/native-stream-source.cpp index fb8b56c4..26b82723 100644 --- a/builtins/web/streams/native-stream-source.cpp +++ b/builtins/web/streams/native-stream-source.cpp @@ -103,6 +103,25 @@ bool NativeStreamSource::lock_stream(JSContext *cx, JS::HandleObject stream) { return true; } +JSObject *NativeStreamSource::get_locked_by_internal_reader(JSContext *cx, JS::HandleObject stream) { + // fprintf(stderr, "get_locked_by_internal_reader\n"); + + JS::RootedObject self(cx, get_stream_source(cx, stream)); + MOZ_ASSERT(is_instance(self)); + + // fprintf(stderr, "is instance\n"); + + // This errors out if a the response is not logged?? + bool locked; + JS::ReadableStreamIsLocked(cx, stream, &locked); + MOZ_ASSERT(locked); + + // fprintf(stderr, "readablestreamislocked\n"); + + JS::RootedObject reader(cx, &JS::GetReservedSlot(self, Slots::InternalReader).toObject()); + return reader; +} + bool NativeStreamSource::start(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(1) diff --git a/builtins/web/streams/native-stream-source.h b/builtins/web/streams/native-stream-source.h index d07ec7bd..e5352a79 100644 --- a/builtins/web/streams/native-stream-source.h +++ b/builtins/web/streams/native-stream-source.h @@ -47,6 +47,7 @@ class NativeStreamSource : public BuiltinNoConstructor { JS::HandleObject writable); static JSObject *piped_to_transform_stream(JSObject *self); static bool lock_stream(JSContext *cx, JS::HandleObject stream); + static JSObject *get_locked_by_internal_reader(JSContext *cx, JS::HandleObject stream); static bool start(JSContext *cx, unsigned argc, JS::Value *vp); static bool pull(JSContext *cx, unsigned argc, JS::Value *vp); static bool cancel(JSContext *cx, unsigned argc, JS::Value *vp); diff --git a/tests/e2e/fetch/expect_serve_body.txt b/tests/e2e/fetch/expect_serve_body.txt new file mode 100644 index 00000000..d9524d8f --- /dev/null +++ b/tests/e2e/fetch/expect_serve_body.txt @@ -0,0 +1 @@ +[{"id":1,"name":"Leanne Graham","username":"Bret","email":"Sincere@april.biz","address":{"street":"Kulas Light","suite":"Apt. 556","city":"Gwenborough","zipcode":"92998-3874","geo":{"lat":"-37.3159","lng":"81.1496"}},"phone":"1-770-736-8031 x56442","website":"hildegard.org","company":{"name":"Romaguera-Crona","catchPhrase":"Multi-layered client-server neural-net","bs":"harness real-time e-markets"}},{"id":2,"name":"Ervin Howell","username":"Antonette","email":"Shanna@melissa.tv","address":{"street":"Victor Plains","suite":"Suite 879","city":"Wisokyburgh","zipcode":"90566-7771","geo":{"lat":"-43.9509","lng":"-34.4618"}},"phone":"010-692-6593 x09125","website":"anastasia.net","company":{"name":"Deckow-Crist","catchPhrase":"Proactive didactic contingency","bs":"synergize scalable supply-chains"}},{"id":3,"name":"Clementine Bauch","username":"Samantha","email":"Nathan@yesenia.net","address":{"street":"Douglas Extension","suite":"Suite 847","city":"McKenziehaven","zipcode":"59590-4157","geo":{"lat":"-68.6102","lng":"-47.0653"}},"phone":"1-463-123-4447","website":"ramiro.info","company":{"name":"Romaguera-Jacobson","catchPhrase":"Face to face bifurcated interface","bs":"e-enable strategic applications"}},{"id":4,"name":"Patricia Lebsack","username":"Karianne","email":"Julianne.OConner@kory.org","address":{"street":"Hoeger Mall","suite":"Apt. 692","city":"South Elvis","zipcode":"53919-4257","geo":{"lat":"29.4572","lng":"-164.2990"}},"phone":"493-170-9623 x156","website":"kale.biz","company":{"name":"Robel-Corkery","catchPhrase":"Multi-tiered zero tolerance productivity","bs":"transition cutting-edge web services"}},{"id":5,"name":"Chelsey Dietrich","username":"Kamren","email":"Lucio_Hettinger@annie.ca","address":{"street":"Skiles Walks","suite":"Suite 351","city":"Roscoeview","zipcode":"33263","geo":{"lat":"-31.8129","lng":"62.5342"}},"phone":"(254)954-1289","website":"demarco.info","company":{"name":"Keebler LLC","catchPhrase":"User-centric fault-tolerant solution","bs":"revolutionize end-to-end systems"}},{"id":6,"name":"Mrs. Dennis Schulist","username":"Leopoldo_Corkery","email":"Karley_Dach@jasper.info","address":{"street":"Norberto Crossing","suite":"Apt. 950","city":"South Christy","zipcode":"23505-1337","geo":{"lat":"-71.4197","lng":"71.7478"}},"phone":"1-477-935-8478 x6430","website":"ola.org","company":{"name":"Considine-Lockman","catchPhrase":"Synchronised bottom-line interface","bs":"e-enable innovative applications"}},{"id":7,"name":"Kurtis Weissnat","username":"Elwyn.Skiles","email":"Telly.Hoeger@billy.biz","address":{"street":"Rex Trail","suite":"Suite 280","city":"Howemouth","zipcode":"58804-1099","geo":{"lat":"24.8918","lng":"21.8984"}},"phone":"210.067.6132","website":"elvis.io","company":{"name":"Johns Group","catchPhrase":"Configurable multimedia task-force","bs":"generate enterprise e-tailers"}},{"id":8,"name":"Nicholas Runolfsdottir V","username":"Maxime_Nienow","email":"Sherwood@rosamond.me","address":{"street":"Ellsworth Summit","suite":"Suite 729","city":"Aliyaview","zipcode":"45169","geo":{"lat":"-14.3990","lng":"-120.7677"}},"phone":"586.493.6943 x140","website":"jacynthe.com","company":{"name":"Abernathy Group","catchPhrase":"Implemented secondary concept","bs":"e-enable extensible e-tailers"}},{"id":9,"name":"Glenna Reichert","username":"Delphine","email":"Chaim_McDermott@dana.io","address":{"street":"Dayna Park","suite":"Suite 449","city":"Bartholomebury","zipcode":"76495-3109","geo":{"lat":"24.6463","lng":"-168.8889"}},"phone":"(775)976-6794 x41206","website":"conrad.com","company":{"name":"Yost and Sons","catchPhrase":"Switchable contextually-based project","bs":"aggregate real-time technologies"}},{"id":10,"name":"Clementina DuBuque","username":"Moriah.Stanton","email":"Rey.Padberg@karina.biz","address":{"street":"Kattie Turnpike","suite":"Suite 198","city":"Lebsackbury","zipcode":"31428-2261","geo":{"lat":"-38.2386","lng":"57.2232"}},"phone":"024-648-3804","website":"ambrose.net","company":{"name":"Hoeger LLC","catchPhrase":"Centralized empowering task-force","bs":"target end-to-end models"}}] \ No newline at end of file diff --git a/tests/e2e/fetch/expect_serve_stderr.txt b/tests/e2e/fetch/expect_serve_stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/fetch/expect_serve_stdout.txt b/tests/e2e/fetch/expect_serve_stdout.txt new file mode 100644 index 00000000..ad7f8187 --- /dev/null +++ b/tests/e2e/fetch/expect_serve_stdout.txt @@ -0,0 +1,2 @@ +stdout [0] :: Log: RESPONSE Response { redirected: false, type: "default", url: "https://jsonplaceholder.typicode.com/users", status: 200, ok: true, statusText: "OK", headers: Headers {}, body: ReadableStream { locked: false }, bodyUsed: false } +stdout [0] :: Log: Successfully received response json body diff --git a/tests/e2e/fetch/fetch.js b/tests/e2e/fetch/fetch.js new file mode 100644 index 00000000..5d1b6508 --- /dev/null +++ b/tests/e2e/fetch/fetch.js @@ -0,0 +1,25 @@ +async function main(event) { + let resolve, reject; + + try { + let responsePromise = new Promise(async (res, rej) => { + resolve = res; + reject = rej; + }); + event.respondWith(responsePromise); + + let p = fetch("https://jsonplaceholder.typicode.com/users"); + let response = await p; + // Not logging the response results in an error + console.log("RESPONSE", response.status); + + let text = await response.json(); + console.log("Successfully received response json body"); + + resolve(new Response(JSON.stringify(text), { headers: response.headers })); + } catch (e) { + console.log(`Error: ${e}. Stack: ${e.stack}`); + } +} + +addEventListener('fetch', main); diff --git a/tests/tests.cmake b/tests/tests.cmake index b65ea74f..940efe54 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -27,9 +27,6 @@ function(test_integration TEST_NAME) endfunction() test_e2e(smoke) -test_e2e(tla) -test_e2e(syntax-err) -test_e2e(tla-err) -test_e2e(tla-runtime-resolve) +test_e2e(fetch) test_integration(btoa) From 2d29ef5db2c5d08eecee545490317b5c7161c1fa Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 30 Apr 2024 11:35:43 -0400 Subject: [PATCH 02/52] fix fetch test by locking stream --- builtins/web/fetch/request-response.cpp | 6 +++++- builtins/web/streams/native-stream-source.cpp | 6 ------ tests/e2e/fetch/expect_serve_stdout.txt | 1 - tests/e2e/fetch/fetch.js | 2 -- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 8726a403..dbc86d60 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -1149,7 +1149,11 @@ JSObject *RequestOrResponse::create_body_stream(JSContext *cx, JS::HandleObject return nullptr; } - // TODO: immediately lock the stream if the owner's body is already used. + // If the body has already been used without being reified as a ReadableStream, + // lock the stream immediately. + if (body_used(owner)) { + MOZ_RELEASE_ASSERT(streams::NativeStreamSource::lock_stream(cx, body_stream)); + } JS_SetReservedSlot(owner, static_cast(Slots::BodyStream), JS::ObjectValue(*body_stream)); diff --git a/builtins/web/streams/native-stream-source.cpp b/builtins/web/streams/native-stream-source.cpp index 26b82723..0dc69cc0 100644 --- a/builtins/web/streams/native-stream-source.cpp +++ b/builtins/web/streams/native-stream-source.cpp @@ -104,20 +104,14 @@ bool NativeStreamSource::lock_stream(JSContext *cx, JS::HandleObject stream) { } JSObject *NativeStreamSource::get_locked_by_internal_reader(JSContext *cx, JS::HandleObject stream) { - // fprintf(stderr, "get_locked_by_internal_reader\n"); JS::RootedObject self(cx, get_stream_source(cx, stream)); MOZ_ASSERT(is_instance(self)); - // fprintf(stderr, "is instance\n"); - - // This errors out if a the response is not logged?? bool locked; JS::ReadableStreamIsLocked(cx, stream, &locked); MOZ_ASSERT(locked); - // fprintf(stderr, "readablestreamislocked\n"); - JS::RootedObject reader(cx, &JS::GetReservedSlot(self, Slots::InternalReader).toObject()); return reader; } diff --git a/tests/e2e/fetch/expect_serve_stdout.txt b/tests/e2e/fetch/expect_serve_stdout.txt index ad7f8187..7bcdc8dc 100644 --- a/tests/e2e/fetch/expect_serve_stdout.txt +++ b/tests/e2e/fetch/expect_serve_stdout.txt @@ -1,2 +1 @@ -stdout [0] :: Log: RESPONSE Response { redirected: false, type: "default", url: "https://jsonplaceholder.typicode.com/users", status: 200, ok: true, statusText: "OK", headers: Headers {}, body: ReadableStream { locked: false }, bodyUsed: false } stdout [0] :: Log: Successfully received response json body diff --git a/tests/e2e/fetch/fetch.js b/tests/e2e/fetch/fetch.js index 5d1b6508..0720affb 100644 --- a/tests/e2e/fetch/fetch.js +++ b/tests/e2e/fetch/fetch.js @@ -10,8 +10,6 @@ async function main(event) { let p = fetch("https://jsonplaceholder.typicode.com/users"); let response = await p; - // Not logging the response results in an error - console.log("RESPONSE", response.status); let text = await response.json(); console.log("Successfully received response json body"); From d2636ec724b08c8514c6369150bdf3b20397250a Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 30 Apr 2024 11:36:50 -0400 Subject: [PATCH 03/52] enable all tests again --- tests/tests.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/tests.cmake b/tests/tests.cmake index 940efe54..0852fbdd 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -28,5 +28,9 @@ endfunction() test_e2e(smoke) test_e2e(fetch) +test_e2e(tla) +test_e2e(syntax-err) +test_e2e(tla-err) +test_e2e(tla-runtime-resolve) test_integration(btoa) From 04730615e9c5550e2c147770eed1a97328a0aa19 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 30 Apr 2024 11:42:13 -0400 Subject: [PATCH 04/52] remove obsolete comment --- builtins/web/fetch/fetch_event.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/builtins/web/fetch/fetch_event.cpp b/builtins/web/fetch/fetch_event.cpp index 5d939519..593a386f 100644 --- a/builtins/web/fetch/fetch_event.cpp +++ b/builtins/web/fetch/fetch_event.cpp @@ -144,7 +144,6 @@ bool send_response(host_api::HttpOutgoingResponse *response, JS::HandleObject se FetchEvent::State new_state) { MOZ_ASSERT(FetchEvent::state(self) == FetchEvent::State::unhandled || FetchEvent::state(self) == FetchEvent::State::waitToRespond); - // This isn't always firing. auto result = response->send(RESPONSE_OUT); FetchEvent::set_state(self, new_state); From fe217cefd397978c083957af31c657caa691fdd0 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 30 Apr 2024 13:59:25 -0400 Subject: [PATCH 05/52] fetch test body ReadableStream --- .../{fetch => fetch-1}/expect_serve_body.txt | 0 .../expect_serve_stderr.txt | 0 .../expect_serve_stdout.txt | 0 .../{fetch/fetch.js => fetch-1/fetch-1.js} | 0 tests/e2e/fetch-2/expect_serve_body.txt | 3502 +++++++++++++++++ tests/e2e/fetch-2/expect_serve_stderr.txt | 0 tests/e2e/fetch-2/expect_serve_stdout.txt | 1 + tests/e2e/fetch-2/fetch-2.js | 31 + tests/tests.cmake | 3 +- 9 files changed, 3536 insertions(+), 1 deletion(-) rename tests/e2e/{fetch => fetch-1}/expect_serve_body.txt (100%) rename tests/e2e/{fetch => fetch-1}/expect_serve_stderr.txt (100%) rename tests/e2e/{fetch => fetch-1}/expect_serve_stdout.txt (100%) rename tests/e2e/{fetch/fetch.js => fetch-1/fetch-1.js} (100%) create mode 100644 tests/e2e/fetch-2/expect_serve_body.txt create mode 100644 tests/e2e/fetch-2/expect_serve_stderr.txt create mode 100644 tests/e2e/fetch-2/expect_serve_stdout.txt create mode 100644 tests/e2e/fetch-2/fetch-2.js diff --git a/tests/e2e/fetch/expect_serve_body.txt b/tests/e2e/fetch-1/expect_serve_body.txt similarity index 100% rename from tests/e2e/fetch/expect_serve_body.txt rename to tests/e2e/fetch-1/expect_serve_body.txt diff --git a/tests/e2e/fetch/expect_serve_stderr.txt b/tests/e2e/fetch-1/expect_serve_stderr.txt similarity index 100% rename from tests/e2e/fetch/expect_serve_stderr.txt rename to tests/e2e/fetch-1/expect_serve_stderr.txt diff --git a/tests/e2e/fetch/expect_serve_stdout.txt b/tests/e2e/fetch-1/expect_serve_stdout.txt similarity index 100% rename from tests/e2e/fetch/expect_serve_stdout.txt rename to tests/e2e/fetch-1/expect_serve_stdout.txt diff --git a/tests/e2e/fetch/fetch.js b/tests/e2e/fetch-1/fetch-1.js similarity index 100% rename from tests/e2e/fetch/fetch.js rename to tests/e2e/fetch-1/fetch-1.js diff --git a/tests/e2e/fetch-2/expect_serve_body.txt b/tests/e2e/fetch-2/expect_serve_body.txt new file mode 100644 index 00000000..0d7ba4cd --- /dev/null +++ b/tests/e2e/fetch-2/expect_serve_body.txt @@ -0,0 +1,3502 @@ +[ + { + "postId": 1, + "id": 1, + "name": "id labore ex et quam laborum", + "email": "Eliseo@gardner.biz", + "body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium" + }, + { + "postId": 1, + "id": 2, + "name": "quo vero reiciendis velit similique earum", + "email": "Jayne_Kuhic@sydney.com", + "body": "est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et" + }, + { + "postId": 1, + "id": 3, + "name": "odio adipisci rerum aut animi", + "email": "Nikita@garfield.biz", + "body": "quia molestiae reprehenderit quasi aspernatur\naut expedita occaecati aliquam eveniet laudantium\nomnis quibusdam delectus saepe quia accusamus maiores nam est\ncum et ducimus et vero voluptates excepturi deleniti ratione" + }, + { + "postId": 1, + "id": 4, + "name": "alias odio sit", + "email": "Lew@alysha.tv", + "body": "non et atque\noccaecati deserunt quas accusantium unde odit nobis qui voluptatem\nquia voluptas consequuntur itaque dolor\net qui rerum deleniti ut occaecati" + }, + { + "postId": 1, + "id": 5, + "name": "vero eaque aliquid doloribus et culpa", + "email": "Hayden@althea.biz", + "body": "harum non quasi et ratione\ntempore iure ex voluptates in ratione\nharum architecto fugit inventore cupiditate\nvoluptates magni quo et" + }, + { + "postId": 2, + "id": 6, + "name": "et fugit eligendi deleniti quidem qui sint nihil autem", + "email": "Presley.Mueller@myrl.com", + "body": "doloribus at sed quis culpa deserunt consectetur qui praesentium\naccusamus fugiat dicta\nvoluptatem rerum ut voluptate autem\nvoluptatem repellendus aspernatur dolorem in" + }, + { + "postId": 2, + "id": 7, + "name": "repellat consequatur praesentium vel minus molestias voluptatum", + "email": "Dallas@ole.me", + "body": "maiores sed dolores similique labore et inventore et\nquasi temporibus esse sunt id et\neos voluptatem aliquam\naliquid ratione corporis molestiae mollitia quia et magnam dolor" + }, + { + "postId": 2, + "id": 8, + "name": "et omnis dolorem", + "email": "Mallory_Kunze@marie.org", + "body": "ut voluptatem corrupti velit\nad voluptatem maiores\net nisi velit vero accusamus maiores\nvoluptates quia aliquid ullam eaque" + }, + { + "postId": 2, + "id": 9, + "name": "provident id voluptas", + "email": "Meghan_Littel@rene.us", + "body": "sapiente assumenda molestiae atque\nadipisci laborum distinctio aperiam et ab ut omnis\net occaecati aspernatur odit sit rem expedita\nquas enim ipsam minus" + }, + { + "postId": 2, + "id": 10, + "name": "eaque et deleniti atque tenetur ut quo ut", + "email": "Carmen_Keeling@caroline.name", + "body": "voluptate iusto quis nobis reprehenderit ipsum amet nulla\nquia quas dolores velit et non\naut quia necessitatibus\nnostrum quaerat nulla et accusamus nisi facilis" + }, + { + "postId": 3, + "id": 11, + "name": "fugit labore quia mollitia quas deserunt nostrum sunt", + "email": "Veronica_Goodwin@timmothy.net", + "body": "ut dolorum nostrum id quia aut est\nfuga est inventore vel eligendi explicabo quis consectetur\naut occaecati repellat id natus quo est\nut blanditiis quia ut vel ut maiores ea" + }, + { + "postId": 3, + "id": 12, + "name": "modi ut eos dolores illum nam dolor", + "email": "Oswald.Vandervort@leanne.org", + "body": "expedita maiores dignissimos facilis\nipsum est rem est fugit velit sequi\neum odio dolores dolor totam\noccaecati ratione eius rem velit" + }, + { + "postId": 3, + "id": 13, + "name": "aut inventore non pariatur sit vitae voluptatem sapiente", + "email": "Kariane@jadyn.tv", + "body": "fuga eos qui dolor rerum\ninventore corporis exercitationem\ncorporis cupiditate et deserunt recusandae est sed quis culpa\neum maiores corporis et" + }, + { + "postId": 3, + "id": 14, + "name": "et officiis id praesentium hic aut ipsa dolorem repudiandae", + "email": "Nathan@solon.io", + "body": "vel quae voluptas qui exercitationem\nvoluptatibus unde sed\nminima et qui ipsam aspernatur\nexpedita magnam laudantium et et quaerat ut qui dolorum" + }, + { + "postId": 3, + "id": 15, + "name": "debitis magnam hic odit aut ullam nostrum tenetur", + "email": "Maynard.Hodkiewicz@roberta.com", + "body": "nihil ut voluptates blanditiis autem odio dicta rerum\nquisquam saepe et est\nsunt quasi nemo laudantium deserunt\nmolestias tempora quo quia" + }, + { + "postId": 4, + "id": 16, + "name": "perferendis temporibus delectus optio ea eum ratione dolorum", + "email": "Christine@ayana.info", + "body": "iste ut laborum aliquid velit facere itaque\nquo ut soluta dicta voluptate\nerror tempore aut et\nsequi reiciendis dignissimos expedita consequuntur libero sed fugiat facilis" + }, + { + "postId": 4, + "id": 17, + "name": "eos est animi quis", + "email": "Preston_Hudson@blaise.tv", + "body": "consequatur necessitatibus totam sed sit dolorum\nrecusandae quae odio excepturi voluptatum harum voluptas\nquisquam sit ad eveniet delectus\ndoloribus odio qui non labore" + }, + { + "postId": 4, + "id": 18, + "name": "aut et tenetur ducimus illum aut nulla ab", + "email": "Vincenza_Klocko@albertha.name", + "body": "veritatis voluptates necessitatibus maiores corrupti\nneque et exercitationem amet sit et\nullam velit sit magnam laborum\nmagni ut molestias" + }, + { + "postId": 4, + "id": 19, + "name": "sed impedit rerum quia et et inventore unde officiis", + "email": "Madelynn.Gorczany@darion.biz", + "body": "doloribus est illo sed minima aperiam\nut dignissimos accusantium tempore atque et aut molestiae\nmagni ut accusamus voluptatem quos ut voluptates\nquisquam porro sed architecto ut" + }, + { + "postId": 4, + "id": 20, + "name": "molestias expedita iste aliquid voluptates", + "email": "Mariana_Orn@preston.org", + "body": "qui harum consequatur fugiat\net eligendi perferendis at molestiae commodi ducimus\ndoloremque asperiores numquam qui\nut sit dignissimos reprehenderit tempore" + }, + { + "postId": 5, + "id": 21, + "name": "aliquid rerum mollitia qui a consectetur eum sed", + "email": "Noemie@marques.me", + "body": "deleniti aut sed molestias explicabo\ncommodi odio ratione nesciunt\nvoluptate doloremque est\nnam autem error delectus" + }, + { + "postId": 5, + "id": 22, + "name": "porro repellendus aut tempore quis hic", + "email": "Khalil@emile.co.uk", + "body": "qui ipsa animi nostrum praesentium voluptatibus odit\nqui non impedit cum qui nostrum aliquid fuga explicabo\nvoluptatem fugit earum voluptas exercitationem temporibus dignissimos distinctio\nesse inventore reprehenderit quidem ut incidunt nihil necessitatibus rerum" + }, + { + "postId": 5, + "id": 23, + "name": "quis tempora quidem nihil iste", + "email": "Sophia@arianna.co.uk", + "body": "voluptates provident repellendus iusto perspiciatis ex fugiat ut\nut dolor nam aliquid et expedita voluptate\nsunt vitae illo rerum in quos\nvel eligendi enim quae fugiat est" + }, + { + "postId": 5, + "id": 24, + "name": "in tempore eos beatae est", + "email": "Jeffery@juwan.us", + "body": "repudiandae repellat quia\nsequi est dolore explicabo nihil et\net sit et\net praesentium iste atque asperiores tenetur" + }, + { + "postId": 5, + "id": 25, + "name": "autem ab ea sit alias hic provident sit", + "email": "Isaias_Kuhic@jarrett.net", + "body": "sunt aut quae laboriosam sit ut impedit\nadipisci harum laborum totam deleniti voluptas odit rem ea\nnon iure distinctio ut velit doloribus\net non ex" + }, + { + "postId": 6, + "id": 26, + "name": "in deleniti sunt provident soluta ratione veniam quam praesentium", + "email": "Russel.Parker@kameron.io", + "body": "incidunt sapiente eaque dolor eos\nad est molestias\nquas sit et nihil exercitationem at cumque ullam\nnihil magnam et" + }, + { + "postId": 6, + "id": 27, + "name": "doloribus quibusdam molestiae amet illum", + "email": "Francesco.Gleason@nella.us", + "body": "nisi vel quas ut laborum ratione\nrerum magni eum\nunde et voluptatem saepe\nvoluptas corporis modi amet ipsam eos saepe porro" + }, + { + "postId": 6, + "id": 28, + "name": "quo voluptates voluptas nisi veritatis dignissimos dolores ut officiis", + "email": "Ronny@rosina.org", + "body": "voluptatem repellendus quo alias at laudantium\nmollitia quidem esse\ntemporibus consequuntur vitae rerum illum\nid corporis sit id" + }, + { + "postId": 6, + "id": 29, + "name": "eum distinctio amet dolor", + "email": "Jennings_Pouros@erica.biz", + "body": "tempora voluptatem est\nmagnam distinctio autem est dolorem\net ipsa molestiae odit rerum itaque corporis nihil nam\neaque rerum error" + }, + { + "postId": 6, + "id": 30, + "name": "quasi nulla ducimus facilis non voluptas aut", + "email": "Lurline@marvin.biz", + "body": "consequuntur quia voluptate assumenda et\nautem voluptatem reiciendis ipsum animi est provident\nearum aperiam sapiente ad vitae iste\naccusantium aperiam eius qui dolore voluptatem et" + }, + { + "postId": 7, + "id": 31, + "name": "ex velit ut cum eius odio ad placeat", + "email": "Buford@shaylee.biz", + "body": "quia incidunt ut\naliquid est ut rerum deleniti iure est\nipsum quia ea sint et\nvoluptatem quaerat eaque repudiandae eveniet aut" + }, + { + "postId": 7, + "id": 32, + "name": "dolorem architecto ut pariatur quae qui suscipit", + "email": "Maria@laurel.name", + "body": "nihil ea itaque libero illo\nofficiis quo quo dicta inventore consequatur voluptas voluptatem\ncorporis sed necessitatibus velit tempore\nrerum velit et temporibus" + }, + { + "postId": 7, + "id": 33, + "name": "voluptatum totam vel voluptate omnis", + "email": "Jaeden.Towne@arlene.tv", + "body": "fugit harum quae vero\nlibero unde tempore\nsoluta eaque culpa sequi quibusdam nulla id\net et necessitatibus" + }, + { + "postId": 7, + "id": 34, + "name": "omnis nemo sunt ab autem", + "email": "Ethelyn.Schneider@emelia.co.uk", + "body": "omnis temporibus quasi ab omnis\nfacilis et omnis illum quae quasi aut\nminus iure ex rem ut reprehenderit\nin non fugit" + }, + { + "postId": 7, + "id": 35, + "name": "repellendus sapiente omnis praesentium aliquam ipsum id molestiae omnis", + "email": "Georgianna@florence.io", + "body": "dolor mollitia quidem facere et\nvel est ut\nut repudiandae est quidem dolorem sed atque\nrem quia aut adipisci sunt" + }, + { + "postId": 8, + "id": 36, + "name": "sit et quis", + "email": "Raheem_Heaney@gretchen.biz", + "body": "aut vero est\ndolor non aut excepturi dignissimos illo nisi aut quas\naut magni quia nostrum provident magnam quas modi maxime\nvoluptatem et molestiae" + }, + { + "postId": 8, + "id": 37, + "name": "beatae veniam nemo rerum voluptate quam aspernatur", + "email": "Jacky@victoria.net", + "body": "qui rem amet aut\ncumque maiores earum ut quia sit nam esse qui\niusto aspernatur quis voluptas\ndolorem distinctio ex temporibus rem" + }, + { + "postId": 8, + "id": 38, + "name": "maiores dolores expedita", + "email": "Piper@linwood.us", + "body": "unde voluptatem qui dicta\nvel ad aut eos error consequatur voluptatem\nadipisci doloribus qui est sit aut\nvelit aut et ea ratione eveniet iure fuga" + }, + { + "postId": 8, + "id": 39, + "name": "necessitatibus ratione aut ut delectus quae ut", + "email": "Gaylord@russell.net", + "body": "atque consequatur dolorem sunt\nadipisci autem et\nvoluptatibus et quae necessitatibus rerum eaque aperiam nostrum nemo\neligendi sed et beatae et inventore" + }, + { + "postId": 8, + "id": 40, + "name": "non minima omnis deleniti pariatur facere quibusdam at", + "email": "Clare.Aufderhar@nicole.ca", + "body": "quod minus alias quos\nperferendis labore molestias quae ut ut corporis deserunt vitae\net quaerat ut et ullam unde asperiores\ncum voluptatem cumque" + }, + { + "postId": 9, + "id": 41, + "name": "voluptas deleniti ut", + "email": "Lucio@gladys.tv", + "body": "facere repudiandae vitae ea aut sed quo ut et\nfacere nihil ut voluptates in\nsaepe cupiditate accusantium numquam dolores\ninventore sint mollitia provident" + }, + { + "postId": 9, + "id": 42, + "name": "nam qui et", + "email": "Shemar@ewell.name", + "body": "aut culpa quaerat veritatis eos debitis\naut repellat eius explicabo et\nofficiis quo sint at magni ratione et iure\nincidunt quo sequi quia dolorum beatae qui" + }, + { + "postId": 9, + "id": 43, + "name": "molestias sint est voluptatem modi", + "email": "Jackeline@eva.tv", + "body": "voluptatem ut possimus laborum quae ut commodi delectus\nin et consequatur\nin voluptas beatae molestiae\nest rerum laborum et et velit sint ipsum dolorem" + }, + { + "postId": 9, + "id": 44, + "name": "hic molestiae et fuga ea maxime quod", + "email": "Marianna_Wilkinson@rupert.io", + "body": "qui sunt commodi\nsint vel optio vitae quis qui non distinctio\nid quasi modi dicta\neos nihil sit inventore est numquam officiis" + }, + { + "postId": 9, + "id": 45, + "name": "autem illo facilis", + "email": "Marcia@name.biz", + "body": "ipsum odio harum voluptatem sunt cumque et dolores\nnihil laboriosam neque commodi qui est\nquos numquam voluptatum\ncorporis quo in vitae similique cumque tempore" + }, + { + "postId": 10, + "id": 46, + "name": "dignissimos et deleniti voluptate et quod", + "email": "Jeremy.Harann@waino.me", + "body": "exercitationem et id quae cum omnis\nvoluptatibus accusantium et quidem\nut ipsam sint\ndoloremque illo ex atque necessitatibus sed" + }, + { + "postId": 10, + "id": 47, + "name": "rerum commodi est non dolor nesciunt ut", + "email": "Pearlie.Kling@sandy.com", + "body": "occaecati laudantium ratione non cumque\nearum quod non enim soluta nisi velit similique voluptatibus\nesse laudantium consequatur voluptatem rem eaque voluptatem aut ut\net sit quam" + }, + { + "postId": 10, + "id": 48, + "name": "consequatur animi dolorem saepe repellendus ut quo aut tenetur", + "email": "Manuela_Stehr@chelsie.tv", + "body": "illum et alias quidem magni voluptatum\nab soluta ea qui saepe corrupti hic et\ncum repellat esse\nest sint vel veritatis officia consequuntur cum" + }, + { + "postId": 10, + "id": 49, + "name": "rerum placeat quae minus iusto eligendi", + "email": "Camryn.Weimann@doris.io", + "body": "id est iure occaecati quam similique enim\nab repudiandae non\nillum expedita quam excepturi soluta qui placeat\nperspiciatis optio maiores non doloremque aut iusto sapiente" + }, + { + "postId": 10, + "id": 50, + "name": "dolorum soluta quidem ex quae occaecati dicta aut doloribus", + "email": "Kiana_Predovic@yasmin.io", + "body": "eum accusamus aut delectus\narchitecto blanditiis quia sunt\nrerum harum sit quos quia aspernatur vel corrupti inventore\nanimi dicta vel corporis" + }, + { + "postId": 11, + "id": 51, + "name": "molestias et odio ut commodi omnis ex", + "email": "Laurie@lincoln.us", + "body": "perferendis omnis esse\nvoluptate sit mollitia sed perferendis\nnemo nostrum qui\nvel quis nisi doloribus animi odio id quas" + }, + { + "postId": 11, + "id": 52, + "name": "esse autem dolorum", + "email": "Abigail.OConnell@june.org", + "body": "et enim voluptatem totam laudantium\nimpedit nam labore repellendus enim earum aut\nconsectetur mollitia fugit qui repellat expedita sunt\naut fugiat vel illo quos aspernatur ducimus" + }, + { + "postId": 11, + "id": 53, + "name": "maiores alias necessitatibus aut non", + "email": "Laverne_Price@scotty.info", + "body": "a at tempore\nmolestiae odit qui dolores molestias dolorem et\nlaboriosam repudiandae placeat quisquam\nautem aperiam consectetur maiores laboriosam nostrum" + }, + { + "postId": 11, + "id": 54, + "name": "culpa eius tempora sit consequatur neque iure deserunt", + "email": "Kenton_Vandervort@friedrich.com", + "body": "et ipsa rem ullam cum pariatur similique quia\ncum ipsam est sed aut inventore\nprovident sequi commodi enim inventore assumenda aut aut\ntempora possimus soluta quia consequatur modi illo" + }, + { + "postId": 11, + "id": 55, + "name": "quas pariatur quia a doloribus", + "email": "Hayden_Olson@marianna.me", + "body": "perferendis eaque labore laudantium ut molestiae soluta et\nvero odio non corrupti error pariatur consectetur et\nenim nam quia voluptatum non\nmollitia culpa facere voluptas suscipit veniam" + }, + { + "postId": 12, + "id": 56, + "name": "et dolorem corrupti sed molestias", + "email": "Vince_Crist@heidi.biz", + "body": "cum esse odio nihil reiciendis illum quaerat\nest facere quia\noccaecati sit totam fugiat in beatae\nut occaecati unde vitae nihil quidem consequatur" + }, + { + "postId": 12, + "id": 57, + "name": "qui quidem sed", + "email": "Darron.Nikolaus@eulah.me", + "body": "dolorem facere itaque fuga odit autem\nperferendis quisquam quis corrupti eius dicta\nrepudiandae error esse itaque aut\ncorrupti sint consequatur aliquid" + }, + { + "postId": 12, + "id": 58, + "name": "sint minus reiciendis qui perspiciatis id", + "email": "Ezra_Abshire@lyda.us", + "body": "veritatis qui nihil\nquia reprehenderit non ullam ea iusto\nconsectetur nam voluptas ut temporibus tempore provident error\neos et nisi et voluptate" + }, + { + "postId": 12, + "id": 59, + "name": "quis ducimus distinctio similique et illum minima ab libero", + "email": "Jameson@tony.info", + "body": "cumque molestiae officia aut fugiat nemo autem\nvero alias atque sed qui ratione quia\nrepellat vel earum\nea laudantium mollitia" + }, + { + "postId": 12, + "id": 60, + "name": "expedita libero quos cum commodi ad", + "email": "Americo@estrella.net", + "body": "error eum quia voluptates alias repudiandae\naccusantium veritatis maiores assumenda\nquod impedit animi tempore veritatis\nanimi et et officiis labore impedit blanditiis repudiandae" + }, + { + "postId": 13, + "id": 61, + "name": "quidem itaque dolores quod laborum aliquid molestiae", + "email": "Aurelio.Pfeffer@griffin.ca", + "body": "deserunt cumque laudantium\net et odit quia sint quia quidem\nquibusdam debitis fuga in tempora deleniti\nimpedit consequatur veniam reiciendis autem porro minima" + }, + { + "postId": 13, + "id": 62, + "name": "libero beatae consequuntur optio est hic", + "email": "Vesta_Crooks@dora.us", + "body": "tempore dolorum corrupti facilis\npraesentium sunt iste recusandae\nunde quisquam similique\nalias consequuntur voluptates velit" + }, + { + "postId": 13, + "id": 63, + "name": "occaecati dolor accusantium et quasi architecto aut eveniet fugiat", + "email": "Margarett_Klein@mike.biz", + "body": "aut eligendi et molestiae voluptatum tempora\nofficia nihil sit voluptatem aut deleniti\nquaerat consequatur eaque\nsapiente tempore commodi tenetur rerum qui quo" + }, + { + "postId": 13, + "id": 64, + "name": "consequatur aut ullam voluptas dolorum voluptatum sequi et", + "email": "Freida@brandt.tv", + "body": "sed illum quis\nut aut culpa labore aspernatur illo\ndolorem quia vitae ut aut quo repellendus est omnis\nesse at est debitis" + }, + { + "postId": 13, + "id": 65, + "name": "earum ea ratione numquam", + "email": "Mollie@agustina.name", + "body": "qui debitis vitae ratione\ntempora impedit aperiam porro molestiae placeat vero laboriosam recusandae\npraesentium consequatur facere et itaque quidem eveniet\nmagnam natus distinctio sapiente" + }, + { + "postId": 14, + "id": 66, + "name": "eius nam consequuntur", + "email": "Janice@alda.io", + "body": "necessitatibus libero occaecati\nvero inventore iste assumenda veritatis\nasperiores non sit et quia omnis facere nemo explicabo\nodit quo nobis porro" + }, + { + "postId": 14, + "id": 67, + "name": "omnis consequatur natus distinctio", + "email": "Dashawn@garry.com", + "body": "nulla quo itaque beatae ad\nofficiis animi aut exercitationem voluptatum dolorem doloremque ducimus in\nrecusandae officia consequuntur quas\naspernatur dolores est esse dolores sit illo laboriosam quaerat" + }, + { + "postId": 14, + "id": 68, + "name": "architecto ut deserunt consequatur cumque sapiente", + "email": "Devan.Nader@ettie.me", + "body": "sed magni accusantium numquam quidem omnis et voluptatem beatae\nquos fugit libero\nvel ipsa et nihil recusandae ea\niste nemo qui optio sit enim ut nostrum odit" + }, + { + "postId": 14, + "id": 69, + "name": "at aut ea iure accusantium voluptatum nihil ipsum", + "email": "Joana.Schoen@leora.co.uk", + "body": "omnis dolor autem qui est natus\nautem animi nemo voluptatum aut natus accusantium iure\ninventore sunt ea tenetur commodi suscipit facere architecto consequatur\ndolorem nihil veritatis consequuntur corporis" + }, + { + "postId": 14, + "id": 70, + "name": "eum magni accusantium labore aut cum et tenetur", + "email": "Minerva.Anderson@devonte.ca", + "body": "omnis aliquam praesentium ad voluptatem harum aperiam dolor autem\nhic asperiores quisquam ipsa necessitatibus suscipit\npraesentium rem deserunt et\nfacere repellendus aut sed fugit qui est" + }, + { + "postId": 15, + "id": 71, + "name": "vel pariatur perferendis vero ab aut voluptates labore", + "email": "Lavinia@lafayette.me", + "body": "mollitia magnam et\nipsum consequatur est expedita\naut rem ut ex doloremque est vitae est\ncumque velit recusandae numquam libero dolor fuga fugit a" + }, + { + "postId": 15, + "id": 72, + "name": "quia sunt dolor dolor suscipit expedita quis", + "email": "Sabrina.Marks@savanah.name", + "body": "quisquam voluptas ut\npariatur eos amet non\nreprehenderit voluptates numquam\nin est voluptatem dicta ipsa qui esse enim" + }, + { + "postId": 15, + "id": 73, + "name": "ut quia ipsa repellat sunt et sequi aut est", + "email": "Desmond_Graham@kailee.biz", + "body": "nam qui possimus deserunt\ninventore dignissimos nihil rerum ut consequatur vel architecto\ntenetur recusandae voluptate\nnumquam dignissimos aliquid ut reprehenderit voluptatibus" + }, + { + "postId": 15, + "id": 74, + "name": "ut non illum pariatur dolor", + "email": "Gussie_Kunde@sharon.biz", + "body": "non accusamus eum aut et est\naccusantium animi nesciunt distinctio ea quas quisquam\nsit ut voluptatem modi natus sint\nfacilis est qui molestias recusandae nemo" + }, + { + "postId": 15, + "id": 75, + "name": "minus laboriosam consequuntur", + "email": "Richard@chelsie.co.uk", + "body": "natus numquam enim asperiores doloremque ullam et\nest molestias doloribus cupiditate labore vitae aut voluptatem\nitaque quos quo consectetur nihil illum veniam\nnostrum voluptatum repudiandae ut" + }, + { + "postId": 16, + "id": 76, + "name": "porro ut soluta repellendus similique", + "email": "Gage_Turner@halle.name", + "body": "sunt qui consequatur similique recusandae repellendus voluptates eos et\nvero nostrum fugit magnam aliquam\nreprehenderit nobis voluptatem eos consectetur possimus\net perferendis qui ea fugiat sit doloremque" + }, + { + "postId": 16, + "id": 77, + "name": "dolores et quo omnis voluptates", + "email": "Alfred@sadye.biz", + "body": "eos ullam dolorem impedit labore mollitia\nrerum non dolores\nmolestiae dignissimos qui maxime sed voluptate consequatur\ndoloremque praesentium magnam odio iste quae totam aut" + }, + { + "postId": 16, + "id": 78, + "name": "voluptas voluptas voluptatibus blanditiis", + "email": "Catharine@jordyn.com", + "body": "qui adipisci eveniet excepturi iusto magni et\nenim ducimus asperiores blanditiis nemo\ncommodi nihil ex\nenim rerum vel nobis nostrum et non" + }, + { + "postId": 16, + "id": 79, + "name": "beatae ut ad quisquam sed repellendus et", + "email": "Esther_Ratke@shayna.biz", + "body": "et inventore sapiente sed\nsunt similique fugiat officia velit doloremque illo eligendi quas\nsed rerum in quidem perferendis facere molestias\ndolore dolor voluptas nam vel quia" + }, + { + "postId": 16, + "id": 80, + "name": "et cumque ad culpa ut eligendi non", + "email": "Evangeline@chad.net", + "body": "dignissimos itaque ab et tempore odio omnis voluptatem\nitaque perferendis rem repellendus tenetur nesciunt velit\nqui cupiditate recusandae\nquis debitis dolores aliquam nam" + }, + { + "postId": 17, + "id": 81, + "name": "aut non consequuntur dignissimos voluptatibus dolorem earum recusandae dolorem", + "email": "Newton.Kertzmann@anjali.io", + "body": "illum et voluptatem quis repellendus quidem repellat\nreprehenderit voluptas consequatur mollitia\npraesentium nisi quo quod et\noccaecati repellendus illo eius harum explicabo doloribus officia" + }, + { + "postId": 17, + "id": 82, + "name": "ea est non dolorum iste nihil est", + "email": "Caleb_Herzog@rosamond.net", + "body": "officiis dolorem voluptas aliquid eveniet tempora qui\nea temporibus labore accusamus sint\nut sunt necessitatibus voluptatum animi cumque\nat reiciendis voluptatem iure aliquid et qui dolores et" + }, + { + "postId": 17, + "id": 83, + "name": "nihil qui accusamus ratione et molestias et minus", + "email": "Sage_Mueller@candace.net", + "body": "et consequatur voluptates magnam fugit sunt repellendus nihil earum\nofficiis aut cupiditate\net distinctio laboriosam\nmolestiae sunt dolor explicabo recusandae" + }, + { + "postId": 17, + "id": 84, + "name": "quia voluptatibus magnam voluptatem optio vel perspiciatis", + "email": "Bernie.Bergnaum@lue.com", + "body": "ratione ut magni voluptas\nexplicabo natus quia consequatur nostrum aut\nomnis enim in qui illum\naut atque laboriosam aliquid blanditiis quisquam et laborum" + }, + { + "postId": 17, + "id": 85, + "name": "non voluptas cum est quis aut consectetur nam", + "email": "Alexzander_Davis@eduardo.name", + "body": "quisquam incidunt dolores sint qui doloribus provident\nvel cupiditate deleniti alias voluptatem placeat ad\nut dolorem illum unde iure quis libero neque\nea et distinctio id" + }, + { + "postId": 18, + "id": 86, + "name": "suscipit est sunt vel illum sint", + "email": "Jacquelyn@krista.info", + "body": "eum culpa debitis sint\neaque quia magni laudantium qui neque voluptas\nvoluptatem qui molestiae vel earum est ratione excepturi\nsit aut explicabo et repudiandae ut perspiciatis" + }, + { + "postId": 18, + "id": 87, + "name": "dolor asperiores autem et omnis quasi nobis", + "email": "Grover_Volkman@coty.tv", + "body": "assumenda corporis architecto repudiandae omnis qui et odit\nperferendis velit enim\net quia reiciendis sint\nquia voluptas quam deserunt facilis harum eligendi" + }, + { + "postId": 18, + "id": 88, + "name": "officiis aperiam odit sint est non", + "email": "Jovanny@abigale.ca", + "body": "laudantium corrupti atque exercitationem quae enim et veniam dicta\nautem perspiciatis sit dolores\nminima consectetur tenetur iste facere\namet est neque" + }, + { + "postId": 18, + "id": 89, + "name": "in voluptatum nostrum voluptas iure nisi rerum est placeat", + "email": "Isac_Schmeler@barton.com", + "body": "quibusdam rerum quia nostrum culpa\nculpa est natus impedit quo rem voluptate quos\nrerum culpa aut ut consectetur\nsunt esse laudantium voluptatibus cupiditate rerum" + }, + { + "postId": 18, + "id": 90, + "name": "eum voluptas dolores molestias odio amet repellendus", + "email": "Sandy.Erdman@sabina.info", + "body": "vitae cupiditate excepturi eum veniam laudantium aspernatur blanditiis\naspernatur quia ut assumenda et magni enim magnam\nin voluptate tempora\nnon qui voluptatem reprehenderit porro qui voluptatibus" + }, + { + "postId": 19, + "id": 91, + "name": "repellendus est laboriosam voluptas veritatis", + "email": "Alexandro@garry.io", + "body": "qui nisi at maxime deleniti quo\nex quas tenetur nam\ndeleniti aut asperiores deserunt cum ex eaque alias sit\net veniam ab consequatur molestiae" + }, + { + "postId": 19, + "id": 92, + "name": "repellendus aspernatur occaecati tempore blanditiis deleniti omnis qui distinctio", + "email": "Vickie_Schuster@harley.net", + "body": "nihil necessitatibus omnis asperiores nobis praesentium quia\nab debitis quo deleniti aut sequi commodi\nut perspiciatis quod est magnam aliquam modi\neum quos aliquid ea est" + }, + { + "postId": 19, + "id": 93, + "name": "mollitia dolor deleniti sed iure laudantium", + "email": "Roma_Doyle@alia.com", + "body": "ut quis et id repellat labore\nnobis itaque quae saepe est ullam aut\ndolor id ut quis\nsunt iure voluptates expedita voluptas doloribus modi saepe autem" + }, + { + "postId": 19, + "id": 94, + "name": "vero repudiandae voluptatem nobis", + "email": "Tatum_Marks@jaylon.name", + "body": "reiciendis delectus nulla quae voluptas nihil provident quia\nab corporis nesciunt blanditiis quibusdam et unde et\nmagni eligendi aperiam corrupti perspiciatis quasi\nneque iure voluptatibus mollitia" + }, + { + "postId": 19, + "id": 95, + "name": "voluptatem unde quos provident ad qui sit et excepturi", + "email": "Juston.Ruecker@scot.tv", + "body": "at ut tenetur rem\nut fuga quis ea magnam alias\naut tempore fugiat laboriosam porro quia iure qui\narchitecto est enim" + }, + { + "postId": 20, + "id": 96, + "name": "non sit ad culpa quis", + "email": "River.Grady@lavada.biz", + "body": "eum itaque quam\nlaboriosam sequi ullam quos nobis\nomnis dignissimos nam dolores\nfacere id suscipit aliquid culpa rerum quis" + }, + { + "postId": 20, + "id": 97, + "name": "reiciendis culpa omnis suscipit est", + "email": "Claudia@emilia.ca", + "body": "est ducimus voluptate saepe iusto repudiandae recusandae et\nsint fugit voluptas eum itaque\nodit ab eos voluptas molestiae necessitatibus earum possimus voluptatem\nquibusdam aut illo beatae qui delectus aut officia veritatis" + }, + { + "postId": 20, + "id": 98, + "name": "praesentium dolorem ea voluptate et", + "email": "Torrey@june.tv", + "body": "ex et expedita cum voluptatem\nvoluptatem ab expedita quis nihil\nesse quo nihil perferendis dolores velit ut culpa aut\ndolor maxime necessitatibus voluptatem" + }, + { + "postId": 20, + "id": 99, + "name": "laudantium delectus nam", + "email": "Hildegard.Aufderhar@howard.com", + "body": "aut quam consequatur sit et\nrepellat maiores laborum similique voluptatem necessitatibus nihil\net debitis nemo occaecati cupiditate\nmodi dolorum quia aut" + }, + { + "postId": 20, + "id": 100, + "name": "et sint quia dolor et est ea nulla cum", + "email": "Leone_Fay@orrin.com", + "body": "architecto dolorem ab explicabo et provident et\net eos illo omnis mollitia ex aliquam\natque ut ipsum nulla nihil\nquis voluptas aut debitis facilis" + }, + { + "postId": 21, + "id": 101, + "name": "perspiciatis magnam ut eum autem similique explicabo expedita", + "email": "Lura@rod.tv", + "body": "ut aut maxime officia sed aliquam et magni autem\nveniam repudiandae nostrum odio enim eum optio aut\nomnis illo quasi quibusdam inventore explicabo\nreprehenderit dolor saepe possimus molestiae" + }, + { + "postId": 21, + "id": 102, + "name": "officia ullam ut neque earum ipsa et fuga", + "email": "Lottie.Zieme@ruben.us", + "body": "aut dolorem quos ut non\naliquam unde iure minima quod ullam qui\nfugiat molestiae tempora voluptate vel labore\nsaepe animi et vitae numquam ipsa" + }, + { + "postId": 21, + "id": 103, + "name": "ipsum a ut", + "email": "Winona_Price@jevon.me", + "body": "totam eum fugiat repellendus\nquae beatae explicabo excepturi iusto et\nrepellat alias iure voluptates consequatur sequi minus\nsed maxime unde" + }, + { + "postId": 21, + "id": 104, + "name": "a assumenda totam", + "email": "Gabriel@oceane.biz", + "body": "qui aperiam labore animi magnam odit est\nut autem eaque ea magni quas voluptatem\ndoloribus vel voluptatem nostrum ut debitis enim quaerat\nut esse eveniet aut" + }, + { + "postId": 21, + "id": 105, + "name": "voluptatem repellat est", + "email": "Adolph.Ondricka@mozell.co.uk", + "body": "ut rerum illum error at inventore ab nobis molestiae\nipsa architecto temporibus non aliquam aspernatur omnis quidem aliquid\nconsequatur non et expedita cumque voluptates ipsam quia\nblanditiis libero itaque sed iusto at" + }, + { + "postId": 22, + "id": 106, + "name": "maiores placeat facere quam pariatur", + "email": "Allen@richard.biz", + "body": "dolores debitis voluptatem ab hic\nmagnam alias qui est sunt\net porro velit et repellendus occaecati est\nsequi quia odio deleniti illum" + }, + { + "postId": 22, + "id": 107, + "name": "in ipsam vel id impedit possimus eos voluptate", + "email": "Nicholaus@mikayla.ca", + "body": "eveniet fugit qui\nporro eaque dolores eos adipisci dolores ut\nfugit perferendis pariatur\nnumquam et repellat occaecati atque ipsum neque" + }, + { + "postId": 22, + "id": 108, + "name": "ut veritatis corporis placeat suscipit consequatur quaerat", + "email": "Kayla@susanna.org", + "body": "at a vel sequi nostrum\nharum nam nihil\ncumque aut in dolore rerum ipsa hic ratione\nrerum cum ratione provident labore ad quisquam repellendus a" + }, + { + "postId": 22, + "id": 109, + "name": "eveniet ut similique accusantium qui dignissimos", + "email": "Gideon@amina.name", + "body": "aliquid qui dolorem deserunt aperiam natus corporis eligendi neque\nat et sunt aut qui\nillum repellat qui excepturi laborum facilis aut omnis consequatur\net aut optio ipsa nisi enim" + }, + { + "postId": 22, + "id": 110, + "name": "sint est odit officiis similique aut corrupti quas autem", + "email": "Cassidy@maribel.io", + "body": "cum sequi in eligendi id eaque\ndolores accusamus dolorem eum est voluptatem quisquam tempore\nin voluptas enim voluptatem asperiores voluptatibus\neius quo quos quasi voluptas earum ut necessitatibus" + }, + { + "postId": 23, + "id": 111, + "name": "possimus facilis deleniti nemo atque voluptate", + "email": "Stefan.Crist@duane.ca", + "body": "ullam autem et\naccusantium quod sequi similique soluta explicabo ipsa\neius ratione quisquam sed et excepturi occaecati pariatur\nmolestiae ut reiciendis eum voluptatem sed" + }, + { + "postId": 23, + "id": 112, + "name": "dolore aut aspernatur est voluptate quia ipsam", + "email": "Aniyah.Ortiz@monte.me", + "body": "ut tempora deleniti quo molestiae eveniet provident earum occaecati\nest nesciunt ut pariatur ipsa voluptas voluptatem aperiam\nqui deleniti quibusdam voluptas molestiae facilis id iusto similique\ntempora aut qui" + }, + { + "postId": 23, + "id": 113, + "name": "sint quo debitis deleniti repellat", + "email": "Laverna@rico.biz", + "body": "voluptatem sint quia modi accusantium alias\nrecusandae rerum voluptatem aut sit et ut magnam\nvoluptas rerum odio quo labore voluptatem facere consequuntur\nut sit voluptatum hic distinctio" + }, + { + "postId": 23, + "id": 114, + "name": "optio et sunt non", + "email": "Derek@hildegard.net", + "body": "nihil labore qui\nquis dolor eveniet iste numquam\nporro velit incidunt\nlaboriosam asperiores aliquam facilis in et voluptas eveniet quasi" + }, + { + "postId": 23, + "id": 115, + "name": "occaecati dolorem eum in veniam quia quo reiciendis", + "email": "Tyrell@abdullah.ca", + "body": "laudantium tempore aut\nmaiores laborum fugit qui suscipit hic sint officiis corrupti\nofficiis eum optio cumque fuga sed voluptatibus similique\nsit consequuntur rerum commodi" + }, + { + "postId": 24, + "id": 116, + "name": "veritatis sit tempora quasi fuga aut dolorum", + "email": "Reyes@hailey.name", + "body": "quia voluptas qui assumenda nesciunt harum iusto\nest corrupti aperiam\nut aut unde maxime consequatur eligendi\nveniam modi id sint rem labore saepe minus" + }, + { + "postId": 24, + "id": 117, + "name": "incidunt quae optio quam corporis iste deleniti accusantium vero", + "email": "Danika.Dicki@mekhi.biz", + "body": "doloribus esse necessitatibus qui eos et ut est saepe\nsed rerum tempore est ut\nquisquam et eligendi accusantium\ncommodi non doloribus" + }, + { + "postId": 24, + "id": 118, + "name": "quisquam laborum reiciendis aut", + "email": "Alessandra.Nitzsche@stephania.us", + "body": "repudiandae aliquam maxime cupiditate consequatur id\nquas error repellendus\ntotam officia dolorem beatae natus cum exercitationem\nasperiores dolor ea" + }, + { + "postId": 24, + "id": 119, + "name": "minus pariatur odit", + "email": "Matteo@marquis.net", + "body": "et omnis consequatur ut\nin suscipit et voluptatem\nanimi at ut\ndolores quos aut numquam esse praesentium aut placeat nam" + }, + { + "postId": 24, + "id": 120, + "name": "harum error sit", + "email": "Joshua.Spinka@toby.io", + "body": "iusto sint recusandae placeat atque perferendis sit corporis molestiae\nrem dolor eius id delectus et qui\nsed dolorem reiciendis error ullam corporis delectus\nexplicabo mollitia odit laborum sed itaque deserunt rem dolorem" + }, + { + "postId": 25, + "id": 121, + "name": "deleniti quo corporis ullam magni praesentium molestiae", + "email": "Annabelle@cole.com", + "body": "soluta mollitia impedit cumque nostrum tempore aut placeat repellat\nenim adipisci dolores aut ut ratione laboriosam necessitatibus vel\net blanditiis est iste sapiente qui atque repellendus alias\nearum consequuntur quia quasi quia" + }, + { + "postId": 25, + "id": 122, + "name": "nihil tempora et reiciendis modi veniam", + "email": "Kacey@jamal.info", + "body": "doloribus veritatis a et quis corrupti incidunt est\nharum maiores impedit et beatae qui velit et aut\nporro sed dignissimos deserunt deleniti\net eveniet voluptas ipsa pariatur rem ducimus" + }, + { + "postId": 25, + "id": 123, + "name": "ad eos explicabo odio velit", + "email": "Mina@mallie.name", + "body": "nostrum perspiciatis doloribus\nexplicabo soluta id libero illo iste et\nab expedita error aliquam eum sint ipsum\nmodi possimus et" + }, + { + "postId": 25, + "id": 124, + "name": "nostrum suscipit aut consequatur magnam sunt fuga nihil", + "email": "Hudson.Blick@ruben.biz", + "body": "ut ut eius qui explicabo quis\niste autem nulla beatae tenetur enim\nassumenda explicabo consequatur harum exercitationem\nvelit itaque consectetur et possimus" + }, + { + "postId": 25, + "id": 125, + "name": "porro et voluptate et reprehenderit", + "email": "Domenic.Durgan@joaquin.name", + "body": "aut voluptas dolore autem\nreprehenderit expedita et nihil pariatur ea animi quo ullam\na ea officiis corporis\neius voluptatum cum mollitia dolore quaerat accusamus" + }, + { + "postId": 26, + "id": 126, + "name": "fuga tenetur id et qui labore delectus", + "email": "Alexie@alayna.org", + "body": "est qui ut tempore temporibus pariatur provident qui consequuntur\nlaboriosam porro dignissimos quos debitis id laborum et totam\naut eius sequi dolor maiores amet\nrerum voluptatibus quod ratione quos labore fuga sit" + }, + { + "postId": 26, + "id": 127, + "name": "consequatur harum magnam", + "email": "Haven_Barrows@brant.org", + "body": "omnis consequatur dignissimos iure rerum odio\nculpa laudantium quia voluptas enim est nisi\ndoloremque consequatur autem officiis necessitatibus beatae et\net itaque animi dolor praesentium" + }, + { + "postId": 26, + "id": 128, + "name": "labore architecto quaerat tempora voluptas consequuntur animi", + "email": "Marianne@maximo.us", + "body": "exercitationem eius aut ullam vero\nimpedit similique maiores ea et in culpa possimus omnis\neos labore autem quam repellendus dolores deserunt voluptatem\nnon ullam eos accusamus" + }, + { + "postId": 26, + "id": 129, + "name": "deleniti facere tempore et perspiciatis voluptas quis voluptatem", + "email": "Fanny@danial.com", + "body": "fugit minima voluptatem est aut sed explicabo\nharum dolores at qui eaque\nmagni velit ut et\nnam et ut sunt excepturi repellat non commodi" + }, + { + "postId": 26, + "id": 130, + "name": "quod est non quia doloribus quam deleniti libero", + "email": "Trevion_Kuphal@bernice.name", + "body": "dicta sit culpa molestiae quasi at voluptate eos\ndolorem perferendis accusamus rerum expedita ipsum quis qui\nquos est deserunt\nrerum fuga qui aliquam in consequatur aspernatur" + }, + { + "postId": 27, + "id": 131, + "name": "voluptas quasi sunt laboriosam", + "email": "Emmet@guy.biz", + "body": "rem magnam at voluptatem\naspernatur et et nostrum rerum\ndignissimos eum quibusdam\noptio quod dolores et" + }, + { + "postId": 27, + "id": 132, + "name": "unde tenetur vero eum iusto", + "email": "Megane.Fritsch@claude.name", + "body": "ullam harum consequatur est rerum est\nmagni tenetur aperiam et\nrepudiandae et reprehenderit dolorum enim voluptas impedit\neligendi quis necessitatibus in exercitationem voluptatem qui" + }, + { + "postId": 27, + "id": 133, + "name": "est adipisci laudantium amet rem asperiores", + "email": "Amya@durward.ca", + "body": "sunt quis iure molestias qui ipsa commodi dolore a\nodio qui debitis earum\nunde ut omnis\ndoloremque corrupti at repellendus earum eum" + }, + { + "postId": 27, + "id": 134, + "name": "reiciendis quo est vitae dignissimos libero ut officiis fugiat", + "email": "Jasen_Rempel@willis.org", + "body": "corrupti perspiciatis eligendi\net omnis tempora nobis dolores hic\ndolorum vitae odit\nreiciendis sunt odit qui" + }, + { + "postId": 27, + "id": 135, + "name": "inventore fugiat dignissimos", + "email": "Harmony@reggie.com", + "body": "sapiente nostrum dolorem odit a\nsed animi non architecto doloremque unde\nnam aut aut ut facilis\net ut autem fugit minima culpa inventore non" + }, + { + "postId": 28, + "id": 136, + "name": "et expedita est odit", + "email": "Rosanna_Kunze@guy.net", + "body": "cum natus qui dolorem dolorum nihil ut nam tempore\nmodi nesciunt ipsum hic\nrem sunt possimus earum magnam similique aspernatur sed\ntotam sed voluptatem iusto id iste qui" + }, + { + "postId": 28, + "id": 137, + "name": "saepe dolore qui tempore nihil perspiciatis omnis omnis magni", + "email": "Ressie.Boehm@flossie.com", + "body": "reiciendis maiores id\nvoluptas sapiente deserunt itaque\nut omnis sunt\nnecessitatibus quibusdam dolorem voluptatem harum error" + }, + { + "postId": 28, + "id": 138, + "name": "ea optio nesciunt officia velit enim facilis commodi", + "email": "Domenic.Wuckert@jazmyne.us", + "body": "dolorem suscipit adipisci ad cum totam quia fugiat\nvel quia dolores molestiae eos\nomnis officia quidem quaerat alias vel distinctio\nvero provident et corporis a quia ut" + }, + { + "postId": 28, + "id": 139, + "name": "ut pariatur voluptate possimus quasi", + "email": "Rhett.OKon@brian.info", + "body": "facilis cumque nostrum dignissimos\ndoloremque saepe quia adipisci sunt\ndicta dolorum quo esse\nculpa iste ut asperiores cum aperiam" + }, + { + "postId": 28, + "id": 140, + "name": "consectetur tempore eum consequuntur", + "email": "Mathias@richmond.info", + "body": "velit ipsa fugiat sit qui vel nesciunt sapiente\nrepudiandae perferendis nemo eos quos perspiciatis aperiam\ndoloremque incidunt nostrum temporibus corrupti repudiandae vitae non corporis\ncupiditate suscipit quod sed numquam excepturi enim labore" + }, + { + "postId": 29, + "id": 141, + "name": "dignissimos perspiciatis voluptate quos rem qui temporibus excepturi", + "email": "Ottis@lourdes.org", + "body": "et ullam id eligendi rem sit\noccaecati et delectus in nemo\naut veritatis deserunt aspernatur dolor enim voluptas quos consequatur\nmolestiae temporibus error" + }, + { + "postId": 29, + "id": 142, + "name": "cum dolore sit quisquam provident nostrum vitae", + "email": "Estel@newton.ca", + "body": "cumque voluptas quo eligendi sit\nnemo ut ut dolor et cupiditate aut\net voluptatem quia aut maiores quas accusantium expedita quia\nbeatae aut ad quis soluta id dolorum" + }, + { + "postId": 29, + "id": 143, + "name": "velit molestiae assumenda perferendis voluptas explicabo", + "email": "Bertha@erik.co.uk", + "body": "est quasi maiores nisi reiciendis enim\ndolores minus facilis laudantium dignissimos\nreiciendis et facere occaecati dolores et\npossimus et vel et aut ipsa ad" + }, + { + "postId": 29, + "id": 144, + "name": "earum ipsum ea quas qui molestiae omnis unde", + "email": "Joesph@matteo.info", + "body": "voluptatem unde consequatur natus nostrum vel ut\nconsequatur sequi doloremque omnis dolorem maxime\neaque sunt excepturi\nfuga qui illum et accusamus" + }, + { + "postId": 29, + "id": 145, + "name": "magni iusto sit", + "email": "Alva@cassandre.net", + "body": "assumenda nihil et\nsed nulla tempora porro iste possimus aut sit officia\ncumque totam quis tenetur qui sequi\ndelectus aut sunt" + }, + { + "postId": 30, + "id": 146, + "name": "est qui debitis", + "email": "Vivienne@willis.org", + "body": "possimus necessitatibus quis\net dicta omnis voluptatem ea est\nsuscipit eum soluta in quia corrupti hic iusto\nconsequatur est aut qui earum nisi officiis sed culpa" + }, + { + "postId": 30, + "id": 147, + "name": "reiciendis et consectetur officiis beatae corrupti aperiam", + "email": "Angelita@aliza.me", + "body": "nihil aspernatur consequatur voluptatem facere sed fugiat ullam\nbeatae accusamus et fuga maxime vero\nomnis necessitatibus quisquam ipsum consectetur incidunt repellat voluptas\nerror quo et ab magnam quisquam" + }, + { + "postId": 30, + "id": 148, + "name": "iusto reprehenderit voluptatem modi", + "email": "Timmothy_Okuneva@alyce.tv", + "body": "nemo corporis quidem eius aut dolores\nitaque rerum quo occaecati mollitia incidunt\nautem est saepe nulla nobis a id\ndolore facilis placeat molestias in fugiat aliquam excepturi" + }, + { + "postId": 30, + "id": 149, + "name": "optio dolorem et reiciendis et recusandae quidem", + "email": "Moriah_Welch@richmond.org", + "body": "veniam est distinctio\nnihil quia eos sed\ndistinctio hic ut sint ducimus debitis dolorem voluptatum assumenda\neveniet ea perspiciatis" + }, + { + "postId": 30, + "id": 150, + "name": "id saepe numquam est facilis sint enim voluptas voluptatem", + "email": "Ramiro_Kuhn@harmon.biz", + "body": "est non atque eligendi aspernatur quidem earum mollitia\nminima neque nam exercitationem provident eum\nmaxime quo et ut illum sequi aut fuga repudiandae\nsapiente sed ea distinctio molestias illum consequatur libero quidem" + }, + { + "postId": 31, + "id": 151, + "name": "ut quas facilis laborum voluptatum consequatur odio voluptate et", + "email": "Cary@taurean.biz", + "body": "quos eos sint voluptatibus similique iusto perferendis omnis voluptas\nearum nulla cumque\ndolorem consequatur officiis quis consequatur aspernatur nihil ullam et\nenim enim unde nihil labore non ducimus" + }, + { + "postId": 31, + "id": 152, + "name": "quod doloremque omnis", + "email": "Tillman_Koelpin@luisa.com", + "body": "itaque veritatis explicabo\nquis voluptatem mollitia soluta id non\ndoloribus nobis fuga provident\nnesciunt saepe molestiae praesentium laboriosam" + }, + { + "postId": 31, + "id": 153, + "name": "dolorum et dolorem optio in provident", + "email": "Aleen@tania.biz", + "body": "et cumque error pariatur\nquo doloribus corrupti voluptates ad voluptatem consequatur voluptas dolores\npariatur at quas iste repellat et sed quasi\nea maiores rerum aut earum" + }, + { + "postId": 31, + "id": 154, + "name": "odit illo optio ea modi in", + "email": "Durward@cindy.com", + "body": "quod magni consectetur\nquod doloremque velit autem ipsam nisi praesentium ut\nlaboriosam quod deleniti\npariatur aliquam sint excepturi a consectetur quas eos" + }, + { + "postId": 31, + "id": 155, + "name": "adipisci laboriosam repudiandae omnis veritatis in facere similique rem", + "email": "Lester@chauncey.ca", + "body": "animi asperiores modi et tenetur vel magni\nid iusto aliquid ad\nnihil dolorem dolorum aut veritatis voluptates\nomnis cupiditate incidunt" + }, + { + "postId": 32, + "id": 156, + "name": "pariatur omnis in", + "email": "Telly_Lynch@karl.co.uk", + "body": "dolorum voluptas laboriosam quisquam ab\ntotam beatae et aut aliquid optio assumenda\nvoluptas velit itaque quidem voluptatem tempore cupiditate\nin itaque sit molestiae minus dolores magni" + }, + { + "postId": 32, + "id": 157, + "name": "aut nobis et consequatur", + "email": "Makenzie@libbie.io", + "body": "voluptas quia quo ad\nipsum voluptatum provident ut ipsam velit dignissimos aut assumenda\nut officia laudantium\nquibusdam sed minima" + }, + { + "postId": 32, + "id": 158, + "name": "explicabo est molestiae aut", + "email": "Amiya@perry.us", + "body": "et qui ad vero quis\nquisquam omnis fuga et vel nihil minima eligendi nostrum\nsed deserunt rem voluptates autem\nquia blanditiis cum sed" + }, + { + "postId": 32, + "id": 159, + "name": "voluptas blanditiis deserunt quia quis", + "email": "Meghan@akeem.tv", + "body": "deserunt deleniti officiis architecto consequatur molestiae facere dolor\nvoluptatem velit eos fuga dolores\nsit quia est a deleniti hic dolor quisquam repudiandae\nvoluptas numquam voluptatem impedit" + }, + { + "postId": 32, + "id": 160, + "name": "sint fugit esse", + "email": "Mitchel.Williamson@fletcher.io", + "body": "non reprehenderit aut sed quos est ad voluptatum\nest ut est dignissimos ut dolores consequuntur\ndebitis aspernatur consequatur est\nporro nulla laboriosam repellendus et nesciunt est libero placeat" + }, + { + "postId": 33, + "id": 161, + "name": "nesciunt quidem veritatis alias odit nisi voluptatem non est", + "email": "Ashlee_Jast@emie.biz", + "body": "sunt totam blanditiis\neum quos fugit et ab rerum nemo\nut iusto architecto\nut et eligendi iure placeat omnis" + }, + { + "postId": 33, + "id": 162, + "name": "animi vitae qui aut corrupti neque culpa modi", + "email": "Antwan@lori.ca", + "body": "nulla impedit porro in sed\nvoluptatem qui voluptas et enim beatae\nnobis et sit ipsam aut\nvoluptatem voluptatibus blanditiis officia quod eos omnis earum dolorem" + }, + { + "postId": 33, + "id": 163, + "name": "omnis ducimus ab temporibus nobis porro natus deleniti", + "email": "Estelle@valentina.info", + "body": "molestiae dolorem quae rem neque sapiente voluptatum nesciunt cum\nid rerum at blanditiis est accusantium est\neos illo porro ad\nquod repellendus ad et labore fugit dolorum" + }, + { + "postId": 33, + "id": 164, + "name": "eius corrupti ea", + "email": "Haylie@gino.name", + "body": "beatae aut ut autem sit officia rerum nostrum\nprovident ratione sed dicta omnis alias commodi rerum expedita\nnon nobis sapiente consectetur odit unde quia\nvoluptas in nihil consequatur doloremque ullam dolorem cum" + }, + { + "postId": 33, + "id": 165, + "name": "quia commodi molestiae assumenda provident sit incidunt laudantium", + "email": "Blake_Spinka@robyn.info", + "body": "sed praesentium ducimus minima autem corporis debitis\naperiam eum sit pariatur\nimpedit placeat illo odio\nodit accusantium expedita quo rerum magnam" + }, + { + "postId": 34, + "id": 166, + "name": "sint alias molestiae qui dolor vel", + "email": "Aimee.Bins@braeden.ca", + "body": "nam quas eaque unde\ndolor blanditiis cumque eaque omnis qui\nrerum modi sint quae numquam exercitationem\natque aut praesentium ipsa sit neque qui sint aut" + }, + { + "postId": 34, + "id": 167, + "name": "ea nam iste est repudiandae", + "email": "Eloy@vladimir.com", + "body": "molestiae voluptatem qui\nid facere nostrum quasi asperiores rerum\nquisquam est repellendus\ndeleniti eos aut sed nemo non" + }, + { + "postId": 34, + "id": 168, + "name": "quis harum voluptatem et culpa", + "email": "Gabrielle@jada.co.uk", + "body": "cupiditate optio in quidem adipisci sit dolor id\net tenetur quo sit odit\naperiam illum optio magnam qui\nmolestiae eligendi harum optio dolores dolor quaerat nostrum" + }, + { + "postId": 34, + "id": 169, + "name": "dolor dolore similique tempore ducimus", + "email": "Lee@dawn.net", + "body": "unde non aliquid magni accusantium architecto et\nrerum autem eos explicabo et\nodio facilis sed\nrerum ex esse beatae quia" + }, + { + "postId": 34, + "id": 170, + "name": "cupiditate labore omnis consequatur", + "email": "Gideon.Hyatt@jalen.tv", + "body": "amet id deserunt ipsam\ncupiditate distinctio nulla voluptatem\ncum possimus voluptate\nipsum quidem laudantium quos nihil" + }, + { + "postId": 35, + "id": 171, + "name": "voluptatibus qui est et", + "email": "Gerda.Reynolds@ceasar.co.uk", + "body": "sed non beatae placeat qui libero nam eaque qui\nquia ut ad doloremque\nsequi unde quidem adipisci debitis autem velit\narchitecto aperiam eos nihil enim dolores veritatis omnis ex" + }, + { + "postId": 35, + "id": 172, + "name": "corporis ullam quo", + "email": "Ivah@brianne.net", + "body": "nemo ullam omnis sit\nlabore perferendis et eveniet nostrum\ndignissimos expedita iusto\noccaecati quia sit quibusdam" + }, + { + "postId": 35, + "id": 173, + "name": "nulla accusamus saepe debitis cum", + "email": "Ethyl_Bogan@candace.co.uk", + "body": "asperiores hic fugiat aut et temporibus mollitia quo quam\ncumque numquam labore qui illum vel provident quod\npariatur natus incidunt\nsunt error voluptatibus vel voluptas maiores est vero possimus" + }, + { + "postId": 35, + "id": 174, + "name": "iure praesentium ipsam", + "email": "Janelle_Guann@americo.info", + "body": "sit dolores consequatur qui voluptas sunt\nearum at natus alias excepturi dolores\nmaiores pariatur at reiciendis\ndolor esse optio" + }, + { + "postId": 35, + "id": 175, + "name": "autem totam velit officiis voluptates et ullam rem", + "email": "Alfonzo.Barton@kelley.co.uk", + "body": "culpa non ea\nperspiciatis exercitationem sed natus sit\nenim voluptatum ratione omnis consequuntur provident commodi omnis\nquae odio sit at tempora" + }, + { + "postId": 36, + "id": 176, + "name": "ipsam deleniti incidunt repudiandae voluptatem maxime provident non dolores", + "email": "Esther@ford.me", + "body": "quam culpa fugiat\nrerum impedit ratione vel ipsam rem\ncommodi qui rem eum\nitaque officiis omnis ad" + }, + { + "postId": 36, + "id": 177, + "name": "ab cupiditate blanditiis ea sunt", + "email": "Naomie_Cronin@rick.co.uk", + "body": "ut facilis sapiente\nquia repellat autem et aut delectus sint\nautem nulla debitis\nomnis consequuntur neque" + }, + { + "postId": 36, + "id": 178, + "name": "rerum ex quam enim sunt", + "email": "Darryl@reginald.us", + "body": "sit maxime fugit\nsequi culpa optio consequatur voluptatem dolor expedita\nenim iure eum reprehenderit doloremque aspernatur modi\nvoluptatem culpa nostrum quod atque rerum sint laboriosam et" + }, + { + "postId": 36, + "id": 179, + "name": "et iure ex rerum qui", + "email": "Thea@aurelio.org", + "body": "non saepe ipsa velit sunt\ntotam ipsum optio voluptatem\nnesciunt qui iste eum\net deleniti ullam" + }, + { + "postId": 36, + "id": 180, + "name": "autem tempora a iusto eum aut voluptatum", + "email": "Carolyn@eloisa.biz", + "body": "recusandae temporibus nihil non alias consequatur\nlibero voluptatem sed soluta accusamus\na qui reiciendis officiis ad\nquia laborum libero et rerum voluptas sed ut et" + }, + { + "postId": 37, + "id": 181, + "name": "similique ut et non laboriosam in eligendi et", + "email": "Milan.Schoen@cortney.io", + "body": "dolor iure corrupti\net eligendi nesciunt voluptatum autem\nconsequatur in sapiente\ndolor voluptas dolores natus iusto qui et in perferendis" + }, + { + "postId": 37, + "id": 182, + "name": "soluta corporis excepturi consequatur perspiciatis quia voluptatem", + "email": "Sabrina@raymond.biz", + "body": "voluptatum voluptatem nisi consequatur et\nomnis nobis odio neque ab enim veniam\nsit qui aperiam odit voluptatem facere\nnesciunt esse nemo" + }, + { + "postId": 37, + "id": 183, + "name": "praesentium quod quidem optio omnis qui", + "email": "Hildegard@alford.ca", + "body": "tempora soluta voluptas deserunt\nnon fugiat similique\nest natus enim eum error magni soluta\ndolores sit doloremque" + }, + { + "postId": 37, + "id": 184, + "name": "veritatis velit quasi quo et voluptates dolore", + "email": "Lowell.Pagac@omari.biz", + "body": "odio saepe ad error minima itaque\nomnis fuga corrupti qui eaque cupiditate eum\nvitae laborum rerum ut reprehenderit architecto sit debitis magnam\nqui corrupti cum quidem commodi facere corporis" + }, + { + "postId": 37, + "id": 185, + "name": "natus assumenda ut", + "email": "Vivianne@ima.us", + "body": "deleniti non et corrupti delectus ea cupiditate\nat nihil fuga rerum\ntemporibus doloribus unde sed alias\nducimus perspiciatis quia debitis fuga" + }, + { + "postId": 38, + "id": 186, + "name": "voluptas distinctio qui similique quasi voluptatem non sit", + "email": "Yasmin.Prohaska@hanna.co.uk", + "body": "asperiores eaque error sunt ut natus et omnis\nexpedita error iste vitae\nsit alias voluptas voluptatibus quia iusto quia ea\nenim facere est quam ex" + }, + { + "postId": 38, + "id": 187, + "name": "maiores iste dolor itaque nemo voluptas", + "email": "Ursula.Kirlin@eino.org", + "body": "et enim necessitatibus velit autem magni voluptas\nat maxime error sunt nobis sit\ndolor beatae harum rerum\ntenetur facere pariatur et perferendis voluptas maiores nihil eaque" + }, + { + "postId": 38, + "id": 188, + "name": "quisquam quod quia nihil animi minima facere odit est", + "email": "Nichole_Bartoletti@mozell.me", + "body": "quam magni adipisci totam\nut reprehenderit ut tempore non asperiores repellendus architecto aperiam\ndignissimos est aut reiciendis consectetur voluptate nihil culpa at\nmolestiae labore qui ea" + }, + { + "postId": 38, + "id": 189, + "name": "ut iusto asperiores delectus", + "email": "Lottie_Wyman@jasen.biz", + "body": "nostrum excepturi debitis cum\narchitecto iusto laudantium odit aut dolor voluptatem consectetur nulla\nmollitia beatae autem quasi nemo repellendus ut ea et\naut architecto odio cum quod optio" + }, + { + "postId": 38, + "id": 190, + "name": "dignissimos voluptatibus libero", + "email": "Dominique_Hermann@paige.ca", + "body": "laudantium vero similique eum\niure iste culpa praesentium\nmolestias doloremque alias et at doloribus\naperiam natus est illo quo ratione porro excepturi" + }, + { + "postId": 39, + "id": 191, + "name": "est perferendis eos dolores maxime rerum qui", + "email": "Eugene@mohammed.net", + "body": "sit vero aut voluptatem soluta corrupti dolor cum\nnulla ipsa accusamus aut suscipit ut dicta ut nemo\nut et ut sit voluptas modi\nillum suscipit ea debitis aut ullam harum" + }, + { + "postId": 39, + "id": 192, + "name": "sunt veritatis quisquam est et porro nesciunt excepturi a", + "email": "Janick@marty.me", + "body": "dolore velit autem perferendis hic\ntenetur quo rerum\nimpedit error sit eaque ut\nad in expedita et nesciunt sit aspernatur repudiandae" + }, + { + "postId": 39, + "id": 193, + "name": "quia velit nostrum eligendi voluptates", + "email": "Alena@deron.name", + "body": "laudantium consequatur sed adipisci a\nodit quia necessitatibus qui\nnumquam expedita est accusantium nostrum\noccaecati perspiciatis molestiae nostrum atque" + }, + { + "postId": 39, + "id": 194, + "name": "non ut sunt ut eius autem ipsa eos sapiente", + "email": "Alphonso_Rosenbaum@valentin.co.uk", + "body": "aut distinctio iusto autem sit libero deleniti\nest soluta non perferendis illo\neveniet corrupti est sint quae\nsed sunt voluptatem" + }, + { + "postId": 39, + "id": 195, + "name": "tempore vel accusantium qui quidem esse ut aut", + "email": "Frank@rosalind.name", + "body": "culpa voluptas quidem eos quis excepturi\nquasi corporis provident enim\nprovident velit ex occaecati deleniti\nid aspernatur fugiat eligendi" + }, + { + "postId": 40, + "id": 196, + "name": "totam vel saepe aut qui velit quis", + "email": "Jenifer_Lowe@reuben.ca", + "body": "eum laborum quidem omnis facere harum ducimus dolores quaerat\ncorporis quidem aliquid\nquod aut aut at dolorum aspernatur reiciendis\nexercitationem quasi consectetur asperiores vero blanditiis dolor" + }, + { + "postId": 40, + "id": 197, + "name": "non perspiciatis omnis facere rem", + "email": "Cecelia_Nitzsche@marty.com", + "body": "fugit ut laborum provident\nquos provident voluptatibus quam non\nsed accusantium explicabo dolore quia distinctio voluptatibus et\nconsequatur eos qui iure minus eaque praesentium" + }, + { + "postId": 40, + "id": 198, + "name": "quod vel enim sit quia ipsa quo dolores", + "email": "Christop_Friesen@jordan.me", + "body": "est veritatis mollitia atque quas et sint et dolor\net ut beatae aut\nmagni corporis dolores voluptatibus optio molestiae enim minus est\nreiciendis facere voluptate rem officia doloribus ut" + }, + { + "postId": 40, + "id": 199, + "name": "pariatur aspernatur nam atque quis", + "email": "Cooper_Boehm@damian.biz", + "body": "veniam eos ab voluptatem in fugiat ipsam quis\nofficiis non qui\nquia ut id voluptates et a molestiae commodi quam\ndolorem enim soluta impedit autem nulla" + }, + { + "postId": 40, + "id": 200, + "name": "aperiam et omnis totam", + "email": "Amir@kaitlyn.org", + "body": "facere maxime alias aspernatur ab quibusdam necessitatibus\nratione similique error enim\nsed minus et\net provident minima voluptatibus" + }, + { + "postId": 41, + "id": 201, + "name": "et adipisci aliquam a aperiam ut soluta", + "email": "Cleve@royal.us", + "body": "est officiis placeat\nid et iusto ut fugit numquam\neos aut voluptas ad quia tempore qui quibusdam doloremque\nrecusandae tempora qui" + }, + { + "postId": 41, + "id": 202, + "name": "blanditiis vel fuga odio qui", + "email": "Donnell@polly.net", + "body": "sequi expedita quibusdam enim ipsam\nbeatae ad eum placeat\nperspiciatis quis in nulla porro voluptas quia\nesse et quibusdam" + }, + { + "postId": 41, + "id": 203, + "name": "ab enim adipisci laudantium impedit qui sed", + "email": "Bonita@karl.biz", + "body": "eum voluptates id autem sequi qui omnis commodi\nveniam et laudantium aut\net molestias esse asperiores et quaerat\npariatur non officia voluptatibus" + }, + { + "postId": 41, + "id": 204, + "name": "autem voluptates voluptas nihil", + "email": "Shea@angelina.biz", + "body": "voluptatibus pariatur illo\nautem quia aut ullam laudantium quod laborum officia\ndicta sit consequatur quis delectus vel\nomnis laboriosam laborum vero ipsa voluptas" + }, + { + "postId": 41, + "id": 205, + "name": "et reiciendis ullam quae", + "email": "Omari@veronica.us", + "body": "voluptatem accusamus delectus natus quasi aliquid\nporro ab id ea aut consequatur dignissimos quod et\naspernatur sapiente cum corrupti\npariatur veritatis unde" + }, + { + "postId": 42, + "id": 206, + "name": "deserunt eveniet quam vitae velit", + "email": "Sophie@antoinette.ca", + "body": "nam iusto minus expedita numquam\net id quis\nvoluptatibus minima porro facilis dolores beatae aut sit\naut quia suscipit" + }, + { + "postId": 42, + "id": 207, + "name": "asperiores sed voluptate est", + "email": "Jessika@crystel.ca", + "body": "nulla quos harum commodi\naperiam qui et dignissimos\nreiciendis ut quia est corrupti itaque\nlaboriosam debitis suscipit" + }, + { + "postId": 42, + "id": 208, + "name": "excepturi aut libero incidunt doloremque at", + "email": "Cesar_Volkman@letitia.biz", + "body": "enim aut doloremque mollitia provident molestiae omnis esse excepturi\nofficiis tempora sequi molestiae veniam voluptatem\nrecusandae omnis temporibus et deleniti laborum sed ipsa\nmolestiae eum aut" + }, + { + "postId": 42, + "id": 209, + "name": "repudiandae consectetur dolore", + "email": "Maureen_Mueller@lance.us", + "body": "officiis qui eos voluptas laborum error\nsunt repellat quis nisi unde velit\ndelectus eum molestias tempora quia ut aut\nconsequatur cupiditate quis sint in eum voluptates" + }, + { + "postId": 42, + "id": 210, + "name": "quibusdam provident accusamus id aut totam eligendi", + "email": "Eriberto@geovany.ca", + "body": "praesentium odit quos et tempora eum voluptatem non\nnon aut eaque consectetur reprehenderit in qui eos nam\nnemo ea eos\nea nesciunt consequatur et" + }, + { + "postId": 43, + "id": 211, + "name": "rerum voluptate dolor", + "email": "Faustino.Keeling@morris.co.uk", + "body": "odio temporibus est ut a\naut commodi minima tempora eum\net fuga omnis alias deleniti facere totam unde\nimpedit voluptas et possimus consequatur necessitatibus qui velit" + }, + { + "postId": 43, + "id": 212, + "name": "et maiores sed temporibus cumque voluptatem sunt necessitatibus in", + "email": "Viola@aric.co.uk", + "body": "aut vero sint ut et voluptate\nsunt quod velit impedit quia\ncupiditate dicta magni ut\neos blanditiis assumenda pariatur nemo est tempore velit" + }, + { + "postId": 43, + "id": 213, + "name": "ratione architecto in est voluptatem quibusdam et", + "email": "Felton_Huel@terrell.biz", + "body": "at non dolore molestiae\nautem rerum id\ncum facilis sit necessitatibus accusamus quia officiis\nsint ea sit natus rerum est nemo perspiciatis ea" + }, + { + "postId": 43, + "id": 214, + "name": "dicta deserunt tempore", + "email": "Ferne_Bogan@angus.info", + "body": "nam nesciunt earum sequi dolorum\net fuga sint quae architecto\nin et et ipsam veniam ad voluptas rerum animi\nillum temporibus enim rerum est" + }, + { + "postId": 43, + "id": 215, + "name": "sint culpa cupiditate ut sit quas qui voluptas qui", + "email": "Amy@reymundo.org", + "body": "esse ab est deleniti dicta non\ninventore veritatis cupiditate\neligendi sequi excepturi assumenda a harum sint aut eaque\nrerum molestias id excepturi quidem aut" + }, + { + "postId": 44, + "id": 216, + "name": "voluptatem esse sint alias", + "email": "Jaylan.Mayert@norbert.biz", + "body": "minima quaerat voluptatibus iusto earum\nquia nihil et\nminus deleniti aspernatur voluptatibus tempore sit molestias\narchitecto velit id debitis" + }, + { + "postId": 44, + "id": 217, + "name": "eos velit velit esse autem minima voluptas", + "email": "Cristina.DAmore@destini.biz", + "body": "aperiam rerum aut provident cupiditate laboriosam\nenim placeat aut explicabo\nvoluptatum similique rerum eaque eligendi\nnobis occaecati perspiciatis sint ullam" + }, + { + "postId": 44, + "id": 218, + "name": "voluptatem qui deserunt dolorum in voluptates similique et qui", + "email": "Ettie_Bashirian@lambert.biz", + "body": "rem qui est\nfacilis qui voluptatem quis est veniam quam aspernatur dicta\ndolore id sapiente saepe consequatur\nenim qui impedit culpa ex qui voluptas dolor" + }, + { + "postId": 44, + "id": 219, + "name": "qui unde recusandae omnis ut explicabo neque magni veniam", + "email": "Lizeth@kellen.org", + "body": "est et dolores voluptas aut molestiae nam eos saepe\nexpedita eum ea tempore sit iure eveniet\niusto enim quos quo\nrepellendus laudantium eum fugiat aut et" + }, + { + "postId": 44, + "id": 220, + "name": "vel autem quia in modi velit", + "email": "Vladimir_Schumm@sharon.tv", + "body": "illum ea eum quia\ndoloremque modi ducimus voluptatum eaque aperiam accusamus eos quia\nsed rerum aperiam sunt quo\nea veritatis natus eos deserunt voluptas ea voluptate" + }, + { + "postId": 45, + "id": 221, + "name": "reprehenderit rem voluptatem voluptate recusandae dolore distinctio", + "email": "Madonna@will.com", + "body": "rerum possimus asperiores non dolores maiores tenetur ab\nsuscipit laudantium possimus ab iure\ndistinctio assumenda iste adipisci optio est sed eligendi\ntemporibus perferendis tempore soluta" + }, + { + "postId": 45, + "id": 222, + "name": "rerum aliquam ducimus repudiandae perferendis", + "email": "Cicero_Goldner@elenor.tv", + "body": "cum officiis impedit neque a sed dolorum accusamus eaque\nrepellat natus aut commodi sint fugit consequatur corporis\nvoluptatum dolorum sequi perspiciatis ut facilis\ndelectus pariatur consequatur at aut temporibus facere vitae" + }, + { + "postId": 45, + "id": 223, + "name": "accusantium aliquid consequuntur minus quae quis et autem", + "email": "Zella@jan.net", + "body": "maiores perspiciatis quo alias doloremque\nillum iusto possimus impedit\nvelit voluptatem assumenda possimus\nut nesciunt eum et deleniti molestias harum excepturi" + }, + { + "postId": 45, + "id": 224, + "name": "eum dolorum ratione vitae expedita", + "email": "Robin_Jacobi@verdie.net", + "body": "perferendis quae est velit ipsa autem adipisci ex rerum\nvoluptatem occaecati velit perferendis aut tenetur\ndeleniti eaque quasi suscipit\ndolorum nobis vel et aut est eos" + }, + { + "postId": 45, + "id": 225, + "name": "occaecati et corrupti expedita", + "email": "Lawson@demarco.co.uk", + "body": "doloribus illum tempora aliquam qui perspiciatis dolorem ratione velit\nfacere nobis et fugiat modi\nfugiat dolore at\nducimus voluptate porro qui architecto optio unde deleniti" + }, + { + "postId": 46, + "id": 226, + "name": "assumenda officia quam ex natus minima sint quia", + "email": "Benton@jayde.tv", + "body": "provident sed similique\nexplicabo fugiat quasi saepe voluptatem corrupti recusandae\nvoluptas repudiandae illum tenetur mollitia\nsint in enim earum est" + }, + { + "postId": 46, + "id": 227, + "name": "omnis error aut doloremque ipsum ducimus", + "email": "Melody@london.name", + "body": "est quo quod tempora fuga debitis\neum nihil nemo nisi consequatur sequi nesciunt dolorum et\ncumque maxime qui consequatur mollitia dicta iusto aut\nvero recusandae ut dolorem provident voluptatibus suscipit sint" + }, + { + "postId": 46, + "id": 228, + "name": "eaque expedita temporibus iure velit eligendi labore dignissimos molestiae", + "email": "Wyman.Swaniawski@marjorie.name", + "body": "quibusdam dolores eveniet qui minima\nmagni perspiciatis pariatur\nullam dolor sit ex molestiae in nulla unde rerum\nquibusdam deleniti nisi" + }, + { + "postId": 46, + "id": 229, + "name": "maxime veniam at", + "email": "Deborah@fletcher.co.uk", + "body": "unde aliquam ipsam eaque quia laboriosam exercitationem totam illo\nnon et dolore ipsa\nlaborum et sapiente fugit voluptatem\net debitis quia optio et minima et nostrum" + }, + { + "postId": 46, + "id": 230, + "name": "illo dolor corrupti quia pariatur in", + "email": "Dario@barton.info", + "body": "neque ullam eos amet\nratione architecto doloribus qui est nisi\noccaecati unde expedita perspiciatis animi tenetur minus eveniet aspernatur\neius nihil adipisci aut" + }, + { + "postId": 47, + "id": 231, + "name": "omnis minima dicta aliquam excepturi", + "email": "Kelton_McKenzie@danial.us", + "body": "veritatis laudantium laboriosam ut maxime sed voluptate\nconsequatur itaque occaecati voluptatum est\nut itaque aperiam eligendi at vel minus\ndicta tempora nihil pariatur libero est" + }, + { + "postId": 47, + "id": 232, + "name": "voluptatem excepturi sit et sed qui ipsum quam consequatur", + "email": "Itzel@fritz.io", + "body": "ullam modi consequatur officia dolor non explicabo et\neum minus dicta dolores blanditiis dolore\nnobis assumenda harum velit ullam et cupiditate\net commodi dolor harum et sed cum reprehenderit omnis" + }, + { + "postId": 47, + "id": 233, + "name": "qui dolores maxime autem enim repellendus culpa nostrum consequuntur", + "email": "Jacquelyn_Kutch@kaya.co.uk", + "body": "aperiam quo quis\nnobis error et culpa veritatis\nquae sapiente nobis architecto doloribus quia laboriosam\nest consequatur et recusandae est eius" + }, + { + "postId": 47, + "id": 234, + "name": "natus et necessitatibus animi", + "email": "Cheyanne.Schowalter@alycia.biz", + "body": "itaque voluptatem voluptas velit non est rerum incidunt\nvitae aut labore accusantium in atque\nrepudiandae quos necessitatibus\nautem ea excepturi" + }, + { + "postId": 47, + "id": 235, + "name": "odio sed accusantium iure repudiandae officiis ut autem illo", + "email": "Macey@abbie.org", + "body": "ea iusto laboriosam sit\nvoluptatibus magni ratione eum\net minus perferendis\neius rerum suscipit velit culpa ipsa ipsam aperiam est" + }, + { + "postId": 48, + "id": 236, + "name": "cupiditate rerum voluptate quo facere repudiandae", + "email": "Freeda.Kirlin@eddie.ca", + "body": "itaque error cupiditate asperiores ut aspernatur veniam qui\ndoloribus sit aliquid pariatur dicta deleniti qui alias dignissimos\nrecusandae eaque repellendus est et dolorem aut non\nexplicabo voluptas est beatae vel temporibus" + }, + { + "postId": 48, + "id": 237, + "name": "recusandae deserunt possimus voluptatibus ipsam iste consequatur consequatur", + "email": "Jennifer.Rowe@zoe.org", + "body": "aut culpa quis modi\nlibero hic dolore provident officiis placeat minima vero\net iste dolores aut voluptatem saepe unde\nvero temporibus sunt corrupti voluptate" + }, + { + "postId": 48, + "id": 238, + "name": "voluptatem nam ducimus non molestiae", + "email": "Providenci.Heller@lenna.info", + "body": "et nostrum cupiditate nobis facere et est illo\nconsequatur harum voluptatem totam\nmolestiae voluptas consequatur quibusdam aut\nmodi impedit necessitatibus et nisi nesciunt adipisci" + }, + { + "postId": 48, + "id": 239, + "name": "voluptatum debitis qui aut voluptas eos quibusdam et", + "email": "Emerald_Murazik@darrell.biz", + "body": "esse rem ut neque magni voluptatem id qui\naut ut vel autem non qui quam sit\nimpedit quis sit illum laborum\naut at vel eos nihil quo" + }, + { + "postId": 48, + "id": 240, + "name": "est dolorem est placeat provident non nihil", + "email": "Joseph@corrine.com", + "body": "necessitatibus nulla perferendis ad inventore earum delectus\nvitae illo sed perferendis\nofficiis quo eligendi voluptatem animi totam perspiciatis\nrepellat quam eum placeat est tempore facere" + }, + { + "postId": 49, + "id": 241, + "name": "reprehenderit inventore soluta ut aliquam", + "email": "Lemuel@willow.name", + "body": "quisquam asperiores voluptas\nmodi tempore officia quod hic dolor omnis asperiores\narchitecto aut vel odio quisquam sunt\ndeserunt soluta illum" + }, + { + "postId": 49, + "id": 242, + "name": "quis sit aut vero quo accusamus", + "email": "Sven@gudrun.info", + "body": "dolores minus sequi laudantium excepturi deserunt rerum voluptatem\npariatur harum natus sed dolore quis\nconsectetur quod inventore laboriosam et in dolor beatae rerum\nquia rerum qui recusandae quo officiis fugit" + }, + { + "postId": 49, + "id": 243, + "name": "quaerat natus illum", + "email": "Jennifer@shania.ca", + "body": "rem ut cumque tempore sed\naperiam unde tenetur ab maiores officiis alias\naut nemo veniam dolor est eum sunt a\nesse ratione deserunt et" + }, + { + "postId": 49, + "id": 244, + "name": "labore temporibus ipsa at blanditiis autem", + "email": "Eldora@madge.com", + "body": "est et itaque qui laboriosam dolor ut debitis\ncumque et et dolor\neaque enim et architecto\net quia reiciendis quis" + }, + { + "postId": 49, + "id": 245, + "name": "et rerum fuga blanditiis provident eligendi iste eos", + "email": "Litzy@kaylie.io", + "body": "vel nam nemo sed vitae\nrepellat necessitatibus dolores deserunt dolorum\nminima quae velit et nemo\nsit expedita nihil consequatur autem quia maiores" + }, + { + "postId": 50, + "id": 246, + "name": "magnam earum qui eaque sunt excepturi", + "email": "Jaycee.Turner@euna.name", + "body": "quia est sed eos animi optio dolorum\nconsequatur reiciendis exercitationem ipsum nihil omnis\nbeatae sed corporis enim quisquam\net blanditiis qui nihil" + }, + { + "postId": 50, + "id": 247, + "name": "vel aut blanditiis magni accusamus dolor soluta", + "email": "Wilbert@cheyenne.ca", + "body": "explicabo nam nihil atque sint aut\nqui qui rerum excepturi soluta quis et\net mollitia et voluptate minima nihil\nsed quaerat dolor earum tempore et non est voluptatem" + }, + { + "postId": 50, + "id": 248, + "name": "voluptatum sint dicta voluptas aut ut", + "email": "Rebecca_Hessel@edna.net", + "body": "assumenda aut quis repellendus\nnihil impedit cupiditate nemo\nsit sequi laudantium aut voluptas nam dolore magnam\nminima aspernatur vero sapiente" + }, + { + "postId": 50, + "id": 249, + "name": "quibusdam dignissimos aperiam sint commodi", + "email": "Christiana@lawrence.info", + "body": "non repudiandae dicta et commodi\nsint dolores facere eos nesciunt autem quia\nplaceat quaerat non culpa quasi dolores voluptas\ndolorum placeat non atque libero odit autem sunt" + }, + { + "postId": 50, + "id": 250, + "name": "perferendis magnam natus exercitationem eveniet sunt", + "email": "Samara@shaun.org", + "body": "doloremque quae ratione cumque\nexcepturi eligendi delectus maiores necessitatibus veniam\nfugiat exercitationem consectetur vel earum\nquia illo explicabo molestiae enim rem deserunt et omnis" + }, + { + "postId": 51, + "id": 251, + "name": "veritatis sint eius", + "email": "Ayden_Hickle@stephany.tv", + "body": "sit vero at voluptatem corporis adipisci\nerror sit aut nihil rerum doloremque dolorum ipsum\neum ut numquam sapiente ipsam nam blanditiis ut quasi\nfacilis optio rerum qui temporibus esse excepturi eaque" + }, + { + "postId": 51, + "id": 252, + "name": "qui alias beatae iusto omnis placeat recusandae ut", + "email": "Carissa.Krajcik@jean.name", + "body": "exercitationem quisquam sed\neius et cum reiciendis deleniti non\nperspiciatis aut voluptatum deserunt\nsint dignissimos est sed architecto sed" + }, + { + "postId": 51, + "id": 253, + "name": "voluptate ipsum corporis quis provident voluptatem eos asperiores", + "email": "Jayde@geovanny.io", + "body": "debitis dignissimos ut illum\nrerum voluptatem ut qui labore\noptio quaerat iure\niste consequuntur praesentium vero blanditiis quibusdam aut" + }, + { + "postId": 51, + "id": 254, + "name": "velit inventore et eius saepe", + "email": "Ardella@khalid.biz", + "body": "laboriosam voluptas aut quibusdam mollitia sunt non\ndolores illum fugiat ex vero nemo aperiam porro quam\nexpedita est vel voluptatem sit voluptas consequuntur quis eligendi\nomnis id nisi ducimus sapiente odit quam" + }, + { + "postId": 51, + "id": 255, + "name": "impedit et sapiente et tempore repellendus", + "email": "Delta_Welch@carleton.tv", + "body": "nihil esse aut\ndebitis nostrum mollitia similique minus aspernatur possimus\nomnis eaque non eveniet\nrerum qui iure laboriosam" + }, + { + "postId": 52, + "id": 256, + "name": "tempore distinctio quam", + "email": "Carlee_Heathcote@harley.tv", + "body": "nemo deleniti sunt praesentium quis quam repellendus\nconsequatur non est ex fugiat distinctio aliquam explicabo\naspernatur omnis debitis sint consequatur\nquo autem natus veritatis" + }, + { + "postId": 52, + "id": 257, + "name": "illum non quod vel voluptas quos", + "email": "Delpha_Cormier@raymond.org", + "body": "facere at voluptatem\nrepellendus omnis perspiciatis placeat aspernatur nobis blanditiis ut deleniti\nquis non cumque laborum sit id ratione vel sequi\nfacere doloremque beatae aut maxime non" + }, + { + "postId": 52, + "id": 258, + "name": "omnis quia fugit nisi officiis aspernatur occaecati et", + "email": "Glenna@caesar.org", + "body": "aut cum sint qui facere blanditiis magnam consequuntur perspiciatis\nharum impedit reprehenderit iste doloribus quia quo facere\net explicabo aut voluptate modi dolorem\nrem aut nobis ut ad voluptatum ipsum eos maxime" + }, + { + "postId": 52, + "id": 259, + "name": "animi minima ducimus tempore officiis qui", + "email": "Hoyt_Dickens@napoleon.ca", + "body": "itaque occaecati non aspernatur\nvelit repudiandae sit rerum esse quibusdam unde molestias\nexplicabo dolorem vero consequatur quis et libero\nvoluptatem totam vel sapiente autem dolorum consequuntur" + }, + { + "postId": 52, + "id": 260, + "name": "qui dolore delectus et omnis quia", + "email": "Wendell.Marvin@maegan.net", + "body": "aliquid molestias nemo aut est maxime\nlaboriosam et consequatur laudantium\ncommodi et corrupti\nharum quasi minima ratione sint magni sapiente ut" + }, + { + "postId": 53, + "id": 261, + "name": "aut veritatis quasi voluptatem enim dolor soluta temporibus", + "email": "Virgie@layne.org", + "body": "sapiente qui est quod\ndebitis qui est optio consequuntur\nalias hic amet ut non ad qui provident\nquia provident aspernatur corrupti occaecati" + }, + { + "postId": 53, + "id": 262, + "name": "ipsa aliquid laborum quidem recusandae dolorum quia", + "email": "Tia@kirsten.info", + "body": "similique harum iste ipsam non dolores facere esse\net beatae error necessitatibus laboriosam fugiat culpa esse occaecati\nut provident ut et dolorum nam\ndelectus impedit aut blanditiis fugiat est unde" + }, + { + "postId": 53, + "id": 263, + "name": "vitae voluptatem dolor iure quo non atque", + "email": "Marco@jennyfer.biz", + "body": "debitis dolore est\nut eos velit accusamus\nnon nobis ipsa nemo quas facilis quia hic\nofficia quam et possimus voluptas voluptatem quisquam tempore delectus" + }, + { + "postId": 53, + "id": 264, + "name": "cum ab voluptates aut", + "email": "Taya@milan.biz", + "body": "consectetur maiores ab est qui aliquid porro\nipsa labore incidunt\niste deserunt quia aperiam quis sit perferendis\net sint iste" + }, + { + "postId": 53, + "id": 265, + "name": "omnis incidunt est molestias", + "email": "Lenora@derrick.biz", + "body": "et quibusdam saepe labore delectus et earum quis perferendis\nlaborum soluta veritatis\nea veritatis quidem accusantium est aut porro rerum\nquia est consequatur voluptatem numquam laudantium repellendus" + }, + { + "postId": 54, + "id": 266, + "name": "eum enim provident atque eum", + "email": "Carolina.Auer@polly.co.uk", + "body": "non et voluptas\neaque atque esse qui molestias porro quam veniam voluptatibus\nminima ut velit velit ut architecto deleniti\nab sint deserunt possimus quas velit et eum" + }, + { + "postId": 54, + "id": 267, + "name": "ea commodi provident veritatis voluptatem voluptates aperiam", + "email": "Jaylan.Braun@lane.us", + "body": "magnam similique animi eos explicabo natus\nprovident cumque sit maxime velit\nveritatis fuga esse dolor hic nihil nesciunt assumenda\naliquid vero modi alias qui quia doloribus est" + }, + { + "postId": 54, + "id": 268, + "name": "eum et eos delectus", + "email": "Javier.Dicki@damien.org", + "body": "velit earum perspiciatis ea recusandae nihil consectetur ut\nmaxime repellendus doloribus\naperiam ut ex ratione quia esse magni\nducimus rerum vel rerum officiis suscipit nihil qui" + }, + { + "postId": 54, + "id": 269, + "name": "molestiae vitae pariatur", + "email": "Khalil_Sawayn@tanya.net", + "body": "quos sed unde repudiandae aut porro dignissimos qui\noccaecati sed alias enim\nvoluptates nesciunt sit ut adipisci est\nexpedita quae corrupti" + }, + { + "postId": 54, + "id": 270, + "name": "rerum adipisci et ut sit sit dolores", + "email": "Tom.Russel@pattie.org", + "body": "quas placeat repudiandae a delectus facere exercitationem consectetur\nfacilis quas sequi est mollitia\nest vero hic laudantium maiores\nquisquam itaque aut maxime ut cumque quia doloremque voluptatem" + }, + { + "postId": 55, + "id": 271, + "name": "et et repellat quasi non ea similique", + "email": "Ethelyn.Moore@gabe.info", + "body": "quae eaque reprehenderit\nsuscipit facilis ut tenetur blanditiis sint occaecati\naccusantium expedita sed nostrum\nrerum sunt nam qui placeat consequatur et" + }, + { + "postId": 55, + "id": 272, + "name": "repudiandae ut velit dignissimos enim rem dolores odit", + "email": "Evangeline_Kuvalis@santina.ca", + "body": "consequuntur molestiae aut distinctio illo qui est sequi reprehenderit\nhic accusamus et officiis reprehenderit culpa\nest et numquam et\neius ipsa rerum velit" + }, + { + "postId": 55, + "id": 273, + "name": "et aperiam autem inventore nisi nulla reiciendis velit", + "email": "Orland@larry.name", + "body": "asperiores et minus non\ndolor dolorem et sint et ipsam\net enim quia sequi\nsed beatae culpa architecto nisi minima" + }, + { + "postId": 55, + "id": 274, + "name": "et vero nostrum tempore", + "email": "Micaela.Powlowski@saul.me", + "body": "quos illo consectetur dolores\ncupiditate enim rerum dicta sequi totam\naspernatur sed praesentium\nipsum voluptates perspiciatis ipsa accusantium et et" + }, + { + "postId": 55, + "id": 275, + "name": "error nulla est laudantium similique ad", + "email": "Imelda_Klein@melany.biz", + "body": "error et quasi qui facilis enim eum adipisci iste\nad nostrum sint corporis quam velit necessitatibus a\neius doloribus optio ad qui molestiae\nquaerat dignissimos voluptatem culpa aliquam explicabo commodi natus" + }, + { + "postId": 56, + "id": 276, + "name": "inventore amet ut debitis ipsam reiciendis molestiae autem sed", + "email": "Matt.Moen@gilda.org", + "body": "dolores tempora totam quas maxime voluptatem voluptas excepturi\nrecusandae quis odio exercitationem in\nconsectetur sed aut\nexcepturi eligendi sint consectetur repellendus aperiam" + }, + { + "postId": 56, + "id": 277, + "name": "dolorem aut ipsum alias ex ea quidem nostrum", + "email": "Rocky_Ullrich@rowena.name", + "body": "nihil ratione aliquam recusandae ipsa sunt doloribus labore molestiae\nofficia cum aliquid repudiandae et error\ninventore minima optio repellat aut\nea et maxime alias voluptas eius" + }, + { + "postId": 56, + "id": 278, + "name": "est pariatur similique quod voluptas et consequuntur nam molestiae", + "email": "Natalia@caitlyn.ca", + "body": "corporis perferendis dolorum error quo rerum aut odio veritatis\nsit deleniti aut eligendi quam doloremque aut ipsam sint\ndoloribus id voluptatem esse reprehenderit molestiae quia voluptatem\nincidunt illo beatae nihil corporis eligendi iure quo" + }, + { + "postId": 56, + "id": 279, + "name": "voluptas nihil aut dolor adipisci non", + "email": "Edythe@general.org", + "body": "natus atque ipsum voluptatem et\nnecessitatibus atque quia asperiores animi odit ratione quos\nest repellendus sit aut repudiandae animi aut\ncum blanditiis repudiandae saepe laborum" + }, + { + "postId": 56, + "id": 280, + "name": "culpa minima non consequatur sit autem quas sed ipsam", + "email": "Aglae@gerardo.name", + "body": "perferendis fugit expedita cumque\nexercitationem animi fugit aut earum\nnihil quia illum eum dicta ut\nquam commodi optio" + }, + { + "postId": 57, + "id": 281, + "name": "consequatur voluptates sed voluptatem fuga", + "email": "Bridie@pearl.ca", + "body": "eius voluptatem minus\net aliquid perspiciatis sint unde ut\nsimilique odio ullam vitae quisquam hic ex consequatur aliquid\nab nihil explicabo sint maiores aut et dolores nostrum" + }, + { + "postId": 57, + "id": 282, + "name": "et vitae culpa corrupti", + "email": "Aglae_Goldner@madisyn.co.uk", + "body": "ea consequatur placeat\nquo omnis illum voluptatem\nvoluptatem fugit aut dolorum recusandae ut et\ntenetur officia voluptas" + }, + { + "postId": 57, + "id": 283, + "name": "iste molestiae aut hic perspiciatis sint", + "email": "Owen_Moore@jeremy.org", + "body": "perspiciatis omnis eum nihil et porro facilis fuga qui\ndeleniti id et velit adipisci fuga voluptatibus voluptatum\ndebitis tempore dolorem atque consequatur ea perspiciatis sed\nqui temporibus doloremque" + }, + { + "postId": 57, + "id": 284, + "name": "soluta omnis maiores animi veniam voluptas et totam repellendus", + "email": "Jarred@dangelo.name", + "body": "rem ut sed\nnon cumque corrupti vel nam rerum autem\nnobis dolorem necessitatibus fugit corporis\nquos sint distinctio ex et animi tempore" + }, + { + "postId": 57, + "id": 285, + "name": "non est sunt consequatur reiciendis", + "email": "Remington_Mohr@vincenza.me", + "body": "est accusamus facere\nreprehenderit corporis ad et est fugit iure nulla et\ndoloribus reiciendis quasi autem voluptas\nipsam labore et pariatur quia" + }, + { + "postId": 58, + "id": 286, + "name": "dolore dignissimos distinctio", + "email": "Marco.Langworth@zoie.org", + "body": "doloremque accusantium necessitatibus architecto ut provident\nquaerat iusto eius omnis\nfuga laborum harum totam sunt velit\naut neque corporis atque" + }, + { + "postId": 58, + "id": 287, + "name": "voluptas ad autem maxime iusto eos dolorem ducimus est", + "email": "Sedrick@mertie.tv", + "body": "voluptatem perspiciatis voluptatum quaerat\nodit voluptates iure\nquisquam magnam voluptates ut non qui\naliquam aut ut amet sit consequatur ut suscipit" + }, + { + "postId": 58, + "id": 288, + "name": "numquam eius voluptas quibusdam soluta exercitationem", + "email": "Caleigh@eleanore.org", + "body": "est sed illo omnis delectus aut\nlaboriosam possimus incidunt est sunt at\nnemo voluptas ex ipsam\nvoluptatibus inventore velit sit et numquam omnis accusamus in" + }, + { + "postId": 58, + "id": 289, + "name": "voluptatem aut harum aut corporis dignissimos occaecati sequi quod", + "email": "Paolo@cristopher.com", + "body": "occaecati tempora unde\nmaiores aliquid in\nquo libero sint quidem at est facilis ipsa facere\nnostrum atque harum" + }, + { + "postId": 58, + "id": 290, + "name": "suscipit debitis eveniet nobis atque commodi quisquam", + "email": "Juana_Stamm@helmer.com", + "body": "pariatur veniam repellat quisquam tempore autem et voluptatem itaque\nea impedit ex molestiae placeat hic harum mollitia dolorem\ninventore accusantium aut quae quia rerum autem numquam\nnulla culpa quasi dolor" + }, + { + "postId": 59, + "id": 291, + "name": "occaecati et dolorum", + "email": "Pascale@fleta.ca", + "body": "nisi dicta numquam dolor\nrerum sed quaerat et\nsed sequi doloribus libero quos temporibus\nblanditiis optio est tempore qui" + }, + { + "postId": 59, + "id": 292, + "name": "consequatur et facere similique beatae explicabo eligendi consequuntur", + "email": "Molly_Kertzmann@tristin.me", + "body": "eos officiis omnis ab laborum nulla saepe exercitationem recusandae\nvoluptate minima voluptatem sint\nsunt est consequuntur dolor voluptatem odit\nmaxime similique deserunt et quod" + }, + { + "postId": 59, + "id": 293, + "name": "qui sint hic", + "email": "Kailee.Larkin@amina.org", + "body": "fugiat dicta quasi voluptatibus ea aut est aspernatur sed\ncorrupti harum non omnis eos eaque quos ut\nquia et et nisi rerum voluptates possimus quis\nrecusandae aperiam quia esse" + }, + { + "postId": 59, + "id": 294, + "name": "autem totam necessitatibus sit sunt minima aut quis", + "email": "Earnest.Sanford@lane.us", + "body": "ut est veritatis animi quos\nnam sed dolor\nitaque omnis nostrum autem molestiae\naut optio tempora ad sapiente quae voluptatem perferendis repellat" + }, + { + "postId": 59, + "id": 295, + "name": "ullam dignissimos non aut dolore", + "email": "Abigail@trudie.com", + "body": "voluptatem est aspernatur consequatur vel facere\nut omnis tenetur non ea eos\nquibusdam error odio\natque consectetur voluptatem eligendi" + }, + { + "postId": 60, + "id": 296, + "name": "hic eum sed dolore velit cupiditate quisquam ut inventore", + "email": "Name.Walter@zoie.me", + "body": "quasi dolorem veniam dignissimos\natque voluptas iure et quidem fugit velit et\nquod magnam illum quia et ea est modi\naut quis dolores" + }, + { + "postId": 60, + "id": 297, + "name": "dignissimos dolor facere", + "email": "Norma@abraham.co.uk", + "body": "eos exercitationem est ut voluptas accusamus qui\nvelit rerum ut\ndolorem eaque omnis eligendi mollitia\natque ea architecto dolorum delectus accusamus" + }, + { + "postId": 60, + "id": 298, + "name": "ipsam ut labore voluptatem quis id velit sunt", + "email": "Norberto_Weimann@ford.tv", + "body": "molestiae accusantium a tempore occaecati qui sunt optio eos\nexercitationem quas eius voluptatem\nomnis quibusdam autem\nmolestiae odio dolor quam laboriosam mollitia in quibusdam sunt" + }, + { + "postId": 60, + "id": 299, + "name": "laborum asperiores nesciunt itaque", + "email": "Nelson@charlene.biz", + "body": "voluptatem omnis pariatur aut saepe enim qui\naut illo aut sed aperiam expedita debitis\ntempore animi dolorem\nut libero et eos unde ex" + }, + { + "postId": 60, + "id": 300, + "name": "in dolore iusto ex molestias vero", + "email": "Letha@liliane.ca", + "body": "dolorem fugit quidem animi quas quisquam reprehenderit\noccaecati et dolor laborum nemo sed quas unde deleniti\nfacere eligendi placeat aliquid aspernatur commodi sunt impedit\nneque corrupti alias molestiae magni tempora" + }, + { + "postId": 61, + "id": 301, + "name": "id voluptatibus voluptas occaecati quia sunt eveniet et eius", + "email": "Tiana@jeramie.tv", + "body": "dolore maxime saepe dolor asperiores cupiditate nisi nesciunt\nvitae tempora ducimus vel eos perferendis\nfuga sequi numquam blanditiis sit sed inventore et\nut possimus soluta voluptas nihil aliquid sed earum" + }, + { + "postId": 61, + "id": 302, + "name": "quia voluptatem sunt voluptate ut ipsa", + "email": "Lindsey@caitlyn.net", + "body": "fuga aut est delectus earum optio impedit qui excepturi\niusto consequatur deserunt soluta sunt\net autem neque\ndolor ut saepe dolores assumenda ipsa eligendi" + }, + { + "postId": 61, + "id": 303, + "name": "excepturi itaque laudantium reiciendis dolorem", + "email": "Gregory.Kutch@shawn.info", + "body": "sit nesciunt id vitae ut itaque sapiente\nneque in at consequuntur perspiciatis dicta consequatur velit\nfacilis iste ut error sed\nin sequi expedita autem" + }, + { + "postId": 61, + "id": 304, + "name": "voluptatem quidem animi sit est nemo non omnis molestiae", + "email": "Murphy.Kris@casimer.me", + "body": "minus ab quis nihil quia suscipit vel\nperspiciatis sunt unde\naut ullam quo laudantium deleniti modi\nrerum illo error occaecati vel officiis facere" + }, + { + "postId": 61, + "id": 305, + "name": "non cum consequatur at nihil aut fugiat delectus quia", + "email": "Isidro_Kiehn@cristina.org", + "body": "repellendus quae labore sunt ut praesentium fuga reiciendis quis\ncorporis aut quis dolor facere earum\nexercitationem enim sunt nihil asperiores expedita\neius nesciunt a sit sit" + }, + { + "postId": 62, + "id": 306, + "name": "omnis nisi libero", + "email": "Kenton_Carter@yolanda.co.uk", + "body": "ab veritatis aspernatur molestiae explicabo ea saepe molestias sequi\nbeatae aut perferendis quaerat aut omnis illo fugiat\nquisquam hic doloribus maiores itaque\nvoluptas amet dolorem blanditiis" + }, + { + "postId": 62, + "id": 307, + "name": "id ab commodi est quis culpa", + "email": "Amos_Rohan@mozelle.tv", + "body": "sit tenetur aut eum quasi reiciendis dignissimos non nulla\nrepellendus ut quisquam\nnumquam provident et repellendus eum nihil blanditiis\nbeatae iusto sed eius sit sed doloremque exercitationem nihil" + }, + { + "postId": 62, + "id": 308, + "name": "enim ut optio architecto dolores nemo quos", + "email": "Timothy_Heathcote@jose.name", + "body": "officiis ipsa exercitationem impedit dolorem repellat adipisci qui\natque illum sapiente omnis et\nnihil esse et eum facilis aut impedit\nmaxime ullam et dolorem" + }, + { + "postId": 62, + "id": 309, + "name": "maiores et quisquam", + "email": "Otilia.Daniel@elvie.io", + "body": "impedit qui nemo\nreprehenderit sequi praesentium ullam veniam quaerat optio qui error\naperiam qui quisquam dolor est blanditiis molestias rerum et\nquae quam eum odit ab quia est ut" + }, + { + "postId": 62, + "id": 310, + "name": "sed qui atque", + "email": "Toni@joesph.biz", + "body": "quae quis qui ab rerum non hic\nconsequatur earum facilis atque quas dolore fuga ipsam\nnihil velit quis\nrerum sit nam est nulla nihil qui excepturi et" + }, + { + "postId": 63, + "id": 311, + "name": "veritatis nulla consequatur sed cumque", + "email": "Brisa@carrie.us", + "body": "officia provident libero explicabo tempora velit eligendi mollitia similique\nrerum sit aut consequatur ullam tenetur qui est vero\nrerum est et explicabo\nsit sunt ea exercitationem molestiae" + }, + { + "postId": 63, + "id": 312, + "name": "libero et distinctio repudiandae voluptatem dolores", + "email": "Jasen.Kihn@devon.biz", + "body": "ipsa id eum dolorum et officiis\nsaepe eos voluptatem\nnesciunt quos voluptas temporibus dolores ad rerum\nnon voluptatem aut fugit" + }, + { + "postId": 63, + "id": 313, + "name": "quia enim et", + "email": "Efren.Konopelski@madisyn.us", + "body": "corporis quo magnam sunt rerum enim vel\nrepudiandae suscipit corrupti ut ab qui debitis quidem adipisci\ndistinctio voluptatibus vitae molestias incidunt laboriosam quia quidem facilis\nquia architecto libero illum ut dicta" + }, + { + "postId": 63, + "id": 314, + "name": "enim voluptatem quam", + "email": "Demetris.Bergnaum@fae.io", + "body": "sunt cupiditate commodi est pariatur incidunt quis\nsuscipit saepe magnam amet enim\nquod quis neque\net modi rerum asperiores consequatur reprehenderit maiores" + }, + { + "postId": 63, + "id": 315, + "name": "maxime nulla perspiciatis ad quo quae consequatur quas", + "email": "Luella.Pollich@gloria.org", + "body": "repudiandae dolores nam quas\net incidunt odio dicta eum vero dolor quidem\ndolorem quisquam cumque\nmolestiae neque maxime rerum deserunt nam sequi" + }, + { + "postId": 64, + "id": 316, + "name": "totam est minima modi sapiente nobis impedit", + "email": "Sister.Morissette@adelia.io", + "body": "consequatur qui doloribus et rerum\ndebitis cum dolorem voluptate qui fuga\nnecessitatibus quod temporibus non voluptates\naut saepe molestiae" + }, + { + "postId": 64, + "id": 317, + "name": "iusto pariatur ea", + "email": "Shyanne@rick.info", + "body": "quam iste aut molestiae nesciunt modi\natque quo laudantium vel tempora quam tenetur neque aut\net ipsum eum nostrum enim laboriosam officia eligendi\nlaboriosam libero ullam sit nulla voluptate in" + }, + { + "postId": 64, + "id": 318, + "name": "vitae porro aut ex est cumque", + "email": "Freeman.Dare@ada.name", + "body": "distinctio placeat nisi repellat animi\nsed praesentium voluptatem\nplaceat eos blanditiis deleniti natus eveniet dolorum quia tempore\npariatur illum dolor aspernatur ratione tenetur autem ipsum fugit" + }, + { + "postId": 64, + "id": 319, + "name": "et eos praesentium porro voluptatibus quas quidem explicabo est", + "email": "Donnell@orland.org", + "body": "occaecati quia ipsa id fugit sunt velit iure adipisci\nullam inventore quidem dolorem adipisci optio quia et\nquis explicabo omnis ipsa quo ut voluptatem aliquam inventore\nminima aut tempore excepturi similique" + }, + { + "postId": 64, + "id": 320, + "name": "fugiat eos commodi consequatur vel qui quasi", + "email": "Robin@gaylord.biz", + "body": "nihil consequatur dolorem voluptatem non molestiae\nodit eum animi\nipsum omnis ut quasi\nvoluptas sed et et qui est aut" + }, + { + "postId": 65, + "id": 321, + "name": "rem ducimus ipsam ut est vero distinctio et", + "email": "Danyka_Stark@jedidiah.name", + "body": "ea necessitatibus eum nesciunt corporis\nminus in quisquam iste recusandae\nqui nobis deleniti asperiores non laboriosam sunt molestiae dolore\ndistinctio qui officiis tempora dolorem ea" + }, + { + "postId": 65, + "id": 322, + "name": "ipsam et commodi", + "email": "Margarita@casper.io", + "body": "id molestiae doloribus\nomnis atque eius sunt aperiam\ntenetur quia natus nihil sunt veritatis recusandae quia\ncorporis quam omnis veniam voluptas amet quidem illo deleniti" + }, + { + "postId": 65, + "id": 323, + "name": "et aut non illo cumque pariatur laboriosam", + "email": "Carlo@cortney.net", + "body": "explicabo dicta quas cum quis rerum dignissimos et\nmagnam sit mollitia est dolor voluptas sed\nipsum et tenetur recusandae\nquod facilis nulla amet deserunt" + }, + { + "postId": 65, + "id": 324, + "name": "ut ut architecto vero est ipsam", + "email": "Mina@nikita.tv", + "body": "ipsam eum ea distinctio\nducimus saepe eos quaerat molestiae\ncorporis aut officia qui ut perferendis\nitaque possimus incidunt aut quis" + }, + { + "postId": 65, + "id": 325, + "name": "odit sit numquam rerum porro dolorem", + "email": "Violette@naomi.tv", + "body": "qui vero recusandae\nporro esse sint doloribus impedit voluptatem commodi\nasperiores laudantium ut dolores\npraesentium distinctio magnam voluptatum aut" + }, + { + "postId": 66, + "id": 326, + "name": "voluptatem laborum incidunt accusamus", + "email": "Lauren.Hodkiewicz@jarret.info", + "body": "perspiciatis vero nulla aut consequatur fuga earum aut\nnemo suscipit totam vitae qui at omnis aut\nincidunt optio dolorem voluptatem vel\nassumenda vero id explicabo deleniti sit corrupti sit" + }, + { + "postId": 66, + "id": 327, + "name": "quisquam necessitatibus commodi iure eum", + "email": "Ernestina@piper.biz", + "body": "consequatur ut aut placeat harum\nquia perspiciatis unde doloribus quae non\nut modi ad unde ducimus omnis nobis voluptatem atque\nmagnam reiciendis dolorem et qui explicabo" + }, + { + "postId": 66, + "id": 328, + "name": "temporibus ut vero quas", + "email": "Hettie_Morar@wiley.info", + "body": "molestiae minima aut rerum nesciunt\nvitae iusto consequatur architecto assumenda dolorum\nnon doloremque tempora possimus qui mollitia omnis\nvitae odit sed" + }, + { + "postId": 66, + "id": 329, + "name": "quasi beatae sapiente voluptates quo temporibus", + "email": "Corbin.Hilll@modesto.biz", + "body": "nulla corrupti delectus est cupiditate explicabo facere\nsapiente quo id quis illo culpa\nut aut sit error magni atque asperiores soluta\naut cumque voluptatem occaecati omnis aliquid" + }, + { + "postId": 66, + "id": 330, + "name": "illo ab quae deleniti", + "email": "Kenyatta@renee.io", + "body": "dolores tenetur rerum et aliquam\nculpa officiis ea rem neque modi quaerat deserunt\nmolestias minus nesciunt iusto impedit enim laborum perferendis\nvelit minima itaque voluptatem fugiat" + }, + { + "postId": 67, + "id": 331, + "name": "nemo cum est officia maiores sint sunt a", + "email": "Don@cameron.co.uk", + "body": "maxime incidunt velit quam vel fugit nostrum veritatis\net ipsam nisi voluptatem voluptas cumque tempora velit et\net quisquam error\nmaiores fugit qui dolor sit doloribus" + }, + { + "postId": 67, + "id": 332, + "name": "dicta vero voluptas hic dolorem", + "email": "Jovan@aaliyah.tv", + "body": "voluptas iste deleniti\nest itaque vel ea incidunt quia voluptates sapiente repellat\naut consectetur vel neque tempora esse similique sed\na qui nobis voluptate hic eligendi doloribus molestiae et" + }, + { + "postId": 67, + "id": 333, + "name": "soluta dicta pariatur reiciendis", + "email": "Jeanie.McGlynn@enoch.ca", + "body": "et dolor error doloremque\nodio quo sunt quod\nest ipsam assumenda in veniam illum rerum deleniti expedita\nvoluptate hic nostrum sed dolor et qui" + }, + { + "postId": 67, + "id": 334, + "name": "et adipisci laboriosam est modi", + "email": "Garett_Gusikowski@abigale.me", + "body": "et voluptatibus est et aperiam quaerat voluptate eius quo\nnihil voluptas doloribus et ea tempore\nlabore non dolorem\noptio consequatur est id quia magni voluptas enim" + }, + { + "postId": 67, + "id": 335, + "name": "voluptatem accusantium beatae veniam voluptatem quo culpa deleniti", + "email": "Doug@alana.co.uk", + "body": "hic et et aspernatur voluptates voluptas ut ut id\nexcepturi eligendi aspernatur nulla dicta ab\nsuscipit quis distinctio nihil\ntemporibus unde quasi expedita et inventore consequatur rerum ab" + }, + { + "postId": 68, + "id": 336, + "name": "eveniet eligendi nisi sunt a error blanditiis et ea", + "email": "Yoshiko@viviane.name", + "body": "similique autem voluptatem ab et et\nodio animi repellendus libero voluptas voluptas quia\nlibero facere saepe nobis\nconsequatur et qui non hic ea maxime nihil" + }, + { + "postId": 68, + "id": 337, + "name": "beatae esse tenetur aut est", + "email": "Micaela_Bins@mertie.us", + "body": "est ut aut ut omnis distinctio\nillum quisquam pariatur qui aspernatur vitae\ndolor explicabo architecto veritatis ipsa et aut est molestiae\nducimus et sapiente error sed omnis" + }, + { + "postId": 68, + "id": 338, + "name": "qui sit quo est ipsam minima neque nobis", + "email": "Loy@gillian.me", + "body": "maiores totam quo atque\nexplicabo perferendis iste facilis odio ab eius consequatur\nsit praesentium ea vitae optio minus\nvoluptate necessitatibus omnis itaque omnis qui" + }, + { + "postId": 68, + "id": 339, + "name": "accusantium et sit nihil quibusdam voluptatum provident est qui", + "email": "Tyrel@hunter.net", + "body": "dicta dolorem veniam ipsa harum minus sequi\nomnis quia voluptatem autem\nest optio doloribus repellendus id commodi quas exercitationem eum\net eum dignissimos accusamus est eaque doloremque" + }, + { + "postId": 68, + "id": 340, + "name": "rerum et quae tenetur soluta voluptatem tempore laborum enim", + "email": "Otilia.Schuppe@randal.com", + "body": "est aut consequatur error illo ut\nenim nihil fuga\nsuscipit inventore officiis iure earum pariatur temporibus in\naperiam qui quod vel necessitatibus velit eos exercitationem culpa" + }, + { + "postId": 69, + "id": 341, + "name": "sunt ut voluptatem cupiditate maxime dolores eligendi", + "email": "April@larissa.co.uk", + "body": "iure ea ea neque est\nesse ab sed hic et ullam sed sequi a\nnon hic tenetur sunt enim ea\nlaudantium ea natus" + }, + { + "postId": 69, + "id": 342, + "name": "corporis at consequuntur consequatur", + "email": "Glenna_Waters@retha.me", + "body": "quis beatae qui\nsequi dicta quas et dolor\nnon enim aspernatur excepturi aut rerum asperiores\naliquid animi nulla ea tempore esse" + }, + { + "postId": 69, + "id": 343, + "name": "repellat sed consequatur suscipit aliquam", + "email": "Cordelia.Oberbrunner@peyton.com", + "body": "ea alias eos et corrupti\nvoluptatem ab incidunt\nvoluptatibus voluptas ea nesciunt\ntotam corporis dolor recusandae voluptas harum" + }, + { + "postId": 69, + "id": 344, + "name": "blanditiis rerum voluptatem quaerat modi saepe ratione assumenda qui", + "email": "Zander@santino.net", + "body": "iusto nihil quae rerum laborum recusandae voluptatem et necessitatibus\nut deserunt cumque qui qui\nnon et et eos adipisci cupiditate dolor sed voluptates\nmaiores commodi eveniet consequuntur" + }, + { + "postId": 69, + "id": 345, + "name": "ut deleniti autem ullam quod provident ducimus enim explicabo", + "email": "Camila_Runolfsdottir@tressa.tv", + "body": "omnis et fugit eos sint saepe ipsam unde est\ndolores sit sit assumenda laboriosam\ndolor deleniti voluptatem id nesciunt et\nplaceat dolorem cumque laboriosam sunt non" + }, + { + "postId": 70, + "id": 346, + "name": "beatae in fuga assumenda dolorem accusantium blanditiis mollitia", + "email": "Kirstin@tina.info", + "body": "quas non magnam\nquia veritatis assumenda reiciendis\nsimilique dolores est ab\npraesentium fuga ut" + }, + { + "postId": 70, + "id": 347, + "name": "tenetur id delectus recusandae voluptates quo aut", + "email": "Anthony.Koepp@savannah.tv", + "body": "consectetur illo corporis sit labore optio quod\nqui occaecati aut sequi quia\nofficiis quia aut odio quo ad\nrerum tenetur aut quasi veniam" + }, + { + "postId": 70, + "id": 348, + "name": "molestias natus autem quae sint qui", + "email": "Bradley.Lang@marilyne.tv", + "body": "perferendis dignissimos soluta ut provident sit et\ndelectus ratione ad sapiente qui excepturi error qui quo\nquo illo commodi\nrerum maxime voluptas voluptatem" + }, + { + "postId": 70, + "id": 349, + "name": "odio maiores a porro dolorum ut pariatur inventore", + "email": "Loren@aric.biz", + "body": "dicta impedit non\net laborum laudantium qui eaque et beatae suscipit\nsequi magnam rem dolorem non quia vel adipisci\ncorrupti officiis laudantium impedit" + }, + { + "postId": 70, + "id": 350, + "name": "eius quia pariatur", + "email": "Arjun@natalie.ca", + "body": "eaque rerum tempore distinctio\nconsequatur fugiat veniam et incidunt ut ut et\nconsequatur blanditiis magnam\ndoloremque voluptate ut architecto facere in dolorem et aut" + }, + { + "postId": 71, + "id": 351, + "name": "quia ex perspiciatis sunt voluptatem quidem", + "email": "Solon.Goldner@judah.org", + "body": "quo nisi impedit velit repellendus esse itaque ut saepe\nvoluptatibus occaecati ab eaque dolores\nmaxime alias velit ducimus placeat sit laudantium quia\ncorrupti doloremque ut" + }, + { + "postId": 71, + "id": 352, + "name": "sit ipsam voluptatem velit", + "email": "Nina@osbaldo.name", + "body": "dolorem eius voluptatem vitae aliquid unde labore est\nmolestiae labore dolorem beatae voluptatem soluta eum eos dolore\net ea quasi aut doloribus sint\nvel suscipit tempora delectus soluta" + }, + { + "postId": 71, + "id": 353, + "name": "consequatur reprehenderit similique vitae dolor debitis", + "email": "Madaline@marlin.org", + "body": "nemo aut laborum expedita nisi sed illum\nab asperiores provident\na sunt recusandae ut rerum itaque est voluptatibus nihil\nesse ipsum et repellendus nobis rerum voluptas et" + }, + { + "postId": 71, + "id": 354, + "name": "eligendi tempora eum deserunt", + "email": "Mike.Kozey@gladyce.us", + "body": "delectus est consequatur\nat excepturi asperiores dolor nesciunt ad\nid non aut ad ut\nnon et voluptatem" + }, + { + "postId": 71, + "id": 355, + "name": "reiciendis ad ea", + "email": "Orval.Treutel@arnold.me", + "body": "vel cumque labore vitae quisquam magnam sequi ut\nmolestiae dolores vel minus aut\nquas repellat nostrum fugit molestiae veritatis sequi\nvel quidem in molestiae id doloribus sed" + }, + { + "postId": 72, + "id": 356, + "name": "qui vel id qui est", + "email": "Trent@samir.net", + "body": "fugiat dolore voluptas tempore\naspernatur quibusdam rem iste sit fugiat nesciunt consequatur\ndolor sed odit similique minima corporis quae in adipisci\nimpedit dolores et cupiditate accusantium perferendis dignissimos error" + }, + { + "postId": 72, + "id": 357, + "name": "consectetur totam fugit et occaecati minima aliquid hic adipisci", + "email": "Ashleigh@annette.ca", + "body": "et eos est quis quia molestiae est\nquasi est quos omnis\naut et sit consectetur ex molestiae\nest sed accusamus asperiores" + }, + { + "postId": 72, + "id": 358, + "name": "accusantium natus ex et consequuntur neque", + "email": "Douglas@anabel.org", + "body": "unde ad id nemo ipsam dolorem autem quaerat\nperspiciatis voluptas corrupti laborum rerum est architecto\neius quos aut et voluptate voluptatem atque necessitatibus\nvoluptate fugiat aut iusto et atque" + }, + { + "postId": 72, + "id": 359, + "name": "esse quia quidem quisquam consequatur nisi deleniti", + "email": "Lowell@mossie.com", + "body": "et explicabo voluptatem ut est nihil culpa et\nveritatis repellendus ipsum velit qui eligendi maxime voluptatem est\ndicta rerum et et nemo quia\neveniet aspernatur nostrum molestiae mollitia ut dolores rem fugiat" + }, + { + "postId": 72, + "id": 360, + "name": "rerum tempore facilis ut quod sit", + "email": "Jacquelyn@kristian.net", + "body": "sit et aut recusandae\ncorrupti nisi vero eius nulla voluptates\nvoluptatem placeat est commodi impedit odio omnis\nsimilique debitis et in molestiae omnis sed non magni" + }, + { + "postId": 73, + "id": 361, + "name": "voluptates qui et corporis", + "email": "Antwon@domenico.me", + "body": "cum ad porro fuga sequi dolores\nipsa error magni itaque labore accusamus\ncorporis odit consequatur quis debitis\ncum et voluptas facilis incidunt ut itaque dolores error" + }, + { + "postId": 73, + "id": 362, + "name": "quia qui quia qui", + "email": "Kenyon@retha.me", + "body": "excepturi omnis occaecati officiis enim saepe id\nnon quo et dignissimos voluptates ipsum\nmolestias facere dolorem qui iure similique corrupti\nneque ducimus sit alias dolor earum autem doloribus consequatur" + }, + { + "postId": 73, + "id": 363, + "name": "nihil consequatur quibusdam", + "email": "Ben@elouise.net", + "body": "est magni totam est\net enim nam voluptas veritatis est\nsint doloremque incidunt et cum a\net eligendi nobis ratione delectus" + }, + { + "postId": 73, + "id": 364, + "name": "vel architecto assumenda et maiores", + "email": "Madisen.Hauck@barney.co.uk", + "body": "architecto quo enim ad et reprehenderit\nlaboriosam quia commodi officia iusto\ndolorem totam consequuntur cupiditate\nveritatis voluptates aspernatur earum saepe et sed consequatur" + }, + { + "postId": 73, + "id": 365, + "name": "aliquam officiis omnis", + "email": "Dock.Parker@roy.biz", + "body": "modi sed aut quidem quisquam optio est\naut facilis sit quia quis facere quod\nfugiat recusandae ex et quisquam ipsum sed sit\nexercitationem quia recusandae dolorem quasi iusto ipsa qui et" + }, + { + "postId": 74, + "id": 366, + "name": "aperiam ut deserunt minus quo dicta nisi", + "email": "Pablo.Ritchie@tyrique.co.uk", + "body": "explicabo perspiciatis quae sit qui nulla incidunt facilis\nrepudiandae perspiciatis voluptate expedita sunt consectetur quasi\nid occaecati nesciunt dolorem aliquid perspiciatis repellat inventore esse\nut possimus exercitationem facere modi" + }, + { + "postId": 74, + "id": 367, + "name": "praesentium eos quam eius optio eveniet", + "email": "Sebastian_Gaylord@freda.org", + "body": "nostrum modi et et dolores maxime facere\nalias doloribus eaque expedita et similique voluptatum magnam est\nomnis eos voluptatem\net unde fugit voluptatem asperiores" + }, + { + "postId": 74, + "id": 368, + "name": "fugiat aliquid sint", + "email": "Lazaro@nadia.ca", + "body": "est dolor eveniet\nest minus eveniet recusandae\niure quo aut eos ut sed ipsa\nharum earum aut nesciunt non dolores" + }, + { + "postId": 74, + "id": 369, + "name": "qui incidunt vel iusto eligendi amet quia qui", + "email": "Jessy.Boyle@vernice.biz", + "body": "qui fugit accusamus\net quo minus cumque hic adipisci\nodio blanditiis omnis et est\narchitecto et facilis inventore quasi provident quaerat ex rem" + }, + { + "postId": 74, + "id": 370, + "name": "libero vero voluptatum sed facilis quos aut reprehenderit ad", + "email": "Mitchel@hal.co.uk", + "body": "beatae hic est et deserunt eius\ncorrupti quam ut commodi sit modi est corporis animi\nharum ut est\naperiam non fugit excepturi quo tenetur totam" + }, + { + "postId": 75, + "id": 371, + "name": "ut quia sequi sed eius voluptas", + "email": "Lindsay@kiley.name", + "body": "est dicta totam architecto et minus id aut non\nut et fugit eaque culpa modi repellendus\naliquid qui veritatis doloribus aut consequatur voluptas sequi accusantium\nvoluptas occaecati saepe reprehenderit ut" + }, + { + "postId": 75, + "id": 372, + "name": "qui cumque eos consequatur fuga ut", + "email": "Erika.Murazik@jorge.me", + "body": "aut illum est asperiores\nrerum laboriosam quis sit dolores magni minima fuga atque\nomnis at et quibusdam earum rem\nearum distinctio autem et enim dolore eos" + }, + { + "postId": 75, + "id": 373, + "name": "nemo voluptatum quo qui atque", + "email": "Olin@edmund.ca", + "body": "iure aliquid qui sit\nexcepturi dolorem rerum possimus suscipit atque nisi\nlabore ut aut nihil voluptatum ut aliquid praesentium\nassumenda tempore dolor velit ratione et corrupti" + }, + { + "postId": 75, + "id": 374, + "name": "quam exercitationem alias totam", + "email": "Lacey@novella.biz", + "body": "eligendi et consequuntur dolor nihil quaerat doloremque ut\ndignissimos sunt veniam non ratione esse\ndebitis omnis similique maxime dolores tempora laborum rerum adipisci\nreiciendis explicabo error quidem quo necessitatibus voluptatibus vitae ipsum" + }, + { + "postId": 75, + "id": 375, + "name": "similique doloribus odit quas magnam omnis dolorem dolore et", + "email": "Sister@miller.net", + "body": "non ea sed reprehenderit reiciendis eaque et neque adipisci\nipsa architecto deserunt ratione nesciunt tempore similique occaecati non\nhic vitae sit neque\nrerum quod dolorem ratione esse aperiam necessitatibus" + }, + { + "postId": 76, + "id": 376, + "name": "dolorem qui architecto provident", + "email": "Raphaelle@miller.com", + "body": "sint qui aut aspernatur necessitatibus\nlaboriosam autem occaecati nostrum non\nofficiis consequuntur odit\net itaque quam placeat aut molestiae saepe veniam provident" + }, + { + "postId": 76, + "id": 377, + "name": "nemo hic sapiente placeat quidem omnis", + "email": "Jaren.Schiller@augusta.org", + "body": "sint fugit et\nid et saepe non molestiae sit earum doloremque\ndolorem nemo earum dignissimos ipsa soluta deleniti quos\nquis numquam ducimus sed corporis dolores sed quisquam suscipit" + }, + { + "postId": 76, + "id": 378, + "name": "vitae aut perspiciatis quia enim voluptas", + "email": "Nikko_Reynolds@harry.me", + "body": "est molestiae non fugiat voluptatem quo porro\naut praesentium ipsam aspernatur perferendis fuga\nofficia sit ut\naspernatur rerum est" + }, + { + "postId": 76, + "id": 379, + "name": "est qui quos exercitationem", + "email": "Afton.Medhurst@mina.info", + "body": "laboriosam quia animi ut\nquasi aut enim sequi numquam similique fugiat voluptatum non\nsed velit quod nisi id quidem\nmagni in eveniet hic" + }, + { + "postId": 76, + "id": 380, + "name": "similique fugiat tenetur ea incidunt numquam", + "email": "Wilson.Nikolaus@fredrick.org", + "body": "voluptatum quis ipsa voluptatem saepe est\nillum error repellat eaque dolor quae qui\neum rerum sunt quam illo\nvoluptates fuga possimus nemo nihil distinctio" + }, + { + "postId": 77, + "id": 381, + "name": "sint porro optio voluptatem", + "email": "Tomasa@lee.us", + "body": "consequatur possimus sit itaque distinctio fugit aut quod\nexplicabo exercitationem voluptas labore rerum\nporro ut in voluptas maiores tempora accusantium\nvoluptatum et sapiente sit quas quis et veniam" + }, + { + "postId": 77, + "id": 382, + "name": "eius itaque ut ipsa quia quis labore", + "email": "Ally_Kassulke@rashad.ca", + "body": "eaque eius delectus molestias suscipit nulla quisquam\ntotam vel quos et autem sed\neligendi et pariatur earum molestias magnam autem\nplaceat culpa est et qui commodi illo et" + }, + { + "postId": 77, + "id": 383, + "name": "provident voluptas perferendis quibusdam libero", + "email": "Reagan_Ziemann@ross.io", + "body": "qui quaerat id repellendus aut qui\nmaiores quidem consequatur dignissimos deleniti deserunt eveniet libero a\nrepellat ducimus quia aut dignissimos numquam molestiae\nconsequatur sit impedit nostrum et sunt iure" + }, + { + "postId": 77, + "id": 384, + "name": "et et voluptas et eligendi distinctio accusantium temporibus enim", + "email": "Angelita@sally.org", + "body": "blanditiis dolor sint nulla cum quidem aliquid voluptatem\nperferendis dolor consequatur voluptas et ut quisquam tempora tenetur\nmaxime minus animi qui id\neum accusantium et optio et blanditiis maxime" + }, + { + "postId": 77, + "id": 385, + "name": "qui voluptates molestias necessitatibus eos odio quo minima", + "email": "Lonzo@lorena.org", + "body": "sit fugiat est autem quia ipsam error ab\nvoluptatem sed ab labore molestiae qui debitis exercitationem\nnon et sunt officia illo possimus iste tenetur est\ndolores voluptas ad aspernatur nihil" + }, + { + "postId": 78, + "id": 386, + "name": "temporibus minus debitis deleniti repellat unde eveniet", + "email": "Alexandre@derrick.co.uk", + "body": "et dicta dolores sit\nrepudiandae id harum temporibus\nvoluptas quia blanditiis numquam a enim quae\nquisquam assumenda nam doloribus vel temporibus distinctio eveniet dolores" + }, + { + "postId": 78, + "id": 387, + "name": "magnam nihil delectus dolor natus ab ea et", + "email": "Judd@lucinda.ca", + "body": "qui recusandae veniam sed voluptatem ullam facilis consequatur\nnumquam ut quod aut et\nnon alias ex quam aut quasi ipsum praesentium\nut aspernatur sit" + }, + { + "postId": 78, + "id": 388, + "name": "laudantium quibusdam blanditiis pariatur non vero deleniti a perferendis", + "email": "Eleanora@karson.net", + "body": "facilis et totam\nvoluptatibus est optio cum\nfacilis qui aut blanditiis rerum voluptatem accusamus\net omnis quasi sint" + }, + { + "postId": 78, + "id": 389, + "name": "excepturi nam cum molestiae et totam voluptatem nisi", + "email": "Enrico_Feil@liana.biz", + "body": "dolore nihil perferendis\ndolor hic repudiandae iste\ndoloribus labore quaerat et molestiae dolores sit excepturi sint\net eum et aut" + }, + { + "postId": 78, + "id": 390, + "name": "temporibus aut et", + "email": "Beverly@perry.org", + "body": "dolor ratione ab repellendus aut quia reiciendis sed\nest alias ex\nodio voluptatem velit odit tempora nihil optio aperiam blanditiis\nlabore porro id velit dolor veritatis" + }, + { + "postId": 79, + "id": 391, + "name": "sed ratione nesciunt odit expedita", + "email": "Corene_Mante@rory.com", + "body": "aut repellat tenetur delectus eaque est nihil consequatur quae\ndeleniti assumenda voluptates sit sit cupiditate maiores\nautem suscipit sint tenetur dolor tempore\ndolorem dolorum alias adipisci" + }, + { + "postId": 79, + "id": 392, + "name": "rerum officiis qui quaerat omnis dolorem iure est repudiandae", + "email": "Emily_Flatley@ephraim.name", + "body": "aut aut ea ut repudiandae ea assumenda laboriosam\nsunt qui laboriosam dicta omnis sit corporis\nvoluptas eos amet quam quisquam officiis facilis laborum\nvoluptatibus accusantium ab aliquid sed id doloremque" + }, + { + "postId": 79, + "id": 393, + "name": "illo quis nostrum accusantium architecto et aliquam ratione", + "email": "Donna@frederik.com", + "body": "et quia explicabo\nut hic commodi quas provident aliquam nihil\nvitae in voluptatem commodi\nvero velit optio omnis accusamus corrupti voluptatem" + }, + { + "postId": 79, + "id": 394, + "name": "reprehenderit eos voluptatem ut", + "email": "Kyleigh@ruben.org", + "body": "voluptatem quisquam pariatur voluptatum qui quaerat\net minus ea aliquam ullam dolorem consequatur\nratione at ad nemo aperiam excepturi deleniti\nqui numquam quis hic nostrum tempora quidem" + }, + { + "postId": 79, + "id": 395, + "name": "excepturi esse laborum ut qui culpa", + "email": "Noemy.Hammes@lisette.net", + "body": "esse vel reiciendis nobis inventore vero est\nfugit inventore ea quo consequatur aut\nautem deserunt ratione et repellendus nihil quam\nquidem iure est nihil mollitia" + }, + { + "postId": 80, + "id": 396, + "name": "qui eos vitae possimus reprehenderit voluptatem voluptatem repellendus", + "email": "Margarett_Jenkins@harley.us", + "body": "perferendis veritatis saepe voluptatem\neum voluptas quis\nsed occaecati nostrum\nfugit animi omnis ratione molestias" + }, + { + "postId": 80, + "id": 397, + "name": "quasi exercitationem molestias dolore non non sed est", + "email": "Dexter.Pacocha@lauren.biz", + "body": "ut nisi sunt perspiciatis qui doloribus quas\nvelit molestiae atque corrupti corporis voluptatem\nvel ratione aperiam tempore est eos\nquia a voluptas" + }, + { + "postId": 80, + "id": 398, + "name": "labore consequuntur vel qui", + "email": "Gennaro@jaunita.co.uk", + "body": "libero atque accusamus blanditiis minima eveniet corporis est aliquid\ndolores asperiores neque quibusdam quaerat error esse non\nqui et adipisci\nmagni illo hic qui qui dignissimos earum" + }, + { + "postId": 80, + "id": 399, + "name": "sunt ut eos", + "email": "Jaycee@aimee.us", + "body": "corrupti ut et eveniet culpa\nveritatis eos sequi fugiat commodi consequuntur\nipsa totam voluptatem perferendis ducimus aut exercitationem magni\neos mollitia quia" + }, + { + "postId": 80, + "id": 400, + "name": "quia aut consequatur sunt iste aliquam impedit sit", + "email": "Brennon@carmela.tv", + "body": "natus iure velit impedit sed officiis sint\nmolestiae non beatae\nillo consequatur minima\nsed ratione est tenetur" + }, + { + "postId": 81, + "id": 401, + "name": "cum voluptate sint voluptas veritatis", + "email": "Vella.Mayer@colten.net", + "body": "sit delectus recusandae qui\net cupiditate sed ipsum culpa et fugiat ab\nillo dignissimos quo est repellat dolorum neque\nvoluptates sed sapiente ab aut rerum enim sint voluptatum" + }, + { + "postId": 81, + "id": 402, + "name": "ut eos mollitia eum eius", + "email": "Caleb_Dach@kathleen.us", + "body": "et nisi fugit totam\nmaiores voluptatibus quis ipsa sunt debitis assumenda\nullam non quasi numquam ut dolores modi recusandae\nut molestias magni est voluptas quibusdam corporis eius" + }, + { + "postId": 81, + "id": 403, + "name": "architecto voluptatum eos blanditiis aliquam debitis beatae nesciunt dolorum", + "email": "Patience_Bahringer@dameon.biz", + "body": "et a et perspiciatis\nautem expedita maiores dignissimos labore minus molestiae enim\net ipsam ea et\nperspiciatis veritatis debitis maxime" + }, + { + "postId": 81, + "id": 404, + "name": "officia qui ut explicabo eos fugit", + "email": "Destinee.Simonis@jose.tv", + "body": "modi est et eveniet facilis explicabo\nvoluptatem saepe quo et sint quas quia corporis\npariatur voluptatibus est iste fugiat delectus animi rerum\ndoloribus est enim" + }, + { + "postId": 81, + "id": 405, + "name": "incidunt commodi voluptatem maiores asperiores blanditiis omnis ratione", + "email": "Keshaun@brown.biz", + "body": "aut aut sit cupiditate maxime praesentium occaecati cumque\nvero sint sit aliquam porro quo consequuntur ut\nnumquam qui maxime voluptas est consequatur ullam\ntenetur commodi qui consectetur distinctio eligendi atque" + }, + { + "postId": 82, + "id": 406, + "name": "sint eaque rerum voluptas fugiat quia qui", + "email": "Merle.Schultz@marcel.org", + "body": "dicta in quam tenetur\nsed molestiae a sit est aut quia autem aut\nnam voluptatem reiciendis corporis voluptatem\nsapiente est id quia explicabo enim tempora asperiores" + }, + { + "postId": 82, + "id": 407, + "name": "eius tempora sint reprehenderit", + "email": "Malvina_Fay@louie.name", + "body": "totam ad quia non vero dolor laudantium sed temporibus\nquia aperiam corrupti sint accusantium eligendi\naliquam rerum voluptatem delectus numquam nihil\nsoluta qui sequi nisi voluptatum eaque voluptas animi ipsam" + }, + { + "postId": 82, + "id": 408, + "name": "non excepturi enim est sapiente numquam repudiandae illo", + "email": "Domenick_Douglas@gabe.us", + "body": "suscipit quidem fugiat consequatur\nquo sequi nesciunt\naliquam ratione possimus\nvoluptatem sit quia repellendus libero excepturi ut temporibus" + }, + { + "postId": 82, + "id": 409, + "name": "dicta dolor voluptate vel praesentium", + "email": "Isaac@allene.net", + "body": "provident illo quis dolor distinctio laborum eius enim\nsuscipit quia error enim eos consequuntur\nest incidunt adipisci beatae tenetur excepturi in labore commodi\nfugiat omnis in et at nam accusamus et" + }, + { + "postId": 82, + "id": 410, + "name": "et dolore hic a cupiditate beatae natus iusto soluta", + "email": "Marianna.Pacocha@george.net", + "body": "in consequatur corporis qui a et magni eum illum\ncorrupti veniam debitis ab iure harum\nenim ut assumenda cum adipisci veritatis et veniam\nrem eius expedita quos corrupti incidunt" + }, + { + "postId": 83, + "id": 411, + "name": "hic rem eligendi tenetur ipsum dolore maxime eum", + "email": "Sister_Barton@lela.com", + "body": "nam voluptatem ex aut voluptatem mollitia sit sapiente\nqui hic ut\nqui natus in iste et magnam dolores et fugit\nea sint ut minima quas eum nobis at reprehenderit" + }, + { + "postId": 83, + "id": 412, + "name": "quaerat et quia accusamus provident earum cumque", + "email": "Autumn.Lebsack@kasandra.ca", + "body": "veniam non culpa aut voluptas rem eum officiis\nmollitia placeat eos cum\nconsequatur eos commodi dolorem\nanimi maiores qui" + }, + { + "postId": 83, + "id": 413, + "name": "atque porro quo voluptas", + "email": "Irma.OKon@arden.me", + "body": "consequatur harum est omnis\nqui recusandae qui voluptatem et distinctio sint eaque\ndolores quo dolorem asperiores\naperiam non quis asperiores aut praesentium" + }, + { + "postId": 83, + "id": 414, + "name": "ut qui voluptatem hic maxime", + "email": "Alaina_Hammes@carter.info", + "body": "molestias debitis magni illo sint officiis ut quia\nsed tenetur dolorem soluta\nvoluptatem fugiat voluptas molestiae magnam fuga\nsimilique enim illum voluptas aspernatur officia" + }, + { + "postId": 83, + "id": 415, + "name": "rerum consequatur ut et voluptate harum amet accusantium est", + "email": "Alia@addison.org", + "body": "iure vitae accusamus tenetur autem ipsa deleniti\nsunt laudantium ut beatae repellendus non eos\nut consequuntur repudiandae ducimus enim\nreiciendis rem explicabo magni dolore" + }, + { + "postId": 84, + "id": 416, + "name": "neque nemo consequatur ea fugit aut esse suscipit dolore", + "email": "Aurelie_McKenzie@providenci.biz", + "body": "enim velit consequatur excepturi corporis voluptatem nostrum\nnesciunt alias perspiciatis corporis\nneque at eius porro sapiente ratione maiores natus\nfacere molestiae vel explicabo voluptas unde" + }, + { + "postId": 84, + "id": 417, + "name": "quia reiciendis nobis minima quia et saepe", + "email": "May_Steuber@virgil.net", + "body": "et vitae consectetur ut voluptatem\net et eveniet sit\nincidunt tenetur voluptatem\nprovident occaecati exercitationem neque placeat" + }, + { + "postId": 84, + "id": 418, + "name": "nesciunt voluptates amet sint et delectus et dolore culpa", + "email": "Tessie@emilie.co.uk", + "body": "animi est eveniet officiis qui\naperiam dolore occaecati enim aut reiciendis\nanimi ad sint labore blanditiis adipisci voluptatibus eius error\nomnis rerum facere architecto occaecati rerum" + }, + { + "postId": 84, + "id": 419, + "name": "omnis voluptate dolorem similique totam", + "email": "Priscilla@colten.org", + "body": "cum neque recusandae occaecati aliquam reprehenderit ullam saepe veniam\nquasi ea provident tenetur architecto ad\ncupiditate molestiae mollitia molestias debitis eveniet doloremque voluptatem aut\ndolore consequatur nihil facere et" + }, + { + "postId": 84, + "id": 420, + "name": "aut recusandae a sit voluptas explicabo nam et", + "email": "Aylin@abigale.me", + "body": "voluptas cum eum minima rem\ndolorem et nemo repellendus voluptatem sit\nrepudiandae nulla qui recusandae nobis\nblanditiis perspiciatis dolor ipsam reprehenderit odio" + }, + { + "postId": 85, + "id": 421, + "name": "non eligendi ipsam provident", + "email": "Holden@kenny.io", + "body": "voluptate libero corrupti facere totam eaque consequatur nemo\nenim aliquid exercitationem nulla dignissimos illo\nest amet non iure\namet sed dolore non ipsam magni" + }, + { + "postId": 85, + "id": 422, + "name": "sit molestiae corporis", + "email": "Guillermo_Dare@dorothea.tv", + "body": "ducimus ut ut fugiat nesciunt labore\ndeleniti non et aut voluptatum quidem consectetur\nincidunt voluptas accusantium\nquo fuga eaque quisquam et et sapiente aut" + }, + { + "postId": 85, + "id": 423, + "name": "assumenda iure a", + "email": "Oscar@pearline.com", + "body": "rerum laborum voluptas ipsa enim praesentium\nquisquam aliquid perspiciatis eveniet id est est facilis\natque aut facere\nprovident reiciendis libero atque est" + }, + { + "postId": 85, + "id": 424, + "name": "molestiae dolores itaque dicta earum eligendi dolores", + "email": "Jonathon_Feest@maxime.io", + "body": "ducimus hic ea velit\ndolorum soluta voluptas similique rerum\ndolorum sint maxime et vel\nvoluptatum nesciunt et id consequatur earum sed" + }, + { + "postId": 85, + "id": 425, + "name": "cumque expedita consequatur qui", + "email": "Micah_Wolf@lennie.co.uk", + "body": "labore necessitatibus et eum quas id ut\ndoloribus aspernatur nostrum sapiente quo aut quia\neos rerum voluptatem\nnumquam minima soluta velit recusandae ut" + }, + { + "postId": 86, + "id": 426, + "name": "deleniti tempora non quia et aut", + "email": "Shany@daisha.biz", + "body": "reiciendis consequatur sunt atque quisquam ut sed iure\nconsequatur laboriosam dicta odio\nquas cumque iure blanditiis ad sed ullam dignissimos\nsunt et exercitationem saepe" + }, + { + "postId": 86, + "id": 427, + "name": "delectus illum minus odit", + "email": "Drew_Lemke@alexis.net", + "body": "in laborum et distinctio nobis maxime\nmaxime id commodi eaque enim amet qui autem\ndebitis et porro eum dicta sapiente iusto nulla sunt\nvoluptate excepturi sint dolorem voluptatem quae explicabo id" + }, + { + "postId": 86, + "id": 428, + "name": "voluptas dolores dolor nisi voluptatem ratione rerum", + "email": "Karina.Donnelly@liam.com", + "body": "excepturi quos omnis aliquam voluptatem iste\nsit unde ab quam ipsa delectus culpa rerum\ncum delectus impedit ut qui modi\nasperiores qui sapiente quia facilis in iure" + }, + { + "postId": 86, + "id": 429, + "name": "sed omnis dolore aperiam", + "email": "Ahmed_Runolfsson@claire.name", + "body": "ab voluptatem nobis unde\ndoloribus aut fugiat\nconsequuntur laboriosam minima inventore sint quis\ndelectus hic et enim sit optio consequuntur" + }, + { + "postId": 86, + "id": 430, + "name": "sint ullam alias et at et", + "email": "Marilou_Halvorson@kane.io", + "body": "debitis ut maiores ut harum sed voluptas\nquae amet eligendi quo quidem odit atque quisquam animi\nut autem est corporis et\nsed tempora facere corrupti magnam" + }, + { + "postId": 87, + "id": 431, + "name": "velit incidunt ut accusantium odit maiores quaerat", + "email": "Bernie.Schoen@seamus.co.uk", + "body": "voluptas minus fugiat vel\nest quos soluta et veniam quia incidunt unde ut\nlaborum odio in eligendi distinctio odit repellat\nnesciunt consequatur blanditiis cupiditate consequatur at et" + }, + { + "postId": 87, + "id": 432, + "name": "quod quia nihil nisi perferendis laborum blanditiis tempora eos", + "email": "Joesph@darryl.net", + "body": "quam et et harum\nplaceat minus neque quae magni inventore saepe deleniti quisquam\nsuscipit dolorum error aliquid dolores\ndignissimos dolorem autem natus iste molestiae est id impedit" + }, + { + "postId": 87, + "id": 433, + "name": "qui ea voluptatem reiciendis enim enim nisi aut", + "email": "Timmothy.Corwin@augustus.co.uk", + "body": "voluptatem minus asperiores quasi\nperspiciatis et aut blanditiis illo deserunt molestiae ab aperiam\nex minima sed omnis at\net repellat aut incidunt" + }, + { + "postId": 87, + "id": 434, + "name": "doloremque eligendi quas voluptatem non quos ex", + "email": "Julien_OHara@vance.io", + "body": "ex eum at culpa quam aliquam\ncupiditate et id dolorem sint quasi et quos et\nomnis et qui minus est quisquam non qui rerum\nquas molestiae tempore veniam" + }, + { + "postId": 87, + "id": 435, + "name": "id voluptatum nulla maiores ipsa eos", + "email": "Susan.Bartell@euna.org", + "body": "reprehenderit molestias sit nemo quas culpa dolorem exercitationem\neos est voluptatem dolores est fugiat dolorem\neos aut quia et amet et beatae harum vitae\nofficia quia animi dicta magnam accusantium" + }, + { + "postId": 88, + "id": 436, + "name": "ea illo ab et maiores eaque non nobis", + "email": "Selena.Quigley@johan.co.uk", + "body": "sit non facilis commodi sapiente officiis aut facere libero\nsed voluptatum eligendi veniam velit explicabo\nsint laborum sunt reprehenderit dolore id nobis accusamus\nfugit voluptatem magni dolor qui dolores ipsa" + }, + { + "postId": 88, + "id": 437, + "name": "magni asperiores in cupiditate", + "email": "Clifton_Boehm@jacynthe.io", + "body": "suscipit ab qui eos et commodi\nquas ad maiores repellat laboriosam voluptatem exercitationem\nquibusdam ullam ratione nulla\nquia iste error dolorem consequatur beatae temporibus fugit" + }, + { + "postId": 88, + "id": 438, + "name": "ullam autem aliquam", + "email": "Lizzie_Bartell@diamond.net", + "body": "voluptas aspernatur eveniet\nquod id numquam dolores quia perspiciatis eum\net delectus quia occaecati adipisci nihil velit accusamus esse\nminus aspernatur repudiandae" + }, + { + "postId": 88, + "id": 439, + "name": "voluptates quasi minus dolorem itaque nemo", + "email": "Yasmeen@golda.ca", + "body": "cupiditate laborum sit reprehenderit ratione est ad\ncorporis rem pariatur enim et omnis dicta\nnobis molestias quo corporis et nihil\nsed et impedit aut quisquam natus expedita voluptate at" + }, + { + "postId": 88, + "id": 440, + "name": "adipisci sapiente libero beatae quas eveniet", + "email": "Adolf.Russel@clark.ca", + "body": "ut nam ut asperiores quis\nexercitationem aspernatur eligendi autem repellendus\nest repudiandae quisquam rerum ad explicabo suscipit deserunt eius\nalias aliquid eos pariatur rerum magnam provident iusto" + }, + { + "postId": 89, + "id": 441, + "name": "nisi qui voluptates recusandae voluptas assumenda et", + "email": "Elian@matilda.me", + "body": "illum qui quis optio\nquasi eius dolores et non numquam et\nqui necessitatibus itaque magnam mollitia earum et\nnisi voluptate eum accusamus ea" + }, + { + "postId": 89, + "id": 442, + "name": "sed molestias sit voluptatibus sit aut alias sunt inventore", + "email": "Salma@francis.net", + "body": "velit ut incidunt accusantium\nsuscipit animi officia iusto\nnemo omnis sunt nobis repellendus corporis\nconsequatur distinctio dolorem" + }, + { + "postId": 89, + "id": 443, + "name": "illum pariatur aliquam esse nisi voluptas quisquam ea", + "email": "Orlando_Dickinson@vern.org", + "body": "reiciendis et distinctio qui totam non aperiam voluptas\nveniam in dolorem pariatur itaque\nvoluptas adipisci velit\nqui voluptates voluptas ut ullam veritatis repudiandae" + }, + { + "postId": 89, + "id": 444, + "name": "incidunt aut qui quis est sit corporis pariatur qui", + "email": "Elda@orval.name", + "body": "eligendi labore aut non modi vel facere qui\naccusamus qui maxime aperiam\ntotam et non ut repudiandae eum corrupti pariatur quia\nnecessitatibus et adipisci ipsa consequuntur enim et nihil vero" + }, + { + "postId": 89, + "id": 445, + "name": "temporibus adipisci eveniet libero ullam", + "email": "Dennis@karley.net", + "body": "est consequuntur cumque\nquo dolorem at ut dolores\nconsequatur quia voluptates reiciendis\nvel rerum id et" + }, + { + "postId": 90, + "id": 446, + "name": "dicta excepturi aut est dolor ab dolores rerum", + "email": "Jedediah@mason.io", + "body": "cum fugit earum vel et nulla et voluptatem\net ipsam aut\net nihil officia nemo eveniet accusamus\nnulla aut impedit veritatis praesentium" + }, + { + "postId": 90, + "id": 447, + "name": "molestiae qui quod quo", + "email": "Maryam@jack.name", + "body": "rerum omnis voluptatem harum aliquid voluptas accusamus\neius dicta animi\nodio non quidem voluptas tenetur\nnostrum deserunt laudantium culpa dolorum" + }, + { + "postId": 90, + "id": 448, + "name": "pariatur consequatur sit commodi aliquam", + "email": "Rick@carlos.tv", + "body": "odio maxime beatae ab labore rerum\nalias ipsa nam est nostrum\net debitis aut\nab molestias assumenda eaque repudiandae" + }, + { + "postId": 90, + "id": 449, + "name": "sunt quia soluta quae sit deleniti dolor ullam veniam", + "email": "Vallie@jerrod.net", + "body": "dolor at accusantium eveniet\nin voluptatem quam et fugiat et quasi dolores\nsunt eligendi voluptatum id voluptas vitae\nquibusdam iure eum perspiciatis" + }, + { + "postId": 90, + "id": 450, + "name": "dolorem corporis facilis et", + "email": "Adolph.Hayes@isobel.biz", + "body": "et provident quo necessitatibus harum excepturi\nsed est ut sed est doloremque labore quod\nquia optio explicabo adipisci magnam doloribus\nveritatis illo aut est inventore" + }, + { + "postId": 91, + "id": 451, + "name": "maiores ut dolores quo sapiente nisi", + "email": "Duane_Dach@demario.us", + "body": "dolor veritatis ipsum accusamus quae voluptates sint voluptatum et\nharum saepe dolorem enim\nexpedita placeat qui quidem aut et et est\nminus odit qui possimus qui saepe" + }, + { + "postId": 91, + "id": 452, + "name": "quia excepturi in harum repellat consequuntur est vel qui", + "email": "General@schuyler.org", + "body": "ratione sequi sed\nearum nam aut sunt\nquam cum qui\nsimilique consequatur et est" + }, + { + "postId": 91, + "id": 453, + "name": "doloremque ut est eaque", + "email": "Stephania_Stanton@demond.biz", + "body": "quidem nisi reprehenderit eligendi fugiat et et\nsapiente adipisci natus nulla similique et est\nesse ea accusantium sunt\ndeleniti molestiae perferendis quam animi similique ut" + }, + { + "postId": 91, + "id": 454, + "name": "magni quos voluptatibus earum et inventore suscipit", + "email": "Reinhold.Schiller@kelly.info", + "body": "consequatur accusamus maiores dolorem impedit repellendus voluptas rerum eum\nquam quia error voluptatem et\ndignissimos fugit qui\net facilis necessitatibus dignissimos consequatur iusto nihil possimus" + }, + { + "postId": 91, + "id": 455, + "name": "assumenda qui et aspernatur", + "email": "Royce@jaiden.co.uk", + "body": "animi qui nostrum rerum velit\nvoluptates sit in laborum dolorum omnis ut omnis\nea optio quia necessitatibus delectus molestias sapiente perferendis\ndolores vel excepturi expedita" + }, + { + "postId": 92, + "id": 456, + "name": "quod voluptatem qui qui sit sed maiores fugit", + "email": "Cassie@diana.org", + "body": "sunt ipsam illum consequuntur\nquasi enim possimus unde qui beatae quo eligendi\nvel quia asperiores est quae voluptate\naperiam et iste perspiciatis" + }, + { + "postId": 92, + "id": 457, + "name": "ipsa animi saepe veritatis voluptatibus ad amet id aut", + "email": "Jena.OKeefe@adonis.net", + "body": "incidunt itaque enim pariatur quibusdam voluptatibus blanditiis sint\nerror laborum voluptas sed officiis molestiae nostrum\ntemporibus culpa aliquam sit\nconsectetur dolores tempore id accusantium dignissimos vel" + }, + { + "postId": 92, + "id": 458, + "name": "fugiat consectetur saepe dicta", + "email": "Magdalen@holly.io", + "body": "eos hic deserunt necessitatibus sed id ut esse nam\nhic eveniet vitae corrupti mollitia doloremque sit ratione\ndeleniti perspiciatis numquam est sapiente quaerat\nest est sit" + }, + { + "postId": 92, + "id": 459, + "name": "nesciunt numquam alias doloremque minus ipsam optio", + "email": "Nyah@otho.com", + "body": "veniam natus aut vero et aliquam doloremque\nalias cupiditate non est\ntempore et non vel error placeat est quisquam ea\nnon dolore aliquid non fuga expedita dicta ut quos" + }, + { + "postId": 92, + "id": 460, + "name": "eum fugit omnis optio", + "email": "Kara_Stokes@connie.co.uk", + "body": "qui qui deserunt expedita at\nprovident sequi veritatis sit qui nam tempora mollitia ratione\ncorporis vitae rerum pariatur unde deleniti ut eos ad\naut non quae nisi saepe" + }, + { + "postId": 93, + "id": 461, + "name": "perferendis nobis praesentium accusantium culpa et et", + "email": "Conner@daron.info", + "body": "eos quidem temporibus eum\nest ipsa sunt illum a facere\nomnis suscipit dolorem voluptatem incidunt\ntenetur deleniti aspernatur at quis" + }, + { + "postId": 93, + "id": 462, + "name": "assumenda quia sint", + "email": "Nathanael@jada.org", + "body": "adipisci et accusantium hic deserunt voluptates consequatur omnis\nquod dolorem voluptatibus quis velit laboriosam mollitia illo et\niure aliquam dolorem nesciunt laborum\naperiam labore repellat et maxime itaque" + }, + { + "postId": 93, + "id": 463, + "name": "cupiditate quidem corporis totam tenetur rem nesciunt et", + "email": "Nicklaus@talon.io", + "body": "voluptate officiis nihil laudantium sint autem adipisci\naspernatur voluptas debitis nam omnis ut non eligendi\naliquam vel commodi velit officiis laboriosam corporis\nquas aliquid aperiam autem" + }, + { + "postId": 93, + "id": 464, + "name": "quisquam quaerat rerum dolor asperiores doloremque", + "email": "Jerald@laura.io", + "body": "consequatur aliquam illum quis\nfacere vel voluptatem rem sint atque\nin nam autem impedit dolores enim\nsoluta rem adipisci odit sint voluptas aliquam" + }, + { + "postId": 93, + "id": 465, + "name": "est sunt est nesciunt distinctio quaerat reprehenderit in vero", + "email": "Jamey_Dare@johnny.org", + "body": "ex corrupti ut pariatur voluptas illo labore non voluptates\nvoluptas sint et est impedit cum\nin fugiat cumque eum id rerum error\nut rerum voluptates facilis molestiae et labore voluptatem corrupti" + }, + { + "postId": 94, + "id": 466, + "name": "impedit autem distinctio omnis ipsam voluptas eaque", + "email": "Brant@yasmin.co.uk", + "body": "aut dignissimos eos facere velit totam\neaque aut voluptas ex similique ut ipsa est\nvoluptates ut tempora\nquis commodi officia et consequatur cumque delectus" + }, + { + "postId": 94, + "id": 467, + "name": "voluptas unde perferendis ut eaque dicta", + "email": "Adrianna_Howell@molly.io", + "body": "deleniti fuga hic autem\nsed rerum non voluptate sit totam consequuntur illo\nquasi quod aut ducimus dolore distinctio molestias\nnon velit quis debitis cumque voluptas" + }, + { + "postId": 94, + "id": 468, + "name": "nam praesentium est ipsa libero aut", + "email": "Amiya.Morar@emma.tv", + "body": "facilis repellendus inventore aperiam corrupti saepe culpa velit\ndolores sint ut\naut quis voluptates iure et a\nneque harum quia similique sunt eum voluptatem a" + }, + { + "postId": 94, + "id": 469, + "name": "vel eum quia esse sapiente", + "email": "Destany@bailey.info", + "body": "dolor unde numquam distinctio\nducimus eum hic rerum non expedita\ndolores et dignissimos rerum\nperspiciatis et porro est minus" + }, + { + "postId": 94, + "id": 470, + "name": "deleniti vitae alias distinctio dignissimos ab accusantium pariatur dicta", + "email": "Katarina.Wolff@joel.io", + "body": "molestias incidunt eaque\nnumquam reprehenderit rerum ut ex ad\nomnis porro maiores quaerat harum nihil non quasi ea\nasperiores quisquam sunt fugiat eos natus iure adipisci" + }, + { + "postId": 95, + "id": 471, + "name": "nihil ad debitis rerum optio est cumque sed voluptates", + "email": "Pearline@veda.ca", + "body": "quia non dolor\ncorporis consectetur velit eos quis\nincidunt ut eos nesciunt repellendus voluptas voluptate sint neque\ndoloribus est minima autem quis velit illo ea neque" + }, + { + "postId": 95, + "id": 472, + "name": "aspernatur ex dolor optio", + "email": "Belle.Braun@otis.name", + "body": "et necessitatibus earum qui velit id explicabo harum optio\ndolor dolores reprehenderit in\na itaque odit esse et et id\npossimus est ut consequuntur velit autem iure ut" + }, + { + "postId": 95, + "id": 473, + "name": "quaerat et excepturi autem animi fuga", + "email": "Eliane@libby.net", + "body": "quod corrupti eum quisquam rerum accusantium tempora\nreprehenderit qui voluptate et sunt optio et\niusto nihil amet omnis labore cumque quo\nsaepe omnis aut quia consectetur" + }, + { + "postId": 95, + "id": 474, + "name": "natus consequatur deleniti ipsum delectus", + "email": "Trey.Harber@christop.biz", + "body": "tempora sint qui iste itaque non neque qui suscipit\nenim quas rerum totam impedit\nesse nulla praesentium natus explicabo doloremque atque maxime\nmollitia impedit dolorem occaecati officia in provident eos" + }, + { + "postId": 95, + "id": 475, + "name": "cumque consequuntur excepturi consequatur consequatur est", + "email": "Kailyn@ivory.info", + "body": "ut in nostrum\nut et incidunt et minus nulla perferendis libero delectus\nnulla nemo deleniti\ndeleniti facere autem vero velit non molestiae assumenda" + }, + { + "postId": 96, + "id": 476, + "name": "quia hic adipisci modi fuga aperiam", + "email": "Amely.Kunde@rodrigo.co.uk", + "body": "officia quas aut culpa eum\neaque quia rem unde ea quae reiciendis omnis\nexcepturi nemo est vel sequi accusantium tenetur at earum\net rerum quisquam temporibus cupiditate" + }, + { + "postId": 96, + "id": 477, + "name": "ut occaecati non", + "email": "Thaddeus.Halvorson@ruthe.ca", + "body": "nulla veniam quo consequuntur ullam\nautem nisi error aut facere distinctio rerum quia tempore\nvelit distinctio occaecati ducimus\nratione similique doloribus" + }, + { + "postId": 96, + "id": 478, + "name": "quo error dignissimos numquam qui nam fugit voluptates et", + "email": "Hannah@emma.ca", + "body": "non similique illo\nquia et rem placeat reprehenderit voluptas\nvelit officiis fugit blanditiis nihil\nab deserunt ullam" + }, + { + "postId": 96, + "id": 479, + "name": "distinctio minima error aspernatur reiciendis inventore quo", + "email": "Maryam.Mann@thelma.info", + "body": "totam explicabo harum quam impedit sunt\ndoloremque consectetur id et minima eos incidunt quibusdam omnis\nsaepe maiores officiis eligendi alias sint est aut cumque\ndebitis cumque hic aut ut dolorum" + }, + { + "postId": 96, + "id": 480, + "name": "accusantium quo error repudiandae", + "email": "Michel@keira.us", + "body": "tenetur qui ut\narchitecto officiis voluptatem velit eos molestias incidunt eum dolorum\ndistinctio quam et\nsequi consequatur nihil voluptates animi" + }, + { + "postId": 97, + "id": 481, + "name": "recusandae dolor similique autem saepe voluptate aut vel sit", + "email": "Domenick@russell.ca", + "body": "dignissimos nobis vitae corporis delectus eligendi et ut ut\namet laudantium neque\net quia cupiditate debitis aliquid\ndolorem aspernatur libero aut autem quo et" + }, + { + "postId": 97, + "id": 482, + "name": "placeat eveniet sunt ut quis", + "email": "Chanelle@samson.me", + "body": "aliquid natus voluptas doloremque fugiat ratione adipisci\nunde eum facilis enim omnis ipsum nobis nihil praesentium\nut blanditiis voluptatem veniam\ntenetur fugit et distinctio aspernatur" + }, + { + "postId": 97, + "id": 483, + "name": "a ipsa nihil sed impedit", + "email": "Hermann.Kunde@rosina.us", + "body": "quos aut rerum nihil est et\ndolores commodi voluptas voluptatem excepturi et\net expedita dignissimos atque aut reprehenderit\nquis quo soluta" + }, + { + "postId": 97, + "id": 484, + "name": "hic inventore sint aut", + "email": "Olen@bryce.net", + "body": "vel libero quo sit vitae\nid nesciunt ipsam non a aut enim itaque totam\nillum est cupiditate sit\nnam exercitationem magnam veniam" + }, + { + "postId": 97, + "id": 485, + "name": "enim asperiores illum", + "email": "Lorenza.Carter@consuelo.ca", + "body": "soluta quia porro mollitia eos accusamus\nvoluptatem illo perferendis earum quia\nquo sed ipsam in omnis cum earum tempore eos\nvoluptatem illum doloremque corporis ipsam facere" + }, + { + "postId": 98, + "id": 486, + "name": "et aut qui eaque porro quo quis velit rerum", + "email": "Lamont@georgiana.biz", + "body": "iste maxime et molestiae\nqui aliquam doloremque earum beatae repellat\nin aut eum libero eos itaque pariatur exercitationem\nvel quam non" + }, + { + "postId": 98, + "id": 487, + "name": "sunt omnis aliquam labore eveniet", + "email": "Colin_Gutkowski@muriel.net", + "body": "sint delectus nesciunt ipsum et aliquid et libero\naut suscipit et molestiae nemo pariatur sequi\nrepudiandae ea placeat neque quas eveniet\nmollitia quae laboriosam" + }, + { + "postId": 98, + "id": 488, + "name": "quo neque dolorem dolorum non incidunt", + "email": "Albert@johnny.biz", + "body": "aut sunt recusandae laboriosam omnis asperiores et\nnulla ipsum rerum quis doloremque rerum optio mollitia provident\nsed iste aut id\nnumquam repudiandae veritatis" + }, + { + "postId": 98, + "id": 489, + "name": "aut quia et corporis voluptas quisquam voluptatem", + "email": "Hilma.Kutch@ottilie.info", + "body": "et dolorem sit\nreprehenderit sapiente occaecati iusto sit impedit nobis ut quia\nmaiores debitis pariatur nostrum et aut\nassumenda error qui deserunt laborum quaerat et" + }, + { + "postId": 98, + "id": 490, + "name": "et eum provident maxime beatae minus et doloremque perspiciatis", + "email": "Donnie@alfreda.biz", + "body": "minus nihil sunt dolor\nipsum a illum quis\nquasi officiis cupiditate architecto sit consequatur ut\net sed quasi quam doloremque" + }, + { + "postId": 99, + "id": 491, + "name": "eos enim odio", + "email": "Maxwell@adeline.me", + "body": "natus commodi debitis cum ex rerum alias quis\nmaxime fugiat fugit sapiente distinctio nostrum tempora\npossimus quod vero itaque enim accusantium perferendis\nfugit ut eum labore accusantium voluptas" + }, + { + "postId": 99, + "id": 492, + "name": "consequatur alias ab fuga tenetur maiores modi", + "email": "Amina@emmet.org", + "body": "iure deleniti aut consequatur necessitatibus\nid atque voluptas mollitia\nvoluptates doloremque dolorem\nrepudiandae hic enim laboriosam consequatur velit minus" + }, + { + "postId": 99, + "id": 493, + "name": "ut praesentium sit eos rerum tempora", + "email": "Gilda@jacques.org", + "body": "est eos doloremque autem\nsimilique sint fuga atque voluptate est\nminus tempore quia asperiores aliquam et corporis voluptatem\nconsequatur et eum illo aut qui molestiae et amet" + }, + { + "postId": 99, + "id": 494, + "name": "molestias facere soluta mollitia totam dolorem commodi itaque", + "email": "Kadin@walter.io", + "body": "est illum quia alias ipsam minus\nut quod vero aut magni harum quis\nab minima voluptates nemo non sint quis\ndistinctio officia ea et maxime" + }, + { + "postId": 99, + "id": 495, + "name": "dolor ut ut aut molestiae esse et tempora numquam", + "email": "Alice_Considine@daren.com", + "body": "pariatur occaecati ea autem at quis et dolorem similique\npariatur ipsa hic et saepe itaque cumque repellendus vel\net quibusdam qui aut nemo et illo\nqui non quod officiis aspernatur qui optio" + }, + { + "postId": 100, + "id": 496, + "name": "et occaecati asperiores quas voluptas ipsam nostrum", + "email": "Zola@lizzie.com", + "body": "neque unde voluptatem iure\nodio excepturi ipsam ad id\nipsa sed expedita error quam\nvoluptatem tempora necessitatibus suscipit culpa veniam porro iste vel" + }, + { + "postId": 100, + "id": 497, + "name": "doloribus dolores ut dolores occaecati", + "email": "Dolly@mandy.co.uk", + "body": "non dolor consequatur\nlaboriosam ut deserunt autem odit\nlibero dolore non nesciunt qui\naut est consequatur quo dolorem" + }, + { + "postId": 100, + "id": 498, + "name": "dolores minus aut libero", + "email": "Davion@eldora.net", + "body": "aliquam pariatur suscipit fugiat eos sunt\noptio voluptatem eveniet rerum dignissimos\nquia aut beatae\nmodi consequatur qui rerum sint veritatis deserunt est" + }, + { + "postId": 100, + "id": 499, + "name": "excepturi sunt cum a et rerum quo voluptatibus quia", + "email": "Wilburn_Labadie@araceli.name", + "body": "et necessitatibus tempora ipsum quaerat inventore est quasi quidem\nea repudiandae laborum omnis ab reprehenderit ut\nratione sit numquam culpa a rem\natque aut et" + }, + { + "postId": 100, + "id": 500, + "name": "ex eaque eum natus", + "email": "Emma@joanny.ca", + "body": "perspiciatis quis doloremque\nveniam nisi eos velit sed\nid totam inventore voluptatem laborum et eveniet\naut aut aut maxime quia temporibus ut omnis" + } +] \ No newline at end of file diff --git a/tests/e2e/fetch-2/expect_serve_stderr.txt b/tests/e2e/fetch-2/expect_serve_stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/fetch-2/expect_serve_stdout.txt b/tests/e2e/fetch-2/expect_serve_stdout.txt new file mode 100644 index 00000000..9b511128 --- /dev/null +++ b/tests/e2e/fetch-2/expect_serve_stdout.txt @@ -0,0 +1 @@ +stdout [0] :: Log: Successfully received response body diff --git a/tests/e2e/fetch-2/fetch-2.js b/tests/e2e/fetch-2/fetch-2.js new file mode 100644 index 00000000..ea4ad0c1 --- /dev/null +++ b/tests/e2e/fetch-2/fetch-2.js @@ -0,0 +1,31 @@ + +async function main(event) { + let resolve, reject; + + try { + const responsePromise = new Promise(async (res, rej) => { + resolve = res; + reject = rej; + }); + event.respondWith(responsePromise); + + const response = await fetch("https://jsonplaceholder.typicode.com/comments"); + const reader = response.body.getReader(); + + let bodyChunks = [] ; + let chunk; + while (!(chunk = await reader.read()).done) { + for (let i = 0; i < chunk.value.length; i++) { + bodyChunks.push(chunk.value[i]); + } + } + const bodyText = new TextDecoder().decode(new Uint8Array(bodyChunks)); + console.log("Successfully received response body"); + + resolve(new Response(bodyText, { headers: response.headers })); + } catch (e) { + console.log(`Error: ${e}. Stack: ${e.stack}`); + } +} + +addEventListener('fetch', main); diff --git a/tests/tests.cmake b/tests/tests.cmake index 0852fbdd..ab0b5dbf 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -27,7 +27,8 @@ function(test_integration TEST_NAME) endfunction() test_e2e(smoke) -test_e2e(fetch) +test_e2e(fetch-1) +test_e2e(fetch-2) test_e2e(tla) test_e2e(syntax-err) test_e2e(tla-err) From 98bf3308334d2b78dedb4eb6b975154ebc062d99 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 30 Apr 2024 14:51:14 -0400 Subject: [PATCH 06/52] add post test (failing) --- tests/e2e/fetch-1/fetch-1.js | 5 ++-- tests/e2e/fetch-3/expect_serve_body.txt | 1 + tests/e2e/fetch-3/expect_serve_stderr.txt | 0 tests/e2e/fetch-3/expect_serve_stdout.txt | 1 + tests/e2e/fetch-3/fetch-3.js | 30 +++++++++++++++++++++++ tests/tests.cmake | 1 + 6 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/fetch-3/expect_serve_body.txt create mode 100644 tests/e2e/fetch-3/expect_serve_stderr.txt create mode 100644 tests/e2e/fetch-3/expect_serve_stdout.txt create mode 100644 tests/e2e/fetch-3/fetch-3.js diff --git a/tests/e2e/fetch-1/fetch-1.js b/tests/e2e/fetch-1/fetch-1.js index 0720affb..a078227a 100644 --- a/tests/e2e/fetch-1/fetch-1.js +++ b/tests/e2e/fetch-1/fetch-1.js @@ -8,10 +8,9 @@ async function main(event) { }); event.respondWith(responsePromise); - let p = fetch("https://jsonplaceholder.typicode.com/users"); - let response = await p; - + let response = await fetch("https://jsonplaceholder.typicode.com/users"); let text = await response.json(); + console.log("Successfully received response json body"); resolve(new Response(JSON.stringify(text), { headers: response.headers })); diff --git a/tests/e2e/fetch-3/expect_serve_body.txt b/tests/e2e/fetch-3/expect_serve_body.txt new file mode 100644 index 00000000..d9524d8f --- /dev/null +++ b/tests/e2e/fetch-3/expect_serve_body.txt @@ -0,0 +1 @@ +[{"id":1,"name":"Leanne Graham","username":"Bret","email":"Sincere@april.biz","address":{"street":"Kulas Light","suite":"Apt. 556","city":"Gwenborough","zipcode":"92998-3874","geo":{"lat":"-37.3159","lng":"81.1496"}},"phone":"1-770-736-8031 x56442","website":"hildegard.org","company":{"name":"Romaguera-Crona","catchPhrase":"Multi-layered client-server neural-net","bs":"harness real-time e-markets"}},{"id":2,"name":"Ervin Howell","username":"Antonette","email":"Shanna@melissa.tv","address":{"street":"Victor Plains","suite":"Suite 879","city":"Wisokyburgh","zipcode":"90566-7771","geo":{"lat":"-43.9509","lng":"-34.4618"}},"phone":"010-692-6593 x09125","website":"anastasia.net","company":{"name":"Deckow-Crist","catchPhrase":"Proactive didactic contingency","bs":"synergize scalable supply-chains"}},{"id":3,"name":"Clementine Bauch","username":"Samantha","email":"Nathan@yesenia.net","address":{"street":"Douglas Extension","suite":"Suite 847","city":"McKenziehaven","zipcode":"59590-4157","geo":{"lat":"-68.6102","lng":"-47.0653"}},"phone":"1-463-123-4447","website":"ramiro.info","company":{"name":"Romaguera-Jacobson","catchPhrase":"Face to face bifurcated interface","bs":"e-enable strategic applications"}},{"id":4,"name":"Patricia Lebsack","username":"Karianne","email":"Julianne.OConner@kory.org","address":{"street":"Hoeger Mall","suite":"Apt. 692","city":"South Elvis","zipcode":"53919-4257","geo":{"lat":"29.4572","lng":"-164.2990"}},"phone":"493-170-9623 x156","website":"kale.biz","company":{"name":"Robel-Corkery","catchPhrase":"Multi-tiered zero tolerance productivity","bs":"transition cutting-edge web services"}},{"id":5,"name":"Chelsey Dietrich","username":"Kamren","email":"Lucio_Hettinger@annie.ca","address":{"street":"Skiles Walks","suite":"Suite 351","city":"Roscoeview","zipcode":"33263","geo":{"lat":"-31.8129","lng":"62.5342"}},"phone":"(254)954-1289","website":"demarco.info","company":{"name":"Keebler LLC","catchPhrase":"User-centric fault-tolerant solution","bs":"revolutionize end-to-end systems"}},{"id":6,"name":"Mrs. Dennis Schulist","username":"Leopoldo_Corkery","email":"Karley_Dach@jasper.info","address":{"street":"Norberto Crossing","suite":"Apt. 950","city":"South Christy","zipcode":"23505-1337","geo":{"lat":"-71.4197","lng":"71.7478"}},"phone":"1-477-935-8478 x6430","website":"ola.org","company":{"name":"Considine-Lockman","catchPhrase":"Synchronised bottom-line interface","bs":"e-enable innovative applications"}},{"id":7,"name":"Kurtis Weissnat","username":"Elwyn.Skiles","email":"Telly.Hoeger@billy.biz","address":{"street":"Rex Trail","suite":"Suite 280","city":"Howemouth","zipcode":"58804-1099","geo":{"lat":"24.8918","lng":"21.8984"}},"phone":"210.067.6132","website":"elvis.io","company":{"name":"Johns Group","catchPhrase":"Configurable multimedia task-force","bs":"generate enterprise e-tailers"}},{"id":8,"name":"Nicholas Runolfsdottir V","username":"Maxime_Nienow","email":"Sherwood@rosamond.me","address":{"street":"Ellsworth Summit","suite":"Suite 729","city":"Aliyaview","zipcode":"45169","geo":{"lat":"-14.3990","lng":"-120.7677"}},"phone":"586.493.6943 x140","website":"jacynthe.com","company":{"name":"Abernathy Group","catchPhrase":"Implemented secondary concept","bs":"e-enable extensible e-tailers"}},{"id":9,"name":"Glenna Reichert","username":"Delphine","email":"Chaim_McDermott@dana.io","address":{"street":"Dayna Park","suite":"Suite 449","city":"Bartholomebury","zipcode":"76495-3109","geo":{"lat":"24.6463","lng":"-168.8889"}},"phone":"(775)976-6794 x41206","website":"conrad.com","company":{"name":"Yost and Sons","catchPhrase":"Switchable contextually-based project","bs":"aggregate real-time technologies"}},{"id":10,"name":"Clementina DuBuque","username":"Moriah.Stanton","email":"Rey.Padberg@karina.biz","address":{"street":"Kattie Turnpike","suite":"Suite 198","city":"Lebsackbury","zipcode":"31428-2261","geo":{"lat":"-38.2386","lng":"57.2232"}},"phone":"024-648-3804","website":"ambrose.net","company":{"name":"Hoeger LLC","catchPhrase":"Centralized empowering task-force","bs":"target end-to-end models"}}] \ No newline at end of file diff --git a/tests/e2e/fetch-3/expect_serve_stderr.txt b/tests/e2e/fetch-3/expect_serve_stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/fetch-3/expect_serve_stdout.txt b/tests/e2e/fetch-3/expect_serve_stdout.txt new file mode 100644 index 00000000..7bcdc8dc --- /dev/null +++ b/tests/e2e/fetch-3/expect_serve_stdout.txt @@ -0,0 +1 @@ +stdout [0] :: Log: Successfully received response json body diff --git a/tests/e2e/fetch-3/fetch-3.js b/tests/e2e/fetch-3/fetch-3.js new file mode 100644 index 00000000..a5335b4b --- /dev/null +++ b/tests/e2e/fetch-3/fetch-3.js @@ -0,0 +1,30 @@ +async function main(event) { + let resolve, reject; + + try { + let responsePromise = new Promise(async (res, rej) => { + resolve = res; + reject = rej; + }); + event.respondWith(responsePromise); + + const headerKey = "X-Test-Header"; + const headerValue = "test-header-value"; + + let response = await fetch("https://echo.free.beeceptor.com", { + method: "POST", + headers: { + [headerKey] :headerValue + }, + body: "hello world" + }) + + let responseJson = await response.json(); + console.log("Successfully received response json body"); + resolve(new Response(JSON.stringify(responseJson))); + } catch (e) { + console.log(`Error: ${e}. Stack: ${e.stack}`); + } +} + +addEventListener('fetch', main); diff --git a/tests/tests.cmake b/tests/tests.cmake index ab0b5dbf..9990959d 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -29,6 +29,7 @@ endfunction() test_e2e(smoke) test_e2e(fetch-1) test_e2e(fetch-2) +test_e2e(fetch-3) test_e2e(tla) test_e2e(syntax-err) test_e2e(tla-err) From ba679d597b8ae4b61ce92336bb51755a6c7da81a Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 30 Apr 2024 17:58:36 -0400 Subject: [PATCH 07/52] comment out headers only body post test --- tests/e2e/fetch-3/fetch-3.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/fetch-3/fetch-3.js b/tests/e2e/fetch-3/fetch-3.js index a5335b4b..33030714 100644 --- a/tests/e2e/fetch-3/fetch-3.js +++ b/tests/e2e/fetch-3/fetch-3.js @@ -13,9 +13,9 @@ async function main(event) { let response = await fetch("https://echo.free.beeceptor.com", { method: "POST", - headers: { - [headerKey] :headerValue - }, + // headers: { + // [headerKey] :headerValue + // }, body: "hello world" }) From 7975f3b2f32fb41200d08ace9b55b653d548a692 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Wed, 1 May 2024 11:46:53 -0400 Subject: [PATCH 08/52] debugging fetch-3 --- builtins/web/fetch/headers.cpp | 21 ++++++++++++++++--- builtins/web/fetch/request-response.cpp | 27 ++++++++++++++++++++++++- clean_test.sh | 7 +++++++ include/host_api.h | 4 +--- tests/e2e/fetch-3/fetch-3.js | 4 ++-- tests/test.sh | 2 +- 6 files changed, 55 insertions(+), 10 deletions(-) create mode 100755 clean_test.sh diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 32f3d8fc..2b24c72b 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -67,11 +67,20 @@ bool lazy_values(JSObject *self) { .toBoolean(); } -Handle *get_handle(JSObject *self) { +Handle* get_handle(JSObject* self) { MOZ_ASSERT(Headers::is_instance(self)); auto handle = JS::GetReservedSlot(self, static_cast(Headers::Slots::Handle)).toPrivate(); - return static_cast(handle); + auto result = static_cast(handle); + + if (result != nullptr) { + int32_t handleValue = result->handle_state_->handle; + fprintf(stderr, "get_handle retrieved Resource %p with handle value %d\n", result, handleValue); + } else { + fprintf(stderr, "get_handle retrieved nullptr\n"); + } + + return result; } /** @@ -444,6 +453,7 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand for (auto value : splitCookiesString(value)) { auto res = handle->append(name, value); if (auto *err = res.to_err()) { + fprintf(stderr, "handle->append error 1\n"); HANDLE_ERROR(cx, *err); return false; } @@ -452,6 +462,7 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand std::string_view value = value_chars; auto res = handle->append(name, value); if (auto *err = res.to_err()) { + fprintf(stderr, "handle->append error 2\n"); HANDLE_ERROR(cx, *err); return false; } @@ -466,9 +477,10 @@ bool Headers::delazify(JSContext *cx, JS::HandleObject headers) { return ensure_all_header_values_from_handle(cx, headers, backing_map); } +// Create header from iterable. JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, JS::HandleObject init_headers) { - JS::RootedObject headers(cx, create(cx, self, handle)); + JS::RootedObject headers(cx, Headers::create(cx, self, handle)); if (!headers) { return nullptr; } @@ -481,6 +493,7 @@ JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHe return nullptr; } + // TODO: does this do anything? JS::RootedObject headers_map(cx, get_backing_map(headers)); JS::RootedObject init_map(cx, get_backing_map(init_headers)); @@ -522,6 +535,7 @@ JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHe JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, JS::HandleValue initv) { + fprintf(stderr, "Headers::create from HandleValue\n"); JS::RootedObject headers(cx, create(cx, self, handle)); if (!headers) return nullptr; @@ -768,6 +782,7 @@ bool Headers::init_class(JSContext *cx, JS::HandleObject global) { } JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle) { + fprintf(stderr, "Headers::create storing handle %p %d\n", handle, handle->handle_state_->handle); JS_SetReservedSlot(self, static_cast(Slots::Handle), JS::PrivateValue(handle)); JS::RootedObject backing_map(cx, JS::NewMapObject(cx)); diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index dbc86d60..2310bbac 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -445,7 +445,13 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, // Step 36.3 of Request constructor / 8.4 of Response constructor. if (content_type) { + // Headers do not contain a valid resource reference. JS::RootedObject headers(cx, RequestOrResponse::headers(cx, self)); + if (!headers) { + fprintf(stderr, "FAILED TO EXTRACT OR CREATE HEADERS\n"); + return false; + } + // THIS TRAPS. if (!Headers::maybe_add(cx, headers, "content-type", content_type)) { return false; } @@ -485,23 +491,38 @@ bool RequestOrResponse::append_body(JSContext *cx, JS::HandleObject self, JS::Ha JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { JSObject *headers = maybe_headers(obj); if (!headers) { + fprintf(stderr, "maybe_headers is null\n"); + JS::RootedObject headersInstance( cx, JS_NewObjectWithGivenProto(cx, &Headers::class_, Headers::proto_obj)); if (!headersInstance) { + fprintf(stderr, "headersInstance is null\n"); return nullptr; + } else { + fprintf(stderr, "headersInstance is NOT null\n"); } auto *headers_handle = RequestOrResponse::headers_handle(obj); if (!headers_handle) { - headers_handle = new host_api::HttpHeaders(); + // Error is here? is this not creating a valid resource? + auto result = new host_api::HttpHeaders(); + fprintf(stderr, "headers_handle is null\n"); + headers_handle = result; + } else { + fprintf(stderr, "headers_handle is NOT null\n"); } headers = Headers::create(cx, headersInstance, headers_handle); if (!headers) { + fprintf(stderr, "Headers::create is null\n"); return nullptr; + } else { + fprintf(stderr, "Headers::create is NOT null\n"); } JS_SetReservedSlot(obj, static_cast(Slots::Headers), JS::ObjectValue(*headers)); + } else { + fprintf(stderr, "maybe_headers is NOT null\n"); } return headers; @@ -1712,6 +1733,8 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // otherwise create it from the `init` object's `headers`, or create a new, // empty one. auto *headers_handle = new host_api::HttpHeaders(); + fprintf(stderr, "Created Header Resource: %p %d\n", headers_handle, headers_handle->handle_state_->handle); + JS::RootedObject headers(cx); if (headers_val.isUndefined() && input_headers) { @@ -1726,6 +1749,8 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H headers = Headers::create(cx, headersInstance, headers_handle, headers_val); if (!headers) { return nullptr; + } else { + fprintf(stderr, "headers successfully created\n"); } } diff --git a/clean_test.sh b/clean_test.sh new file mode 100755 index 00000000..9b0ef01f --- /dev/null +++ b/clean_test.sh @@ -0,0 +1,7 @@ +rm -rf cmake-build-debug + +cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug + +cmake --build cmake-build-debug --target integration-test-server + +CTEST_OUTPUT_ON_FAILURE=1 ctest --test-dir cmake-build-debug -j8 diff --git a/include/host_api.h b/include/host_api.h index b1336582..99f19d7e 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -217,10 +217,8 @@ class HandleState { }; class Resource { -protected: - HandleState *handle_state_; - public: + HandleState *handle_state_; virtual ~Resource() = default; /// Returns true when this resource handle is valid. diff --git a/tests/e2e/fetch-3/fetch-3.js b/tests/e2e/fetch-3/fetch-3.js index 33030714..1f9e5343 100644 --- a/tests/e2e/fetch-3/fetch-3.js +++ b/tests/e2e/fetch-3/fetch-3.js @@ -8,8 +8,8 @@ async function main(event) { }); event.respondWith(responsePromise); - const headerKey = "X-Test-Header"; - const headerValue = "test-header-value"; + // const headerKey = "X-Test-Header"; + // const headerValue = "test-header-value"; let response = await fetch("https://echo.free.beeceptor.com", { method: "POST", diff --git a/tests/test.sh b/tests/test.sh index 426b6c86..353cd8f8 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -70,7 +70,7 @@ if [ -z "$test_component" ]; then fi fi -$wasmtime serve -S common --addr 0.0.0.0:0 "$test_component" 1> "$stdout_log" 2> "$stderr_log" & +WASMTIME_BACKTRACE_DETAILS=1 WASMTIME_LOG=wasmtime_wasi $wasmtime serve -S common --addr 0.0.0.0:0 "$test_component" 1> "$stdout_log" 2> "$stderr_log" & wasmtime_pid="$!" function cleanup { From 6347825be4b07be7e0447763fed689f37675ecf3 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Tue, 7 May 2024 20:11:43 -0400 Subject: [PATCH 09/52] add header logs --- builtins/web/fetch/request-response.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 2310bbac..9bae3fb8 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -446,12 +446,12 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, // Step 36.3 of Request constructor / 8.4 of Response constructor. if (content_type) { // Headers do not contain a valid resource reference. + fprintf(stderr, "Creating headers %s\n", content_type); JS::RootedObject headers(cx, RequestOrResponse::headers(cx, self)); if (!headers) { fprintf(stderr, "FAILED TO EXTRACT OR CREATE HEADERS\n"); return false; } - // THIS TRAPS. if (!Headers::maybe_add(cx, headers, "content-type", content_type)) { return false; } From 2e883a7035f6afda0dd3755f4d6e070a03b84ab0 Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Mon, 13 May 2024 11:33:05 -0600 Subject: [PATCH 10/52] Debug and fix attempts --- builtins/web/fetch/request-response.cpp | 4 ++-- host-apis/wasi-0.2.0/host_api.cpp | 31 +++++++++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index a41730ab..ffd14b3f 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -1726,7 +1726,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // `init["headers"]` exists, create the request's `headers` from that, // otherwise create it from the `init` object's `headers`, or create a new, // empty one. - auto *headers_handle = new host_api::HttpHeaders(); + auto *headers_handle = new host_api::HttpHeaders(); // ************************* fprintf(stderr, "Created Header Resource: %p %d\n", headers_handle, headers_handle->handle_state_->handle); JS::RootedObject headers(cx); @@ -1804,7 +1804,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // ``Content-Type``, then append (``Content-Type``, `Content-Type`) to // this’s headers. // Note: these steps are all inlined into RequestOrResponse::extract_body. - if (!RequestOrResponse::extract_body(cx, request, body_val)) { + if (!RequestOrResponse::extract_body(cx, request, body_val)) { // ************************* return nullptr; } } else if (input_has_body) { diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 39b4f61a..43d730e2 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -187,6 +187,16 @@ void MonotonicClock::unsubscribe(const int32_t handle_id) { HttpHeaders::HttpHeaders() { this->handle_state_ = new HandleState(wasi_http_0_2_0_types_constructor_fields().__handle); + fprintf(stderr, "wasi_http_0_2_0_types_constructor_fields returned with %d\n", this->handle_state_->handle); + + Result> names_result = this->names(); + MOZ_ASSERT(!names_result.is_err()); + vector &names = names_result.unwrap(); + fprintf(stderr, "wasi_http_0_2_0_types_constructor_fields ==> len: %zu", names.size()); + for (auto &str : names) { + fprintf(stderr, "wasi_http_0_2_0_types_constructor_fields ==> %zu", str.size()); + } + } HttpHeaders::HttpHeaders(Handle handle) { handle_state_ = new HandleState(handle); } @@ -709,21 +719,24 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< auto *state = new HandleState(handle.__handle); auto *resp = new HttpOutgoingRequest(state); - resp->headers_ = headers; - return resp; } Result HttpOutgoingRequest::method() { MOZ_ASSERT(valid()); - MOZ_ASSERT(headers_); return Result::ok(method_); } Result HttpOutgoingRequest::headers() { + typedef Result Res; MOZ_ASSERT(valid()); - MOZ_ASSERT(headers_); - return Result::ok(headers_); + + if (!this->headers_) { + auto res = wasi_http_0_2_0_types_method_outgoing_request_headers( + wasi_http_0_2_0_types_borrow_outgoing_request({handle_state_->handle})); + headers_ = new HttpHeaders(res.__handle); + } + return Res::ok(this->headers_); } Result HttpOutgoingRequest::body() { @@ -742,6 +755,14 @@ Result HttpOutgoingRequest::body() { Result HttpOutgoingRequest::send() { MOZ_ASSERT(valid()); + + if (this->headers_) { + fprintf(stderr, "HttpOutgoingRequest::send dropping borrowed headers"); + wasi_http_0_2_0_types_fields_drop_borrow({this->headers_->handle_state_->handle}); + } + + fprintf(stderr, "HttpOutgoingRequest::send calling handle"); + future_incoming_response_t ret; wasi_http_0_2_0_outgoing_handler_error_code_t err; wasi_http_0_2_0_outgoing_handler_handle({handle_state_->handle}, nullptr, &ret, &err); From b5092864af155e6956f44690d8f2fb4f0c5a3bb8 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 13 May 2024 16:00:29 -0600 Subject: [PATCH 11/52] attempt to extract value before subscribing --- builtins/web/fetch/fetch-api.cpp | 71 +++++++++++++++++++++++-- builtins/web/fetch/fetch-api.h | 3 ++ builtins/web/fetch/request-response.cpp | 5 ++ host-apis/wasi-0.2.0/host_api.cpp | 7 ++- 4 files changed, 80 insertions(+), 6 deletions(-) diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 9f45119d..586bac1e 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -15,24 +15,31 @@ class ResponseFutureTask final : public api::AsyncTask { explicit ResponseFutureTask(const HandleObject request, host_api::FutureHttpIncomingResponse *future) : request_(request), future_(future) { + fprintf(stderr, "ResponseFutureTask::ResponseFutureTask\n"); + ensure_no_response(ENGINE->cx(), request, future); auto res = future->subscribe(); MOZ_ASSERT(!res.is_err(), "Subscribing to a future should never fail"); - handle_ = res.unwrap(); + this->handle_ = res.unwrap(); + + fprintf(stderr, "ResponseFutureTask::ResponseFutureTask END!\n"); } [[nodiscard]] bool run(api::Engine *engine) override { + fprintf(stderr, "ResponseFutureTask::run\n"); // MOZ_ASSERT(ready()); JSContext *cx = engine->cx(); const RootedObject request(cx, request_); RootedObject response_promise(cx, Request::response_promise(request)); + fprintf(stderr, "ResponseFutureTask::run: maybe_response\n"); auto res = future_->maybe_response(); if (res.is_err()) { JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource."); return RejectPromiseWithPendingError(cx, response_promise); } + fprintf(stderr, "ResponseFutureTask::run: maybe_response\n"); auto maybe_response = res.unwrap(); MOZ_ASSERT(maybe_response.has_value()); auto response = maybe_response.value(); @@ -57,6 +64,7 @@ class ResponseFutureTask final : public api::AsyncTask { } [[nodiscard]] bool cancel(api::Engine *engine) override { + fprintf(stderr, "ResponseFutureTask::cancel\n"); // TODO(TS): implement handle_ = -1; return true; @@ -102,7 +110,9 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { host_api::FutureHttpIncomingResponse *pending_handle; { auto request_handle = Request::outgoing_handle(request); + fprintf(stderr, "fetch: sending request\n"); auto res = request_handle->send(); + fprintf(stderr, "fetch: send initiated\n"); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); @@ -112,19 +122,70 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { pending_handle = res.unwrap(); } + JS::SetReservedSlot(request, static_cast(Request::Slots::ResponsePromise), + JS::ObjectValue(*response_promise)); + // If the request body is streamed, we need to wait for streaming to complete // before marking the request as pending. if (!streaming) { - ENGINE->queue_async_task(new ResponseFutureTask(request, pending_handle)); + fprintf(stderr, "fetch: scheduling response future task\n"); + + if (ensure_no_response(cx, request, pending_handle)) { + fprintf(stderr, "fetch: maybe_response has value!\n"); + } else { + fprintf(stderr, "fetch: maybe_response has no value! Future queued.\n"); + + ENGINE->queue_async_task(new ResponseFutureTask(request, pending_handle)); + } } - JS::SetReservedSlot(request, static_cast(Request::Slots::ResponsePromise), - JS::ObjectValue(*response_promise)); - + fprintf(stderr, "fetch: returning response promise\n"); args.rval().setObject(*response_promise); return true; } +bool ensure_no_response(JSContext *cx, JS::HandleObject req, + host_api::FutureHttpIncomingResponse *future) { + + const RootedObject request(cx, req); + RootedObject response_promise(cx, Request::response_promise(request)); + + fprintf(stderr, "ensure_no_response: maybe_response\n"); + auto res = future->maybe_response(); + if (res.is_err()) { + JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource."); + return RejectPromiseWithPendingError(cx, response_promise); + } + + fprintf(stderr, "ensure_no_response: maybe_response is not error \n"); + auto maybe_response = res.unwrap(); + if (maybe_response.has_value()) { + fprintf(stderr, "ensure_no_response: maybe_response has value!\n"); + auto response = maybe_response.value(); + RootedObject response_obj( + cx, JS_NewObjectWithGivenProto(cx, &Response::class_, Response::proto_obj)); + if (!response_obj) { + return false; + } + + response_obj = Response::create(cx, response_obj, response); + if (!response_obj) { + return false; + } + + RequestOrResponse::set_url(response_obj, RequestOrResponse::url(request)); + RootedValue response_val(cx, ObjectValue(*response_obj)); + if (!ResolvePromise(cx, response_promise, response_val)) { + return false; + } else { + return true; + } + } else { + fprintf(stderr, "ensure_no_response: maybe_response has no value\n"); + return false; + } +} + const JSFunctionSpec methods[] = {JS_FN("fetch", fetch, 2, JSPROP_ENUMERATE), JS_FS_END}; bool install(api::Engine *engine) { diff --git a/builtins/web/fetch/fetch-api.h b/builtins/web/fetch/fetch-api.h index 9d6807e6..8af9d063 100644 --- a/builtins/web/fetch/fetch-api.h +++ b/builtins/web/fetch/fetch-api.h @@ -2,12 +2,15 @@ #define BUILTINS_WEB_FETCH_API_H #include "builtin.h" +#include "request-response.h" namespace builtins { namespace web { namespace fetch { bool install(api::Engine *engine); +bool ensure_no_response(JSContext *cx, HandleObject request, + host_api::FutureHttpIncomingResponse *future); } // namespace fetch } // namespace web diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index ffd14b3f..70100f9e 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -2648,6 +2648,8 @@ JSObject *Response::create(JSContext *cx, JS::HandleObject response, MOZ_ASSERT(is_instance(response)); MOZ_ASSERT(response_handle); + fprintf(stderr, "Response::create\n"); + JS::SetReservedSlot(response, static_cast(Slots::Response), JS::PrivateValue(response_handle)); JS::SetReservedSlot(response, static_cast(Slots::Headers), JS::NullValue()); @@ -2657,6 +2659,7 @@ JSObject *Response::create(JSContext *cx, JS::HandleObject response, JS::SetReservedSlot(response, static_cast(Slots::Redirected), JS::FalseValue()); if (response_handle->is_incoming()) { + fprintf(stderr, "Response::create: response handle INCOMING\n"); auto res = reinterpret_cast(response_handle)->status(); MOZ_ASSERT(!res.is_err(), "TODO: proper error handling"); auto status = res.unwrap(); @@ -2666,6 +2669,8 @@ JSObject *Response::create(JSContext *cx, JS::HandleObject response, if (!(status == 204 || status == 205 || status == 304)) { JS::SetReservedSlot(response, static_cast(Slots::HasBody), JS::TrueValue()); } + } else { + fprintf(stderr, "Response::create: response handle NOT INCOMING\n"); } return response; diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 43d730e2..98bde901 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -98,14 +98,19 @@ template <> struct HandleOps { size_t api::AsyncTask::select(std::vector *tasks) { auto count = tasks->size(); + fprintf(stderr, "api::AsyncTask::select %zu\n", count); vector> handles; for (const auto task : *tasks) { - handles.emplace_back(task->id()); + auto id = task->id(); + fprintf(stderr, "api::AsyncTask::select task %d\n", reinterpret_cast(id)); + handles.emplace_back(id); } auto list = list_borrow_pollable_t{ reinterpret_cast::borrow *>(handles.data()), count}; + fprintf(stderr, "api::AsyncTask::select before poll\n"); wasi_io_0_2_0_poll_list_u32_t result{nullptr, 0}; wasi_io_0_2_0_poll_poll(&list, &result); + fprintf(stderr, "api::AsyncTask::select after poll\n"); MOZ_ASSERT(result.len > 0); const auto ready_index = result.ptr[0]; free(result.ptr); From a1df1baed9b4edb0327bdb9b187eb62a1af7a2f5 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 13 May 2024 16:04:42 -0600 Subject: [PATCH 12/52] update fetch-3 unit test --- tests/e2e/fetch-3/fetch-3.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/e2e/fetch-3/fetch-3.js b/tests/e2e/fetch-3/fetch-3.js index 1f9e5343..778303de 100644 --- a/tests/e2e/fetch-3/fetch-3.js +++ b/tests/e2e/fetch-3/fetch-3.js @@ -8,18 +8,20 @@ async function main(event) { }); event.respondWith(responsePromise); - // const headerKey = "X-Test-Header"; - // const headerValue = "test-header-value"; + console.log("fetching") let response = await fetch("https://echo.free.beeceptor.com", { method: "POST", - // headers: { - // [headerKey] :headerValue - // }, - body: "hello world" + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({hello: "world"}) }) + console.log("fetch executed") + let responseJson = await response.json(); + console.log("Successfully received response json body"); resolve(new Response(JSON.stringify(responseJson))); } catch (e) { From 0de1f1158f8b7652a3ee10a7194a31477c6c11fb Mon Sep 17 00:00:00 2001 From: Daniel Vigovszky Date: Mon, 13 May 2024 17:30:32 -0600 Subject: [PATCH 13/52] More debug and fixes --- builtins/web/fetch/request-response.cpp | 4 ++++ host-apis/wasi-0.2.0/host_api.cpp | 7 +++++++ tests/e2e/fetch-3/expect_serve_body.txt | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 70100f9e..35a97fd5 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -423,6 +423,10 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, auto body = RequestOrResponse::outgoing_body_handle(self); auto write_res = body->write_all(reinterpret_cast(buf), length); + fprintf(stderr, "Closing outgoing body\n"); + body->close(); + fprintf(stderr, "Closed outgoing body\n"); + // Ensure that the NoGC is reset, so throwing an error in HANDLE_ERROR // succeeds. if (maybeNoGC.isSome()) { diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 98bde901..ed46e9dd 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -107,9 +107,16 @@ size_t api::AsyncTask::select(std::vector *tasks) { } auto list = list_borrow_pollable_t{ reinterpret_cast::borrow *>(handles.data()), count}; + + for (int i = 0; i < list.len; i++) { + bool ready = wasi_io_0_2_0_poll_method_pollable_ready(list.ptr[i]); + fprintf(stderr, "api::AsyncTask::select %d ready: %d\n", i, ready); + } + fprintf(stderr, "api::AsyncTask::select before poll\n"); wasi_io_0_2_0_poll_list_u32_t result{nullptr, 0}; wasi_io_0_2_0_poll_poll(&list, &result); + fprintf(stderr, "api::AsyncTask::select after poll\n"); MOZ_ASSERT(result.len > 0); const auto ready_index = result.ptr[0]; diff --git a/tests/e2e/fetch-3/expect_serve_body.txt b/tests/e2e/fetch-3/expect_serve_body.txt index d9524d8f..e6535c21 100644 --- a/tests/e2e/fetch-3/expect_serve_body.txt +++ b/tests/e2e/fetch-3/expect_serve_body.txt @@ -1 +1 @@ -[{"id":1,"name":"Leanne Graham","username":"Bret","email":"Sincere@april.biz","address":{"street":"Kulas Light","suite":"Apt. 556","city":"Gwenborough","zipcode":"92998-3874","geo":{"lat":"-37.3159","lng":"81.1496"}},"phone":"1-770-736-8031 x56442","website":"hildegard.org","company":{"name":"Romaguera-Crona","catchPhrase":"Multi-layered client-server neural-net","bs":"harness real-time e-markets"}},{"id":2,"name":"Ervin Howell","username":"Antonette","email":"Shanna@melissa.tv","address":{"street":"Victor Plains","suite":"Suite 879","city":"Wisokyburgh","zipcode":"90566-7771","geo":{"lat":"-43.9509","lng":"-34.4618"}},"phone":"010-692-6593 x09125","website":"anastasia.net","company":{"name":"Deckow-Crist","catchPhrase":"Proactive didactic contingency","bs":"synergize scalable supply-chains"}},{"id":3,"name":"Clementine Bauch","username":"Samantha","email":"Nathan@yesenia.net","address":{"street":"Douglas Extension","suite":"Suite 847","city":"McKenziehaven","zipcode":"59590-4157","geo":{"lat":"-68.6102","lng":"-47.0653"}},"phone":"1-463-123-4447","website":"ramiro.info","company":{"name":"Romaguera-Jacobson","catchPhrase":"Face to face bifurcated interface","bs":"e-enable strategic applications"}},{"id":4,"name":"Patricia Lebsack","username":"Karianne","email":"Julianne.OConner@kory.org","address":{"street":"Hoeger Mall","suite":"Apt. 692","city":"South Elvis","zipcode":"53919-4257","geo":{"lat":"29.4572","lng":"-164.2990"}},"phone":"493-170-9623 x156","website":"kale.biz","company":{"name":"Robel-Corkery","catchPhrase":"Multi-tiered zero tolerance productivity","bs":"transition cutting-edge web services"}},{"id":5,"name":"Chelsey Dietrich","username":"Kamren","email":"Lucio_Hettinger@annie.ca","address":{"street":"Skiles Walks","suite":"Suite 351","city":"Roscoeview","zipcode":"33263","geo":{"lat":"-31.8129","lng":"62.5342"}},"phone":"(254)954-1289","website":"demarco.info","company":{"name":"Keebler LLC","catchPhrase":"User-centric fault-tolerant solution","bs":"revolutionize end-to-end systems"}},{"id":6,"name":"Mrs. Dennis Schulist","username":"Leopoldo_Corkery","email":"Karley_Dach@jasper.info","address":{"street":"Norberto Crossing","suite":"Apt. 950","city":"South Christy","zipcode":"23505-1337","geo":{"lat":"-71.4197","lng":"71.7478"}},"phone":"1-477-935-8478 x6430","website":"ola.org","company":{"name":"Considine-Lockman","catchPhrase":"Synchronised bottom-line interface","bs":"e-enable innovative applications"}},{"id":7,"name":"Kurtis Weissnat","username":"Elwyn.Skiles","email":"Telly.Hoeger@billy.biz","address":{"street":"Rex Trail","suite":"Suite 280","city":"Howemouth","zipcode":"58804-1099","geo":{"lat":"24.8918","lng":"21.8984"}},"phone":"210.067.6132","website":"elvis.io","company":{"name":"Johns Group","catchPhrase":"Configurable multimedia task-force","bs":"generate enterprise e-tailers"}},{"id":8,"name":"Nicholas Runolfsdottir V","username":"Maxime_Nienow","email":"Sherwood@rosamond.me","address":{"street":"Ellsworth Summit","suite":"Suite 729","city":"Aliyaview","zipcode":"45169","geo":{"lat":"-14.3990","lng":"-120.7677"}},"phone":"586.493.6943 x140","website":"jacynthe.com","company":{"name":"Abernathy Group","catchPhrase":"Implemented secondary concept","bs":"e-enable extensible e-tailers"}},{"id":9,"name":"Glenna Reichert","username":"Delphine","email":"Chaim_McDermott@dana.io","address":{"street":"Dayna Park","suite":"Suite 449","city":"Bartholomebury","zipcode":"76495-3109","geo":{"lat":"24.6463","lng":"-168.8889"}},"phone":"(775)976-6794 x41206","website":"conrad.com","company":{"name":"Yost and Sons","catchPhrase":"Switchable contextually-based project","bs":"aggregate real-time technologies"}},{"id":10,"name":"Clementina DuBuque","username":"Moriah.Stanton","email":"Rey.Padberg@karina.biz","address":{"street":"Kattie Turnpike","suite":"Suite 198","city":"Lebsackbury","zipcode":"31428-2261","geo":{"lat":"-38.2386","lng":"57.2232"}},"phone":"024-648-3804","website":"ambrose.net","company":{"name":"Hoeger LLC","catchPhrase":"Centralized empowering task-force","bs":"target end-to-end models"}}] \ No newline at end of file +{"method":"POST","protocol":"https","host":"echo.free.beeceptor.com","path":"/","ip":"4.1.157.141:65103","headers":{"Host":"echo.free.beeceptor.com","Transfer-Encoding":"chunked","Content-Type":"application/json","Accept-Encoding":"gzip"},"parsedQueryParams":{},"parsedBody":{"hello":"world"}} \ No newline at end of file From ef48a6ed304e9854d72dba14c036c4c85ba74b5a Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 07:12:01 -0400 Subject: [PATCH 14/52] add async stream fetch test 4 --- tests/e2e/fetch-3/expect_serve_body.txt | 2 +- tests/e2e/fetch-3/fetch-3.js | 11 ++-- tests/e2e/fetch-4/expect_serve_body.txt | 1 + tests/e2e/fetch-4/expect_serve_stderr.txt | 0 tests/e2e/fetch-4/expect_serve_stdout.txt | 1 + tests/e2e/fetch-4/fetch-4.js | 62 +++++++++++++++++++++++ tests/tests.cmake | 1 + 7 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 tests/e2e/fetch-4/expect_serve_body.txt create mode 100644 tests/e2e/fetch-4/expect_serve_stderr.txt create mode 100644 tests/e2e/fetch-4/expect_serve_stdout.txt create mode 100644 tests/e2e/fetch-4/fetch-4.js diff --git a/tests/e2e/fetch-3/expect_serve_body.txt b/tests/e2e/fetch-3/expect_serve_body.txt index e6535c21..76088acf 100644 --- a/tests/e2e/fetch-3/expect_serve_body.txt +++ b/tests/e2e/fetch-3/expect_serve_body.txt @@ -1 +1 @@ -{"method":"POST","protocol":"https","host":"echo.free.beeceptor.com","path":"/","ip":"4.1.157.141:65103","headers":{"Host":"echo.free.beeceptor.com","Transfer-Encoding":"chunked","Content-Type":"application/json","Accept-Encoding":"gzip"},"parsedQueryParams":{},"parsedBody":{"hello":"world"}} \ No newline at end of file +{"method":"POST","parsedBody":{"hello":"world"}} \ No newline at end of file diff --git a/tests/e2e/fetch-3/fetch-3.js b/tests/e2e/fetch-3/fetch-3.js index 778303de..6390b508 100644 --- a/tests/e2e/fetch-3/fetch-3.js +++ b/tests/e2e/fetch-3/fetch-3.js @@ -8,8 +8,6 @@ async function main(event) { }); event.respondWith(responsePromise); - console.log("fetching") - let response = await fetch("https://echo.free.beeceptor.com", { method: "POST", headers: { @@ -18,12 +16,15 @@ async function main(event) { body: JSON.stringify({hello: "world"}) }) - console.log("fetch executed") - let responseJson = await response.json(); + let result = { + method: responseJson.method, + parsedBody: responseJson.parsedBody, + }; + console.log("Successfully received response json body"); - resolve(new Response(JSON.stringify(responseJson))); + resolve(new Response(JSON.stringify(result))); } catch (e) { console.log(`Error: ${e}. Stack: ${e.stack}`); } diff --git a/tests/e2e/fetch-4/expect_serve_body.txt b/tests/e2e/fetch-4/expect_serve_body.txt new file mode 100644 index 00000000..76088acf --- /dev/null +++ b/tests/e2e/fetch-4/expect_serve_body.txt @@ -0,0 +1 @@ +{"method":"POST","parsedBody":{"hello":"world"}} \ No newline at end of file diff --git a/tests/e2e/fetch-4/expect_serve_stderr.txt b/tests/e2e/fetch-4/expect_serve_stderr.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/fetch-4/expect_serve_stdout.txt b/tests/e2e/fetch-4/expect_serve_stdout.txt new file mode 100644 index 00000000..7bcdc8dc --- /dev/null +++ b/tests/e2e/fetch-4/expect_serve_stdout.txt @@ -0,0 +1 @@ +stdout [0] :: Log: Successfully received response json body diff --git a/tests/e2e/fetch-4/fetch-4.js b/tests/e2e/fetch-4/fetch-4.js new file mode 100644 index 00000000..65d9e5c3 --- /dev/null +++ b/tests/e2e/fetch-4/fetch-4.js @@ -0,0 +1,62 @@ +async function main(event) { + let resolve, reject; + + // Function to create an async stream + function createAsyncStream() { + const encoder = new TextEncoder(); + const readableStream = new ReadableStream({ + start(controller) { + const chunk = { hello: "world" }; + const encodedChunk = encoder.encode(JSON.stringify(chunk)); + + // Simulate an async operation + setTimeout(() => { + controller.enqueue(encodedChunk); + controller.close(); + }, 1000); // 1 second delay + } + }); + return readableStream; + } + + try { + let responsePromise = new Promise(async (res, rej) => { + resolve = res; + reject = rej; + }); + event.respondWith(responsePromise); + + console.log("Sending fetch request with async stream"); + + // Create the async stream + const asyncStream = createAsyncStream(); + + // Perform the fetch request with the async stream as the body + let response = await fetch("https://echo.free.beeceptor.com", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: asyncStream + }); + + console.log("Fetch request success"); + + let responseJson = await response.json(); + + console.log("Response JSON:", responseJson); + + let result = { + method: responseJson.method, + parsedBody: responseJson.parsedBody, + }; + + console.log("Successfully received response json body:", result); + + resolve(new Response(JSON.stringify(result))); + } catch (e) { + console.log(`Error: ${e}. Stack: ${e.stack}`); + } +} + +addEventListener('fetch', main); diff --git a/tests/tests.cmake b/tests/tests.cmake index c0362570..dbf9b222 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -33,6 +33,7 @@ test_e2e(smoke) test_e2e(fetch-1) test_e2e(fetch-2) test_e2e(fetch-3) +test_e2e(fetch-4) test_e2e(tla) test_e2e(syntax-err) test_e2e(tla-err) From 383f1380395ed6144dcbf07ae7e6e791b6617428 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 07:21:23 -0400 Subject: [PATCH 15/52] remove debug stderror logs --- builtins/web/fetch/fetch-api.cpp | 24 ++------------- builtins/web/fetch/headers.cpp | 11 ------- builtins/web/fetch/request-response.cpp | 41 +++++-------------------- host-apis/wasi-0.2.0/host_api.cpp | 21 ------------- 4 files changed, 10 insertions(+), 87 deletions(-) diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 586bac1e..8bc67a16 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -15,31 +15,26 @@ class ResponseFutureTask final : public api::AsyncTask { explicit ResponseFutureTask(const HandleObject request, host_api::FutureHttpIncomingResponse *future) : request_(request), future_(future) { - fprintf(stderr, "ResponseFutureTask::ResponseFutureTask\n"); ensure_no_response(ENGINE->cx(), request, future); auto res = future->subscribe(); MOZ_ASSERT(!res.is_err(), "Subscribing to a future should never fail"); this->handle_ = res.unwrap(); - fprintf(stderr, "ResponseFutureTask::ResponseFutureTask END!\n"); } [[nodiscard]] bool run(api::Engine *engine) override { - fprintf(stderr, "ResponseFutureTask::run\n"); // MOZ_ASSERT(ready()); JSContext *cx = engine->cx(); const RootedObject request(cx, request_); RootedObject response_promise(cx, Request::response_promise(request)); - fprintf(stderr, "ResponseFutureTask::run: maybe_response\n"); auto res = future_->maybe_response(); if (res.is_err()) { JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource."); return RejectPromiseWithPendingError(cx, response_promise); } - fprintf(stderr, "ResponseFutureTask::run: maybe_response\n"); auto maybe_response = res.unwrap(); MOZ_ASSERT(maybe_response.has_value()); auto response = maybe_response.value(); @@ -64,7 +59,6 @@ class ResponseFutureTask final : public api::AsyncTask { } [[nodiscard]] bool cancel(api::Engine *engine) override { - fprintf(stderr, "ResponseFutureTask::cancel\n"); // TODO(TS): implement handle_ = -1; return true; @@ -110,9 +104,7 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { host_api::FutureHttpIncomingResponse *pending_handle; { auto request_handle = Request::outgoing_handle(request); - fprintf(stderr, "fetch: sending request\n"); auto res = request_handle->send(); - fprintf(stderr, "fetch: send initiated\n"); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); @@ -128,18 +120,10 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { // If the request body is streamed, we need to wait for streaming to complete // before marking the request as pending. if (!streaming) { - fprintf(stderr, "fetch: scheduling response future task\n"); - - if (ensure_no_response(cx, request, pending_handle)) { - fprintf(stderr, "fetch: maybe_response has value!\n"); - } else { - fprintf(stderr, "fetch: maybe_response has no value! Future queued.\n"); - - ENGINE->queue_async_task(new ResponseFutureTask(request, pending_handle)); - } + ensure_no_response(cx, request, pending_handle); + ENGINE->queue_async_task(new ResponseFutureTask(request, pending_handle)); } - fprintf(stderr, "fetch: returning response promise\n"); args.rval().setObject(*response_promise); return true; } @@ -150,17 +134,14 @@ bool ensure_no_response(JSContext *cx, JS::HandleObject req, const RootedObject request(cx, req); RootedObject response_promise(cx, Request::response_promise(request)); - fprintf(stderr, "ensure_no_response: maybe_response\n"); auto res = future->maybe_response(); if (res.is_err()) { JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource."); return RejectPromiseWithPendingError(cx, response_promise); } - fprintf(stderr, "ensure_no_response: maybe_response is not error \n"); auto maybe_response = res.unwrap(); if (maybe_response.has_value()) { - fprintf(stderr, "ensure_no_response: maybe_response has value!\n"); auto response = maybe_response.value(); RootedObject response_obj( cx, JS_NewObjectWithGivenProto(cx, &Response::class_, Response::proto_obj)); @@ -181,7 +162,6 @@ bool ensure_no_response(JSContext *cx, JS::HandleObject req, return true; } } else { - fprintf(stderr, "ensure_no_response: maybe_response has no value\n"); return false; } } diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 2b24c72b..c58e640e 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -73,13 +73,6 @@ Handle* get_handle(JSObject* self) { JS::GetReservedSlot(self, static_cast(Headers::Slots::Handle)).toPrivate(); auto result = static_cast(handle); - if (result != nullptr) { - int32_t handleValue = result->handle_state_->handle; - fprintf(stderr, "get_handle retrieved Resource %p with handle value %d\n", result, handleValue); - } else { - fprintf(stderr, "get_handle retrieved nullptr\n"); - } - return result; } @@ -453,7 +446,6 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand for (auto value : splitCookiesString(value)) { auto res = handle->append(name, value); if (auto *err = res.to_err()) { - fprintf(stderr, "handle->append error 1\n"); HANDLE_ERROR(cx, *err); return false; } @@ -462,7 +454,6 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand std::string_view value = value_chars; auto res = handle->append(name, value); if (auto *err = res.to_err()) { - fprintf(stderr, "handle->append error 2\n"); HANDLE_ERROR(cx, *err); return false; } @@ -535,7 +526,6 @@ JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHe JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, JS::HandleValue initv) { - fprintf(stderr, "Headers::create from HandleValue\n"); JS::RootedObject headers(cx, create(cx, self, handle)); if (!headers) return nullptr; @@ -782,7 +772,6 @@ bool Headers::init_class(JSContext *cx, JS::HandleObject global) { } JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle) { - fprintf(stderr, "Headers::create storing handle %p %d\n", handle, handle->handle_state_->handle); JS_SetReservedSlot(self, static_cast(Slots::Handle), JS::PrivateValue(handle)); JS::RootedObject backing_map(cx, JS::NewMapObject(cx)); diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 35a97fd5..da45de0d 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -423,9 +423,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, auto body = RequestOrResponse::outgoing_body_handle(self); auto write_res = body->write_all(reinterpret_cast(buf), length); - fprintf(stderr, "Closing outgoing body\n"); body->close(); - fprintf(stderr, "Closed outgoing body\n"); // Ensure that the NoGC is reset, so throwing an error in HANDLE_ERROR // succeeds. @@ -442,10 +440,8 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, // Step 36.3 of Request constructor / 8.4 of Response constructor. if (content_type) { // Headers do not contain a valid resource reference. - fprintf(stderr, "Creating headers %s\n", content_type); JS::RootedObject headers(cx, RequestOrResponse::headers(cx, self)); if (!headers) { - fprintf(stderr, "FAILED TO EXTRACT OR CREATE HEADERS\n"); return false; } if (!Headers::maybe_add(cx, headers, "content-type", content_type)) { @@ -487,39 +483,26 @@ bool RequestOrResponse::append_body(JSContext *cx, JS::HandleObject self, JS::Ha JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { JSObject *headers = maybe_headers(obj); if (!headers) { - fprintf(stderr, "maybe_headers is null\n"); - JS::RootedObject headersInstance( cx, JS_NewObjectWithGivenProto(cx, &Headers::class_, Headers::proto_obj)); if (!headersInstance) { - fprintf(stderr, "headersInstance is null\n"); return nullptr; - } else { - fprintf(stderr, "headersInstance is NOT null\n"); - } + } auto *headers_handle = RequestOrResponse::headers_handle(obj); if (!headers_handle) { // Error is here? is this not creating a valid resource? auto result = new host_api::HttpHeaders(); - fprintf(stderr, "headers_handle is null\n"); headers_handle = result; - } else { - fprintf(stderr, "headers_handle is NOT null\n"); - } + } headers = Headers::create(cx, headersInstance, headers_handle); if (!headers) { - fprintf(stderr, "Headers::create is null\n"); return nullptr; - } else { - fprintf(stderr, "Headers::create is NOT null\n"); - } + } JS_SetReservedSlot(obj, static_cast(Slots::Headers), JS::ObjectValue(*headers)); - } else { - fprintf(stderr, "maybe_headers is NOT null\n"); - } + } return headers; } @@ -1730,8 +1713,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // `init["headers"]` exists, create the request's `headers` from that, // otherwise create it from the `init` object's `headers`, or create a new, // empty one. - auto *headers_handle = new host_api::HttpHeaders(); // ************************* - fprintf(stderr, "Created Header Resource: %p %d\n", headers_handle, headers_handle->handle_state_->handle); + auto *headers_handle = new host_api::HttpHeaders(); JS::RootedObject headers(cx); @@ -1747,9 +1729,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H headers = Headers::create(cx, headersInstance, headers_handle, headers_val); if (!headers) { return nullptr; - } else { - fprintf(stderr, "headers successfully created\n"); - } + } } // 33. Let `inputBody` be `input`’s requests body if `input` is a `Request` @@ -1808,7 +1788,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // ``Content-Type``, then append (``Content-Type``, `Content-Type`) to // this’s headers. // Note: these steps are all inlined into RequestOrResponse::extract_body. - if (!RequestOrResponse::extract_body(cx, request, body_val)) { // ************************* + if (!RequestOrResponse::extract_body(cx, request, body_val)) { return nullptr; } } else if (input_has_body) { @@ -2652,8 +2632,6 @@ JSObject *Response::create(JSContext *cx, JS::HandleObject response, MOZ_ASSERT(is_instance(response)); MOZ_ASSERT(response_handle); - fprintf(stderr, "Response::create\n"); - JS::SetReservedSlot(response, static_cast(Slots::Response), JS::PrivateValue(response_handle)); JS::SetReservedSlot(response, static_cast(Slots::Headers), JS::NullValue()); @@ -2663,7 +2641,6 @@ JSObject *Response::create(JSContext *cx, JS::HandleObject response, JS::SetReservedSlot(response, static_cast(Slots::Redirected), JS::FalseValue()); if (response_handle->is_incoming()) { - fprintf(stderr, "Response::create: response handle INCOMING\n"); auto res = reinterpret_cast(response_handle)->status(); MOZ_ASSERT(!res.is_err(), "TODO: proper error handling"); auto status = res.unwrap(); @@ -2673,9 +2650,7 @@ JSObject *Response::create(JSContext *cx, JS::HandleObject response, if (!(status == 204 || status == 205 || status == 304)) { JS::SetReservedSlot(response, static_cast(Slots::HasBody), JS::TrueValue()); } - } else { - fprintf(stderr, "Response::create: response handle NOT INCOMING\n"); - } + } return response; } diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index ed46e9dd..0b9f8b9c 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -98,26 +98,17 @@ template <> struct HandleOps { size_t api::AsyncTask::select(std::vector *tasks) { auto count = tasks->size(); - fprintf(stderr, "api::AsyncTask::select %zu\n", count); vector> handles; for (const auto task : *tasks) { auto id = task->id(); - fprintf(stderr, "api::AsyncTask::select task %d\n", reinterpret_cast(id)); handles.emplace_back(id); } auto list = list_borrow_pollable_t{ reinterpret_cast::borrow *>(handles.data()), count}; - for (int i = 0; i < list.len; i++) { - bool ready = wasi_io_0_2_0_poll_method_pollable_ready(list.ptr[i]); - fprintf(stderr, "api::AsyncTask::select %d ready: %d\n", i, ready); - } - - fprintf(stderr, "api::AsyncTask::select before poll\n"); wasi_io_0_2_0_poll_list_u32_t result{nullptr, 0}; wasi_io_0_2_0_poll_poll(&list, &result); - fprintf(stderr, "api::AsyncTask::select after poll\n"); MOZ_ASSERT(result.len > 0); const auto ready_index = result.ptr[0]; free(result.ptr); @@ -199,16 +190,6 @@ void MonotonicClock::unsubscribe(const int32_t handle_id) { HttpHeaders::HttpHeaders() { this->handle_state_ = new HandleState(wasi_http_0_2_0_types_constructor_fields().__handle); - fprintf(stderr, "wasi_http_0_2_0_types_constructor_fields returned with %d\n", this->handle_state_->handle); - - Result> names_result = this->names(); - MOZ_ASSERT(!names_result.is_err()); - vector &names = names_result.unwrap(); - fprintf(stderr, "wasi_http_0_2_0_types_constructor_fields ==> len: %zu", names.size()); - for (auto &str : names) { - fprintf(stderr, "wasi_http_0_2_0_types_constructor_fields ==> %zu", str.size()); - } - } HttpHeaders::HttpHeaders(Handle handle) { handle_state_ = new HandleState(handle); } @@ -769,11 +750,9 @@ Result HttpOutgoingRequest::send() { MOZ_ASSERT(valid()); if (this->headers_) { - fprintf(stderr, "HttpOutgoingRequest::send dropping borrowed headers"); wasi_http_0_2_0_types_fields_drop_borrow({this->headers_->handle_state_->handle}); } - fprintf(stderr, "HttpOutgoingRequest::send calling handle"); future_incoming_response_t ret; wasi_http_0_2_0_outgoing_handler_error_code_t err; From b1019053ad10c8f82d1c562d814c675a34909d75 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 07:37:43 -0400 Subject: [PATCH 16/52] small clean up --- CMakeLists.txt | 1 - builtins/web/fetch/fetch-api.cpp | 40 ------------------------- builtins/web/fetch/fetch-api.h | 2 -- builtins/web/fetch/headers.cpp | 1 - builtins/web/fetch/request-response.cpp | 6 ++-- 5 files changed, 3 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c16b500..92184809 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,5 @@ function(componentize OUTPUT) endfunction() componentize(smoke-test SOURCES tests/cases/smoke/smoke.js) -componentize(fetch-test SOURCES tests/cases/fetch/fetch.js) include("tests/tests.cmake") diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 8bc67a16..768c6de5 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -15,7 +15,6 @@ class ResponseFutureTask final : public api::AsyncTask { explicit ResponseFutureTask(const HandleObject request, host_api::FutureHttpIncomingResponse *future) : request_(request), future_(future) { - ensure_no_response(ENGINE->cx(), request, future); auto res = future->subscribe(); MOZ_ASSERT(!res.is_err(), "Subscribing to a future should never fail"); this->handle_ = res.unwrap(); @@ -120,7 +119,6 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { // If the request body is streamed, we need to wait for streaming to complete // before marking the request as pending. if (!streaming) { - ensure_no_response(cx, request, pending_handle); ENGINE->queue_async_task(new ResponseFutureTask(request, pending_handle)); } @@ -128,44 +126,6 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { return true; } -bool ensure_no_response(JSContext *cx, JS::HandleObject req, - host_api::FutureHttpIncomingResponse *future) { - - const RootedObject request(cx, req); - RootedObject response_promise(cx, Request::response_promise(request)); - - auto res = future->maybe_response(); - if (res.is_err()) { - JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource."); - return RejectPromiseWithPendingError(cx, response_promise); - } - - auto maybe_response = res.unwrap(); - if (maybe_response.has_value()) { - auto response = maybe_response.value(); - RootedObject response_obj( - cx, JS_NewObjectWithGivenProto(cx, &Response::class_, Response::proto_obj)); - if (!response_obj) { - return false; - } - - response_obj = Response::create(cx, response_obj, response); - if (!response_obj) { - return false; - } - - RequestOrResponse::set_url(response_obj, RequestOrResponse::url(request)); - RootedValue response_val(cx, ObjectValue(*response_obj)); - if (!ResolvePromise(cx, response_promise, response_val)) { - return false; - } else { - return true; - } - } else { - return false; - } -} - const JSFunctionSpec methods[] = {JS_FN("fetch", fetch, 2, JSPROP_ENUMERATE), JS_FS_END}; bool install(api::Engine *engine) { diff --git a/builtins/web/fetch/fetch-api.h b/builtins/web/fetch/fetch-api.h index 8af9d063..a0b45c3d 100644 --- a/builtins/web/fetch/fetch-api.h +++ b/builtins/web/fetch/fetch-api.h @@ -9,8 +9,6 @@ namespace web { namespace fetch { bool install(api::Engine *engine); -bool ensure_no_response(JSContext *cx, HandleObject request, - host_api::FutureHttpIncomingResponse *future); } // namespace fetch } // namespace web diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index c58e640e..edd53f2e 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -484,7 +484,6 @@ JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHe return nullptr; } - // TODO: does this do anything? JS::RootedObject headers_map(cx, get_backing_map(headers)); JS::RootedObject init_map(cx, get_backing_map(init_headers)); diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index da45de0d..5291e859 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -499,7 +499,7 @@ JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { headers = Headers::create(cx, headersInstance, headers_handle); if (!headers) { return nullptr; - } + } JS_SetReservedSlot(obj, static_cast(Slots::Headers), JS::ObjectValue(*headers)); } @@ -1729,7 +1729,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H headers = Headers::create(cx, headersInstance, headers_handle, headers_val); if (!headers) { return nullptr; - } + } } // 33. Let `inputBody` be `input`’s requests body if `input` is a `Request` @@ -1788,7 +1788,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // ``Content-Type``, then append (``Content-Type``, `Content-Type`) to // this’s headers. // Note: these steps are all inlined into RequestOrResponse::extract_body. - if (!RequestOrResponse::extract_body(cx, request, body_val)) { + if (!RequestOrResponse::extract_body(cx, request, body_val)) { return nullptr; } } else if (input_has_body) { From f666d6dfec176d0611a040dc345d3c2b84a45188 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 08:08:42 -0400 Subject: [PATCH 17/52] clean up --- builtins/web/fetch/fetch-api.cpp | 6 +++--- builtins/web/fetch/fetch-api.h | 1 - builtins/web/fetch/headers.cpp | 4 +--- builtins/web/fetch/request-response.cpp | 4 +--- include/host_api.h | 4 ++-- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 768c6de5..51e990d5 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -113,15 +113,15 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { pending_handle = res.unwrap(); } - JS::SetReservedSlot(request, static_cast(Request::Slots::ResponsePromise), - JS::ObjectValue(*response_promise)); - // If the request body is streamed, we need to wait for streaming to complete // before marking the request as pending. if (!streaming) { ENGINE->queue_async_task(new ResponseFutureTask(request, pending_handle)); } + JS::SetReservedSlot(request, static_cast(Request::Slots::ResponsePromise), + JS::ObjectValue(*response_promise)); + args.rval().setObject(*response_promise); return true; } diff --git a/builtins/web/fetch/fetch-api.h b/builtins/web/fetch/fetch-api.h index a0b45c3d..9d6807e6 100644 --- a/builtins/web/fetch/fetch-api.h +++ b/builtins/web/fetch/fetch-api.h @@ -2,7 +2,6 @@ #define BUILTINS_WEB_FETCH_API_H #include "builtin.h" -#include "request-response.h" namespace builtins { namespace web { diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index edd53f2e..d52828ce 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -71,9 +71,7 @@ Handle* get_handle(JSObject* self) { MOZ_ASSERT(Headers::is_instance(self)); auto handle = JS::GetReservedSlot(self, static_cast(Headers::Slots::Handle)).toPrivate(); - auto result = static_cast(handle); - - return result; + return static_cast(handle); } /** diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 5291e859..1b961e5a 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -439,7 +439,6 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, // Step 36.3 of Request constructor / 8.4 of Response constructor. if (content_type) { - // Headers do not contain a valid resource reference. JS::RootedObject headers(cx, RequestOrResponse::headers(cx, self)); if (!headers) { return false; @@ -492,7 +491,6 @@ JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { auto *headers_handle = RequestOrResponse::headers_handle(obj); if (!headers_handle) { - // Error is here? is this not creating a valid resource? auto result = new host_api::HttpHeaders(); headers_handle = result; } @@ -2650,7 +2648,7 @@ JSObject *Response::create(JSContext *cx, JS::HandleObject response, if (!(status == 204 || status == 205 || status == 304)) { JS::SetReservedSlot(response, static_cast(Slots::HasBody), JS::TrueValue()); } - } + } return response; } diff --git a/include/host_api.h b/include/host_api.h index 8d3f1a9e..36f220a4 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -217,10 +217,10 @@ class HandleState { }; class Resource { -public: +protected: HandleState *handle_state_; virtual ~Resource() = default; - +public: /// Returns true when this resource handle is valid. virtual bool valid() const { return this->handle_state_ != nullptr; } }; From 76f5f40f8b8d4578afde43f11118bd92e6d66dcf Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 08:10:54 -0400 Subject: [PATCH 18/52] small cleanup --- builtins/web/fetch/request-response.cpp | 7 +++---- host-apis/wasi-0.2.0/host_api.cpp | 3 +-- include/host_api.h | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 1b961e5a..b4212950 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -487,12 +487,11 @@ JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { if (!headersInstance) { return nullptr; - } + } auto *headers_handle = RequestOrResponse::headers_handle(obj); if (!headers_handle) { - auto result = new host_api::HttpHeaders(); - headers_handle = result; + headers_handle = new host_api::HttpHeaders(); } headers = Headers::create(cx, headersInstance, headers_handle); if (!headers) { @@ -500,7 +499,7 @@ JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { } JS_SetReservedSlot(obj, static_cast(Slots::Headers), JS::ObjectValue(*headers)); - } + } return headers; } diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 0b9f8b9c..86c0a961 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -100,8 +100,7 @@ size_t api::AsyncTask::select(std::vector *tasks) { auto count = tasks->size(); vector> handles; for (const auto task : *tasks) { - auto id = task->id(); - handles.emplace_back(id); + handles.emplace_back(task->id()); } auto list = list_borrow_pollable_t{ reinterpret_cast::borrow *>(handles.data()), count}; diff --git a/include/host_api.h b/include/host_api.h index 36f220a4..81441b80 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -219,8 +219,8 @@ class HandleState { class Resource { protected: HandleState *handle_state_; - virtual ~Resource() = default; public: + virtual ~Resource() = default; /// Returns true when this resource handle is valid. virtual bool valid() const { return this->handle_state_ != nullptr; } }; From c9b42e4f5cdc3158d3ebd0355b065e36124b626c Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 08:14:51 -0400 Subject: [PATCH 19/52] small cleanup --- builtins/web/fetch/fetch-api.cpp | 2 +- builtins/web/fetch/headers.cpp | 1 - builtins/web/fetch/request-response.cpp | 2 +- include/host_api.h | 2 ++ 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 51e990d5..9a7eaf38 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -120,7 +120,7 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { } JS::SetReservedSlot(request, static_cast(Request::Slots::ResponsePromise), - JS::ObjectValue(*response_promise)); + JS::ObjectValue(*response_promise)); args.rval().setObject(*response_promise); return true; diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index d52828ce..6560e7d3 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -466,7 +466,6 @@ bool Headers::delazify(JSContext *cx, JS::HandleObject headers) { return ensure_all_header_values_from_handle(cx, headers, backing_map); } -// Create header from iterable. JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, JS::HandleObject init_headers) { JS::RootedObject headers(cx, Headers::create(cx, self, handle)); diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index b4212950..c4dfdd84 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -492,7 +492,7 @@ JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { auto *headers_handle = RequestOrResponse::headers_handle(obj); if (!headers_handle) { headers_handle = new host_api::HttpHeaders(); - } + } headers = Headers::create(cx, headersInstance, headers_handle); if (!headers) { return nullptr; diff --git a/include/host_api.h b/include/host_api.h index 81441b80..4e8abba7 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -219,8 +219,10 @@ class HandleState { class Resource { protected: HandleState *handle_state_; + public: virtual ~Resource() = default; + /// Returns true when this resource handle is valid. virtual bool valid() const { return this->handle_state_ != nullptr; } }; From 62ea77d72288bf188078ca2b9bbec7ce0e05b852 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 08:17:08 -0400 Subject: [PATCH 20/52] more cleanup --- builtins/web/fetch/fetch-api.cpp | 3 +-- builtins/web/fetch/headers.cpp | 6 +++--- host-apis/wasi-0.2.0/host_api.cpp | 2 -- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 9a7eaf38..9f45119d 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -17,8 +17,7 @@ class ResponseFutureTask final : public api::AsyncTask { : request_(request), future_(future) { auto res = future->subscribe(); MOZ_ASSERT(!res.is_err(), "Subscribing to a future should never fail"); - this->handle_ = res.unwrap(); - + handle_ = res.unwrap(); } [[nodiscard]] bool run(api::Engine *engine) override { diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 6560e7d3..32f3d8fc 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -67,11 +67,11 @@ bool lazy_values(JSObject *self) { .toBoolean(); } -Handle* get_handle(JSObject* self) { +Handle *get_handle(JSObject *self) { MOZ_ASSERT(Headers::is_instance(self)); auto handle = JS::GetReservedSlot(self, static_cast(Headers::Slots::Handle)).toPrivate(); - return static_cast(handle); + return static_cast(handle); } /** @@ -468,7 +468,7 @@ bool Headers::delazify(JSContext *cx, JS::HandleObject headers) { JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, JS::HandleObject init_headers) { - JS::RootedObject headers(cx, Headers::create(cx, self, handle)); + JS::RootedObject headers(cx, create(cx, self, handle)); if (!headers) { return nullptr; } diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 86c0a961..1f3e312e 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -104,10 +104,8 @@ size_t api::AsyncTask::select(std::vector *tasks) { } auto list = list_borrow_pollable_t{ reinterpret_cast::borrow *>(handles.data()), count}; - wasi_io_0_2_0_poll_list_u32_t result{nullptr, 0}; wasi_io_0_2_0_poll_poll(&list, &result); - MOZ_ASSERT(result.len > 0); const auto ready_index = result.ptr[0]; free(result.ptr); From d0bb3bf9a6c5ce29dbc840b0a3a628acdd5e2854 Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 08:37:15 -0400 Subject: [PATCH 21/52] remove fetch-4 test --- tests/e2e/fetch-4/expect_serve_body.txt | 1 - tests/e2e/fetch-4/expect_serve_stderr.txt | 0 tests/e2e/fetch-4/expect_serve_stdout.txt | 1 - tests/e2e/fetch-4/fetch-4.js | 62 ----------------------- tests/tests.cmake | 1 - 5 files changed, 65 deletions(-) delete mode 100644 tests/e2e/fetch-4/expect_serve_body.txt delete mode 100644 tests/e2e/fetch-4/expect_serve_stderr.txt delete mode 100644 tests/e2e/fetch-4/expect_serve_stdout.txt delete mode 100644 tests/e2e/fetch-4/fetch-4.js diff --git a/tests/e2e/fetch-4/expect_serve_body.txt b/tests/e2e/fetch-4/expect_serve_body.txt deleted file mode 100644 index 76088acf..00000000 --- a/tests/e2e/fetch-4/expect_serve_body.txt +++ /dev/null @@ -1 +0,0 @@ -{"method":"POST","parsedBody":{"hello":"world"}} \ No newline at end of file diff --git a/tests/e2e/fetch-4/expect_serve_stderr.txt b/tests/e2e/fetch-4/expect_serve_stderr.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/e2e/fetch-4/expect_serve_stdout.txt b/tests/e2e/fetch-4/expect_serve_stdout.txt deleted file mode 100644 index 7bcdc8dc..00000000 --- a/tests/e2e/fetch-4/expect_serve_stdout.txt +++ /dev/null @@ -1 +0,0 @@ -stdout [0] :: Log: Successfully received response json body diff --git a/tests/e2e/fetch-4/fetch-4.js b/tests/e2e/fetch-4/fetch-4.js deleted file mode 100644 index 65d9e5c3..00000000 --- a/tests/e2e/fetch-4/fetch-4.js +++ /dev/null @@ -1,62 +0,0 @@ -async function main(event) { - let resolve, reject; - - // Function to create an async stream - function createAsyncStream() { - const encoder = new TextEncoder(); - const readableStream = new ReadableStream({ - start(controller) { - const chunk = { hello: "world" }; - const encodedChunk = encoder.encode(JSON.stringify(chunk)); - - // Simulate an async operation - setTimeout(() => { - controller.enqueue(encodedChunk); - controller.close(); - }, 1000); // 1 second delay - } - }); - return readableStream; - } - - try { - let responsePromise = new Promise(async (res, rej) => { - resolve = res; - reject = rej; - }); - event.respondWith(responsePromise); - - console.log("Sending fetch request with async stream"); - - // Create the async stream - const asyncStream = createAsyncStream(); - - // Perform the fetch request with the async stream as the body - let response = await fetch("https://echo.free.beeceptor.com", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: asyncStream - }); - - console.log("Fetch request success"); - - let responseJson = await response.json(); - - console.log("Response JSON:", responseJson); - - let result = { - method: responseJson.method, - parsedBody: responseJson.parsedBody, - }; - - console.log("Successfully received response json body:", result); - - resolve(new Response(JSON.stringify(result))); - } catch (e) { - console.log(`Error: ${e}. Stack: ${e.stack}`); - } -} - -addEventListener('fetch', main); diff --git a/tests/tests.cmake b/tests/tests.cmake index 23b6704e..5f4ea8b3 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -33,7 +33,6 @@ test_e2e(smoke) test_e2e(fetch-1) test_e2e(fetch-2) test_e2e(fetch-3) -test_e2e(fetch-4) test_e2e(tla) test_e2e(syntax-err) test_e2e(tla-err) From f7aacf1ff87e4b8c805c219d0238277009865c1e Mon Sep 17 00:00:00 2001 From: Nico Burniske Date: Mon, 20 May 2024 08:43:26 -0400 Subject: [PATCH 22/52] remove clean_test.sh --- clean_test.sh | 7 ------- 1 file changed, 7 deletions(-) delete mode 100755 clean_test.sh diff --git a/clean_test.sh b/clean_test.sh deleted file mode 100755 index 9b0ef01f..00000000 --- a/clean_test.sh +++ /dev/null @@ -1,7 +0,0 @@ -rm -rf cmake-build-debug - -cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug - -cmake --build cmake-build-debug --target integration-test-server - -CTEST_OUTPUT_ON_FAILURE=1 ctest --test-dir cmake-build-debug -j8 From 34cf1ef78905ff6491c9659a69b7331a99955bf3 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Wed, 29 May 2024 13:32:27 +0200 Subject: [PATCH 23/52] Use SpiderMonkey's encoding_c instead of our own crate SpiderMonkey uses the `encoding_c` Rust crate and comes bundled with it. This lead to either duplication or linking errors when using our own crate re-exporting `encoding_c`. Instead, with this PR we just bundle a C header file for interacting with the crate's functionality, and rely on SpiderMonkey's version. --- CMakeLists.txt | 2 +- cmake/build-crates.cmake | 3 -- crates/rust-encoding/.gitignore | 1 - crates/rust-encoding/Cargo.lock | 34 ------------ crates/rust-encoding/Cargo.toml | 17 ------ crates/rust-encoding/cbindgen.toml | 54 ------------------- crates/rust-encoding/src/lib.rs | 1 - .../include}/rust-encoding.h | 3 ++ 8 files changed, 4 insertions(+), 111 deletions(-) delete mode 100644 crates/rust-encoding/.gitignore delete mode 100644 crates/rust-encoding/Cargo.lock delete mode 100644 crates/rust-encoding/Cargo.toml delete mode 100644 crates/rust-encoding/cbindgen.toml delete mode 100644 crates/rust-encoding/src/lib.rs rename {crates/rust-encoding => deps/include}/rust-encoding.h (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92184809..0691f3d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ include("host_api") include("build-crates") add_library(extension_api INTERFACE include/extension-api.h runtime/encode.h) -target_link_libraries(extension_api INTERFACE rust-url rust-encoding spidermonkey) +target_link_libraries(extension_api INTERFACE rust-url spidermonkey) target_include_directories(extension_api INTERFACE include deps/include runtime) include("builtins") diff --git a/cmake/build-crates.cmake b/cmake/build-crates.cmake index 39a86451..6c8cc5dc 100644 --- a/cmake/build-crates.cmake +++ b/cmake/build-crates.cmake @@ -1,5 +1,2 @@ corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url/Cargo.toml NO_LINKER_OVERRIDE) set_property(TARGET rust-url PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-url/) - -corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-encoding/Cargo.toml NO_LINKER_OVERRIDE) -set_property(TARGET rust-encoding PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/crates/rust-encoding/) diff --git a/crates/rust-encoding/.gitignore b/crates/rust-encoding/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/crates/rust-encoding/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/crates/rust-encoding/Cargo.lock b/crates/rust-encoding/Cargo.lock deleted file mode 100644 index 8e0ce86a..00000000 --- a/crates/rust-encoding/Cargo.lock +++ /dev/null @@ -1,34 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "encoding_c" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af727805f3b0d79956bde5b35732669fb5c5d45a94893798e7b7e70cfbf9cc1" -dependencies = [ - "encoding_rs", -] - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "rust-encoding" -version = "0.1.0" -dependencies = [ - "encoding_c", -] diff --git a/crates/rust-encoding/Cargo.toml b/crates/rust-encoding/Cargo.toml deleted file mode 100644 index 94194989..00000000 --- a/crates/rust-encoding/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "rust-encoding" -version = "0.1.0" -edition = "2018" - -[lib] -crate-type = ["staticlib"] - -[dependencies] -encoding_c = { version = "0.9.8", features = [] } - -[profile.release] -lto = true -panic = 'abort' - -[profile.dev] -panic = 'abort' diff --git a/crates/rust-encoding/cbindgen.toml b/crates/rust-encoding/cbindgen.toml deleted file mode 100644 index db253919..00000000 --- a/crates/rust-encoding/cbindgen.toml +++ /dev/null @@ -1,54 +0,0 @@ -language = "C++" - -header = """ -// The constructor created by cbindgen's `derive_constructor` causes this warning. -// Gecko's various uses of cbindgen silence it, so we do, too. -#ifdef __clang__ -# pragma GCC diagnostic ignored "-Wreturn-type-c-linkage" -#endif -""" - -pragma_once = true -include_guard = "rust_encoding_bindings_h" - -autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" - -include_version = false -namespace = "jsencoding" -using_namespaces = [] -sys_includes = [] -includes = [] -no_includes = false -after_includes = "" - -braces = "SameLine" -line_length = 100 -tab_width = 2 -documentation = true -documentation_style = "auto" -line_endings = "LF" - -usize_is_size_t = true - -[struct] -derive_constructor = true - - -[parse] -parse_deps = true -# include = [] -exclude = [] -clean = false -extra_bindings = ["encoding_c"] - - -[export] -# A list of additional items to always include in the generated bindings if they're -# found but otherwise don't appear to be used by the public API. -# -# default: [] -include = [ - "Decoder", - "Encoder", - "BIG5_ENCODING", -] \ No newline at end of file diff --git a/crates/rust-encoding/src/lib.rs b/crates/rust-encoding/src/lib.rs deleted file mode 100644 index 76bd65e7..00000000 --- a/crates/rust-encoding/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub use encoding_c; diff --git a/crates/rust-encoding/rust-encoding.h b/deps/include/rust-encoding.h similarity index 99% rename from crates/rust-encoding/rust-encoding.h rename to deps/include/rust-encoding.h index 6f67cc9a..70eeea60 100644 --- a/crates/rust-encoding/rust-encoding.h +++ b/deps/include/rust-encoding.h @@ -1,3 +1,6 @@ +// NOTE: This file was generated by cbindgen for the encoding_c crate. +// Since that crate is included in SpiderMonkey, we just include the header file here. +// TODO: look into whether we can bundle the header file with SpiderMonkey's includes. // The constructor created by cbindgen's `derive_constructor` causes this warning. // Gecko's various uses of cbindgen silence it, so we do, too. #ifdef __clang__ From 0d022837ad4fde232127f7b5253f6f00fb6f84b5 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Thu, 18 Apr 2024 13:38:42 +0200 Subject: [PATCH 24/52] Change WPT support to use a CMake option instead of an env var --- CMakeLists.txt | 3 ++- README.md | 4 ++-- tests/wpt-harness/wpt.cmake | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0691f3d6..1aa0c152 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,8 @@ target_include_directories(extension_api INTERFACE include deps/include runtime) include("builtins") -if (DEFINED ENV{WPT}) +option(ENABLE_WPT "Enable WPT harness support" OFF) +if (ENABLE_WPT) include("tests/wpt-harness/wpt.cmake") endif() diff --git a/README.md b/README.md index 46133736..c0bde81f 100644 --- a/README.md +++ b/README.md @@ -80,12 +80,12 @@ StarlingMonkey includes a test runner for the [Web Platform Tests](https://web-p ### Requirements -The WPT runner requires `Node.js` to be installed, and during build configuration the environment variable `WPT` must be defined. +The WPT runner requires `Node.js` to be installed, and during build configuration the option `ENABLE_WPT:BOOL=ON` must be set. When running the test, `WPT_ROOT` must be set to the path of a checkout of the WPT suite at revision `1014eae5e66f8f334610d5a1521756f7a2fb769f`: ```bash -WPT=1 WPT_ROOT=[path to your WPT checkout] cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug +WPT_ROOT=[path to your WPT checkout] cmake -S . -B cmake-build-debug -DENABLE_WPT:BOOL=ON -DCMAKE_BUILD_TYPE=Debug cmake --build cmake-build-debug --parallel 8 --target wpt-runtime cd cmake-build-debug ctest --verbose # Note: some of the tests run fairly slowly in debug builds, so be patient diff --git a/tests/wpt-harness/wpt.cmake b/tests/wpt-harness/wpt.cmake index f0d2933f..bfb541d2 100644 --- a/tests/wpt-harness/wpt.cmake +++ b/tests/wpt-harness/wpt.cmake @@ -1,7 +1,8 @@ enable_testing() -add_builtin(wpt_builtins SRC "${CMAKE_CURRENT_LIST_DIR}/wpt_builtins.cpp") -target_include_directories(wpt_builtins PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/builtins/web/") +add_builtin(wpt_support + SRC "${CMAKE_CURRENT_LIST_DIR}/wpt_builtins.cpp" + INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/builtins/web/") if(NOT DEFINED ENV{WPT_ROOT}) message(FATAL_ERROR "WPT_ROOT environment variable is not set") From bfe3158c4f2487534cbbf7bc4c2e28cf6ed87f45 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Wed, 13 Mar 2024 01:02:58 +0100 Subject: [PATCH 25/52] WIP: rework headers --- builtins/web/fetch/fetch_event.cpp | 2 +- builtins/web/fetch/headers.cpp | 112 +----------------------- builtins/web/fetch/headers.h | 18 ++-- builtins/web/fetch/request-response.cpp | 2 +- builtins/web/fetch/request-response.h | 2 +- host-apis/wasi-0.2.0/host_api.cpp | 48 +++++----- include/host_api.h | 47 +++++++--- 7 files changed, 74 insertions(+), 157 deletions(-) diff --git a/builtins/web/fetch/fetch_event.cpp b/builtins/web/fetch/fetch_event.cpp index 87724642..8279b2e5 100644 --- a/builtins/web/fetch/fetch_event.cpp +++ b/builtins/web/fetch/fetch_event.cpp @@ -168,7 +168,7 @@ bool start_response(JSContext *cx, JS::HandleObject response_obj, bool streaming auto incoming_response = static_cast(generic_response); auto status = incoming_response->status(); MOZ_RELEASE_ASSERT(!status.is_err(), "Incoming response must have a status code"); - auto headers = new host_api::HttpHeaders(*incoming_response->headers().unwrap()); + auto headers = incoming_response->headers().unwrap()->clone(); response = host_api::HttpOutgoingResponse::make(status.unwrap(), headers); auto *source_body = incoming_response->body().unwrap(); auto *dest_body = response->body().unwrap(); diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 32f3d8fc..87e6f2e6 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -56,17 +56,6 @@ const char VALID_NAME_CHARS[128] = { return false; \ } -JSObject *get_backing_map(JSObject *self) { - MOZ_ASSERT(Headers::is_instance(self)); - return &JS::GetReservedSlot(self, static_cast(Headers::Slots::BackingMap)).toObject(); -} - -bool lazy_values(JSObject *self) { - MOZ_ASSERT(Headers::is_instance(self)); - return JS::GetReservedSlot(self, static_cast(Headers::Slots::HasLazyValues)) - .toBoolean(); -} - Handle *get_handle(JSObject *self) { MOZ_ASSERT(Headers::is_instance(self)); auto handle = @@ -291,46 +280,10 @@ bool retrieve_value_for_header_from_handle(JSContext *cx, JS::HandleObject self, return true; } -/** - * Ensures that a value for the given header is available to client code. - * - * The calling code must ensure that a header with the given name exists, but - * might not yet have been retrieved from the host, i.e., it might be a "lazy" - * value. - * - * The value is returned via the `values` outparam, but *only* if the Headers - * object has lazy values at all. This is to avoid the map lookup in those cases - * where none is necessary in this function, and the consumer wouldn't use the - * value anyway. - */ -bool ensure_value_for_header(JSContext *cx, JS::HandleObject self, JS::HandleValue normalized_name, - JS::MutableHandleValue values) { - if (!lazy_values(self)) - return true; - - JS::RootedObject map(cx, get_backing_map(self)); - if (!JS::MapGet(cx, map, normalized_name, values)) - return false; - - // Value isn't lazy, just return it. - if (!values.isNull()) - return true; - - return retrieve_value_for_header_from_handle(cx, self, normalized_name, values); -} - bool get_header_value_for_name(JSContext *cx, JS::HandleObject self, JS::HandleValue name, JS::MutableHandleValue rval, const char *fun_name) { NORMALIZE_NAME(name, fun_name) - if (!ensure_value_for_header(cx, self, normalized_name, rval)) { - return false; - } - - if (rval.isString()) { - return true; - } - JS::RootedObject map(cx, get_backing_map(self)); if (!JS::MapGet(cx, map, normalized_name, rval)) { return false; @@ -390,39 +343,6 @@ std::vector splitCookiesString(std::string_view cookiesString) return cookiesStrings; } -bool ensure_all_header_values_from_handle(JSContext *cx, JS::HandleObject self, - JS::HandleObject backing_map) { - if (!lazy_values(self)) - return true; - - JS::RootedValue iterable(cx); - if (!JS::MapKeys(cx, backing_map, &iterable)) - return false; - - JS::ForOfIterator it(cx); - if (!it.init(iterable)) - return false; - - JS::RootedValue name(cx); - JS::RootedValue v(cx); - while (true) { - bool done; - if (!it.next(&name, &done)) - return false; - - if (done) - break; - - if (!ensure_value_for_header(cx, self, name, &v)) - return false; - } - - JS_SetReservedSlot(self, static_cast(Headers::Slots::HasLazyValues), - JS::BooleanValue(false)); - - return true; -} - } // namespace bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::HandleValue name, @@ -430,12 +350,6 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand NORMALIZE_NAME(name, fun_name) NORMALIZE_VALUE(value, fun_name) - // Ensure that any host-side values have been applied JS-side. - JS::RootedValue v(cx); - if (!ensure_value_for_header(cx, self, normalized_name, &v)) { - return false; - } - auto handle = get_handle(self); if (handle) { std::string_view name = name_chars; @@ -461,11 +375,6 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand return append_header_value_to_map(cx, self, normalized_name, &normalized_value); } -bool Headers::delazify(JSContext *cx, JS::HandleObject headers) { - JS::RootedObject backing_map(cx, get_backing_map(headers)); - return ensure_all_header_values_from_handle(cx, headers, backing_map); -} - JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, JS::HandleObject init_headers) { JS::RootedObject headers(cx, create(cx, self, handle)); @@ -767,26 +676,9 @@ bool Headers::init_class(JSContext *cx, JS::HandleObject global) { return JS_DefinePropertyById(cx, proto_obj, iteratorId, entries, 0); } -JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle) { +JSObject *Headers::create(JSContext *cx, JS::HandleObject self, + host_api::HttpHeadersReadOnly *handle) { JS_SetReservedSlot(self, static_cast(Slots::Handle), JS::PrivateValue(handle)); - - JS::RootedObject backing_map(cx, JS::NewMapObject(cx)); - if (!backing_map) { - return nullptr; - } - JS::SetReservedSlot(self, static_cast(Slots::BackingMap), - JS::ObjectValue(*backing_map)); - - bool lazy = false; - if (handle) { - lazy = true; - if (!get_header_names_from_handle(cx, handle, backing_map)) { - return nullptr; - } - } - - JS_SetReservedSlot(self, static_cast(Slots::HasLazyValues), JS::BooleanValue(lazy)); - return self; } diff --git a/builtins/web/fetch/headers.h b/builtins/web/fetch/headers.h index 8bfdf69f..2b61a867 100644 --- a/builtins/web/fetch/headers.h +++ b/builtins/web/fetch/headers.h @@ -23,14 +23,10 @@ class Headers final : public BuiltinImpl { static constexpr const char *class_name = "Headers"; enum class Slots { - BackingMap, Handle, - HasLazyValues, Count, }; - static bool delazify(JSContext *cx, JS::HandleObject headers); - /** * Adds the given header name/value to `self`'s list of headers iff `self` * doesn't already contain a header with that name. @@ -55,14 +51,14 @@ class Headers final : public BuiltinImpl { static const unsigned ctor_length = 1; - static bool init_class(JSContext *cx, JS::HandleObject global); - static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp); + static bool init_class(JSContext *cx, HandleObject global); + static bool constructor(JSContext *cx, unsigned argc, Value *vp); - static JSObject *create(JSContext *cx, JS::HandleObject headers, host_api::HttpHeaders *handle, - JS::HandleObject init_headers); - static JSObject *create(JSContext *cx, JS::HandleObject headers, host_api::HttpHeaders *handle, - JS::HandleValue initv); - static JSObject *create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle); + static JSObject *create(JSContext *cx, HandleObject self, host_api::HttpHeaders *handle, + HandleObject init_headers); + static JSObject *create(JSContext *cx, HandleObject self, host_api::HttpHeaders *handle, + HandleValue init_headers); + static JSObject *create(JSContext *cx, HandleObject self, host_api::HttpHeadersReadOnly *handle); }; } // namespace fetch diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 36c66dfa..48b0e34b 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -227,7 +227,7 @@ bool RequestOrResponse::is_instance(JSObject *obj) { bool RequestOrResponse::is_incoming(JSObject *obj) { return handle(obj)->is_incoming(); } -host_api::HttpHeaders *RequestOrResponse::headers_handle(JSObject *obj) { +host_api::HttpHeadersReadOnly *RequestOrResponse::headers_handle(JSObject *obj) { MOZ_ASSERT(is_instance(obj)); auto res = handle(obj)->headers(); MOZ_ASSERT(!res.is_err(), "TODO: proper error handling"); diff --git a/builtins/web/fetch/request-response.h b/builtins/web/fetch/request-response.h index e1b2f1d5..be2f4fb8 100644 --- a/builtins/web/fetch/request-response.h +++ b/builtins/web/fetch/request-response.h @@ -31,7 +31,7 @@ class RequestOrResponse final { static bool is_instance(JSObject *obj); static bool is_incoming(JSObject *obj); static host_api::HttpRequestResponseBase *handle(JSObject *obj); - static host_api::HttpHeaders *headers_handle(JSObject *obj); + static host_api::HttpHeadersReadOnly *headers_handle(JSObject *obj); static bool has_body(JSObject *obj); static host_api::HttpIncomingBody *incoming_body_handle(JSObject *obj); static host_api::HttpOutgoingBody *outgoing_body_handle(JSObject *obj); diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 39b4f61a..1d3427b9 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -185,13 +185,13 @@ void MonotonicClock::unsubscribe(const int32_t handle_id) { wasi_io_0_2_0_poll_pollable_drop_own(own_pollable_t{handle_id}); } -HttpHeaders::HttpHeaders() { +HttpHeaders::HttpHeaders(Handle handle) : HttpHeadersReadOnly(handle) {} + +HttpHeaders::HttpHeaders() : HttpHeadersReadOnly() { this->handle_state_ = new HandleState(wasi_http_0_2_0_types_constructor_fields().__handle); } -HttpHeaders::HttpHeaders(Handle handle) { handle_state_ = new HandleState(handle); } -// TODO: make this a factory function -HttpHeaders::HttpHeaders(const vector>> &entries) { +Result HttpHeaders::FromEntries(const vector>> &entries) { std::vector pairs; for (const auto &[name, values] : entries) { @@ -207,16 +207,16 @@ HttpHeaders::HttpHeaders(const vector>> & wasi_http_0_2_0_types_static_fields_from_list(&tuples, &ret, &err); // TODO: handle `err` - this->handle_state_ = new HandleState(ret.__handle); + return Result::ok(new HttpHeaders(ret.__handle)); } -HttpHeaders::HttpHeaders(const HttpHeaders &headers) { +HttpHeaders::HttpHeaders(const HttpHeadersReadOnly &headers) : HttpHeadersReadOnly() { Borrow borrow(headers.handle_state_); auto handle = wasi_http_0_2_0_types_method_fields_clone(borrow); this->handle_state_ = new HandleState(handle.__handle); } -Result>> HttpHeaders::entries() const { +Result>> HttpHeadersReadOnly::entries() const { Result>> res; MOZ_ASSERT(valid()); @@ -237,7 +237,7 @@ Result>> HttpHeaders::entries() const { return res; } -Result> HttpHeaders::names() const { +Result> HttpHeadersReadOnly::names() const { Result> res; MOZ_ASSERT(valid()); @@ -256,7 +256,7 @@ Result> HttpHeaders::names() const { return res; } -Result>> HttpHeaders::get(string_view name) const { +Result>> HttpHeadersReadOnly::get(string_view name) const { Result>> res; MOZ_ASSERT(valid()); @@ -720,10 +720,10 @@ Result HttpOutgoingRequest::method() { return Result::ok(method_); } -Result HttpOutgoingRequest::headers() { +Result HttpOutgoingRequest::headers() { MOZ_ASSERT(valid()); MOZ_ASSERT(headers_); - return Result::ok(headers_); + return Result::ok(headers_); } Result HttpOutgoingRequest::body() { @@ -838,6 +838,10 @@ void FutureHttpIncomingResponse::unsubscribe() { // TODO: implement } +HttpHeadersReadOnly::HttpHeadersReadOnly(Handle handle) { + handle_state_ = new HandleState(handle); +} + Result HttpIncomingResponse::status() { if (status_ == UNSET_STATUS) { if (!valid()) { @@ -853,17 +857,17 @@ HttpIncomingResponse::HttpIncomingResponse(Handle handle) { handle_state_ = new HandleState(handle); } -Result HttpIncomingResponse::headers() { +Result HttpIncomingResponse::headers() { if (!headers_) { if (!valid()) { - return Result::err(154); + return Result::err(154); } auto res = wasi_http_0_2_0_types_method_incoming_response_headers( wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->handle})); - headers_ = new HttpHeaders(res.__handle); + headers_ = new HttpHeadersReadOnly(res.__handle); } - return Result::ok(headers_); + return Result::ok(headers_); } Result HttpIncomingResponse::body() { @@ -907,11 +911,11 @@ HttpOutgoingResponse *HttpOutgoingResponse::make(const uint16_t status, HttpHead return resp; } -Result HttpOutgoingResponse::headers() { +Result HttpOutgoingResponse::headers() { if (!valid()) { - return Result::err(154); + return Result::err(154); } - return Result::ok(headers_); + return Result::ok(headers_); } Result HttpOutgoingResponse::body() { @@ -963,17 +967,17 @@ Result HttpIncomingRequest::method() { return Result::ok(method_); } -Result HttpIncomingRequest::headers() { +Result HttpIncomingRequest::headers() { if (!headers_) { if (!valid()) { - return Result::err(154); + return Result::err(154); } borrow_incoming_request_t borrow(handle_state_->handle); auto res = wasi_http_0_2_0_types_method_incoming_request_headers(borrow); - headers_ = new HttpHeaders(res.__handle); + headers_ = new HttpHeadersReadOnly(res.__handle); } - return Result::ok(headers_); + return Result::ok(headers_); } Result HttpIncomingRequest::body() { diff --git a/include/host_api.h b/include/host_api.h index 4e8abba7..f0632648 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -310,6 +310,8 @@ class HttpBodyPipe { }; class HttpIncomingResponse; +class HttpHeaders; + class FutureHttpIncomingResponse final : public Pollable { public: FutureHttpIncomingResponse() = delete; @@ -322,21 +324,44 @@ class FutureHttpIncomingResponse final : public Pollable { void unsubscribe() override; }; -class HttpHeaders final : public Resource { +class HttpHeadersReadOnly : public Resource { friend HttpIncomingResponse; friend HttpIncomingRequest; friend HttpOutgoingResponse; friend HttpOutgoingRequest; + friend HttpHeaders; + +protected: + explicit HttpHeadersReadOnly(Handle handle); + HttpHeadersReadOnly() = default; public: - HttpHeaders(); - explicit HttpHeaders(Handle handle); - explicit HttpHeaders(const vector>> &entries); - HttpHeaders(const HttpHeaders &headers); + HttpHeadersReadOnly(const HttpHeadersReadOnly &headers) = delete; + + HttpHeaders* clone(); + + virtual bool is_writable() { return false; }; Result>> entries() const; Result> names() const; Result>> get(string_view name) const; +}; + +class HttpHeaders final : public HttpHeadersReadOnly { + friend HttpIncomingResponse; + friend HttpIncomingRequest; + friend HttpOutgoingResponse; + friend HttpOutgoingRequest; + + explicit HttpHeaders(Handle handle); + +public: + HttpHeaders(); + explicit HttpHeaders(const HttpHeadersReadOnly &headers); + + static Result FromEntries(const vector>> &entries); + + bool is_writable() override { return true; }; Result set(string_view name, string_view value); Result append(string_view name, string_view value); @@ -345,13 +370,13 @@ class HttpHeaders final : public Resource { class HttpRequestResponseBase : public Resource { protected: - HttpHeaders *headers_ = nullptr; + HttpHeadersReadOnly *headers_ = nullptr; std::string *_url = nullptr; public: ~HttpRequestResponseBase() override = default; - virtual Result headers() = 0; + virtual Result headers() = 0; virtual string_view url(); virtual bool is_incoming() = 0; @@ -400,7 +425,7 @@ class HttpIncomingRequest final : public HttpRequest, public HttpIncomingBodyOwn bool is_request() override { return true; } [[nodiscard]] Result method() override; - Result headers() override; + Result headers() override; Result body() override; }; @@ -417,7 +442,7 @@ class HttpOutgoingRequest final : public HttpRequest, public HttpOutgoingBodyOwn bool is_request() override { return true; } [[nodiscard]] Result method() override; - Result headers() override; + Result headers() override; Result body() override; Result send(); @@ -440,7 +465,7 @@ class HttpIncomingResponse final : public HttpResponse, public HttpIncomingBodyO bool is_incoming() override { return true; } bool is_request() override { return false; } - Result headers() override; + Result headers() override; Result body() override; [[nodiscard]] Result status() override; }; @@ -458,7 +483,7 @@ class HttpOutgoingResponse final : public HttpResponse, public HttpOutgoingBodyO bool is_incoming() override { return false; } bool is_request() override { return false; } - Result headers() override; + Result headers() override; Result body() override; [[nodiscard]] Result status() override; From 86db17e9fadce9db613b74aada5916d002cac0da Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Fri, 22 Mar 2024 16:34:39 +0100 Subject: [PATCH 26/52] WIP: rework headers --- CMakeLists.txt | 3 +- builtins/web/fetch/headers.cpp | 483 ++++++++++++++---------------- builtins/web/fetch/headers.h | 38 ++- builtins/web/url.cpp | 2 +- crates/rust-url/rust-url.h | 11 + host-apis/wasi-0.2.0/host_api.cpp | 12 + include/host_api.h | 1 + runtime/decode.cpp | 10 + runtime/decode.h | 12 + 9 files changed, 301 insertions(+), 271 deletions(-) create mode 100644 runtime/decode.cpp create mode 100644 runtime/decode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1aa0c152..b143611f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ include("openssl") include("host_api") include("build-crates") -add_library(extension_api INTERFACE include/extension-api.h runtime/encode.h) +add_library(extension_api INTERFACE include/extension-api.h runtime/encode.h runtime/decode.h) target_link_libraries(extension_api INTERFACE rust-url spidermonkey) target_include_directories(extension_api INTERFACE include deps/include runtime) @@ -51,6 +51,7 @@ add_executable(starling.wasm runtime/js.cpp runtime/allocator.cpp runtime/encode.cpp + runtime/decode.cpp runtime/engine.cpp runtime/event_loop.cpp runtime/builtin.cpp diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 87e6f2e6..83ab631b 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -1,22 +1,20 @@ #include "headers.h" // #include "request-response.h" #include "encode.h" +#include "decode.h" #include "sequence.hpp" #include "js/Conversions.h" -namespace builtins { -namespace web { -namespace fetch { - +namespace builtins::web::fetch { namespace { using Handle = host_api::HttpHeaders; #define HEADERS_ITERATION_METHOD(argc) \ METHOD_HEADER(argc) \ - JS::RootedObject backing_map(cx, get_backing_map(self)); \ - if (!ensure_all_header_values_from_handle(cx, self, backing_map)) { \ + JS::RootedObject entries(cx, get_entries(cx, self)); \ + if (!entries) { \ return false; \ } @@ -43,16 +41,16 @@ const char VALID_NAME_CHARS[128] = { }; #define NORMALIZE_NAME(name, fun_name) \ - JS::RootedValue normalized_name(cx, name); \ - auto name_chars = normalize_header_name(cx, &normalized_name, fun_name); \ + bool name_changed; \ + auto name_chars = normalize_header_name(cx, name, &name_changed, fun_name); \ if (!name_chars) { \ return false; \ } #define NORMALIZE_VALUE(value, fun_name) \ - JS::RootedValue normalized_value(cx, value); \ - auto value_chars = normalize_header_value(cx, &normalized_value, fun_name); \ - if (!value_chars) { \ + bool value_changed; \ + auto value_chars = normalize_header_value(cx, value, &value_changed, fun_name); \ + if (!value_chars.ptr) { \ return false; \ } @@ -71,14 +69,10 @@ Handle *get_handle(JSObject *self) { * See * https://searchfox.org/mozilla-central/rev/9f76a47f4aa935b49754c5608a1c8e72ee358c46/netwerk/protocol/http/nsHttp.cpp#172-215 * For details on validation. - * - * Mutates `name_val` in place, and returns the name as UniqueChars. - * This is done because most uses of header names require handling of both the - * JSString and the char* version, so they'd otherwise have to recreate one of - * the two. */ -host_api::HostString normalize_header_name(JSContext *cx, JS::MutableHandleValue name_val, +host_api::HostString normalize_header_name(JSContext *cx, HandleValue name_val, bool* named_changed, const char *fun_name) { + *named_changed = false; JS::RootedString name_str(cx, JS::ToString(cx, name_val)); if (!name_str) { return nullptr; @@ -94,42 +88,42 @@ host_api::HostString normalize_header_name(JSContext *cx, JS::MutableHandleValue return nullptr; } - bool changed = false; - char *name_chars = name.begin(); for (size_t i = 0; i < name.len; i++) { - unsigned char ch = name_chars[i]; + const unsigned char ch = name_chars[i]; if (ch > 127 || !VALID_NAME_CHARS[ch]) { JS_ReportErrorUTF8(cx, "%s: Invalid header name '%s'", fun_name, name_chars); return nullptr; } if (ch >= 'A' && ch <= 'Z') { + *named_changed = true; name_chars[i] = ch - 'A' + 'a'; - changed = true; - } - } - - if (changed) { - name_str = JS_NewStringCopyN(cx, name_chars, name.len); - if (!name_str) { - return nullptr; } } - name_val.setString(name_str); return name; } -host_api::HostString normalize_header_value(JSContext *cx, JS::MutableHandleValue value_val, - const char *fun_name) { +/** + * Validates and normalizes the given header value, by + * - stripping leading and trailing whitespace + * - checking for interior line breaks and `\0` + * + * See + * https://searchfox.org/mozilla-central/rev/9f76a47f4aa935b49754c5608a1c8e72ee358c46/netwerk/protocol/http/nsHttp.cpp#247-260 + * For details on validation. + */ +host_api::HostString normalize_header_value(JSContext *cx, HandleValue value_val, + bool* value_changed, const char *fun_name) { + *value_changed = false; JS::RootedString value_str(cx, JS::ToString(cx, value_val)); if (!value_str) { return nullptr; } auto value = core::encode(cx, value_str); - if (!value) { + if (!value.ptr) { return nullptr; } @@ -137,11 +131,6 @@ host_api::HostString normalize_header_value(JSContext *cx, JS::MutableHandleValu size_t start = 0; size_t end = value.len; - // We follow Gecko's interpretation of what's a valid header value. After - // stripping leading and trailing whitespace, all interior line breaks and - // `\0` are considered invalid. See - // https://searchfox.org/mozilla-central/rev/9f76a47f4aa935b49754c5608a1c8e72ee358c46/netwerk/protocol/http/nsHttp.cpp#247-260 - // for details. while (start < end) { unsigned char ch = value_chars[start]; if (ch == '\t' || ch == ' ' || ch == '\r' || ch == '\n') { @@ -160,6 +149,10 @@ host_api::HostString normalize_header_value(JSContext *cx, JS::MutableHandleValu } } + if (start != 0 || end != value.len) { + *value_changed = true; + } + for (size_t i = start; i < end; i++) { unsigned char ch = value_chars[i]; if (ch == '\r' || ch == '\n' || ch == '\0') { @@ -168,91 +161,16 @@ host_api::HostString normalize_header_value(JSContext *cx, JS::MutableHandleValu } } - if (start != 0 || end != value.len) { - value_str = JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(value_chars + start, end - start)); - if (!value_str) { - return nullptr; - } - } - - value_val.setString(value_str); - return value; } JS::PersistentRooted comma; -// Append an already normalized value for an already normalized header name -// to the JS side map, but not the host. -// -// Returns the resulting combined value in `normalized_value`. -bool append_header_value_to_map(JSContext *cx, JS::HandleObject self, - JS::HandleValue normalized_name, - JS::MutableHandleValue normalized_value) { - JS::RootedValue existing(cx); - JS::RootedObject map(cx, get_backing_map(self)); - if (!JS::MapGet(cx, map, normalized_name, &existing)) - return false; - - // Existing value must only be null if we're in the process if applying - // header values from a handle. - if (!existing.isNullOrUndefined()) { - if (!comma.get()) { - comma.init(cx, JS_NewStringCopyN(cx, ", ", 2)); - if (!comma) { - return false; - } - } - - JS::RootedString str(cx, existing.toString()); - str = JS_ConcatStrings(cx, str, comma); - if (!str) { - return false; - } - - JS::RootedString val_str(cx, normalized_value.toString()); - str = JS_ConcatStrings(cx, str, val_str); - if (!str) { - return false; - } - - normalized_value.setString(str); - } - - return JS::MapSet(cx, map, normalized_name, normalized_value); -} - -bool get_header_names_from_handle(JSContext *cx, Handle *handle, JS::HandleObject backing_map) { - - auto names = handle->names(); - if (auto *err = names.to_err()) { - HANDLE_ERROR(cx, *err); - return false; - } - - JS::RootedString name(cx); - JS::RootedValue name_val(cx); - for (auto &str : names.unwrap()) { - // TODO: can `name` take ownership of the buffer here instead? - name = JS_NewStringCopyN(cx, str.ptr.get(), str.len); - if (!name) { - return false; - } - - name_val.setString(name); - JS::MapSet(cx, backing_map, name_val, JS::NullHandleValue); - } - - return true; -} - bool retrieve_value_for_header_from_handle(JSContext *cx, JS::HandleObject self, - JS::HandleValue name, JS::MutableHandleValue value) { + const host_api::HostString &name, + MutableHandleValue value) { auto handle = get_handle(self); - - JS::RootedString name_str(cx, name.toString()); - auto name_chars = core::encode(cx, name_str); - auto ret = handle->get(name_chars); + auto ret = handle->get(name); if (auto *err = ret.to_err()) { HANDLE_ERROR(cx, *err); @@ -261,39 +179,33 @@ bool retrieve_value_for_header_from_handle(JSContext *cx, JS::HandleObject self, auto &values = ret.unwrap(); if (!values.has_value()) { + value.setNull(); return true; } - JS::RootedString val_str(cx); + RootedString res_str(cx); + RootedString val_str(cx); for (auto &str : values.value()) { val_str = JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(str.ptr.get(), str.len)); if (!val_str) { return false; } - value.setString(val_str); - if (!append_header_value_to_map(cx, self, name, value)) { - return false; + if (!res_str) { + res_str = val_str; + } else { + res_str = JS_ConcatStrings(cx, res_str, comma); + if (!res_str) { + return false; + } + res_str = JS_ConcatStrings(cx, res_str, val_str); + if (!res_str) { + return false; + } } } - return true; -} - -bool get_header_value_for_name(JSContext *cx, JS::HandleObject self, JS::HandleValue name, - JS::MutableHandleValue rval, const char *fun_name) { - NORMALIZE_NAME(name, fun_name) - - JS::RootedObject map(cx, get_backing_map(self)); - if (!JS::MapGet(cx, map, normalized_name, rval)) { - return false; - } - - // Return `null` for non-existent headers. - if (rval.isUndefined()) { - rval.setNull(); - } - + value.setString(res_str); return true; } @@ -311,7 +223,7 @@ std::vector splitCookiesString(std::string_view cookiesString) start = currentPosition; // Iterate until we find a comma that might be used as a separator. - while ((currentPosition = cookiesString.find_first_of(",", currentPosition)) != + while ((currentPosition = cookiesString.find_first_of(',', currentPosition)) != std::string_view::npos) { // ',' is a cookie separator only if we later have '=', before having ';' or ',' lastComma = currentPosition; @@ -345,6 +257,11 @@ std::vector splitCookiesString(std::string_view cookiesString) } // namespace +void switch_to_content_only_mode(JSObject* self) { + SetReservedSlot(self, static_cast(Headers::Slots::Mode), + JS::Int32Value(static_cast(Headers::Mode::ContentOnly))); +} + bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::HandleValue name, JS::HandleValue value, const char *fun_name) { NORMALIZE_NAME(name, fun_name) @@ -354,17 +271,15 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand if (handle) { std::string_view name = name_chars; if (name == "set-cookie") { - std::string_view value = value_chars; - for (auto value : splitCookiesString(value)) { - auto res = handle->append(name, value); + for (auto value : splitCookiesString(value_chars)) { + auto res = handle->append(name_chars, value); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; } } } else { - std::string_view value = value_chars; - auto res = handle->append(name, value); + auto res = handle->append(name_chars, value_chars); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -372,65 +287,12 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand } } - return append_header_value_to_map(cx, self, normalized_name, &normalized_value); -} - -JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, - JS::HandleObject init_headers) { - JS::RootedObject headers(cx, create(cx, self, handle)); - if (!headers) { - return nullptr; - } - - if (!init_headers) { - return headers; - } - - if (!Headers::delazify(cx, init_headers)) { - return nullptr; - } - - JS::RootedObject headers_map(cx, get_backing_map(headers)); - JS::RootedObject init_map(cx, get_backing_map(init_headers)); - - JS::RootedValue iterable(cx); - if (!JS::MapEntries(cx, init_map, &iterable)) { - return nullptr; - } - - JS::ForOfIterator it(cx); - if (!it.init(iterable)) { - return nullptr; - } - - JS::RootedObject entry(cx); - JS::RootedValue entry_val(cx); - JS::RootedValue name_val(cx); - JS::RootedValue value_val(cx); - while (true) { - bool done; - if (!it.next(&entry_val, &done)) { - return nullptr; - } - - if (done) { - break; - } - - entry = &entry_val.toObject(); - JS_GetElement(cx, entry, 0, &name_val); - JS_GetElement(cx, entry, 1, &value_val); - - if (!Headers::append_header_value(cx, headers, name_val, value_val, "Headers constructor")) { - return nullptr; - } - } - - return headers; + return true; } -JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, +JSObject *Headers::create(JSContext *cx, JS::HandleObject self, Handle *handle, JS::HandleValue initv) { + // TODO: check if initv is a Headers instance and clone its handle if so. JS::RootedObject headers(cx, create(cx, self, handle)); if (!headers) return nullptr; @@ -449,12 +311,42 @@ JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHe return headers; } +bool redecode_str_if_changed(JSContext* cx, HandleValue str_val, host_api::HostString& chars, + bool changed, MutableHandleValue rval) { + if (!changed) { + rval.set(str_val); + return true; + } + + RootedString str(cx, core::decode(cx, chars)); + if (!str) { + return false; + } + + rval.setString(str); + return true; +} + bool Headers::get(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(1) NORMALIZE_NAME(args[0], "Headers.get") - return get_header_value_for_name(cx, self, normalized_name, args.rval(), "Headers.get"); + Mode mode = Headers::mode(self); + if (mode == Mode::HostOnly) { + return retrieve_value_for_header_from_handle(cx, self, name_chars, args.rval()); + } + + RootedObject entries(cx, get_entries(cx, self)); + if (!entries) { + return false; + } + + RootedValue name_val(cx); + if (!redecode_str_if_changed(cx, args[0], name_chars, name_changed, &name_val)) { + return false; + } + return MapGet(cx, entries, name_val, args.rval()); } bool Headers::set(JSContext *cx, unsigned argc, JS::Value *vp) { @@ -463,20 +355,38 @@ bool Headers::set(JSContext *cx, unsigned argc, JS::Value *vp) { NORMALIZE_NAME(args[0], "Headers.set") NORMALIZE_VALUE(args[1], "Headers.set") - auto handle = get_handle(self); - if (handle) { - std::string_view name = name_chars; - std::string_view val = value_chars; - auto res = handle->set(name, val); + Mode mode = Headers::mode(self); + if (mode == Mode::HostOnly) { + auto handle = get_handle(self); + MOZ_ASSERT(handle); + auto res = handle->set(name_chars, value_chars); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; } + } else if (mode == Mode::CachedInContent) { + switch_to_content_only_mode(self); } - JS::RootedObject map(cx, get_backing_map(self)); - if (!JS::MapSet(cx, map, normalized_name, normalized_value)) { - return false; + if (mode == Mode::ContentOnly) { + RootedObject entries(cx, get_entries(cx, self)); + if (!entries) { + return false; + } + + RootedValue name_val(cx); + if (!redecode_str_if_changed(cx, args[0], name_chars, name_changed, &name_val)) { + return false; + } + + RootedValue value_val(cx); + if (!redecode_str_if_changed(cx, args[0], value_chars, value_changed, &value_val)) { + return false; + } + + if (!MapSet(cx, entries, name_val, value_val)) { + return false; + } } args.rval().setUndefined(); @@ -487,20 +397,33 @@ bool Headers::has(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(1) NORMALIZE_NAME(args[0], "Headers.has") - bool has; - JS::RootedObject map(cx, get_backing_map(self)); - if (!JS::MapHas(cx, map, normalized_name, &has)) { - return false; + + Mode mode = Headers::mode(self); + if (mode == Mode::HostOnly) { + auto handle = get_handle(self); + MOZ_ASSERT(handle); + auto res = handle->has(name_chars); + MOZ_ASSERT(!res.is_err()); + args.rval().setBoolean(res.unwrap()); + } else { + RootedObject entries(cx, get_entries(cx, self)); + if (!entries) { + return false; + } + bool has; + if (!MapHas(cx, entries, args[0], &has)) { + return false; + } + args.rval().setBoolean(has); } - args.rval().setBoolean(has); return true; } bool Headers::append(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(2) - if (!Headers::append_header_value(cx, self, args[0], args[1], "Headers.append")) { + if (!append_header_value(cx, self, args[0], args[1], "Headers.append")) { return false; } @@ -509,58 +432,51 @@ bool Headers::append(JSContext *cx, unsigned argc, JS::Value *vp) { } bool Headers::maybe_add(JSContext *cx, JS::HandleObject self, const char *name, const char *value) { - MOZ_ASSERT(Headers::is_instance(self)); - JS::RootedString name_str(cx, JS_NewStringCopyN(cx, name, strlen(name))); - if (!name_str) { - return false; - } - JS::RootedValue name_val(cx, JS::StringValue(name_str)); - - JS::RootedObject map(cx, get_backing_map(self)); - bool has; - if (!JS::MapHas(cx, map, name_val, &has)) { - return false; - } - if (has) { + MOZ_ASSERT(mode(self) == Mode::HostOnly); + auto handle = get_handle(self); + auto has = handle->has(name); + MOZ_ASSERT(!has.is_err()); + if (has.unwrap()) { return true; } - JS::RootedString value_str(cx, JS_NewStringCopyN(cx, value, strlen(value))); - if (!value_str) { + auto res = handle->append(name, value); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); return false; } - JS::RootedValue value_val(cx, JS::StringValue(value_str)); - return Headers::append_header_value(cx, self, name_val, value_val, "internal_maybe_add"); + return true; } bool Headers::delete_(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER_WITH_NAME(1, "delete") - NORMALIZE_NAME(args[0], "Headers.delete") - - bool has; - JS::RootedObject map(cx, get_backing_map(self)); - if (!JS::MapDelete(cx, map, normalized_name, &has)) { - return false; - } - - // If no header with the given name exists, `delete` is a no-op. - if (!has) { - args.rval().setUndefined(); - return true; - } - - auto handle = get_handle(self); - if (handle) { + Mode mode = Headers::mode(self); + if (mode == Mode::HostOnly) { + auto handle = get_handle(self); + MOZ_ASSERT(handle); std::string_view name = name_chars; auto res = handle->remove(name); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; } + } else if (mode == Mode::CachedInContent) { + switch_to_content_only_mode(self); + } + + if (mode == Mode::ContentOnly) { + RootedObject entries(cx, get_entries(cx, self)); + if (!entries) { + return false; + } + + bool had; + return MapDelete(cx, entries, args[0], &had); } + args.rval().setUndefined(); return true; } @@ -580,7 +496,7 @@ bool Headers::forEach(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedValue rval(cx); JS::RootedValue iterable(cx); - if (!JS::MapEntries(cx, backing_map, &iterable)) + if (!JS::MapEntries(cx, entries, &iterable)) return false; JS::ForOfIterator it(cx); @@ -609,19 +525,19 @@ bool Headers::forEach(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -bool Headers::entries(JSContext *cx, unsigned argc, JS::Value *vp) { +bool Headers::entries(JSContext *cx, unsigned argc, Value *vp) { HEADERS_ITERATION_METHOD(0) - return JS::MapEntries(cx, backing_map, args.rval()); + return MapEntries(cx, entries, args.rval()); } -bool Headers::keys(JSContext *cx, unsigned argc, JS::Value *vp) { +bool Headers::keys(JSContext *cx, unsigned argc, Value *vp) { HEADERS_ITERATION_METHOD(0) - return JS::MapKeys(cx, backing_map, args.rval()); + return MapKeys(cx, entries, args.rval()); } -bool Headers::values(JSContext *cx, unsigned argc, JS::Value *vp) { +bool Headers::values(JSContext *cx, unsigned argc, Value *vp) { HEADERS_ITERATION_METHOD(0) - return JS::MapValues(cx, backing_map, args.rval()); + return MapValues(cx, entries, args.rval()); } const JSFunctionSpec Headers::static_methods[] = { @@ -667,6 +583,12 @@ bool Headers::init_class(JSContext *cx, JS::HandleObject global) { if (!ok) return false; + auto comma_str = JS_NewStringCopyN(cx, ", ", 2); + if (!comma_str) { + return false; + } + comma.init(cx, comma_str); + JS::RootedValue entries(cx); if (!JS_GetProperty(cx, proto_obj, "entries", &entries)) return false; @@ -676,12 +598,55 @@ bool Headers::init_class(JSContext *cx, JS::HandleObject global) { return JS_DefinePropertyById(cx, proto_obj, iteratorId, entries, 0); } -JSObject *Headers::create(JSContext *cx, JS::HandleObject self, - host_api::HttpHeadersReadOnly *handle) { - JS_SetReservedSlot(self, static_cast(Slots::Handle), JS::PrivateValue(handle)); +JSObject *Headers::create(JSContext *cx, HandleObject self, host_api::HttpHeadersReadOnly *handle) { + SetReservedSlot(self, static_cast(Slots::Handle), PrivateValue(handle)); + SetReservedSlot(self, static_cast(Slots::Mode), + JS::Int32Value(static_cast(Mode::HostOnly))); return self; } +JSObject *Headers::get_entries(JSContext *cx, HandleObject self) { + MOZ_ASSERT(is_instance(self)); + if (mode(self) != Mode::HostOnly) { + return &GetReservedSlot(self, static_cast(Slots::Entries)).toObject(); + } + + host_api::HttpHeadersReadOnly *handle = get_handle(self); + MOZ_ASSERT(handle); + auto res = handle->entries(); + if (res.is_err()) { + HANDLE_ERROR(cx, *res.to_err()); + return nullptr; + } + + RootedObject map(cx, JS::NewMapObject(cx)); + if (!map) { + return nullptr; + } + + RootedString key(cx); + RootedValue key_val(cx); + RootedString value(cx); + RootedValue value_val(cx); + for (auto& entry : std::move(res.unwrap())) { + key = core::decode(cx, std::get<0>(entry)); + if (!key) { + return nullptr; + } + value = core::decode(cx, std::get<1>(entry)); + if (!value) { + return nullptr; + } + key_val.setString(key); + value_val.setString(value); + if (!MapSet(cx, map, key_val, value_val)) { + return nullptr; + } + } + + SetReservedSlot(self, static_cast(Slots::Entries), ObjectValue(*map)); + SetReservedSlot(self, static_cast(Slots::Mode), + JS::Int32Value(static_cast(Mode::CachedInContent))); + return map; +} -} // namespace fetch -} // namespace web -} // namespace builtins +} // namespace builtins::web::fetch diff --git a/builtins/web/fetch/headers.h b/builtins/web/fetch/headers.h index 2b61a867..36b75de6 100644 --- a/builtins/web/fetch/headers.h +++ b/builtins/web/fetch/headers.h @@ -22,43 +22,61 @@ class Headers final : public BuiltinImpl { public: static constexpr const char *class_name = "Headers"; + /// Usually, headers are stored on the host only, including when they're created in-content. + /// However, iterating over headers (as keys, values, or entries) would be extremely slow if + /// we retrieved all of them from the host for each iteration step. + /// So, when we start iterating, we retrieve them once and store them in a Map as a cache. + /// If, while iterating, a header is added, deleted, or replaced, we start treating the Map as + /// the canonical store for headers, and discard the underlying resource handle entirely. + enum class Mode { + HostOnly, // Headers are stored in the host. + CachedInContent, // Host holds canonical headers, content a cached copy. + ContentOnly, // Headers are stored in a Map held by the `Entries` slot. + }; + enum class Slots { Handle, + Entries, // Map holding headers if they are available in-content. + Mode, Count, }; /** * Adds the given header name/value to `self`'s list of headers iff `self` * doesn't already contain a header with that name. - * - * Assumes that both the name and value are valid and normalized. - * TODO(performance): fully skip normalization. - * https://github.com/fastly/js-compute-runtime/issues/221 */ static bool maybe_add(JSContext *cx, JS::HandleObject self, const char *name, const char *value); - // Appends a non-normalized value for a non-normalized header name to both - // the JS side Map and, in non-standalone mode, the host. + /// Appends a value for a header name. // - // Verifies and normalizes the name and value. + /// Validates and normalizes the name and value. static bool append_header_value(JSContext *cx, JS::HandleObject self, JS::HandleValue name, JS::HandleValue value, const char *fun_name); + static Mode mode(JSObject* self) const { + MOZ_ASSERT(Headers::is_instance(self)); + return static_cast(JS::GetReservedSlot(self, static_cast(Slots::Mode)).toInt32()); + } + static const JSFunctionSpec static_methods[]; static const JSPropertySpec static_properties[]; static const JSFunctionSpec methods[]; static const JSPropertySpec properties[]; - static const unsigned ctor_length = 1; + static constexpr unsigned ctor_length = 1; static bool init_class(JSContext *cx, HandleObject global); static bool constructor(JSContext *cx, unsigned argc, Value *vp); - static JSObject *create(JSContext *cx, HandleObject self, host_api::HttpHeaders *handle, - HandleObject init_headers); static JSObject *create(JSContext *cx, HandleObject self, host_api::HttpHeaders *handle, HandleValue init_headers); static JSObject *create(JSContext *cx, HandleObject self, host_api::HttpHeadersReadOnly *handle); + + /// Returns a Map object containing the headers. + /// + /// Depending on the `Mode` the instance is in, this can be a cache or the canonical store for + /// the headers. + static JSObject* get_entries(JSContext *cx, HandleObject self); }; } // namespace fetch diff --git a/builtins/web/url.cpp b/builtins/web/url.cpp index e05fa15a..90de01ec 100644 --- a/builtins/web/url.cpp +++ b/builtins/web/url.cpp @@ -24,7 +24,7 @@ bool URLSearchParamsIterator::next(JSContext *cx, unsigned argc, JS::Value *vp) if (!result) return false; - jsurl::JSSearchParam param{jsurl::SpecSlice(nullptr, 0), jsurl::SpecSlice(nullptr, 0), false}; + jsurl::JSSearchParam param{}; jsurl::params_at(params, index, ¶m); if (param.done) { diff --git a/crates/rust-url/rust-url.h b/crates/rust-url/rust-url.h index 9a7cdb2e..5706b3f4 100644 --- a/crates/rust-url/rust-url.h +++ b/crates/rust-url/rust-url.h @@ -47,6 +47,11 @@ struct SpecSlice { const uint8_t *data; size_t len; + SpecSlice() + : data(nullptr), + len(0) + {} + SpecSlice(const uint8_t *const& data, size_t const& len) : data(data), @@ -60,6 +65,12 @@ struct JSSearchParam { SpecSlice value; bool done; + JSSearchParam() + : name(SpecSlice()), + value(SpecSlice()), + done(false) + {} + JSSearchParam(SpecSlice const& name, SpecSlice const& value, bool const& done) diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 1d3427b9..aaa4b3c8 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -216,6 +216,10 @@ HttpHeaders::HttpHeaders(const HttpHeadersReadOnly &headers) : HttpHeadersReadOn this->handle_state_ = new HandleState(handle.__handle); } +HttpHeaders *HttpHeadersReadOnly::clone() { + return new HttpHeaders(*this); +} + Result>> HttpHeadersReadOnly::entries() const { Result>> res; MOZ_ASSERT(valid()); @@ -280,6 +284,14 @@ Result>> HttpHeadersReadOnly::get(string_view name) return res; } +Result HttpHeadersReadOnly::has(string_view name) const { + MOZ_ASSERT(valid()); + + auto hdr = string_view_to_world_string(name); + Borrow borrow(this->handle_state_); + return Result::ok(wasi_http_0_2_0_types_method_fields_has(borrow, &hdr)); +} + Result HttpHeaders::set(string_view name, string_view value) { MOZ_ASSERT(valid()); auto hdr = from_string_view(name); diff --git a/include/host_api.h b/include/host_api.h index f0632648..1d27436b 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -345,6 +345,7 @@ class HttpHeadersReadOnly : public Resource { Result>> entries() const; Result> names() const; Result>> get(string_view name) const; + Result has(string_view name) const; }; class HttpHeaders final : public HttpHeadersReadOnly { diff --git a/runtime/decode.cpp b/runtime/decode.cpp new file mode 100644 index 00000000..4f925644 --- /dev/null +++ b/runtime/decode.cpp @@ -0,0 +1,10 @@ +#include "encode.h" + +namespace core { + +JSString* decode(JSContext* cx, host_api::HostString& str) { + JS::UTF8Chars ret_chars(str.ptr.get(), str.len); + return JS_NewStringCopyUTF8N(cx, ret_chars); +} + +} // namespace core diff --git a/runtime/decode.h b/runtime/decode.h new file mode 100644 index 00000000..93ecb804 --- /dev/null +++ b/runtime/decode.h @@ -0,0 +1,12 @@ +#ifndef JS_COMPUTE_RUNTIME_DECODE_H +#define JS_COMPUTE_RUNTIME_DECODE_H + +#include "host_api.h" + +namespace core { + +JSString* decode(JSContext *cx, host_api::HostString& str); + +} // namespace core + +#endif From d4e43db26a8cd152b81b4d378a25dff50393e972 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sat, 6 Apr 2024 13:49:08 +0200 Subject: [PATCH 27/52] Immediately lock reified body stream when the underlying body has already been consumed --- builtins/web/fetch/request-response.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 48b0e34b..5ce59012 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -456,13 +456,12 @@ bool RequestOrResponse::append_body(JSContext *cx, JS::HandleObject self, JS::Ha return false; } - bool success = mark_body_used(cx, source); + mozilla::DebugOnly success = mark_body_used(cx, source); MOZ_ASSERT(success); if (body_stream(source) != body_stream(self)) { success = mark_body_used(cx, self); MOZ_ASSERT(success); } - (void)success; return true; } @@ -1135,7 +1134,11 @@ JSObject *RequestOrResponse::create_body_stream(JSContext *cx, JS::HandleObject return nullptr; } - // TODO: immediately lock the stream if the owner's body is already used. + // If the body has already been used without being reified as a ReadableStream, + // lock the stream immediately. + if (body_used(owner)) { + MOZ_RELEASE_ASSERT(streams::NativeStreamSource::lock_stream(cx, body_stream)); + } JS_SetReservedSlot(owner, static_cast(Slots::BodyStream), JS::ObjectValue(*body_stream)); @@ -1544,6 +1547,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H host_api::HostString method; if (init_val.isObject()) { + // TODO: investigate special-casing native Request objects here to not reify headers and bodies. JS::RootedObject init(cx, init_val.toObjectOrNull()); if (!JS_GetProperty(cx, init, "method", &method_val) || !JS_GetProperty(cx, init, "headers", &headers_val) || From dae69535926c1f724d3f6d82de487d724416e6ca Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Wed, 10 Apr 2024 12:23:16 +0200 Subject: [PATCH 28/52] More headers rework! --- builtins/web/fetch/headers.cpp | 40 ++++++++++++++----------------- builtins/web/fetch/headers.h | 2 +- host-apis/wasi-0.2.0/host_api.cpp | 14 ++++++++--- include/host_api.h | 6 +++++ spin.toml | 2 +- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 83ab631b..199b9efc 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -9,8 +9,6 @@ namespace builtins::web::fetch { namespace { -using Handle = host_api::HttpHeaders; - #define HEADERS_ITERATION_METHOD(argc) \ METHOD_HEADER(argc) \ JS::RootedObject entries(cx, get_entries(cx, self)); \ @@ -54,11 +52,11 @@ const char VALID_NAME_CHARS[128] = { return false; \ } -Handle *get_handle(JSObject *self) { +host_api::HttpHeadersReadOnly *get_handle(JSObject *self) { MOZ_ASSERT(Headers::is_instance(self)); auto handle = JS::GetReservedSlot(self, static_cast(Headers::Slots::Handle)).toPrivate(); - return static_cast(handle); + return static_cast(handle); } /** @@ -267,30 +265,28 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand NORMALIZE_NAME(name, fun_name) NORMALIZE_VALUE(value, fun_name) - auto handle = get_handle(self); - if (handle) { - std::string_view name = name_chars; - if (name == "set-cookie") { - for (auto value : splitCookiesString(value_chars)) { - auto res = handle->append(name_chars, value); - if (auto *err = res.to_err()) { - HANDLE_ERROR(cx, *err); - return false; - } - } - } else { - auto res = handle->append(name_chars, value_chars); + auto handle = get_handle(self)->as_writable(); + std::string_view name_str = name_chars; + if (name_str == "set-cookie") { + for (auto value : splitCookiesString(value_chars)) { + auto res = handle->append(name_chars, value); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; } } + } else { + auto res = handle->append(name_chars, value_chars); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } } return true; } -JSObject *Headers::create(JSContext *cx, JS::HandleObject self, Handle *handle, +JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, JS::HandleValue initv) { // TODO: check if initv is a Headers instance and clone its handle if so. JS::RootedObject headers(cx, create(cx, self, handle)); @@ -357,7 +353,7 @@ bool Headers::set(JSContext *cx, unsigned argc, JS::Value *vp) { Mode mode = Headers::mode(self); if (mode == Mode::HostOnly) { - auto handle = get_handle(self); + auto handle = get_handle(self)->as_writable(); MOZ_ASSERT(handle); auto res = handle->set(name_chars, value_chars); if (auto *err = res.to_err()) { @@ -433,7 +429,7 @@ bool Headers::append(JSContext *cx, unsigned argc, JS::Value *vp) { bool Headers::maybe_add(JSContext *cx, JS::HandleObject self, const char *name, const char *value) { MOZ_ASSERT(mode(self) == Mode::HostOnly); - auto handle = get_handle(self); + auto handle = get_handle(self)->as_writable(); auto has = handle->has(name); MOZ_ASSERT(!has.is_err()); if (has.unwrap()) { @@ -454,7 +450,7 @@ bool Headers::delete_(JSContext *cx, unsigned argc, JS::Value *vp) { NORMALIZE_NAME(args[0], "Headers.delete") Mode mode = Headers::mode(self); if (mode == Mode::HostOnly) { - auto handle = get_handle(self); + auto handle = get_handle(self)->as_writable(); MOZ_ASSERT(handle); std::string_view name = name_chars; auto res = handle->remove(name); @@ -610,7 +606,7 @@ JSObject *Headers::get_entries(JSContext *cx, HandleObject self) { return &GetReservedSlot(self, static_cast(Slots::Entries)).toObject(); } - host_api::HttpHeadersReadOnly *handle = get_handle(self); + auto handle = get_handle(self); MOZ_ASSERT(handle); auto res = handle->entries(); if (res.is_err()) { diff --git a/builtins/web/fetch/headers.h b/builtins/web/fetch/headers.h index 36b75de6..d9a44046 100644 --- a/builtins/web/fetch/headers.h +++ b/builtins/web/fetch/headers.h @@ -53,7 +53,7 @@ class Headers final : public BuiltinImpl { static bool append_header_value(JSContext *cx, JS::HandleObject self, JS::HandleValue name, JS::HandleValue value, const char *fun_name); - static Mode mode(JSObject* self) const { + static Mode mode(JSObject* self) { MOZ_ASSERT(Headers::is_instance(self)); return static_cast(JS::GetReservedSlot(self, static_cast(Slots::Mode)).toInt32()); } diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index aaa4b3c8..ecf3a9fc 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -313,10 +313,18 @@ Result HttpHeaders::append(string_view name, string_view value) { auto val = from_string_view(value); Borrow borrow(this->handle_state_); + // TODO: properly handle `err` wasi_http_0_2_0_types_header_error_t err; - wasi_http_0_2_0_types_method_fields_append(borrow, &hdr, &val, &err); - - // TODO: handle `err` + if (!wasi_http_0_2_0_types_method_fields_append(borrow, &hdr, &val, &err)) { + switch (err.tag) { + case WASI_HTTP_0_2_0_TYPES_HEADER_ERROR_INVALID_SYNTAX: + case WASI_HTTP_0_2_0_TYPES_HEADER_ERROR_FORBIDDEN: + return Result::err(154); + case WASI_HTTP_0_2_0_TYPES_HEADER_ERROR_IMMUTABLE: + fprintf(stderr, "Headers %d should not be immutable", this->handle_state_->handle); + MOZ_ASSERT_UNREACHABLE(); + } + } return {}; } diff --git a/include/host_api.h b/include/host_api.h index 1d27436b..3333cffc 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -341,6 +341,9 @@ class HttpHeadersReadOnly : public Resource { HttpHeaders* clone(); virtual bool is_writable() { return false; }; + virtual HttpHeaders* as_writable() { + MOZ_ASSERT_UNREACHABLE(); + }; Result>> entries() const; Result> names() const; @@ -363,6 +366,9 @@ class HttpHeaders final : public HttpHeadersReadOnly { static Result FromEntries(const vector>> &entries); bool is_writable() override { return true; }; + HttpHeaders* as_writable() override { + return this; + }; Result set(string_view name, string_view value); Result append(string_view name, string_view value); diff --git a/spin.toml b/spin.toml index c63db163..a76fa6cd 100644 --- a/spin.toml +++ b/spin.toml @@ -12,7 +12,7 @@ component = "js-component" [component.js-component] source = "smoke-test.wasm" -allowed_outbound_hosts = ["https://fermyon.com/", "https://example.com:443"] +allowed_outbound_hosts = ["https://fermyon.com/", "https://example.com:443", "http://localhost:3000"] [component.js-component.build] command = "" watch = ["smoke-test.wasm"] From aa5a32c112e5a9a1a4d3766f6c3b145f496a235b Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Tue, 30 Apr 2024 00:02:47 +0200 Subject: [PATCH 29/52] Use `string_view` instead of `HostString in core::decode` `HostString` converts to `string_view`, so this makes `decode` easy to use from more places. --- runtime/decode.cpp | 4 ++-- runtime/decode.h | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/runtime/decode.cpp b/runtime/decode.cpp index 4f925644..2bd7daf6 100644 --- a/runtime/decode.cpp +++ b/runtime/decode.cpp @@ -2,8 +2,8 @@ namespace core { -JSString* decode(JSContext* cx, host_api::HostString& str) { - JS::UTF8Chars ret_chars(str.ptr.get(), str.len); +JSString* decode(JSContext* cx, string_view str) { + JS::UTF8Chars ret_chars(str.data(), str.length()); return JS_NewStringCopyUTF8N(cx, ret_chars); } diff --git a/runtime/decode.h b/runtime/decode.h index 93ecb804..e473fcd7 100644 --- a/runtime/decode.h +++ b/runtime/decode.h @@ -1,11 +1,9 @@ #ifndef JS_COMPUTE_RUNTIME_DECODE_H #define JS_COMPUTE_RUNTIME_DECODE_H -#include "host_api.h" - namespace core { -JSString* decode(JSContext *cx, host_api::HostString& str); +JSString* decode(JSContext *cx, string_view str); } // namespace core From d1984e9487a90939dee82f47ec34374184e8701f Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Tue, 30 Apr 2024 00:03:16 +0200 Subject: [PATCH 30/52] Adjust namespace of wpt support functionality --- tests/wpt-harness/wpt_builtins.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wpt-harness/wpt_builtins.cpp b/tests/wpt-harness/wpt_builtins.cpp index ab83cfc5..d62566b9 100644 --- a/tests/wpt-harness/wpt_builtins.cpp +++ b/tests/wpt-harness/wpt_builtins.cpp @@ -32,7 +32,7 @@ const JSPropertySpec properties[] = { JS_PSGS("baseURL", baseURL_get, baseURL_set, JSPROP_ENUMERATE), JS_PS_END}; -namespace wpt_builtins { +namespace wpt_support { bool install(api::Engine* engine) { engine->enable_module_mode(false); From ca335a770b4868fdb18fedd0962bdc9cbab93319 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Tue, 30 Apr 2024 00:05:34 +0200 Subject: [PATCH 31/52] =?UTF-8?q?And=20yet=20more=20headers=20rework?= =?UTF-8?q?=E2=80=94along=20with=20an=20overhaul=20of=20lots=20of=20the=20?= =?UTF-8?q?host=20API=20abstraction=20and=20various=20other=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- builtins/web/fetch/fetch-api.cpp | 2 +- builtins/web/fetch/fetch_event.cpp | 163 ++++----- builtins/web/fetch/headers.cpp | 434 ++++++++++++++++++------ builtins/web/fetch/headers.h | 61 +++- builtins/web/fetch/request-response.cpp | 192 ++++------- builtins/web/fetch/request-response.h | 20 +- builtins/web/url.cpp | 7 +- host-apis/wasi-0.2.0/host_api.cpp | 322 ++++++++++++------ include/host_api.h | 63 ++-- 9 files changed, 785 insertions(+), 479 deletions(-) diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 9f45119d..25c2cdf2 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -42,7 +42,7 @@ class ResponseFutureTask final : public api::AsyncTask { return false; } - response_obj = Response::create(cx, response_obj, response); + response_obj = Response::create_incoming(cx, response_obj, response); if (!response_obj) { return false; } diff --git a/builtins/web/fetch/fetch_event.cpp b/builtins/web/fetch/fetch_event.cpp index 8279b2e5..ca6a5e00 100644 --- a/builtins/web/fetch/fetch_event.cpp +++ b/builtins/web/fetch/fetch_event.cpp @@ -20,8 +20,6 @@ api::Engine *ENGINE; PersistentRooted INSTANCE; JS::PersistentRootedObjectVector *FETCH_HANDLERS; - -host_api::HttpOutgoingResponse::ResponseOutparam RESPONSE_OUT; host_api::HttpOutgoingBody *STREAMING_BODY; void inc_pending_promise_count(JSObject *self) { @@ -70,7 +68,7 @@ JSObject *FetchEvent::prepare_downstream_request(JSContext *cx) { cx, JS_NewObjectWithGivenProto(cx, &Request::class_, Request::proto_obj)); if (!requestInstance) return nullptr; - return Request::create(cx, requestInstance, nullptr); + return Request::create(cx, requestInstance); } bool FetchEvent::init_incoming_request(JSContext *cx, JS::HandleObject self, @@ -149,7 +147,7 @@ bool send_response(host_api::HttpOutgoingResponse *response, JS::HandleObject se FetchEvent::State new_state) { MOZ_ASSERT(FetchEvent::state(self) == FetchEvent::State::unhandled || FetchEvent::state(self) == FetchEvent::State::waitToRespond); - auto result = response->send(RESPONSE_OUT); + auto result = response->send(); FetchEvent::set_state(self, new_state); if (auto *err = result.to_err()) { @@ -161,26 +159,27 @@ bool send_response(host_api::HttpOutgoingResponse *response, JS::HandleObject se } bool start_response(JSContext *cx, JS::HandleObject response_obj, bool streaming) { - auto generic_response = Response::response_handle(response_obj); - host_api::HttpOutgoingResponse *response; - - if (generic_response->is_incoming()) { - auto incoming_response = static_cast(generic_response); - auto status = incoming_response->status(); - MOZ_RELEASE_ASSERT(!status.is_err(), "Incoming response must have a status code"); - auto headers = incoming_response->headers().unwrap()->clone(); - response = host_api::HttpOutgoingResponse::make(status.unwrap(), headers); - auto *source_body = incoming_response->body().unwrap(); - auto *dest_body = response->body().unwrap(); - - auto res = dest_body->append(ENGINE, source_body); - if (auto *err = res.to_err()) { - HANDLE_ERROR(cx, *err); - return false; + auto status = Response::status(response_obj); + auto headers = RequestOrResponse::headers_clone(cx, response_obj); + if (!headers) { + return false; + } + + auto incoming_response = Response::response_handle(response_obj); + host_api::HttpOutgoingResponse* response = + host_api::HttpOutgoingResponse::make(status, headers.get()); + if (incoming_response) { + if (streaming) { + auto *source_body = incoming_response->body().unwrap(); + auto *dest_body = response->body().unwrap(); + + auto res = dest_body->append(ENGINE, source_body); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + MOZ_RELEASE_ASSERT(RequestOrResponse::mark_body_used(cx, response_obj)); } - MOZ_RELEASE_ASSERT(RequestOrResponse::mark_body_used(cx, response_obj)); - } else { - response = static_cast(generic_response); } if (streaming && response->has_body()) { @@ -509,6 +508,49 @@ static void dispatch_fetch_event(HandleObject event, double *total_compute) { // LOG("Request handler took %fms\n", diff / 1000); } +bool handle_incoming_request(host_api::HttpIncomingRequest * request) { + HandleObject fetch_event = FetchEvent::instance(); + MOZ_ASSERT(FetchEvent::is_instance(fetch_event)); + if (!FetchEvent::init_incoming_request(ENGINE->cx(), fetch_event, request)) { + ENGINE->dump_pending_exception("initialization of FetchEvent"); + return false; + } + + double total_compute = 0; + + dispatch_fetch_event(fetch_event, &total_compute); + + // track fetch event interest, which when decremented ends the event loop + ENGINE->incr_event_loop_interest(); + + bool success = ENGINE->run_event_loop(); + + if (JS_IsExceptionPending(ENGINE->cx())) { + ENGINE->dump_pending_exception("evaluating incoming request"); + } + + if (!success) { + fprintf(stderr, "Internal error."); + } + + if (ENGINE->debug_logging_enabled() && ENGINE->has_pending_async_tasks()) { + fprintf(stderr, "Event loop terminated with async tasks pending. " + "Use FetchEvent#waitUntil to extend the component's " + "lifetime if needed.\n"); + } + + if (!FetchEvent::response_started(fetch_event)) { + FetchEvent::respondWithError(ENGINE->cx(), fetch_event); + return true; + } + + if (STREAMING_BODY && STREAMING_BODY->valid()) { + STREAMING_BODY->close(); + } + + return true; +} + bool install(api::Engine *engine) { ENGINE = engine; FETCH_HANDLERS = new JS::PersistentRootedObjectVector(engine->cx()); @@ -544,81 +586,8 @@ bool install(api::Engine *engine) { // } // } + host_api::HttpIncomingRequest::set_handler(handle_incoming_request); return true; } } // namespace builtins::web::fetch::fetch_event - -// #define S_TO_NS(s) ((s) * 1000000000) -// static int64_t now_ns() { -// timespec now{}; -// clock_gettime(CLOCK_MONOTONIC, &now); -// return S_TO_NS(now.tv_sec) + now.tv_nsec; -// } -using namespace builtins::web::fetch::fetch_event; -// TODO: change this to fully work in terms of host_api. -void exports_wasi_http_incoming_handler(exports_wasi_http_incoming_request request_handle, - exports_wasi_http_response_outparam response_out) { - - // auto begin = now_ns(); - // auto id1 = host_api::MonotonicClock::subscribe(begin + 1, true); - // auto id2 = host_api::MonotonicClock::subscribe(begin + 1000000*1000, true); - // bindings_borrow_pollable_t handles[2] = {bindings_borrow_pollable_t{id2}, - // bindings_borrow_pollable_t{id1}}; auto list = bindings_list_borrow_pollable_t{handles, 2}; - // bindings_list_u32_t res = {.ptr = nullptr,.len = 0}; - // wasi_io_0_2_0_rc_2023_10_18_poll_poll_list(&list, &res); - // fprintf(stderr, "first ready after first poll: %d. diff: %lld\n", handles[res.ptr[0]].__handle, - // (now_ns() - begin) / 1000); - // - // wasi_io_0_2_0_rc_2023_10_18_poll_pollable_drop_own(bindings_own_pollable_t{id1}); - // - // bindings_borrow_pollable_t handles2[1] = {bindings_borrow_pollable_t{id2}}; - // list = bindings_list_borrow_pollable_t{handles2, 1}; - // wasi_io_0_2_0_rc_2023_10_18_poll_poll_list(&list, &res); - // fprintf(stderr, "first ready after second poll: %d. diff: %lld\n", - // handles2[res.ptr[0]].__handle, (now_ns() - begin) / 1000); - // - // return; - - RESPONSE_OUT = response_out.__handle; - - auto *request = new host_api::HttpIncomingRequest(request_handle.__handle); - HandleObject fetch_event = FetchEvent::instance(); - MOZ_ASSERT(FetchEvent::is_instance(fetch_event)); - if (!FetchEvent::init_incoming_request(ENGINE->cx(), fetch_event, request)) { - ENGINE->dump_pending_exception("initialization of FetchEvent"); - return; - } - - double total_compute = 0; - - dispatch_fetch_event(fetch_event, &total_compute); - - // track fetch event interest, which when decremented ends the event loop - ENGINE->incr_event_loop_interest(); - - bool success = ENGINE->run_event_loop(); - - if (JS_IsExceptionPending(ENGINE->cx())) { - ENGINE->dump_pending_exception("evaluating incoming request"); - } - - if (!success) { - fprintf(stderr, "Internal error."); - } - - if (ENGINE->debug_logging_enabled() && ENGINE->has_pending_async_tasks()) { - fprintf(stderr, "Event loop terminated with async tasks pending. " - "Use FetchEvent#waitUntil to extend the component's " - "lifetime if needed.\n"); - } - - if (!FetchEvent::response_started(fetch_event)) { - FetchEvent::respondWithError(ENGINE->cx(), fetch_event); - return; - } - - if (STREAMING_BODY && STREAMING_BODY->valid()) { - STREAMING_BODY->close(); - } -} diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 199b9efc..6139b0c2 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -255,9 +255,213 @@ std::vector splitCookiesString(std::string_view cookiesString) } // namespace -void switch_to_content_only_mode(JSObject* self) { +bool redecode_str_if_changed(JSContext* cx, HandleValue str_val, string_view chars, + bool changed, MutableHandleValue rval) { + if (!changed) { + rval.set(str_val); + return true; + } + + RootedString str(cx, core::decode(cx, chars)); + if (!str) { + return false; + } + + rval.setString(str); + return true; +} + +static bool switch_mode(JSContext* cx, HandleObject self, const Headers::Mode mode) { + auto current_mode = Headers::mode(self); + if (mode == current_mode) { + return true; + } + + if (current_mode == Headers::Mode::Uninitialized) { + if (mode == Headers::Mode::ContentOnly) { + RootedObject map(cx, JS::NewMapObject(cx)); + if (!map) { + return false; + } + SetReservedSlot(self, static_cast(Headers::Slots::Entries), ObjectValue(*map)); + } else { + MOZ_ASSERT(mode == Headers::Mode::HostOnly); + auto handle = new host_api::HttpHeaders(); + SetReservedSlot(self, static_cast(Headers::Slots::Handle), PrivateValue(handle)); + } + + SetReservedSlot(self, static_cast(Headers::Slots::Mode), JS::Int32Value(static_cast(mode))); + return true; + } + + if (current_mode == Headers::Mode::ContentOnly) { + MOZ_ASSERT(mode == Headers::Mode::CachedInContent, + "Switching from ContentOnly to HostOnly is wasteful and not implemented"); + RootedObject entries(cx, Headers::get_entries(cx, self)); + MOZ_ASSERT(entries); + RootedValue iterable(cx); + if (!MapEntries(cx, entries, &iterable)) { + return false; + } + + JS::ForOfIterator it(cx); + if (!it.init(iterable)) { + return false; + } + + vector> string_entries; + + RootedValue entry_val(cx); + RootedObject entry(cx); + RootedValue name_val(cx); + RootedString name_str(cx); + RootedValue value_val(cx); + RootedString value_str(cx); + while (true) { + bool done; + if (!it.next(&entry_val, &done)) { + return false; + } + + if (done) { + break; + } + + entry = &entry_val.toObject(); + JS_GetElement(cx, entry, 1, &name_val); + JS_GetElement(cx, entry, 0, &value_val); + name_str = name_val.toString(); + value_str = value_val.toString(); + + auto name = core::encode(cx, name_str); + if (!name.ptr) { + return false; + } + + auto value = core::encode(cx, value_str); + if (!value.ptr) { + return false; + } + + string_entries.emplace_back(name, value); + } + + auto handle = host_api::HttpHeaders::FromEntries(string_entries); + if (handle.is_err()) { + JS_ReportErrorASCII(cx, "Failed to clone headers"); + return false; + } + SetReservedSlot(self, static_cast(Headers::Slots::Handle), + PrivateValue(handle.unwrap())); + } + + // Regardless of whether we're switching to CachedInContent or ContentOnly, + // get all entries into content. + if (current_mode == Headers::Mode::HostOnly) { + auto handle = get_handle(self); + MOZ_ASSERT(handle); + auto res = handle->entries(); + if (res.is_err()) { + HANDLE_ERROR(cx, *res.to_err()); + return false; + } + + RootedObject map(cx, JS::NewMapObject(cx)); + if (!map) { + return false; + } + + RootedString key(cx); + RootedValue key_val(cx); + RootedString value(cx); + RootedValue value_val(cx); + for (auto &entry : std::move(res.unwrap())) { + key = core::decode(cx, std::get<0>(entry)); + if (!key) { + return false; + } + value = core::decode(cx, std::get<1>(entry)); + if (!value) { + return false; + } + key_val.setString(key); + value_val.setString(value); + if (!MapSet(cx, map, key_val, value_val)) { + return false; + } + } + + SetReservedSlot(self, static_cast(Headers::Slots::Entries), ObjectValue(*map)); + } + + if (mode == Headers::Mode::ContentOnly) { + auto handle = get_handle(self); + delete handle; + SetReservedSlot(self, static_cast(Headers::Slots::Handle), PrivateValue(nullptr)); + SetReservedSlot(self, static_cast(Headers::Slots::Mode), + JS::Int32Value(static_cast(Headers::Mode::CachedInContent))); + } + SetReservedSlot(self, static_cast(Headers::Slots::Mode), - JS::Int32Value(static_cast(Headers::Mode::ContentOnly))); + JS::Int32Value(static_cast(mode))); + return true; +} + +bool prepare_for_entries_modification(JSContext* cx, JS::HandleObject self) { + auto mode = Headers::mode(self); + if (mode == Headers::Mode::HostOnly) { + auto handle = get_handle(self); + if (!handle->is_writable()) { + auto new_handle = handle->clone(); + if (!new_handle) { + JS_ReportErrorASCII(cx, "Failed to clone headers"); + return false; + } + delete handle; + SetReservedSlot(self, static_cast(Headers::Slots::Handle), PrivateValue(new_handle)); + } + } else if (mode == Headers::Mode::CachedInContent || mode == Headers::Mode::Uninitialized) { + return switch_mode(cx, self, Headers::Mode::ContentOnly); + } + return true; +} + +bool append_single_normalized_header_value(JSContext *cx, HandleObject self, + HandleValue name, string_view name_chars, bool name_changed, + HandleValue value, string_view value_chars, bool value_changed, + const char *fun_name) { + Headers::Mode mode = Headers::mode(self); + if (mode == Headers::Mode::HostOnly) { + auto handle = get_handle(self)->as_writable(); + MOZ_ASSERT(handle); + auto res = handle->append(name_chars, value_chars); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + } else { + MOZ_ASSERT(mode == Headers::Mode::ContentOnly); + RootedObject entries(cx, Headers::get_entries(cx, self)); + if (!entries) { + return false; + } + + RootedValue name_val(cx); + if (!redecode_str_if_changed(cx, name, name_chars, name_changed, &name_val)) { + return false; + } + + RootedValue value_val(cx); + if (!redecode_str_if_changed(cx, value, value_chars, value_changed, &value_val)) { + return false; + } + + if (!MapSet(cx, entries, name_val, value_val)) { + return false; + } + } + + return true; } bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::HandleValue name, @@ -265,20 +469,21 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand NORMALIZE_NAME(name, fun_name) NORMALIZE_VALUE(value, fun_name) - auto handle = get_handle(self)->as_writable(); + if (!prepare_for_entries_modification(cx, self)) { + return false; + } + std::string_view name_str = name_chars; if (name_str == "set-cookie") { for (auto value : splitCookiesString(value_chars)) { - auto res = handle->append(name_chars, value); - if (auto *err = res.to_err()) { - HANDLE_ERROR(cx, *err); + if (!append_single_normalized_header_value(cx, self, name, name_chars, name_changed, UndefinedHandleValue, + value, true, fun_name)) { return false; } } } else { - auto res = handle->append(name_chars, value_chars); - if (auto *err = res.to_err()) { - HANDLE_ERROR(cx, *err); + if (!append_single_normalized_header_value(cx, self, name, name_chars, name_changed, value, + value_chars, value_changed, fun_name)) { return false; } } @@ -286,41 +491,45 @@ bool Headers::append_header_value(JSContext *cx, JS::HandleObject self, JS::Hand return true; } -JSObject *Headers::create(JSContext *cx, JS::HandleObject self, host_api::HttpHeaders *handle, - JS::HandleValue initv) { - // TODO: check if initv is a Headers instance and clone its handle if so. - JS::RootedObject headers(cx, create(cx, self, handle)); - if (!headers) - return nullptr; +void init_from_handle(JSObject* self, host_api::HttpHeadersReadOnly* handle) { + MOZ_ASSERT(Headers::is_instance(self)); + MOZ_ASSERT(Headers::mode(self) == Headers::Mode::Uninitialized); + SetReservedSlot(self, static_cast(Headers::Slots::Mode), + JS::Int32Value(static_cast(Headers::Mode::HostOnly))); + SetReservedSlot(self, static_cast(Headers::Slots::Handle), PrivateValue(handle)); +} - bool consumed = false; - if (!core::maybe_consume_sequence_or_record(cx, initv, headers, - &consumed, "Headers")) { +JSObject *Headers::create(JSContext *cx, host_api::HttpHeadersReadOnly *handle) { + RootedObject self(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj)); + if (!self) { return nullptr; } + init_from_handle(self, handle); + return self; +} - if (!consumed) { - core::report_sequence_or_record_arg_error(cx, "Headers", ""); +JSObject *Headers::create(JSContext *cx, HandleValue init_headers) { + RootedObject self(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj)); + if (!self) { return nullptr; } - - return headers; + return create(cx, self, init_headers); } -bool redecode_str_if_changed(JSContext* cx, HandleValue str_val, host_api::HostString& chars, - bool changed, MutableHandleValue rval) { - if (!changed) { - rval.set(str_val); - return true; +JSObject *Headers::create(JSContext *cx, HandleObject self, HandleValue initv) { + // TODO: check if initv is a Headers instance and clone its handle if so. + bool consumed = false; + if (!core::maybe_consume_sequence_or_record(cx, initv, self, + &consumed, "Headers")) { + return nullptr; } - RootedString str(cx, core::decode(cx, chars)); - if (!str) { - return false; + if (!consumed) { + core::report_sequence_or_record_arg_error(cx, "Headers", ""); + return nullptr; } - rval.setString(str); - return true; + return self; } bool Headers::get(JSContext *cx, unsigned argc, JS::Value *vp) { @@ -329,6 +538,11 @@ bool Headers::get(JSContext *cx, unsigned argc, JS::Value *vp) { NORMALIZE_NAME(args[0], "Headers.get") Mode mode = Headers::mode(self); + if (mode == Headers::Mode::Uninitialized) { + args.rval().setNull(); + return true; + } + if (mode == Mode::HostOnly) { return retrieve_value_for_header_from_handle(cx, self, name_chars, args.rval()); } @@ -342,7 +556,15 @@ bool Headers::get(JSContext *cx, unsigned argc, JS::Value *vp) { if (!redecode_str_if_changed(cx, args[0], name_chars, name_changed, &name_val)) { return false; } - return MapGet(cx, entries, name_val, args.rval()); + if (!MapGet(cx, entries, name_val, args.rval())) { + return false; + } + + if (args.rval().isUndefined()) { + args.rval().setNull(); + } + + return true; } bool Headers::set(JSContext *cx, unsigned argc, JS::Value *vp) { @@ -351,6 +573,10 @@ bool Headers::set(JSContext *cx, unsigned argc, JS::Value *vp) { NORMALIZE_NAME(args[0], "Headers.set") NORMALIZE_VALUE(args[1], "Headers.set") + if (!prepare_for_entries_modification(cx, self)) { + return false; + } + Mode mode = Headers::mode(self); if (mode == Mode::HostOnly) { auto handle = get_handle(self)->as_writable(); @@ -360,11 +586,8 @@ bool Headers::set(JSContext *cx, unsigned argc, JS::Value *vp) { HANDLE_ERROR(cx, *err); return false; } - } else if (mode == Mode::CachedInContent) { - switch_to_content_only_mode(self); - } - - if (mode == Mode::ContentOnly) { + } else { + MOZ_ASSERT(mode == Mode::ContentOnly); RootedObject entries(cx, get_entries(cx, self)); if (!entries) { return false; @@ -376,7 +599,7 @@ bool Headers::set(JSContext *cx, unsigned argc, JS::Value *vp) { } RootedValue value_val(cx); - if (!redecode_str_if_changed(cx, args[0], value_chars, value_changed, &value_val)) { + if (!redecode_str_if_changed(cx, args[1], value_chars, value_changed, &value_val)) { return false; } @@ -395,6 +618,11 @@ bool Headers::has(JSContext *cx, unsigned argc, JS::Value *vp) { NORMALIZE_NAME(args[0], "Headers.has") Mode mode = Headers::mode(self); + if (mode == Headers::Mode::Uninitialized) { + args.rval().setBoolean(false); + return true; + } + if (mode == Mode::HostOnly) { auto handle = get_handle(self); MOZ_ASSERT(handle); @@ -427,26 +655,59 @@ bool Headers::append(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -bool Headers::maybe_add(JSContext *cx, JS::HandleObject self, const char *name, const char *value) { - MOZ_ASSERT(mode(self) == Mode::HostOnly); - auto handle = get_handle(self)->as_writable(); - auto has = handle->has(name); - MOZ_ASSERT(!has.is_err()); - if (has.unwrap()) { +bool Headers::set_if_undefined(JSContext *cx, JS::HandleObject self, const char *name, const char *value) { + if (!prepare_for_entries_modification(cx, self)) { + return false; + } + + if (mode(self) == Mode::HostOnly) { + auto handle = get_handle(self)->as_writable(); + auto has = handle->has(name); + MOZ_ASSERT(!has.is_err()); + if (has.unwrap()) { + return true; + } + + auto res = handle->append(name, value); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } return true; } - auto res = handle->append(name, value); - if (auto *err = res.to_err()) { - HANDLE_ERROR(cx, *err); + MOZ_ASSERT(mode(self) == Mode::ContentOnly); + RootedObject entries(cx, get_entries(cx, self)); + RootedString name_str(cx, JS_NewStringCopyZ(cx, name)); + if (!name_str) { return false; } + RootedValue name_val(cx, StringValue(name_str)); - return true; + bool has; + if (!MapHas(cx, entries, name_val, &has)) { + return false; + } + if (has) { + return true; + } + + RootedString value_str(cx, JS_NewStringCopyZ(cx, value)); + if (!value_str) { + return false; + } + RootedValue value_val(cx, StringValue(value_str)); + + return JS::MapSet(cx, entries, name_val, value_val); } bool Headers::delete_(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER_WITH_NAME(1, "delete") + + if (!prepare_for_entries_modification(cx, self)) { + return false; + } + NORMALIZE_NAME(args[0], "Headers.delete") Mode mode = Headers::mode(self); if (mode == Mode::HostOnly) { @@ -458,18 +719,19 @@ bool Headers::delete_(JSContext *cx, unsigned argc, JS::Value *vp) { HANDLE_ERROR(cx, *err); return false; } - } else if (mode == Mode::CachedInContent) { - switch_to_content_only_mode(self); - } - - if (mode == Mode::ContentOnly) { + } else { + MOZ_ASSERT(mode == Mode::ContentOnly); RootedObject entries(cx, get_entries(cx, self)); if (!entries) { return false; } + RootedValue name_val(cx); + if (!redecode_str_if_changed(cx, args[0], name_chars, name_changed, &name_val)) { + return false; + } bool had; - return MapDelete(cx, entries, args[0], &had); + return MapDelete(cx, entries, name_val, &had); } @@ -564,8 +826,12 @@ const JSPropertySpec Headers::properties[] = { bool Headers::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { CTOR_HEADER("Headers", 0); - JS::RootedObject headersInstance(cx, JS_NewObjectForConstructor(cx, &class_, args)); - JS::RootedObject headers(cx, create(cx, headersInstance, nullptr, args.get(0))); + HandleValue headersInit = args.get(0); + RootedObject headersInstance(cx, JS_NewObjectForConstructor(cx, &class_, args)); + if (!headersInstance) { + return false; + } + JS::RootedObject headers(cx, create(cx, headersInstance, headersInit)); if (!headers) { return false; } @@ -594,55 +860,29 @@ bool Headers::init_class(JSContext *cx, JS::HandleObject global) { return JS_DefinePropertyById(cx, proto_obj, iteratorId, entries, 0); } -JSObject *Headers::create(JSContext *cx, HandleObject self, host_api::HttpHeadersReadOnly *handle) { - SetReservedSlot(self, static_cast(Slots::Handle), PrivateValue(handle)); - SetReservedSlot(self, static_cast(Slots::Mode), - JS::Int32Value(static_cast(Mode::HostOnly))); - return self; -} JSObject *Headers::get_entries(JSContext *cx, HandleObject self) { MOZ_ASSERT(is_instance(self)); - if (mode(self) != Mode::HostOnly) { - return &GetReservedSlot(self, static_cast(Slots::Entries)).toObject(); - } - - auto handle = get_handle(self); - MOZ_ASSERT(handle); - auto res = handle->entries(); - if (res.is_err()) { - HANDLE_ERROR(cx, *res.to_err()); + if (mode(self) == Mode::HostOnly && !switch_mode(cx, self, Mode::CachedInContent)) { return nullptr; } - RootedObject map(cx, JS::NewMapObject(cx)); - if (!map) { + return &GetReservedSlot(self, static_cast(Slots::Entries)).toObject(); +} + +unique_ptr Headers::handle_clone(JSContext* cx, HandleObject self) { + auto mode = Headers::mode(self); + if (mode == Mode::ContentOnly && !switch_mode(cx, self, Mode::CachedInContent)) { + // Switch to Mode::CachedInContent to ensure that the latest data is available on the handle, + // but without discarding the existing entries, in case content reads them later. return nullptr; } - RootedString key(cx); - RootedValue key_val(cx); - RootedString value(cx); - RootedValue value_val(cx); - for (auto& entry : std::move(res.unwrap())) { - key = core::decode(cx, std::get<0>(entry)); - if (!key) { - return nullptr; - } - value = core::decode(cx, std::get<1>(entry)); - if (!value) { - return nullptr; - } - key_val.setString(key); - value_val.setString(value); - if (!MapSet(cx, map, key_val, value_val)) { - return nullptr; - } + auto handle = unique_ptr(get_handle(self)->clone()); + if (!handle) { + JS_ReportErrorASCII(cx, "Failed to clone headers"); + return nullptr; } - - SetReservedSlot(self, static_cast(Slots::Entries), ObjectValue(*map)); - SetReservedSlot(self, static_cast(Slots::Mode), - JS::Int32Value(static_cast(Mode::CachedInContent))); - return map; + return handle; } } // namespace builtins::web::fetch diff --git a/builtins/web/fetch/headers.h b/builtins/web/fetch/headers.h index d9a44046..e9a946fb 100644 --- a/builtins/web/fetch/headers.h +++ b/builtins/web/fetch/headers.h @@ -22,16 +22,41 @@ class Headers final : public BuiltinImpl { public: static constexpr const char *class_name = "Headers"; - /// Usually, headers are stored on the host only, including when they're created in-content. - /// However, iterating over headers (as keys, values, or entries) would be extremely slow if - /// we retrieved all of them from the host for each iteration step. - /// So, when we start iterating, we retrieve them once and store them in a Map as a cache. - /// If, while iterating, a header is added, deleted, or replaced, we start treating the Map as - /// the canonical store for headers, and discard the underlying resource handle entirely. + /// Headers instances can be in one of three modes: + /// - `HostOnly`: Headers are stored in the host only. + /// - `CachedInContent`: Host holds canonical headers, content a cached copy. + /// - `ContentOnly`: Headers are stored in a Map held by the `Entries` slot. + /// + /// For Headers instances created in-content, the mode is determined by the `HeadersInit` + /// argument: + /// - If `HeadersInit` is a `Headers` instance, the mode is inherited from that instance, + /// as is the underlying data. + /// - If `HeadersInit` is empty or a sequence of header name/value pairs, the mode is + /// `ContentOnly`. + /// + /// The mode of Headers instances created via the `headers` accessor on `Request` and `Response` + /// instances is determined by how those instances themselves were created: + /// - If a `Request` or `Response` instance represents an incoming request or response, the mode + /// will initially be `HostOnly`. + /// - If a `Request` or `Response` instance represents an outgoing request or response, the mode + /// of the `Headers` instance depends on the `HeadersInit` argument passed to the `Request` or + /// `Response` constructor (see above). + /// + /// A `Headers` instance can transition from `HostOnly` to `CachedInContent` or `ContentOnly` + /// mode: + /// Iterating over headers (as keys, values, or entries) would be extremely slow if we retrieved + /// all of them from the host for each iteration step. + /// Instead, when iterating over the headers of a `HostOnly` mode `Headers` instance, the instance + /// is transitioned to `CachedInContent` mode, and the entries are stored in a Map in the + /// `Entries` slot. + /// + /// If a header is added, deleted, or replaced on an instance in `CachedInContent` mode, the + /// instance transitions to `ContentOnly` mode, and the underlying resource handle is discarded. enum class Mode { HostOnly, // Headers are stored in the host. CachedInContent, // Host holds canonical headers, content a cached copy. ContentOnly, // Headers are stored in a Map held by the `Entries` slot. + Uninitialized, // Headers have not been initialized. }; enum class Slots { @@ -45,7 +70,7 @@ class Headers final : public BuiltinImpl { * Adds the given header name/value to `self`'s list of headers iff `self` * doesn't already contain a header with that name. */ - static bool maybe_add(JSContext *cx, JS::HandleObject self, const char *name, const char *value); + static bool set_if_undefined(JSContext *cx, JS::HandleObject self, const char *name, const char *value); /// Appends a value for a header name. // @@ -55,7 +80,11 @@ class Headers final : public BuiltinImpl { static Mode mode(JSObject* self) { MOZ_ASSERT(Headers::is_instance(self)); - return static_cast(JS::GetReservedSlot(self, static_cast(Slots::Mode)).toInt32()); + Value modeVal = JS::GetReservedSlot(self, static_cast(Slots::Mode)); + if (modeVal.isUndefined()) { + return Mode::Uninitialized; + } + return static_cast(modeVal.toInt32()); } static const JSFunctionSpec static_methods[]; @@ -68,15 +97,25 @@ class Headers final : public BuiltinImpl { static bool init_class(JSContext *cx, HandleObject global); static bool constructor(JSContext *cx, unsigned argc, Value *vp); - static JSObject *create(JSContext *cx, HandleObject self, host_api::HttpHeaders *handle, - HandleValue init_headers); - static JSObject *create(JSContext *cx, HandleObject self, host_api::HttpHeadersReadOnly *handle); + static JSObject *create(JSContext *cx, HandleValue init_headers); + static JSObject *create(JSContext *cx, HandleObject self, HandleValue init_headers); + static JSObject *create(JSContext *cx, host_api::HttpHeadersReadOnly *handle); /// Returns a Map object containing the headers. /// /// Depending on the `Mode` the instance is in, this can be a cache or the canonical store for /// the headers. static JSObject* get_entries(JSContext *cx, HandleObject self); + + /** + * Returns a cloned handle representing the contents of this Headers object. + * + * The main purposes for this function are use in sending outgoing requests/responses and + * in the constructor of request/response objects when a HeadersInit object is passed. + * + * The handle is guaranteed to be uniquely owned by the caller. + */ + static unique_ptr handle_clone(JSContext*, HandleObject self); }; } // namespace fetch diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 5ce59012..c6f70b29 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -127,66 +127,6 @@ class BodyFutureTask final : public api::AsyncTask { void trace(JSTracer *trc) override { TraceEdge(trc, &body_source_, "body source for future"); } }; -class ResponseFutureTask final : public api::AsyncTask { - Heap request_; - host_api::FutureHttpIncomingResponse *future_; - -public: - explicit ResponseFutureTask(const HandleObject request, - host_api::FutureHttpIncomingResponse *future) - : request_(request), future_(future) { - auto res = future->subscribe(); - MOZ_ASSERT(!res.is_err(), "Subscribing to a future should never fail"); - handle_ = res.unwrap(); - } - - ResponseFutureTask(const RootedObject &rooted); - - [[nodiscard]] bool run(api::Engine *engine) override { - // MOZ_ASSERT(ready()); - JSContext *cx = engine->cx(); - - const RootedObject request(cx, request_); - RootedObject response_promise(cx, Request::response_promise(request)); - - auto res = future_->maybe_response(); - if (res.is_err()) { - JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource."); - return RejectPromiseWithPendingError(cx, response_promise); - } - - auto maybe_response = res.unwrap(); - MOZ_ASSERT(maybe_response.has_value()); - auto response = maybe_response.value(); - RootedObject response_obj( - cx, JS_NewObjectWithGivenProto(cx, &Response::class_, Response::proto_obj)); - if (!response_obj) { - return false; - } - - response_obj = Response::create(cx, response_obj, response); - if (!response_obj) { - return false; - } - - RequestOrResponse::set_url(response_obj, RequestOrResponse::url(request)); - RootedValue response_val(cx, ObjectValue(*response_obj)); - if (!ResolvePromise(cx, response_promise, response_val)) { - return false; - } - - return cancel(engine); - } - - [[nodiscard]] bool cancel(api::Engine *engine) override { - // TODO(TS): implement - handle_ = -1; - return true; - } - - void trace(JSTracer *trc) override { TraceEdge(trc, &request_, "Request for response future"); } -}; - namespace { // https://fetch.spec.whatwg.org/#concept-method-normalize // Returns `true` if the method name was normalized, `false` otherwise. @@ -225,7 +165,7 @@ bool RequestOrResponse::is_instance(JSObject *obj) { return Request::is_instance(obj) || Response::is_instance(obj); } -bool RequestOrResponse::is_incoming(JSObject *obj) { return handle(obj)->is_incoming(); } +bool RequestOrResponse::is_incoming(JSObject *obj) { return !!handle(obj); } host_api::HttpHeadersReadOnly *RequestOrResponse::headers_handle(JSObject *obj) { MOZ_ASSERT(is_instance(obj)); @@ -413,8 +353,9 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, content_type = "text/plain;charset=UTF-8"; } - auto body = RequestOrResponse::outgoing_body_handle(self); - auto write_res = body->write_all(reinterpret_cast(buf), length); + // TODO: restore + // auto body = RequestOrResponse::outgoing_body_handle(self); + // auto write_res = body->write_all(reinterpret_cast(buf), length); // Ensure that the NoGC is reset, so throwing an error in HANDLE_ERROR // succeeds. @@ -422,16 +363,16 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, maybeNoGC.reset(); } - if (auto *err = write_res.to_err()) { - HANDLE_ERROR(cx, *err); - return false; - } + // if (auto *err = write_res.to_err()) { + // HANDLE_ERROR(cx, *err); + // return false; + // } } // Step 36.3 of Request constructor / 8.4 of Response constructor. if (content_type) { JS::RootedObject headers(cx, RequestOrResponse::headers(cx, self)); - if (!Headers::maybe_add(cx, headers, "content-type", content_type)) { + if (!Headers::set_if_undefined(cx, headers, "content-type", content_type)) { return false; } } @@ -445,6 +386,23 @@ JSObject *RequestOrResponse::maybe_headers(JSObject *obj) { return JS::GetReservedSlot(obj, static_cast(Slots::Headers)).toObjectOrNull(); } +unique_ptr RequestOrResponse::headers_clone(JSContext* cx, + HandleObject self) { + MOZ_ASSERT(is_instance(self)); + + RootedObject headers(cx, maybe_headers(self)); + if (headers) { + return Headers::handle_clone(cx, headers); + } + + auto res = handle(self)->headers(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return nullptr; + } + return unique_ptr(res.unwrap()->clone()); +} + bool RequestOrResponse::append_body(JSContext *cx, JS::HandleObject self, JS::HandleObject source) { MOZ_ASSERT(!body_used(source)); MOZ_ASSERT(!body_used(self)); @@ -469,18 +427,9 @@ bool RequestOrResponse::append_body(JSContext *cx, JS::HandleObject self, JS::Ha JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { JSObject *headers = maybe_headers(obj); if (!headers) { - JS::RootedObject headersInstance( - cx, JS_NewObjectWithGivenProto(cx, &Headers::class_, Headers::proto_obj)); - - if (!headersInstance) { - return nullptr; - } - - auto *headers_handle = RequestOrResponse::headers_handle(obj); - if (!headers_handle) { - headers_handle = new host_api::HttpHeaders(); + if (auto *headers_handle = RequestOrResponse::headers_handle(obj)) { + headers = Headers::create(cx, headers_handle); } - headers = Headers::create(cx, headersInstance, headers_handle); if (!headers) { return nullptr; } @@ -1032,6 +981,8 @@ bool RequestOrResponse::body_reader_catch_handler(JSContext *cx, JS::HandleObjec bool RequestOrResponse::maybe_stream_body(JSContext *cx, JS::HandleObject body_owner, bool *requires_streaming) { + *requires_streaming = false; + if (is_incoming(body_owner) && has_body(body_owner)) { *requires_streaming = true; return true; @@ -1058,6 +1009,8 @@ bool RequestOrResponse::maybe_stream_body(JSContext *cx, JS::HandleObject body_o // If the body stream is backed by an HTTP body handle, we can directly pipe // that handle into the body we're about to send. + // TODO: check if this case can still be hit. Given that we're not creating outgoing responses + // anymore until sending them, it probably can't. if (streams::NativeStreamSource::stream_is_body(cx, stream)) { MOZ_ASSERT(!is_incoming(body_owner)); // First, directly append the source's body to the target's and lock the stream. @@ -1400,10 +1353,8 @@ bool Request::init_class(JSContext *cx, JS::HandleObject global) { return !!GET_atom; } -JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, - host_api::HttpRequest *request_handle) { - JS::SetReservedSlot(requestInstance, static_cast(Slots::Request), - JS::PrivateValue(request_handle)); +JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance) { + JS::SetReservedSlot(requestInstance, static_cast(Slots::Request), JS::PrivateValue(nullptr)); JS::SetReservedSlot(requestInstance, static_cast(Slots::Headers), JS::NullValue()); JS::SetReservedSlot(requestInstance, static_cast(Slots::BodyStream), JS::NullValue()); JS::SetReservedSlot(requestInstance, static_cast(Slots::HasBody), JS::FalseValue()); @@ -1697,7 +1648,6 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // `init["headers"]` exists, create the request's `headers` from that, // otherwise create it from the `init` object's `headers`, or create a new, // empty one. - auto *headers_handle = new host_api::HttpHeaders(); JS::RootedObject headers(cx); if (headers_val.isUndefined() && input_headers) { @@ -1709,7 +1659,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H if (!headersInstance) return nullptr; - headers = Headers::create(cx, headersInstance, headers_handle, headers_val); + headers = Headers::create(cx, headersInstance, headers_val); if (!headers) { return nullptr; } @@ -1744,8 +1694,8 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // Actually create the instance, now that we have all the parts required for // it. We have to delay this step to here because the wasi-http API requires // that all the request's properties are provided to the constructor. - auto request_handle = host_api::HttpOutgoingRequest::make(method, std::move(url), headers_handle); - RootedObject request(cx, create(cx, requestInstance, request_handle)); + // auto request_handle = host_api::HttpOutgoingRequest::make(method, std::move(url), headers); + RootedObject request(cx, create(cx, requestInstance)); if (!request) { return nullptr; } @@ -1837,8 +1787,7 @@ JSObject *Request::create_instance(JSContext *cx) { } bool Request::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { - REQUEST_HANDLER_ONLY("The Request builtin"); - CTOR_HEADER("Request", 1); + CTOR_HEADER("Reques", 1); JS::RootedObject requestInstance(cx, JS_NewObjectForConstructor(cx, &class_, args)); JS::RootedObject request(cx, create(cx, requestInstance, args[0], args.get(1))); if (!request) @@ -1855,9 +1804,9 @@ static_assert((int)Response::Slots::BodyUsed == (int)Request::Slots::BodyUsed); static_assert((int)Response::Slots::Headers == (int)Request::Slots::Headers); static_assert((int)Response::Slots::Response == (int)Request::Slots::Request); -host_api::HttpResponse *Response::response_handle(JSObject *obj) { +host_api::HttpIncomingResponse *Response::response_handle(JSObject *obj) { MOZ_ASSERT(is_instance(obj)); - return static_cast( + return static_cast( JS::GetReservedSlot(obj, static_cast(Slots::Response)).toPrivate()); } @@ -2452,8 +2401,6 @@ const JSPropertySpec Response::properties[] = { * The `Response` constructor https://fetch.spec.whatwg.org/#dom-response */ bool Response::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { - REQUEST_HANDLER_ONLY("The Response builtin"); - CTOR_HEADER("Response", 0); JS::RootedValue body_val(cx, args.get(0)); @@ -2520,19 +2467,16 @@ bool Response::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { if (!headersInstance) return false; - auto *headers_handle = new host_api::HttpHeaders(); - headers = Headers::create(cx, headersInstance, headers_handle, headers_val); + headers = Headers::create(cx, headersInstance, headers_val); if (!headers) { return false; } - auto *response_handle = host_api::HttpOutgoingResponse::make(status, headers_handle); - JS::RootedObject responseInstance(cx, JS_NewObjectForConstructor(cx, &class_, args)); if (!responseInstance) { return false; } - JS::RootedObject response(cx, create(cx, responseInstance, response_handle)); + JS::RootedObject response(cx, create(cx, responseInstance)); if (!response) { return false; } @@ -2585,13 +2529,15 @@ bool Response::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { if (!RequestOrResponse::extract_body(cx, response, body_val)) { return false; } - if (RequestOrResponse::has_body(response)) { - if (response_handle->body().is_err()) { - auto err = response_handle->body().to_err(); - HANDLE_ERROR(cx, *err); - return false; - } - } + + // TODO: presumably remove this code, but check what it's for first + // if (RequestOrResponse::has_body(response)) { + // if (response_handle->body().is_err()) { + // auto err = response_handle->body().to_err(); + // HANDLE_ERROR(cx, *err); + // return false; + // } + // } } args.rval().setObject(*response); @@ -2609,33 +2555,39 @@ bool Response::init_class(JSContext *cx, JS::HandleObject global) { (type_error_atom = JS_AtomizeAndPinString(cx, "error")); } -JSObject *Response::create(JSContext *cx, JS::HandleObject response, - host_api::HttpResponse *response_handle) { +JSObject *Response::create(JSContext *cx, JS::HandleObject response) { MOZ_ASSERT(cx); MOZ_ASSERT(is_instance(response)); - MOZ_ASSERT(response_handle); - JS::SetReservedSlot(response, static_cast(Slots::Response), - JS::PrivateValue(response_handle)); + JS::SetReservedSlot(response, static_cast(Slots::Response), JS::PrivateValue(nullptr)); JS::SetReservedSlot(response, static_cast(Slots::Headers), JS::NullValue()); JS::SetReservedSlot(response, static_cast(Slots::BodyStream), JS::NullValue()); JS::SetReservedSlot(response, static_cast(Slots::HasBody), JS::FalseValue()); JS::SetReservedSlot(response, static_cast(Slots::BodyUsed), JS::FalseValue()); JS::SetReservedSlot(response, static_cast(Slots::Redirected), JS::FalseValue()); - if (response_handle->is_incoming()) { - auto res = reinterpret_cast(response_handle)->status(); - MOZ_ASSERT(!res.is_err(), "TODO: proper error handling"); - auto status = res.unwrap(); - JS::SetReservedSlot(response, static_cast(Slots::Status), JS::Int32Value(status)); - set_status_message_from_code(cx, response, status); + return response; +} - if (!(status == 204 || status == 205 || status == 304)) { - JS::SetReservedSlot(response, static_cast(Slots::HasBody), JS::TrueValue()); - } +JSObject * Response::create_incoming(JSContext *cx, HandleObject obj, host_api::HttpIncomingResponse *response) { + RootedObject self(cx, create(cx, obj)); + if (!self) { + return nullptr; } - return response; + JS::SetReservedSlot(self, static_cast(Slots::Response), PrivateValue(response)); + + auto res = response->status(); + MOZ_ASSERT(!res.is_err(), "TODO: proper error handling"); + auto status = res.unwrap(); + JS::SetReservedSlot(self, static_cast(Slots::Status), JS::Int32Value(status)); + set_status_message_from_code(cx, self, status); + + if (!(status == 204 || status == 205 || status == 304)) { + JS::SetReservedSlot(self, static_cast(Slots::HasBody), JS::TrueValue()); + } + + return self; } namespace request_response { diff --git a/builtins/web/fetch/request-response.h b/builtins/web/fetch/request-response.h index be2f4fb8..5f435b50 100644 --- a/builtins/web/fetch/request-response.h +++ b/builtins/web/fetch/request-response.h @@ -50,6 +50,16 @@ class RequestOrResponse final { */ static JSObject *maybe_headers(JSObject *obj); + /** + * Returns a handle to a clone of the RequestOrResponse's Headers. + * + * The main purposes for this function are use in sending outgoing requests/responses and + * in the constructor of request/response objects when a HeadersInit object is passed. + * + * The handle is guaranteed to be uniquely owned by the caller. + */ + static unique_ptr headers_clone(JSContext *, HandleObject self); + /** * Returns the RequestOrResponse's Headers, reifying it if necessary. */ @@ -148,8 +158,7 @@ class Request final : public BuiltinImpl { static bool init_class(JSContext *cx, JS::HandleObject global); static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp); - static JSObject *create(JSContext *cx, JS::HandleObject requestInstance, - host_api::HttpRequest *request_handle); + static JSObject *create(JSContext *cx, JS::HandleObject requestInstance); static JSObject *create(JSContext *cx, JS::HandleObject requestInstance, JS::HandleValue input, JS::HandleValue init_val); @@ -198,10 +207,11 @@ class Response final : public BuiltinImpl { static bool init_class(JSContext *cx, JS::HandleObject global); static bool constructor(JSContext *cx, unsigned argc, JS::Value *vp); - static JSObject *create(JSContext *cx, JS::HandleObject response, - host_api::HttpResponse *response_handle); + static JSObject *create(JSContext *cx, JS::HandleObject response); + static JSObject* create_incoming(JSContext * cx, HandleObject self, + host_api::HttpIncomingResponse* response); - static host_api::HttpResponse *response_handle(JSObject *obj); + static host_api::HttpIncomingResponse *response_handle(JSObject *obj); static uint16_t status(JSObject *obj); static JSString *status_message(JSObject *obj); static void set_status_message_from_code(JSContext *cx, JSObject *obj, uint16_t code); diff --git a/builtins/web/url.cpp b/builtins/web/url.cpp index 90de01ec..b326a536 100644 --- a/builtins/web/url.cpp +++ b/builtins/web/url.cpp @@ -635,9 +635,10 @@ JSObject *URL::create(JSContext *cx, JS::HandleObject self, JS::HandleValue url_ JSObject *URL::create(JSContext *cx, JS::HandleObject self, JS::HandleValue url_val, JS::HandleObject base_obj) { - MOZ_RELEASE_ASSERT(is_instance(base_obj)); - const auto *base = - static_cast(JS::GetReservedSlot(base_obj, Slots::Url).toPrivate()); + jsurl::JSUrl* base = nullptr; + if (is_instance(base_obj)) { + base = static_cast(JS::GetReservedSlot(base_obj, Slots::Url).toPrivate()); + } return create(cx, self, url_val, base); } diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index ecf3a9fc..e0363367 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -2,6 +2,8 @@ #include "bindings/bindings.h" #include +#include +#include using std::optional; using std::string_view; @@ -17,6 +19,7 @@ typedef wasi_http_0_2_0_types_own_incoming_request_t incoming_request_t; typedef wasi_http_0_2_0_types_borrow_incoming_request_t borrow_incoming_request_t; typedef wasi_http_0_2_0_types_own_incoming_response_t incoming_response_t; typedef wasi_http_0_2_0_types_borrow_outgoing_request_t borrow_outgoing_request_t; +typedef wasi_http_0_2_0_types_borrow_outgoing_response_t borrow_outgoing_response_t; typedef wasi_http_0_2_0_types_own_future_incoming_response_t future_incoming_response_t; typedef wasi_http_0_2_0_types_borrow_future_incoming_response_t borrow_future_incoming_response_t; @@ -39,8 +42,72 @@ typedef wasi_io_0_2_0_streams_borrow_input_stream_t borrow_input_stream_t; typedef wasi_io_0_2_0_streams_own_output_stream_t own_output_stream_t; +/// The type of handles used by the host interface. +typedef int32_t Handle; +constexpr Handle UNINITIALIZED_HANDLE = -1; + +/// An abstract base class to be used in classes representing host resources. +/// +/// Some host resources have different requirements for their client-side representation +/// depending on the host API. To accommodate this, we introduce a base class to use for +/// all of them, which the API-specific implementation can subclass as needed. +class host_api::HandleState { + Handle handle_ = UNINITIALIZED_HANDLE; + +#ifdef DEBUG + #define ASSERT_VALID()\ + MOZ_ASSERT(handle_state_.get() != nullptr, "Handle state missing"); \ + MOZ_ASSERT(handle_state_.get()->initialized(), "Handle not initialized"); \ + MOZ_ASSERT(!handle_state_.get()->poisoned(), "Handle poisoned"); + bool poisoned_ = false; +#else +#define ASSERT_VALID +#endif + +public: + HandleState() {} + explicit HandleState(Handle handle) : handle_{handle} { + DBG("Adding handle %d\n", handle); + // MOZ_ASSERT(handle > UNINITIALIZED_HANDLE); + // MOZ_ASSERT(!used_handles.has(handle)); + // MOZ_ASSERT(used_handles.put(handle)); + } + virtual ~HandleState() { + // TODO: support dropping handles for all handle types. Probably using a template class. + DBG("Removing handle %d\n", handle_); +#ifdef DEBUG + // MOZ_ASSERT(used_handles.has(handle)); + // used_handles.remove(handle); +#endif + } + + Handle get() const { + DBG("Getting handle %d\n", handle_); + MOZ_ASSERT(handle_ != UNINITIALIZED_HANDLE, "Handle is uninitialized"); +#ifdef DEBUG + if (poisoned_) { + fprintf(stderr, "Handle %d is poisened", handle_); fflush(stderr); + MOZ_ASSERT(false); + } +#endif + return handle_; + } + + Handle take() { + auto handle = get(); + DBG("Consuming handle %d\n", handle); + poisoned_ = true; + return handle; + } + + bool valid() const { return handle_ != UNINITIALIZED_HANDLE && !poisoned_; } + bool initialized() const { return handle_ != UNINITIALIZED_HANDLE;} + bool poisoned() const { return poisoned_; } +}; + namespace { +// TODO: merge HandleOps into a WASIHandleState subclass of HandleState, and use that everywhere. /// This is the type contract for using the Own and Borrow templates. template struct HandleOps {}; @@ -57,10 +124,12 @@ template class Borrow final { Borrow(HandleOps::own handle) : handle{HandleOps::borrow_owned(handle)} {} // Construct a borrow from a raw `Handle` value. - Borrow(host_api::Handle handle) : Borrow{typename HandleOps::own{handle}} {} + Borrow(Handle handle) : Borrow{typename HandleOps::own{handle}} { + MOZ_ASSERT(valid()); + } // Convenience wrapper for constructing a borrow of a HandleState. - Borrow(host_api::HandleState *state) : Borrow{typename HandleOps::own{state->handle}} {} + Borrow(host_api::HandleState *state) : Borrow{typename HandleOps::own{state->get()}} {} bool valid() const { return this->handle.__handle != Borrow::invalid.__handle; } @@ -185,19 +254,17 @@ void MonotonicClock::unsubscribe(const int32_t handle_id) { wasi_io_0_2_0_poll_pollable_drop_own(own_pollable_t{handle_id}); } -HttpHeaders::HttpHeaders(Handle handle) : HttpHeadersReadOnly(handle) {} +HttpHeaders::HttpHeaders(std::unique_ptr state) : HttpHeadersReadOnly(std::move(state)) {} -HttpHeaders::HttpHeaders() : HttpHeadersReadOnly() { - this->handle_state_ = new HandleState(wasi_http_0_2_0_types_constructor_fields().__handle); +HttpHeaders::HttpHeaders() : HttpHeadersReadOnly(std::make_unique(wasi_http_0_2_0_types_constructor_fields().__handle)) { } -Result HttpHeaders::FromEntries(const vector>> &entries) { +Result HttpHeaders::FromEntries(const vector> &entries) { std::vector pairs; + pairs.reserve(entries.size()); - for (const auto &[name, values] : entries) { - for (const auto &value : values) { - pairs.emplace_back(from_string_view(name), from_string_view(value)); - } + for (const auto &[name, value] : entries) { + pairs.emplace_back(from_string_view(name), from_string_view(value)); } wasi_http_0_2_0_types_list_tuple2_field_key_field_value_t tuples{pairs.data(), entries.size()}; @@ -207,13 +274,13 @@ Result HttpHeaders::FromEntries(const vector::ok(new HttpHeaders(ret.__handle)); + return Result::ok(new HttpHeaders(std::make_unique(ret.__handle))); } -HttpHeaders::HttpHeaders(const HttpHeadersReadOnly &headers) : HttpHeadersReadOnly() { - Borrow borrow(headers.handle_state_); +HttpHeaders::HttpHeaders(const HttpHeadersReadOnly &headers) : HttpHeadersReadOnly(nullptr) { + Borrow borrow(headers.handle_state_.get()); auto handle = wasi_http_0_2_0_types_method_fields_clone(borrow); - this->handle_state_ = new HandleState(handle.__handle); + this->handle_state_ = std::unique_ptr(new HandleState(handle.__handle)); } HttpHeaders *HttpHeadersReadOnly::clone() { @@ -222,10 +289,10 @@ HttpHeaders *HttpHeadersReadOnly::clone() { Result>> HttpHeadersReadOnly::entries() const { Result>> res; - MOZ_ASSERT(valid()); + ASSERT_VALID(); wasi_http_0_2_0_types_list_tuple2_field_key_field_value_t entries; - Borrow borrow(this->handle_state_); + Borrow borrow(this->handle_state_.get()); wasi_http_0_2_0_types_method_fields_entries(borrow, &entries); vector> entries_vec; @@ -243,10 +310,10 @@ Result>> HttpHeadersReadOnly::entries() con Result> HttpHeadersReadOnly::names() const { Result> res; - MOZ_ASSERT(valid()); + ASSERT_VALID(); wasi_http_0_2_0_types_list_tuple2_field_key_field_value_t entries; - Borrow borrow(this->handle_state_); + Borrow borrow(this->handle_state_.get()); wasi_http_0_2_0_types_method_fields_entries(borrow, &entries); vector names; @@ -262,11 +329,11 @@ Result> HttpHeadersReadOnly::names() const { Result>> HttpHeadersReadOnly::get(string_view name) const { Result>> res; - MOZ_ASSERT(valid()); + ASSERT_VALID(); wasi_http_0_2_0_types_list_field_value_t values; auto hdr = string_view_to_world_string(name); - Borrow borrow(this->handle_state_); + Borrow borrow(this->handle_state_.get()); wasi_http_0_2_0_types_method_fields_get(borrow, &hdr, &values); if (values.len > 0) { @@ -285,19 +352,19 @@ Result>> HttpHeadersReadOnly::get(string_view name) } Result HttpHeadersReadOnly::has(string_view name) const { - MOZ_ASSERT(valid()); + ASSERT_VALID(); auto hdr = string_view_to_world_string(name); - Borrow borrow(this->handle_state_); + Borrow borrow(this->handle_state_.get()); return Result::ok(wasi_http_0_2_0_types_method_fields_has(borrow, &hdr)); } Result HttpHeaders::set(string_view name, string_view value) { - MOZ_ASSERT(valid()); + ASSERT_VALID(); auto hdr = from_string_view(name); auto val = from_string_view(value); wasi_http_0_2_0_types_list_field_value_t host_values{&val, 1}; - Borrow borrow(this->handle_state_); + Borrow borrow(this->handle_state_.get()); wasi_http_0_2_0_types_header_error_t err; wasi_http_0_2_0_types_method_fields_set(borrow, &hdr, &host_values, &err); @@ -308,10 +375,10 @@ Result HttpHeaders::set(string_view name, string_view value) { } Result HttpHeaders::append(string_view name, string_view value) { - MOZ_ASSERT(valid()); + ASSERT_VALID(); auto hdr = from_string_view(name); auto val = from_string_view(value); - Borrow borrow(this->handle_state_); + Borrow borrow(this->handle_state_.get()); // TODO: properly handle `err` wasi_http_0_2_0_types_header_error_t err; @@ -321,7 +388,7 @@ Result HttpHeaders::append(string_view name, string_view value) { case WASI_HTTP_0_2_0_TYPES_HEADER_ERROR_FORBIDDEN: return Result::err(154); case WASI_HTTP_0_2_0_TYPES_HEADER_ERROR_IMMUTABLE: - fprintf(stderr, "Headers %d should not be immutable", this->handle_state_->handle); + fprintf(stderr, "Headers %d should not be immutable", this->handle_state_->get()); MOZ_ASSERT_UNREACHABLE(); } } @@ -330,9 +397,9 @@ Result HttpHeaders::append(string_view name, string_view value) { } Result HttpHeaders::remove(string_view name) { - MOZ_ASSERT(valid()); + ASSERT_VALID(); auto hdr = string_view_to_world_string(name); - Borrow borrow(this->handle_state_); + Borrow borrow(this->handle_state_.get()); wasi_http_0_2_0_types_header_error_t err; wasi_http_0_2_0_types_method_fields_delete(borrow, &hdr, &err); @@ -348,7 +415,7 @@ string_view HttpRequestResponseBase::url() { return string_view(*_url); } - auto borrow = borrow_incoming_request_t{handle_state_->handle}; + auto borrow = borrow_incoming_request_t{handle_state_->get()}; wasi_http_0_2_0_types_scheme_t scheme; bool success; @@ -380,7 +447,7 @@ bool write_to_outgoing_body(Borrow borrow, const uint8_t *ptr, con return wasi_io_0_2_0_streams_method_output_stream_write(borrow, &list, &err); } -class OutgoingBodyHandleState final : HandleState { +class OutgoingBodyHandleState final : public HandleState { Handle stream_handle_; PollableHandle pollable_handle_; @@ -398,8 +465,8 @@ class OutgoingBodyHandleState final : HandleState { } }; -HttpOutgoingBody::HttpOutgoingBody(Handle handle) : Pollable() { - handle_state_ = new OutgoingBodyHandleState(handle); +HttpOutgoingBody::HttpOutgoingBody(std::unique_ptr state) : Pollable() { + handle_state_ = std::move(state); } Result HttpOutgoingBody::capacity() { if (!valid()) { @@ -407,7 +474,7 @@ Result HttpOutgoingBody::capacity() { return Result::err(154); } - auto *state = static_cast(this->handle_state_); + auto *state = static_cast(this->handle_state_.get()); Borrow borrow(state->stream_handle_); uint64_t capacity = 0; wasi_io_0_2_0_streams_stream_error_t err; @@ -426,7 +493,7 @@ Result HttpOutgoingBody::write(const uint8_t *bytes, size_t len) { auto capacity = res.unwrap(); auto bytes_to_write = std::min(len, static_cast(capacity)); - auto *state = static_cast(this->handle_state_); + auto *state = static_cast(this->handle_state_.get()); Borrow borrow(state->stream_handle_); if (!write_to_outgoing_body(borrow, bytes, bytes_to_write)) { return Result::err(154); @@ -441,7 +508,7 @@ Result HttpOutgoingBody::write_all(const uint8_t *bytes, size_t len) { return Result::err({}); } - auto *state = static_cast(handle_state_); + auto *state = static_cast(handle_state_.get()); Borrow borrow(state->stream_handle_); while (len > 0) { @@ -594,15 +661,15 @@ class BodyAppendTask final : public api::AsyncTask { }; Result HttpOutgoingBody::append(api::Engine *engine, HttpIncomingBody *other) { - MOZ_ASSERT(valid()); + ASSERT_VALID(); engine->queue_async_task(new BodyAppendTask(other, this)); return {}; } Result HttpOutgoingBody::close() { - MOZ_ASSERT(valid()); + ASSERT_VALID(); - auto state = static_cast(handle_state_); + auto state = static_cast(handle_state_.get()); // A blocking flush is required here to ensure that all buffered contents are // actually written before finishing the body. Borrow borrow{state->stream_handle_}; @@ -621,17 +688,14 @@ Result HttpOutgoingBody::close() { { wasi_http_0_2_0_types_error_code_t err; - wasi_http_0_2_0_types_static_outgoing_body_finish({state->handle}, nullptr, &err); + wasi_http_0_2_0_types_static_outgoing_body_finish({state->take()}, nullptr, &err); // TODO: handle `err` } - delete handle_state_; - handle_state_ = nullptr; - return {}; } Result HttpOutgoingBody::subscribe() { - auto state = static_cast(handle_state_); + auto state = static_cast(handle_state_.get()); if (state->pollable_handle_ == INVALID_POLLABLE_HANDLE) { Borrow borrow(state->stream_handle_); state->pollable_handle_ = wasi_io_0_2_0_streams_method_output_stream_subscribe(borrow).__handle; @@ -640,7 +704,7 @@ Result HttpOutgoingBody::subscribe() { } void HttpOutgoingBody::unsubscribe() { - auto state = static_cast(handle_state_); + auto state = static_cast(handle_state_.get()); if (state->pollable_handle_ == INVALID_POLLABLE_HANDLE) { return; } @@ -670,10 +734,10 @@ wasi_http_0_2_0_types_method_t http_method_to_host(string_view method_str) { return wasi_http_0_2_0_types_method_t{WASI_HTTP_0_2_0_TYPES_METHOD_OTHER, {val}}; } -HttpOutgoingRequest::HttpOutgoingRequest(HandleState *state) { this->handle_state_ = state; } +HttpOutgoingRequest::HttpOutgoingRequest(std::unique_ptr state) { this->handle_state_ = std::move(state); } HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional url_str, - HttpHeaders *headers) { + HttpHeadersReadOnly *headers) { bindings_string_t path_with_query; wasi_http_0_2_0_types_scheme_t scheme; bindings_string_t authority; @@ -706,8 +770,10 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< maybe_path_with_query = &path_with_query; } + Handle headers_handle = headers->handle_state_->take(); + DBG("HH: %d\n", headers_handle); auto handle = - wasi_http_0_2_0_types_constructor_outgoing_request({headers->handle_state_->handle}); + wasi_http_0_2_0_types_constructor_outgoing_request({headers_handle}); { auto borrow = wasi_http_0_2_0_types_borrow_outgoing_request(handle); @@ -727,7 +793,7 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< } auto *state = new HandleState(handle.__handle); - auto *resp = new HttpOutgoingRequest(state); + auto *resp = new HttpOutgoingRequest(std::unique_ptr(state)); resp->headers_ = headers; @@ -735,41 +801,47 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< } Result HttpOutgoingRequest::method() { - MOZ_ASSERT(valid()); - MOZ_ASSERT(headers_); + ASSERT_VALID(); return Result::ok(method_); } Result HttpOutgoingRequest::headers() { - MOZ_ASSERT(valid()); - MOZ_ASSERT(headers_); + if (!headers_) { + if (!valid()) { + return Result::err(154); + } + borrow_outgoing_request_t borrow(handle_state_->get()); + auto res = wasi_http_0_2_0_types_method_outgoing_request_headers(borrow); + headers_ = new HttpHeadersReadOnly(std::unique_ptr(new HandleState(res.__handle))); + } + return Result::ok(headers_); } Result HttpOutgoingRequest::body() { typedef Result Res; - MOZ_ASSERT(valid()); + ASSERT_VALID(); if (!this->body_) { outgoing_body_t body; if (!wasi_http_0_2_0_types_method_outgoing_request_body( - wasi_http_0_2_0_types_borrow_outgoing_request({handle_state_->handle}), &body)) { + wasi_http_0_2_0_types_borrow_outgoing_request({handle_state_->take()}), &body)) { return Res::err(154); } - this->body_ = new HttpOutgoingBody(body.__handle); + this->body_ = new HttpOutgoingBody(std::unique_ptr(new HandleState(body.__handle))); } return Res::ok(body_); } Result HttpOutgoingRequest::send() { - MOZ_ASSERT(valid()); + ASSERT_VALID(); future_incoming_response_t ret; wasi_http_0_2_0_outgoing_handler_error_code_t err; - wasi_http_0_2_0_outgoing_handler_handle({handle_state_->handle}, nullptr, &ret, &err); - auto res = new FutureHttpIncomingResponse(ret.__handle); + wasi_http_0_2_0_outgoing_handler_handle({handle_state_->take()}, nullptr, &ret, &err); + auto res = new FutureHttpIncomingResponse(std::unique_ptr(new HandleState(ret.__handle))); return Result::ok(res); } -class IncomingBodyHandleState final : HandleState { +class IncomingBodyHandleState final : public HandleState { Handle stream_handle_; PollableHandle pollable_handle_; @@ -787,8 +859,16 @@ class IncomingBodyHandleState final : HandleState { } }; -HttpIncomingBody::HttpIncomingBody(const Handle handle) : Pollable() { - handle_state_ = new IncomingBodyHandleState(handle); +HttpIncomingBody::HttpIncomingBody(std::unique_ptr state) : Pollable() { handle_state_ = std::move(state); } + +Resource::~Resource() { + if (handle_state_ != nullptr) { + delete handle_state_.release(); + } +} + +bool Resource::valid() const { + return this->handle_state_ != nullptr && this->handle_state_->valid(); } Result HttpIncomingBody::read(uint32_t chunk_size) { @@ -797,7 +877,7 @@ Result HttpIncomingBody::read(uint32_t chunk_size) wasi_io_0_2_0_streams_list_u8_t ret{}; wasi_io_0_2_0_streams_stream_error_t err{}; auto borrow = borrow_input_stream_t( - {static_cast(handle_state_)->stream_handle_}); + {static_cast(handle_state_.get())->stream_handle_}); bool success = wasi_io_0_2_0_streams_method_input_stream_read(borrow, chunk_size, &ret, &err); if (!success) { if (err.tag == WASI_IO_0_2_0_STREAMS_STREAM_ERROR_CLOSED) { @@ -813,12 +893,12 @@ Result HttpIncomingBody::close() { return {}; } Result HttpIncomingBody::subscribe() { auto borrow = borrow_input_stream_t( - {static_cast(handle_state_)->stream_handle_}); + {static_cast(handle_state_.get())->stream_handle_}); auto pollable = wasi_io_0_2_0_streams_method_input_stream_subscribe(borrow); return Result::ok(pollable.__handle); } void HttpIncomingBody::unsubscribe() { - auto state = static_cast(handle_state_); + auto state = static_cast(handle_state_.get()); if (state->pollable_handle_ == INVALID_POLLABLE_HANDLE) { return; } @@ -826,14 +906,14 @@ void HttpIncomingBody::unsubscribe() { state->pollable_handle_ = INVALID_POLLABLE_HANDLE; } -FutureHttpIncomingResponse::FutureHttpIncomingResponse(Handle handle) { - handle_state_ = new HandleState(handle); +FutureHttpIncomingResponse::FutureHttpIncomingResponse(std::unique_ptr state) { + handle_state_ = std::move(state); } Result> FutureHttpIncomingResponse::maybe_response() { typedef Result> Res; wasi_http_0_2_0_types_result_result_own_incoming_response_error_code_void_t res; - auto borrow = wasi_http_0_2_0_types_borrow_future_incoming_response({handle_state_->handle}); + auto borrow = wasi_http_0_2_0_types_borrow_future_incoming_response({handle_state_->get()}); if (!wasi_http_0_2_0_types_method_future_incoming_response_get(borrow, &res)) { return Res::ok(std::nullopt); } @@ -846,11 +926,11 @@ Result> FutureHttpIncomingResponse::maybe_respo return Res::err(154); } - return Res::ok(new HttpIncomingResponse(val.ok.__handle)); + return Res::ok(new HttpIncomingResponse(std::unique_ptr(new HandleState(val.ok.__handle)))); } Result FutureHttpIncomingResponse::subscribe() { - auto borrow = wasi_http_0_2_0_types_borrow_future_incoming_response({handle_state_->handle}); + auto borrow = wasi_http_0_2_0_types_borrow_future_incoming_response({handle_state_->get()}); auto pollable = wasi_http_0_2_0_types_method_future_incoming_response_subscribe(borrow); return Result::ok(pollable.__handle); } @@ -858,8 +938,8 @@ void FutureHttpIncomingResponse::unsubscribe() { // TODO: implement } -HttpHeadersReadOnly::HttpHeadersReadOnly(Handle handle) { - handle_state_ = new HandleState(handle); +HttpHeadersReadOnly::HttpHeadersReadOnly(std::unique_ptr state) { + handle_state_ = std::move(state); } Result HttpIncomingResponse::status() { @@ -867,14 +947,14 @@ Result HttpIncomingResponse::status() { if (!valid()) { return Result::err(154); } - auto borrow = wasi_http_0_2_0_types_borrow_incoming_response_t({handle_state_->handle}); + auto borrow = wasi_http_0_2_0_types_borrow_incoming_response_t({handle_state_->get()}); status_ = wasi_http_0_2_0_types_method_incoming_response_status(borrow); } return Result::ok(status_); } -HttpIncomingResponse::HttpIncomingResponse(Handle handle) { - handle_state_ = new HandleState(handle); +HttpIncomingResponse::HttpIncomingResponse(std::unique_ptr state) { + handle_state_ = std::move(state); } Result HttpIncomingResponse::headers() { @@ -883,8 +963,8 @@ Result HttpIncomingResponse::headers() { return Result::err(154); } auto res = wasi_http_0_2_0_types_method_incoming_response_headers( - wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->handle})); - headers_ = new HttpHeadersReadOnly(res.__handle); + wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->get()})); + headers_ = new HttpHeadersReadOnly(std::unique_ptr(new HandleState(res.__handle))); } return Result::ok(headers_); @@ -897,23 +977,24 @@ Result HttpIncomingResponse::body() { } incoming_body_t body; if (!wasi_http_0_2_0_types_method_incoming_response_consume( - wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->handle}), &body)) { + wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->get()}), &body)) { return Result::err(154); } - body_ = new HttpIncomingBody(body.__handle); + body_ = new HttpIncomingBody(std::unique_ptr(new HandleState(body.__handle))); } return Result::ok(body_); } -HttpOutgoingResponse::HttpOutgoingResponse(HandleState *state) { this->handle_state_ = state; } +HttpOutgoingResponse::HttpOutgoingResponse(std::unique_ptr state) { this->handle_state_ = std::move(state); } +// TODO: change this to take a unique_ptr to prevent reuse after consumption HttpOutgoingResponse *HttpOutgoingResponse::make(const uint16_t status, HttpHeaders *headers) { - wasi_http_0_2_0_types_own_headers_t owned{headers->handle_state_->handle}; + wasi_http_0_2_0_types_own_headers_t owned{headers->handle_state_->take()}; auto handle = wasi_http_0_2_0_types_constructor_outgoing_response(owned); auto borrow = wasi_http_0_2_0_types_borrow_outgoing_response(handle); auto *state = new HandleState(handle.__handle); - auto *resp = new HttpOutgoingResponse(state); + auto *resp = new HttpOutgoingResponse(std::unique_ptr(state)); // Set the status if (status != 200) { @@ -921,9 +1002,7 @@ HttpOutgoingResponse *HttpOutgoingResponse::make(const uint16_t status, HttpHead wasi_http_0_2_0_types_method_outgoing_response_set_status_code(borrow, status); } - // Freshen the headers handle to point to an immutable version of the outgoing headers. - headers->handle_state_->handle = - wasi_http_0_2_0_types_method_outgoing_response_headers(borrow).__handle; + // TODO: ensure that the passed-in headers aren't used anywhere anymore. resp->status_ = status; resp->headers_ = headers; @@ -932,43 +1011,37 @@ HttpOutgoingResponse *HttpOutgoingResponse::make(const uint16_t status, HttpHead } Result HttpOutgoingResponse::headers() { - if (!valid()) { - return Result::err(154); + if (!headers_) { + if (!valid()) { + return Result::err(154); + } + borrow_outgoing_response_t borrow(handle_state_->get()); + auto res = wasi_http_0_2_0_types_method_outgoing_response_headers(borrow); + headers_ = new HttpHeadersReadOnly(std::unique_ptr(new HandleState(res.__handle))); } + return Result::ok(headers_); } Result HttpOutgoingResponse::body() { typedef Result Res; - MOZ_ASSERT(valid()); + ASSERT_VALID(); if (!this->body_) { outgoing_body_t body; if (!wasi_http_0_2_0_types_method_outgoing_response_body( - wasi_http_0_2_0_types_borrow_outgoing_response({handle_state_->handle}), &body)) { + wasi_http_0_2_0_types_borrow_outgoing_response({handle_state_->get()}), &body)) { return Res::err(154); } - this->body_ = new HttpOutgoingBody(body.__handle); + this->body_ = new HttpOutgoingBody(std::unique_ptr(new OutgoingBodyHandleState(body.__handle))); } return Res::ok(this->body_); } Result HttpOutgoingResponse::status() { return Result::ok(status_); } -Result HttpOutgoingResponse::send(ResponseOutparam out_param) { - // Drop the headers that we eagerly grab in the factory function - wasi_http_0_2_0_types_fields_drop_own({this->headers_->handle_state_->handle}); - - wasi_http_0_2_0_types_result_own_outgoing_response_error_code_t result; - - result.is_err = false; - result.val.ok = {this->handle_state_->handle}; - - wasi_http_0_2_0_types_static_response_outparam_set({out_param}, &result); - - return {}; +HttpIncomingRequest::HttpIncomingRequest(std::unique_ptr state) { + handle_state_ = std::move(state); } -HttpIncomingRequest::HttpIncomingRequest(Handle handle) { handle_state_ = new HandleState(handle); } - Result HttpIncomingRequest::method() { if (method_.empty()) { if (!valid()) { @@ -977,7 +1050,7 @@ Result HttpIncomingRequest::method() { } wasi_http_0_2_0_types_method_t method; wasi_http_0_2_0_types_method_incoming_request_method( - borrow_incoming_request_t(handle_state_->handle), &method); + borrow_incoming_request_t(handle_state_->get()), &method); if (method.tag != WASI_HTTP_0_2_0_TYPES_METHOD_OTHER) { method_ = std::string(http_method_names[method.tag], strlen(http_method_names[method.tag])); } else { @@ -992,9 +1065,9 @@ Result HttpIncomingRequest::headers() { if (!valid()) { return Result::err(154); } - borrow_incoming_request_t borrow(handle_state_->handle); + borrow_incoming_request_t borrow(handle_state_->get()); auto res = wasi_http_0_2_0_types_method_incoming_request_headers(borrow); - headers_ = new HttpHeadersReadOnly(res.__handle); + headers_ = new HttpHeadersReadOnly(std::unique_ptr(new HandleState(res.__handle))); } return Result::ok(headers_); @@ -1007,12 +1080,43 @@ Result HttpIncomingRequest::body() { } incoming_body_t body; if (!wasi_http_0_2_0_types_method_incoming_request_consume( - borrow_incoming_request_t(handle_state_->handle), &body)) { + borrow_incoming_request_t(handle_state_->get()), &body)) { return Result::err(154); } - body_ = new HttpIncomingBody(body.__handle); + body_ = new HttpIncomingBody(std::unique_ptr(new IncomingBodyHandleState(body.__handle))); } return Result::ok(body_); } } // namespace host_api + +static host_api::HttpIncomingRequest::RequestHandler REQUEST_HANDLER = nullptr; +static host_api::HandleState *RESPONSE_OUT = nullptr; + +void host_api::HttpIncomingRequest::set_handler(RequestHandler handler) { + MOZ_ASSERT(!REQUEST_HANDLER); + REQUEST_HANDLER = handler; +} + +host_api::Result host_api::HttpOutgoingResponse::send() { + // Drop the headers that we eagerly grab in the factory function + wasi_http_0_2_0_types_fields_drop_own({this->headers_->handle_state_->take()}); + + wasi_http_0_2_0_types_result_own_outgoing_response_error_code_t result; + + result.is_err = false; + result.val.ok = {this->handle_state_->take()}; + + wasi_http_0_2_0_types_static_response_outparam_set({RESPONSE_OUT->take()}, &result); + + return {}; +} + +void exports_wasi_http_incoming_handler(exports_wasi_http_incoming_request request_handle, +exports_wasi_http_response_outparam response_out) { + RESPONSE_OUT = new host_api::HandleState(response_out.__handle); + auto *request = new host_api::HttpIncomingRequest(std::unique_ptr(new host_api::HandleState(request_handle.__handle))); + auto res = REQUEST_HANDLER(request); + MOZ_RELEASE_ASSERT(res); + MOZ_RELEASE_ASSERT(!RESPONSE_OUT->valid()); +} diff --git a/include/host_api.h b/include/host_api.h index 3333cffc..83f4d1f2 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -198,33 +198,24 @@ struct HostBytes final { operator std::span() const { return std::span(this->ptr.get(), this->len); } }; -/// The type of handles used by the host interface. -typedef int32_t Handle; - -/// An abstract base class to be used in classes representing host resources. +/// An opaque class to be used in classes representing host resources. /// /// Some host resources have different requirements for their client-side representation -/// depending on the host API. To accommodate this, we introduce a base class to use for -/// all of them, which the API-specific implementation can subclass as needed. -class HandleState { -public: - Handle handle; - HandleState() = delete; - explicit HandleState(Handle handle) : handle{handle} {} - virtual ~HandleState() = default; - - bool valid() const { return handle != -1; } -}; +/// depending on the host API. To accommodate this, we introduce an opaque class to use for +/// all of them, which the API-specific implementation can define as needed. +class HandleState; class Resource { protected: - HandleState *handle_state_; + std::unique_ptr handle_state_ = nullptr; + + // explicit Resource(HandleState *handle_state); public: - virtual ~Resource() = default; + virtual ~Resource(); - /// Returns true when this resource handle is valid. - virtual bool valid() const { return this->handle_state_ != nullptr; } + /// Returns true if this resource handle has been initialized and is still valid. + bool valid() const; }; class Pollable : public Resource { @@ -241,7 +232,7 @@ class Pollable : public Resource { class HttpIncomingBody final : public Pollable { public: HttpIncomingBody() = delete; - explicit HttpIncomingBody(Handle handle); + explicit HttpIncomingBody(std::unique_ptr handle); class ReadResult final { public: @@ -267,7 +258,7 @@ class HttpIncomingBody final : public Pollable { class HttpOutgoingBody final : public Pollable { public: HttpOutgoingBody() = delete; - explicit HttpOutgoingBody(Handle handle); + explicit HttpOutgoingBody(std::unique_ptr handle); /// Get the body's stream's current capacity. Result capacity(); @@ -315,7 +306,7 @@ class HttpHeaders; class FutureHttpIncomingResponse final : public Pollable { public: FutureHttpIncomingResponse() = delete; - explicit FutureHttpIncomingResponse(Handle handle); + explicit FutureHttpIncomingResponse(std::unique_ptr handle); /// Returns the response if it is ready, or `nullopt` if it is not. Result> maybe_response(); @@ -331,11 +322,9 @@ class HttpHeadersReadOnly : public Resource { friend HttpOutgoingRequest; friend HttpHeaders; -protected: - explicit HttpHeadersReadOnly(Handle handle); - HttpHeadersReadOnly() = default; - public: + HttpHeadersReadOnly() = delete; + explicit HttpHeadersReadOnly(std::unique_ptr handle); HttpHeadersReadOnly(const HttpHeadersReadOnly &headers) = delete; HttpHeaders* clone(); @@ -357,13 +346,13 @@ class HttpHeaders final : public HttpHeadersReadOnly { friend HttpOutgoingResponse; friend HttpOutgoingRequest; - explicit HttpHeaders(Handle handle); + explicit HttpHeaders(std::unique_ptr handle); public: HttpHeaders(); explicit HttpHeaders(const HttpHeadersReadOnly &headers); - static Result FromEntries(const vector>> &entries); + static Result FromEntries(const vector> &entries); bool is_writable() override { return true; }; HttpHeaders* as_writable() override { @@ -425,8 +414,10 @@ class HttpRequest : public HttpRequestResponseBase { class HttpIncomingRequest final : public HttpRequest, public HttpIncomingBodyOwner { public: + using RequestHandler = bool (*)(HttpIncomingRequest* request); + HttpIncomingRequest() = delete; - explicit HttpIncomingRequest(Handle handle); + explicit HttpIncomingRequest(std::unique_ptr handle); bool is_incoming() override { return true; } bool is_request() override { return true; } @@ -434,16 +425,18 @@ class HttpIncomingRequest final : public HttpRequest, public HttpIncomingBodyOwn [[nodiscard]] Result method() override; Result headers() override; Result body() override; + + static void set_handler(RequestHandler handler); }; class HttpOutgoingRequest final : public HttpRequest, public HttpOutgoingBodyOwner { - HttpOutgoingRequest(HandleState *state); + HttpOutgoingRequest(std::unique_ptr handle); public: HttpOutgoingRequest() = delete; static HttpOutgoingRequest *make(string_view method, optional url, - HttpHeaders *headers); + HttpHeadersReadOnly *headers); bool is_incoming() override { return false; } bool is_request() override { return true; } @@ -467,7 +460,7 @@ class HttpResponse : public HttpRequestResponseBase { class HttpIncomingResponse final : public HttpResponse, public HttpIncomingBodyOwner { public: HttpIncomingResponse() = delete; - explicit HttpIncomingResponse(Handle handle); + explicit HttpIncomingResponse(std::unique_ptr handle); bool is_incoming() override { return true; } bool is_request() override { return false; } @@ -478,11 +471,9 @@ class HttpIncomingResponse final : public HttpResponse, public HttpIncomingBodyO }; class HttpOutgoingResponse final : public HttpResponse, public HttpOutgoingBodyOwner { - HttpOutgoingResponse(HandleState *state); + HttpOutgoingResponse(std::unique_ptr handle); public: - using ResponseOutparam = Handle; - HttpOutgoingResponse() = delete; static HttpOutgoingResponse *make(uint16_t status, HttpHeaders *headers); @@ -494,7 +485,7 @@ class HttpOutgoingResponse final : public HttpResponse, public HttpOutgoingBodyO Result body() override; [[nodiscard]] Result status() override; - Result send(ResponseOutparam out_param); + Result send(); }; class Random final { From 3bb6b5d9719c798b5e4e7d86e333c93a9523b040 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Tue, 28 May 2024 14:17:35 +0200 Subject: [PATCH 32/52] Support creating a component without evaluating a top-level script during wizening The idea is that with this, builtins handling runtime events can dynamically load and execute a top-level script as needed. This functionality isn't actually used in this commit, but will be in another commit changing how incoming HTTP requests are handled. --- CMakeLists.txt | 1 + componentize.sh | 65 ++++++++++++++++++++++--- include/extension-api.h | 6 +++ runtime/engine.cpp | 65 ++++++++++++++++++++++++- runtime/js.cpp | 34 +------------ runtime/script_loader.cpp | 100 +++++++++++++++++++++----------------- runtime/script_loader.h | 3 +- 7 files changed, 187 insertions(+), 87 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b143611f..e2c35fdf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,5 +104,6 @@ function(componentize OUTPUT) endfunction() componentize(smoke-test SOURCES tests/cases/smoke/smoke.js) +componentize(runtime-eval) include("tests/tests.cmake") diff --git a/componentize.sh b/componentize.sh index 22aff032..0d8419e7 100755 --- a/componentize.sh +++ b/componentize.sh @@ -1,18 +1,69 @@ #!/usr/bin/env bash -set -euo pipefail +#set -euo pipefail wizer="${WIZER:-wizer}" wasm_tools="${WASM_TOOLS:-wasm-tools}" -# Use $2 as output file if provided, otherwise use the input base name with a .wasm extension -if [ $# -gt 1 ] +usage() { + echo "Usage: $(basename "$0") [input.js] [-o output.wasm]" + echo " Providing an input file but no output uses the input base name with a .wasm extension" + echo " Providing an output file but no input creates a component without running any top-level script" + exit 1 +} + +if [ $# -lt 1 ] then - OUT_FILE="$2" -else - BASENAME="$(basename "$1")" + usage +fi + +IN_FILE="" +OUT_FILE="" + +while [ $# -gt 0 ] +do + case "$1" in + -o|--output) + OUT_FILE="$2" + shift 2 + ;; + *) + if [ -n "$IN_FILE" ] && [ -z "$OUT_FILE" ] && [ $# -eq 1 ] + then + OUT_FILE="$1" + else + IN_FILE="$1" + fi + shift + ;; + esac +done + +# Exit if neither input file nor output file is provided. +if [ -z "$IN_FILE" ] && [ -z "$OUT_FILE" ] +then + usage +fi + +# Use the -o param as output file if provided, otherwise use the input base name with a .wasm +# extension. +if [ -z "$OUT_FILE" ] +then + BASENAME="$(basename "$IN_FILE")" OUT_FILE="${BASENAME%.*}.wasm" fi -echo "$1" | WASMTIME_BACKTRACE_DETAILS=1 $wizer --allow-wasi --wasm-bulk-memory true --inherit-stdio true --dir "$(dirname "$1")" -o "$OUT_FILE" -- "$(dirname "$0")/starling.wasm" +PREOPEN_DIR="" +if [ -n "$IN_FILE" ] +then + PREOPEN_DIR="--dir "$(dirname "$IN_FILE")"" + echo "Componentizing $IN_FILE into $OUT_FILE" +else + echo "Creating runtime-script component $OUT_FILE" +fi + + +echo "$IN_FILE" | WASMTIME_BACKTRACE_DETAILS=1 $wizer --allow-wasi --wasm-bulk-memory true \ + --inherit-stdio true --inherit-env true $PREOPEN_DIR -o "$OUT_FILE" \ + -- "$(dirname "$0")/starling.wasm" $wasm_tools component new -v --adapt "wasi_snapshot_preview1=$(dirname "$0")/preview1-adapter.wasm" --output "$OUT_FILE" "$OUT_FILE" diff --git a/include/extension-api.h b/include/extension-api.h index f6558b4b..86192760 100644 --- a/include/extension-api.h +++ b/include/extension-api.h @@ -38,6 +38,9 @@ class Engine { JSContext *cx(); HandleObject global(); + /// Initialize the engine with the given filename + bool initialize(const char * filename); + /** * Define a new builtin module * @@ -62,6 +65,9 @@ class Engine { */ void enable_module_mode(bool enable); bool eval_toplevel(const char *path, MutableHandleValue result); + bool eval_toplevel(JS::SourceText &source, const char *path, + MutableHandleValue result); + bool toplevel_evaluated(); /** * Run the async event loop as long as there's interest registered in keeping it running. diff --git a/runtime/engine.cpp b/runtime/engine.cpp index 8286cdf9..4d8529d4 100644 --- a/runtime/engine.cpp +++ b/runtime/engine.cpp @@ -327,17 +327,60 @@ static void abort(JSContext *cx, const char *description) { exit(1); } +static api::Engine *ENGINE; + api::Engine::Engine() { // total_compute = 0; bool result = init_js(); MOZ_RELEASE_ASSERT(result); JS::EnterRealm(cx(), global()); core::EventLoop::init(cx()); + ENGINE = this; } JSContext *api::Engine::cx() { return CONTEXT; } HandleObject api::Engine::global() { return GLOBAL; } + +extern bool install_builtins(api::Engine *engine); + +#ifdef DEBUG +static bool trap(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + ENGINE->dump_value(args.get(0)); + MOZ_ASSERT(false, "trap function called"); + return false; +} +#endif + +bool api::Engine::initialize(const char *filename) { + if (!install_builtins(this)) { + return false; + } + +#ifdef DEBUG + if (!JS_DefineFunction(cx(), global(), "trap", trap, 1, 0)) { + return false; + } +#endif + + if (!filename || strlen(filename) == 0) { + return true; + } + + RootedValue result(cx()); + + if (!eval_toplevel(filename, &result)) { + if (JS_IsExceptionPending(cx())) { + dump_pending_exception("pre-initializing"); + } + return false; + } + + js::ResetMathRandomSeed(cx()); + + return true; +} void api::Engine::enable_module_mode(bool enable) { scriptLoader->enable_module_mode(enable); } @@ -353,11 +396,14 @@ bool api::Engine::define_builtin_module(const char* id, HandleValue builtin) { return scriptLoader->define_builtin_module(id, builtin); } -bool api::Engine::eval_toplevel(const char *path, MutableHandleValue result) { +static bool TOPLEVEL_EVALUATED = false; + +bool api::Engine::eval_toplevel(JS::SourceText &source, const char *path, + MutableHandleValue result) { JSContext *cx = CONTEXT; RootedValue ns(cx); RootedValue tla_promise(cx); - if (!scriptLoader->load_top_level_script(path, &ns, &tla_promise)) { + if (!scriptLoader->eval_top_level_script(path, source, &ns, &tla_promise)) { return false; } @@ -410,9 +456,24 @@ bool api::Engine::eval_toplevel(const char *path, MutableHandleValue result) { // dedicated log target for telemetry messages like this. JS_SetGCCallback(cx, gc_callback, nullptr); + TOPLEVEL_EVALUATED = true; + return true; } +bool api::Engine::eval_toplevel(const char *path, MutableHandleValue result) { + JS::SourceText source; + if (!scriptLoader->load_script(CONTEXT, path, source)) { + return false; + } + + return eval_toplevel(source, path, result); +} + +bool api::Engine::toplevel_evaluated() { + return TOPLEVEL_EVALUATED; +} + bool api::Engine::run_event_loop() { return core::EventLoop::run_event_loop(this, 0); } diff --git a/runtime/js.cpp b/runtime/js.cpp index ba050fb4..49a0d4f0 100644 --- a/runtime/js.cpp +++ b/runtime/js.cpp @@ -16,42 +16,10 @@ bool WIZENED = false; extern "C" void __wasm_call_ctors(); api::Engine *engine; -extern bool install_builtins(api::Engine *engine); - -#ifdef DEBUG -static bool trap(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = CallArgsFromVp(argc, vp); - engine->dump_value(args.get(0)); - MOZ_ASSERT(false, "trap function called"); - return false; -} -#endif bool initialize(const char *filename) { auto engine = api::Engine(); - - if (!install_builtins(&engine)) { - return false; - } - -#ifdef DEBUG - if (!JS_DefineFunction(engine.cx(), engine.global(), "trap", trap, 1, 0)) { - return false; - } -#endif - - RootedValue result(engine.cx()); - - if (!engine.eval_toplevel(filename, &result)) { - if (JS_IsExceptionPending(engine.cx())) { - engine.dump_pending_exception("pre-initializing"); - } - return false; - } - - js::ResetMathRandomSeed(engine.cx()); - - return true; + return engine.initialize(filename); } extern "C" bool exports_wasi_cli_run_run() { diff --git a/runtime/script_loader.cpp b/runtime/script_loader.cpp index ad1f0a5a..06ca71ad 100644 --- a/runtime/script_loader.cpp +++ b/runtime/script_loader.cpp @@ -4,9 +4,9 @@ #include #include #include -#include #include #include +#include #include static JSContext* CONTEXT; @@ -135,50 +135,61 @@ static const char* resolve_path(const char* path, const char* base, size_t base_ static bool load_script(JSContext *cx, const char *script_path, const char* resolved_path, JS::SourceText &script); -static JSObject* get_module(JSContext* cx, const char* specifier, const char* resolved_path, - const JS::CompileOptions &opts) { - RootedString resolved_path_str(cx, JS_NewStringCopyZ(cx, resolved_path)); - if (!resolved_path_str) { +static JSObject* get_module(JSContext* cx, JS::SourceText &source, + const char* resolved_path, const JS::CompileOptions &opts) { + RootedObject module(cx, JS::CompileModule(cx, opts, source)); + if (!module) { return nullptr; } + RootedValue module_val(cx, ObjectValue(*module)); - RootedValue module_val(cx); - RootedValue resolved_path_val(cx, StringValue(resolved_path_str)); - if (!JS::MapGet(cx, moduleRegistry, resolved_path_val, &module_val)) { + RootedObject info(cx, JS_NewPlainObject(cx)); + if (!info) { return nullptr; } - if (!module_val.isUndefined()) { - return &module_val.toObject(); + RootedString resolved_path_str(cx, JS_NewStringCopyZ(cx, resolved_path)); + if (!resolved_path_str) { + return nullptr; } + RootedValue resolved_path_val(cx, StringValue(resolved_path_str)); - JS::SourceText source; - if (!load_script(cx, specifier, resolved_path, source)) { + if (!JS_DefineProperty(cx, info, "id", resolved_path_val, JSPROP_ENUMERATE)) { return nullptr; } - RootedObject module(cx, JS::CompileModule(cx, opts, source)); - if (!module) { + SetModulePrivate(module, ObjectValue(*info)); + + if (!MapSet(cx, moduleRegistry, resolved_path_val, module_val)) { return nullptr; } - module_val.setObject(*module); - RootedObject info(cx, JS_NewPlainObject(cx)); - if (!info) { + return module; +} + +static JSObject* get_module(JSContext* cx, const char* specifier, const char* resolved_path, + const JS::CompileOptions &opts) { + RootedString resolved_path_str(cx, JS_NewStringCopyZ(cx, resolved_path)); + if (!resolved_path_str) { return nullptr; } + RootedValue resolved_path_val(cx, StringValue(resolved_path_str)); - if (!JS_DefineProperty(cx, info, "id", resolved_path_val, JSPROP_ENUMERATE)) { + RootedValue module_val(cx); + if (!JS::MapGet(cx, moduleRegistry, resolved_path_val, &module_val)) { return nullptr; } - SetModulePrivate(module, ObjectValue(*info)); + if (!module_val.isUndefined()) { + return &module_val.toObject(); + } - if (!MapSet(cx, moduleRegistry, resolved_path_val, module_val)) { + JS::SourceText source; + if (!load_script(cx, specifier, resolved_path, source)) { return nullptr; } - return module; + return get_module(cx, source, resolved_path, opts); } static JSObject* get_builtin_module(JSContext* cx, HandleValue id, HandleObject builtin) { @@ -377,8 +388,8 @@ static bool load_script(JSContext *cx, const char *specifier, const char* resolv JS::SourceText &script) { FILE *file = fopen(resolved_path, "r"); if (!file) { - std::cerr << "Error opening file " << specifier << " (resolved to " << resolved_path << ")" - << std::endl; + std::cerr << "Error opening file " << specifier << " (resolved to " << resolved_path << "): " + << std::strerror(errno) << std::endl; return false; } @@ -409,27 +420,32 @@ static bool load_script(JSContext *cx, const char *specifier, const char* resolv bool ScriptLoader::load_script(JSContext *cx, const char *script_path, JS::SourceText &script) { - auto resolved_path = resolve_path(script_path, BASE_PATH, strlen(BASE_PATH)); + const char *resolved_path; + if (!BASE_PATH) { + auto last_slash = strrchr(script_path, '/'); + size_t base_len; + if (last_slash) { + last_slash++; + base_len = last_slash - script_path; + BASE_PATH = new char[base_len + 1]; + MOZ_ASSERT(BASE_PATH); + strncpy(BASE_PATH, script_path, base_len); + BASE_PATH[base_len] = '\0'; + } else { + BASE_PATH = strdup("./"); + } + resolved_path = script_path; + } else { + resolved_path = resolve_path(script_path, BASE_PATH, strlen(BASE_PATH)); + } + return ::load_script(cx, script_path, resolved_path, script); } -bool ScriptLoader::load_top_level_script(const char *path, MutableHandleValue result, MutableHandleValue tla_promise) { +bool ScriptLoader::eval_top_level_script(const char *path, JS::SourceText &source, + MutableHandleValue result, MutableHandleValue tla_promise) { JSContext *cx = CONTEXT; - MOZ_ASSERT(!BASE_PATH); - auto last_slash = strrchr(path, '/'); - size_t base_len; - if (last_slash) { - last_slash++; - base_len = last_slash - path; - BASE_PATH = new char[base_len + 1]; - MOZ_ASSERT(BASE_PATH); - strncpy(BASE_PATH, path, base_len); - BASE_PATH[base_len] = '\0'; - } else { - BASE_PATH = strdup("./"); - } - JS::CompileOptions opts(cx, *COMPILE_OPTS); opts.setFileAndLine(strip_base(path, BASE_PATH), 1); JS::RootedScript script(cx); @@ -440,7 +456,7 @@ bool ScriptLoader::load_top_level_script(const char *path, MutableHandleValue re // (Whereas disabling it during execution below meaningfully increases it, // which is why this is scoped to just compilation.) JS::AutoDisableGenerationalGC noGGC(cx); - module = get_module(cx, path, path, opts); + module = get_module(cx, source, path, opts); if (!module) { return false; } @@ -448,10 +464,6 @@ bool ScriptLoader::load_top_level_script(const char *path, MutableHandleValue re return false; } } else { - JS::SourceText source; - if (!::load_script(cx, path, path, source)) { - return false; - } // See comment above about disabling GGC during compilation. JS::AutoDisableGenerationalGC noGGC(cx); script = JS::Compile(cx, opts, source); diff --git a/runtime/script_loader.h b/runtime/script_loader.h index 4a645a4c..460b58db 100644 --- a/runtime/script_loader.h +++ b/runtime/script_loader.h @@ -19,7 +19,8 @@ class ScriptLoader { bool define_builtin_module(const char* id, HandleValue builtin); void enable_module_mode(bool enable); - bool load_top_level_script(const char *path, MutableHandleValue result, MutableHandleValue tla_promise); + bool eval_top_level_script(const char *path, JS::SourceText &source, + MutableHandleValue result, MutableHandleValue tla_promise); bool load_script(JSContext* cx, const char *script_path, JS::SourceText &script); }; From c8cf151a3ce1b7e3e60b52aceeb005f074b239e0 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Tue, 28 May 2024 14:19:53 +0200 Subject: [PATCH 33/52] Slight improvements to the WPT harness --- tests/wpt-harness/run-wpt.mjs | 15 ++++++++++----- tests/wpt-harness/tests.json | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/wpt-harness/run-wpt.mjs b/tests/wpt-harness/run-wpt.mjs index 0200109e..bad8b455 100644 --- a/tests/wpt-harness/run-wpt.mjs +++ b/tests/wpt-harness/run-wpt.mjs @@ -14,6 +14,7 @@ function relativePath(path) { return new URL(path, import.meta.url).pathname; } +const SKIP_PREFIX = "SKIP "; const SLOW_PREFIX = "SLOW "; const config = { @@ -280,9 +281,9 @@ async function startWptServer(root, logLevel) { async function ensureWasmtime(config, logLevel) { if (config.external) { - let wasmtime = { ...config }; + let wasmtime = { ...config, host: `http://${config.addr}/` }; if (logLevel > LogLevel.Quiet) { - console.info(`Using external Wasmtime host ${config.host}`); + console.info(`Using external Wasmtime host ${wasmtime.host}`); } return wasmtime; } else { @@ -379,6 +380,7 @@ function getTests(pattern) { console.log(`Loading tests list from ${config.tests.list}`); let testPaths = JSON.parse(readFileSync(config.tests.list, { encoding: "utf-8" })); + testPaths = testPaths.filter(path => !path.startsWith(SKIP_PREFIX)); let totalCount = testPaths.length; if (config.skipSlowTests) { testPaths = testPaths.filter(path => !path.startsWith(SLOW_PREFIX)); @@ -412,10 +414,11 @@ async function runTests(testPaths, wasmtime, resultCallback, errorCallback) { let t1 = Date.now(); let response, body; try { - response = await fetch(`${wasmtime.host}${path}`); - body = await response.text(); + if (config.logLevel >= LogLevel.VeryVerbose) { + console.log(`Sending request to ${wasmtime.host}${path}`); + } } catch (e) { - shutdown(`Wasmtime bailed while running test ${path}`); + shutdown(`Error while running test ${path}: ${e}`); } let stats = { count: 0, @@ -428,6 +431,8 @@ async function runTests(testPaths, wasmtime, resultCallback, errorCallback) { totalStats.duration += stats.duration; let results; try { + response = await fetch(`${wasmtime.host}${path}`); + body = await response.text(); results = JSON.parse(body); if (response.status == 500) { throw {message: results.error.message, stack: results.error.stack}; diff --git a/tests/wpt-harness/tests.json b/tests/wpt-harness/tests.json index 2545d8de..686903c3 100644 --- a/tests/wpt-harness/tests.json +++ b/tests/wpt-harness/tests.json @@ -72,7 +72,7 @@ "fetch/api/headers/headers-record.any.js", "fetch/api/headers/headers-structure.any.js", "fetch/api/request/forbidden-method.any.js", - "fetch/api/request/request-bad-port.any.js", + "SKIP [tests restrictions we're not imposing] fetch/api/request/request-bad-port.any.js", "fetch/api/request/request-cache-default-conditional.any.js", "fetch/api/request/request-cache-default.any.js", "fetch/api/request/request-cache-force-cache.any.js", From c44b816cf64610dab66d39754e3ba2f2f7af2c48 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Tue, 28 May 2024 14:34:25 +0200 Subject: [PATCH 34/52] Substantially more reworking of the fetch related host API surface Gets back to passing all current tests, and most of the WPT suite that Fastly's JS Compute runtime passes. --- builtins/web/fetch/fetch-api.cpp | 64 ++++++-- builtins/web/fetch/fetch_event.cpp | 79 +++++++--- builtins/web/fetch/headers.cpp | 23 ++- builtins/web/fetch/request-response.cpp | 161 +++++++++++--------- builtins/web/fetch/request-response.h | 2 +- host-apis/wasi-0.2.0/host_api.cpp | 194 ++++++++++++++---------- include/host_api.h | 15 +- 7 files changed, 342 insertions(+), 196 deletions(-) diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 25c2cdf2..520de251 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -3,6 +3,10 @@ #include "headers.h" #include "request-response.h" +#include + +#include + namespace builtins::web::fetch { static api::Engine *ENGINE; @@ -80,30 +84,66 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { return ReturnPromiseRejectedWithPendingError(cx, args); } - RootedObject requestInstance( + RootedObject request_obj( cx, JS_NewObjectWithGivenProto(cx, &Request::class_, Request::proto_obj)); - if (!requestInstance) - return false; + if (!request_obj) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + if (!Request::create(cx, request_obj, args[0], args.get(1))) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + RootedString method_str(cx, Request::method(cx, request_obj)); + if (!method_str) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } - RootedObject request(cx, Request::create(cx, requestInstance, args[0], args.get(1))); - if (!request) { + host_api::HostString method = core::encode(cx, method_str); + if (!method.ptr) { return ReturnPromiseRejectedWithPendingError(cx, args); } + RootedValue url_val(cx, RequestOrResponse::url(request_obj)); + host_api::HostString url = core::encode(cx, url_val); + if (!url.ptr) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + unique_ptr headers; + RootedObject headers_obj(cx, RequestOrResponse::maybe_headers(request_obj)); + if (headers_obj) { + headers = Headers::handle_clone(cx, headers_obj); + } else { + headers = std::make_unique(); + } + + if (!headers) { + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + auto request = host_api::HttpOutgoingRequest::make(method, std::move(url), + std::move(headers)); + MOZ_RELEASE_ASSERT(request); + JS_SetReservedSlot(request_obj, static_cast(Request::Slots::Request), + PrivateValue(request)); + RootedObject response_promise(cx, JS::NewPromiseObject(cx, nullptr)); if (!response_promise) return ReturnPromiseRejectedWithPendingError(cx, args); bool streaming = false; - if (!RequestOrResponse::maybe_stream_body(cx, request, &streaming)) { + if (!RequestOrResponse::maybe_stream_body(cx, request_obj, &streaming)) { return false; } + if (streaming) { + // Ensure that the body handle is stored before making the request handle invalid by sending it. + request->body(); + } host_api::FutureHttpIncomingResponse *pending_handle; { - auto request_handle = Request::outgoing_handle(request); - auto res = request_handle->send(); - + auto res = request->send(); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return ReturnPromiseRejectedWithPendingError(cx, args); @@ -115,11 +155,11 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { // If the request body is streamed, we need to wait for streaming to complete // before marking the request as pending. if (!streaming) { - ENGINE->queue_async_task(new ResponseFutureTask(request, pending_handle)); + ENGINE->queue_async_task(new ResponseFutureTask(request_obj, pending_handle)); } - JS::SetReservedSlot(request, static_cast(Request::Slots::ResponsePromise), - JS::ObjectValue(*response_promise)); + JS::SetReservedSlot(request_obj, static_cast(Request::Slots::ResponsePromise), + ObjectValue(*response_promise)); args.rval().setObject(*response_promise); return true; diff --git a/builtins/web/fetch/fetch_event.cpp b/builtins/web/fetch/fetch_event.cpp index ca6a5e00..d1fbb10a 100644 --- a/builtins/web/fetch/fetch_event.cpp +++ b/builtins/web/fetch/fetch_event.cpp @@ -3,10 +3,13 @@ #include "../url.h" #include "../worker-location.h" #include "encode.h" -#include "exports.h" #include "request-response.h" #include "bindings.h" + +#include +#include + #include #include @@ -165,12 +168,22 @@ bool start_response(JSContext *cx, JS::HandleObject response_obj, bool streaming return false; } - auto incoming_response = Response::response_handle(response_obj); host_api::HttpOutgoingResponse* response = - host_api::HttpOutgoingResponse::make(status, headers.get()); - if (incoming_response) { + host_api::HttpOutgoingResponse::make(status, std::move(headers)); + if (streaming) { + // Get the body here, so it will be stored on the response object. + // Otherwise, it'd not be available anymore, because the response handle itself + // is consumed by sending it off. + auto body = response->body().unwrap(); + MOZ_RELEASE_ASSERT(body); + } + MOZ_RELEASE_ASSERT(response); + + auto existing_handle = Response::response_handle(response_obj); + if (existing_handle) { + MOZ_ASSERT(existing_handle->is_incoming()); if (streaming) { - auto *source_body = incoming_response->body().unwrap(); + auto *source_body = static_cast(existing_handle)->body().unwrap(); auto *dest_body = response->body().unwrap(); auto res = dest_body->append(ENGINE, source_body); @@ -180,6 +193,9 @@ bool start_response(JSContext *cx, JS::HandleObject response_obj, bool streaming } MOZ_RELEASE_ASSERT(RequestOrResponse::mark_body_used(cx, response_obj)); } + } else { + SetReservedSlot(response_obj, static_cast(Response::Slots::Response), + PrivateValue(response)); } if (streaming && response->has_body()) { @@ -216,17 +232,6 @@ bool response_promise_then_handler(JSContext *cx, JS::HandleObject event, JS::Ha // very different.) JS::RootedObject response_obj(cx, &args[0].toObject()); - // Ensure that all headers are stored client-side, so we retain access to them - // after sending the response off. - // TODO(TS): restore proper headers handling - // if (Response::is_upstream(response_obj)) { - // JS::RootedObject headers(cx); - // headers = - // RequestOrResponse::headers(cx, response_obj); - // if (!Headers::delazify(cx, headers)) - // return false; - // } - bool streaming = false; if (!RequestOrResponse::maybe_stream_body(cx, response_obj, &streaming)) { return false; @@ -304,7 +309,7 @@ bool FetchEvent::respondWithError(JSContext *cx, JS::HandleObject self) { MOZ_RELEASE_ASSERT(state(self) == State::unhandled || state(self) == State::waitToRespond); auto headers = std::make_unique(); - auto *response = host_api::HttpOutgoingResponse::make(500, headers.get()); + auto *response = host_api::HttpOutgoingResponse::make(500, std::move(headers)); auto body_res = response->body(); if (auto *err = body_res.to_err()) { @@ -509,6 +514,46 @@ static void dispatch_fetch_event(HandleObject event, double *total_compute) { } bool handle_incoming_request(host_api::HttpIncomingRequest * request) { + if (!ENGINE->toplevel_evaluated()) { + JS::SourceText source; + auto body = request->body().unwrap(); + auto pollable = body->subscribe().unwrap(); + size_t len = 0; + vector chunks; + + while (true) { + host_api::block_on_pollable_handle(pollable); + auto result = body->read(4096); + if (result.unwrap().done) { + break; + } + + auto chunk = std::move(result.unwrap().bytes); + len += chunk.size(); + chunks.push_back(std::move(chunk)); + } + + // Merge all chunks into one buffer + auto buffer = new char[len]; + size_t offset = 0; + for (auto &chunk : chunks) { + memcpy(buffer + offset, chunk.ptr.get(), chunk.size()); + offset += chunk.size(); + } + + if (!source.init(CONTEXT, buffer, len, JS::SourceOwnership::TakeOwnership)) { + return false; + } + + RootedValue rval(ENGINE->cx()); + if (!ENGINE->eval_toplevel(source, "", &rval)) { + if (JS_IsExceptionPending(ENGINE->cx())) { + ENGINE->dump_pending_exception("Runtime script evaluation"); + } + return false; + } + } + HandleObject fetch_event = FetchEvent::instance(); MOZ_ASSERT(FetchEvent::is_instance(fetch_event)); if (!FetchEvent::init_incoming_request(ENGINE->cx(), fetch_event, request)) { diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 6139b0c2..2281190c 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -1,5 +1,4 @@ #include "headers.h" -// #include "request-response.h" #include "encode.h" #include "decode.h" #include "sequence.hpp" @@ -70,7 +69,7 @@ host_api::HttpHeadersReadOnly *get_handle(JSObject *self) { */ host_api::HostString normalize_header_name(JSContext *cx, HandleValue name_val, bool* named_changed, const char *fun_name) { - *named_changed = false; + *named_changed = !name_val.isString(); JS::RootedString name_str(cx, JS::ToString(cx, name_val)); if (!name_str) { return nullptr; @@ -114,7 +113,7 @@ host_api::HostString normalize_header_name(JSContext *cx, HandleValue name_val, */ host_api::HostString normalize_header_value(JSContext *cx, HandleValue value_val, bool* value_changed, const char *fun_name) { - *value_changed = false; + *value_changed = !value_val.isString(); JS::RootedString value_str(cx, JS::ToString(cx, value_val)); if (!value_str) { return nullptr; @@ -309,7 +308,8 @@ static bool switch_mode(JSContext* cx, HandleObject self, const Headers::Mode mo return false; } - vector> string_entries; + using host_api::HostString; + vector> string_entries; RootedValue entry_val(cx); RootedObject entry(cx); @@ -328,8 +328,8 @@ static bool switch_mode(JSContext* cx, HandleObject self, const Headers::Mode mo } entry = &entry_val.toObject(); - JS_GetElement(cx, entry, 1, &name_val); - JS_GetElement(cx, entry, 0, &value_val); + JS_GetElement(cx, entry, 0, &name_val); + JS_GetElement(cx, entry, 1, &value_val); name_str = name_val.toString(); value_str = value_val.toString(); @@ -343,7 +343,7 @@ static bool switch_mode(JSContext* cx, HandleObject self, const Headers::Mode mo return false; } - string_entries.emplace_back(name, value); + string_entries.emplace_back(std::move(name), std::move(value)); } auto handle = host_api::HttpHeaders::FromEntries(string_entries); @@ -862,6 +862,9 @@ bool Headers::init_class(JSContext *cx, JS::HandleObject global) { JSObject *Headers::get_entries(JSContext *cx, HandleObject self) { MOZ_ASSERT(is_instance(self)); + if (mode(self) == Mode::Uninitialized && !switch_mode(cx, self, Mode::ContentOnly)) { + return nullptr; + } if (mode(self) == Mode::HostOnly && !switch_mode(cx, self, Mode::CachedInContent)) { return nullptr; } @@ -871,6 +874,12 @@ JSObject *Headers::get_entries(JSContext *cx, HandleObject self) { unique_ptr Headers::handle_clone(JSContext* cx, HandleObject self) { auto mode = Headers::mode(self); + + // If this instance uninitialized, return an empty handle without initializing this instance. + if (mode == Mode::Uninitialized) { + return std::make_unique(); + } + if (mode == Mode::ContentOnly && !switch_mode(cx, self, Mode::CachedInContent)) { // Switch to Mode::CachedInContent to ensure that the latest data is available on the handle, // but without discarding the existing entries, in case content reads them later. diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index c6f70b29..d0654113 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -157,6 +157,7 @@ struct ReadResult { } // namespace host_api::HttpRequestResponseBase *RequestOrResponse::handle(JSObject *obj) { + MOZ_ASSERT(is_instance(obj)); auto slot = JS::GetReservedSlot(obj, static_cast(Slots::RequestOrResponse)); return static_cast(slot.toPrivate()); } @@ -165,7 +166,10 @@ bool RequestOrResponse::is_instance(JSObject *obj) { return Request::is_instance(obj) || Response::is_instance(obj); } -bool RequestOrResponse::is_incoming(JSObject *obj) { return !!handle(obj); } +bool RequestOrResponse::is_incoming(JSObject *obj) { + auto handle = RequestOrResponse::handle(obj); + return handle && handle->is_incoming(); +} host_api::HttpHeadersReadOnly *RequestOrResponse::headers_handle(JSObject *obj) { MOZ_ASSERT(is_instance(obj)); @@ -190,15 +194,11 @@ host_api::HttpIncomingBody *RequestOrResponse::incoming_body_handle(JSObject *ob host_api::HttpOutgoingBody *RequestOrResponse::outgoing_body_handle(JSObject *obj) { MOZ_ASSERT(!is_incoming(obj)); - host_api::Result res; - if (Request::is_instance(obj)) { - auto owner = reinterpret_cast(handle(obj)); - res = owner->body(); + if (handle(obj)->is_request()) { + return reinterpret_cast(handle(obj))->body().unwrap(); } else { - auto owner = reinterpret_cast(handle(obj)); - res = owner->body(); + return reinterpret_cast(handle(obj))->body().unwrap(); } - return res.unwrap(); } JSObject *RequestOrResponse::body_stream(JSObject *obj) { @@ -318,60 +318,97 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, } } } else { - mozilla::Maybe maybeNoGC; - JS::UniqueChars text; - char *buf; - size_t length; + RootedValue chunk(cx); + RootedObject buffer(cx); + char *buf = nullptr; + size_t length = 0; + + // Must be declared here to keep the buffer alive. + host_api::HostString text; if (body_obj && JS_IsArrayBufferViewObject(body_obj)) { - // Short typed arrays have inline data which can move on GC, so assert - // that no GC happens. (Which it doesn't, because we're not allocating - // before `buf` goes out of scope.) - maybeNoGC.emplace(cx); - JS::AutoCheckCannotGC &noGC = maybeNoGC.ref(); - bool is_shared; length = JS_GetArrayBufferViewByteLength(body_obj); - buf = (char *)JS_GetArrayBufferViewData(body_obj, &is_shared, noGC); - } else if (body_obj && JS::IsArrayBufferObject(body_obj)) { + buf = static_cast(js_malloc(length)); + if (!buf) { + return false; + } + bool is_shared; - JS::GetArrayBufferLengthAndData(body_obj, &length, &is_shared, (uint8_t **)&buf); + JS::AutoCheckCannotGC noGC(cx); + auto temp_buf = JS_GetArrayBufferViewData(body_obj, &is_shared, noGC); + memcpy(buf, temp_buf, length); + } else if (body_obj && IsArrayBufferObject(body_obj)) { + buffer = CopyArrayBuffer(cx, body_obj); + if (!buffer) { + return false; + } } else if (body_obj && url::URLSearchParams::is_instance(body_obj)) { auto slice = url::URLSearchParams::serialize(cx, body_obj); buf = (char *)slice.data; length = slice.len; content_type = "application/x-www-form-urlencoded;charset=UTF-8"; } else { - { - auto str = core::encode(cx, body_val); - text = std::move(str.ptr); - length = str.len; + text = core::encode(cx, body_val); + if (!text.ptr) { + return false; } + buf = text.ptr.get(); + length = text.len; + content_type = "text/plain;charset=UTF-8"; + } - if (!text) + if (buf) { + MOZ_ASSERT(!buffer); + buffer = NewArrayBufferWithContents(cx, length, buf, + JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory); + if (text.ptr) { + static_cast(text.ptr.release()); + } + if (!buffer) { + js_free(buf); return false; - buf = text.get(); - content_type = "text/plain;charset=UTF-8"; + } + chunk.setObject(*buffer); } - // TODO: restore - // auto body = RequestOrResponse::outgoing_body_handle(self); - // auto write_res = body->write_all(reinterpret_cast(buf), length); + if (buffer) { + RootedObject array(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, length)); + if (!array) { + return false; + } + chunk.setObject(*array); + } - // Ensure that the NoGC is reset, so throwing an error in HANDLE_ERROR - // succeeds. - if (maybeNoGC.isSome()) { - maybeNoGC.reset(); + RootedObject source(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); + if (!source) { + return false; + } + RootedObject body_stream(cx, JS::NewReadableDefaultStreamObject(cx, source, + nullptr, 0.0)); + if (!body_stream) { + return false; } - // if (auto *err = write_res.to_err()) { - // HANDLE_ERROR(cx, *err); - // return false; - // } + mozilla::DebugOnly disturbed; + MOZ_ASSERT(ReadableStreamIsDisturbed(cx, body_stream, &disturbed)); + MOZ_ASSERT(!disturbed); + + + if (!ReadableStreamEnqueue(cx, body_stream, chunk) || + !ReadableStreamClose(cx, body_stream)) { + return false; + } + + JS_SetReservedSlot(self, static_cast(Slots::BodyStream), + ObjectValue(*body_stream)); } // Step 36.3 of Request constructor / 8.4 of Response constructor. if (content_type) { JS::RootedObject headers(cx, RequestOrResponse::headers(cx, self)); + if (!headers) { + return false; + } if (!Headers::set_if_undefined(cx, headers, "content-type", content_type)) { return false; } @@ -427,8 +464,11 @@ bool RequestOrResponse::append_body(JSContext *cx, JS::HandleObject self, JS::Ha JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { JSObject *headers = maybe_headers(obj); if (!headers) { - if (auto *headers_handle = RequestOrResponse::headers_handle(obj)) { - headers = Headers::create(cx, headers_handle); + host_api::HttpHeadersReadOnly *handle; + if (is_incoming(obj) && (handle = headers_handle(obj))) { + headers = Headers::create(cx, handle); + } else { + headers = Headers::create(cx, NullHandleValue); } if (!headers) { return nullptr; @@ -788,10 +828,6 @@ bool RequestOrResponse::bodyAll(JSContext *cx, JS::CallArgs args, JS::HandleObje return true; } - if (!mark_body_used(cx, self)) { - return ReturnPromiseRejectedWithPendingError(cx, args); - } - JS::RootedValue body_parser(cx, JS::PrivateValue((void *)parse_body)); // TODO(performance): don't reify a ReadableStream for body handles—use an AsyncTask instead @@ -896,10 +932,6 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject return false; } - if (Request::is_instance(body_owner)) { - ENGINE->queue_async_task(new BodyFutureTask(body_owner)); - } - return true; } @@ -939,6 +971,7 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject bool is_shared; uint8_t *bytes = JS_GetUint8ArrayData(array, &is_shared, nogc); size_t length = JS_GetTypedArrayByteLength(array); + // TODO: change this to write in chunks, respecting backpressure. res = body->write_all(bytes, length); } @@ -1029,17 +1062,6 @@ bool RequestOrResponse::maybe_stream_body(JSContext *cx, JS::HandleObject body_o if (!reader) return false; - bool is_closed; - if (!JS::ReadableStreamReaderIsClosed(cx, reader, &is_closed)) - return false; - - // It's ok for the stream to be closed, as its contents might - // already have fully been written to the body handle. - // In that case, we can do a blocking send instead. - if (is_closed) { - return true; - } - // Create handlers for both `then` and `catch`. // These are functions with two reserved slots, in which we store all // information required to perform the reactions. We store the actually @@ -1124,20 +1146,24 @@ host_api::HttpRequest *Request::request_handle(JSObject *obj) { host_api::HttpOutgoingRequest *Request::outgoing_handle(JSObject *obj) { auto base = RequestOrResponse::handle(obj); + MOZ_ASSERT(base->is_outgoing()); return reinterpret_cast(base); } host_api::HttpIncomingRequest *Request::incoming_handle(JSObject *obj) { auto base = RequestOrResponse::handle(obj); + MOZ_ASSERT(base->is_incoming()); return reinterpret_cast(base); } JSObject *Request::response_promise(JSObject *obj) { + MOZ_ASSERT(is_instance(obj)); return &JS::GetReservedSlot(obj, static_cast(Request::Slots::ResponsePromise)) .toObject(); } JSString *Request::method(JSContext *cx, JS::HandleObject obj) { + MOZ_ASSERT(is_instance(obj)); return JS::GetReservedSlot(obj, static_cast(Slots::Method)).toString(); } @@ -1374,6 +1400,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance) { */ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::HandleValue input, JS::HandleValue init_val) { + create(cx, requestInstance); JS::RootedString url_str(cx); JS::RootedString method_str(cx); bool method_needs_normalization = false; @@ -1804,10 +1831,9 @@ static_assert((int)Response::Slots::BodyUsed == (int)Request::Slots::BodyUsed); static_assert((int)Response::Slots::Headers == (int)Request::Slots::Headers); static_assert((int)Response::Slots::Response == (int)Request::Slots::Request); -host_api::HttpIncomingResponse *Response::response_handle(JSObject *obj) { +host_api::HttpResponse *Response::response_handle(JSObject *obj) { MOZ_ASSERT(is_instance(obj)); - return static_cast( - JS::GetReservedSlot(obj, static_cast(Slots::Response)).toPrivate()); + return static_cast(RequestOrResponse::handle(obj)); } uint16_t Response::status(JSObject *obj) { @@ -2529,15 +2555,6 @@ bool Response::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { if (!RequestOrResponse::extract_body(cx, response, body_val)) { return false; } - - // TODO: presumably remove this code, but check what it's for first - // if (RequestOrResponse::has_body(response)) { - // if (response_handle->body().is_err()) { - // auto err = response_handle->body().to_err(); - // HANDLE_ERROR(cx, *err); - // return false; - // } - // } } args.rval().setObject(*response); diff --git a/builtins/web/fetch/request-response.h b/builtins/web/fetch/request-response.h index 5f435b50..b7af49ee 100644 --- a/builtins/web/fetch/request-response.h +++ b/builtins/web/fetch/request-response.h @@ -211,7 +211,7 @@ class Response final : public BuiltinImpl { static JSObject* create_incoming(JSContext * cx, HandleObject self, host_api::HttpIncomingResponse* response); - static host_api::HttpIncomingResponse *response_handle(JSObject *obj); + static host_api::HttpResponse *response_handle(JSObject *obj); static uint16_t status(JSObject *obj); static JSString *status_message(JSObject *obj); static void set_status_message_from_code(JSContext *cx, JSObject *obj, uint16_t code); diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index e0363367..1322fed3 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -2,9 +2,12 @@ #include "bindings/bindings.h" #include -#include #include +#ifdef DEBUG +#include +#endif +using host_api::HostString; using std::optional; using std::string_view; using std::tuple; @@ -46,56 +49,67 @@ typedef wasi_io_0_2_0_streams_own_output_stream_t own_output_stream_t; typedef int32_t Handle; constexpr Handle UNINITIALIZED_HANDLE = -1; -/// An abstract base class to be used in classes representing host resources. -/// -/// Some host resources have different requirements for their client-side representation -/// depending on the host API. To accommodate this, we introduce a base class to use for -/// all of them, which the API-specific implementation can subclass as needed. class host_api::HandleState { Handle handle_ = UNINITIALIZED_HANDLE; #ifdef DEBUG - #define ASSERT_VALID()\ - MOZ_ASSERT(handle_state_.get() != nullptr, "Handle state missing"); \ - MOZ_ASSERT(handle_state_.get()->initialized(), "Handle not initialized"); \ - MOZ_ASSERT(!handle_state_.get()->poisoned(), "Handle poisoned"); + uint8_t handle_ns_; bool poisoned_ = false; -#else -#define ASSERT_VALID + + static std::set used_handles; #endif + static int32_t to_namespaced(Handle handle, uint8_t handle_ns) { + return (handle_ns << 24) | handle; + } + public: - HandleState() {} - explicit HandleState(Handle handle) : handle_{handle} { - DBG("Adding handle %d\n", handle); - // MOZ_ASSERT(handle > UNINITIALIZED_HANDLE); - // MOZ_ASSERT(!used_handles.has(handle)); - // MOZ_ASSERT(used_handles.put(handle)); + HandleState() = delete; + HandleState(Handle handle, uint8_t handle_ns) : handle_{handle} { +#ifdef DEBUG + handle_ns_ = handle_ns; + // DBG("Adding handle %d,%d\n", handle_ns_, handle); + // TODO: remove this, and replace all this with better things + MOZ_ASSERT(handle < 500000); + auto ns_handle = to_namespaced(handle, handle_ns); + MOZ_ASSERT(handle > UNINITIALIZED_HANDLE); + MOZ_ASSERT(!used_handles.contains(ns_handle)); + used_handles.insert(ns_handle); +#endif } virtual ~HandleState() { // TODO: support dropping handles for all handle types. Probably using a template class. - DBG("Removing handle %d\n", handle_); + // DBG("Removing handle %d,%d\n", handle_ns_, handle_); #ifdef DEBUG - // MOZ_ASSERT(used_handles.has(handle)); - // used_handles.remove(handle); + if (!poisoned_) { + auto ns_handle = to_namespaced(handle_, handle_ns_); + MOZ_ASSERT(used_handles.contains(ns_handle)); + used_handles.erase(ns_handle); + } #endif } - Handle get() const { - DBG("Getting handle %d\n", handle_); - MOZ_ASSERT(handle_ != UNINITIALIZED_HANDLE, "Handle is uninitialized"); + void assert_valid() const { #ifdef DEBUG - if (poisoned_) { - fprintf(stderr, "Handle %d is poisened", handle_); fflush(stderr); + MOZ_ASSERT(initialized(), "Handle not initialized"); + if (poisoned()) { + fprintf(stderr, "Handle %d,%d is poisened", handle_ns_, handle_); fflush(stderr); MOZ_ASSERT(false); } #endif + } + + Handle get() const { + // DBG("Getting handle %d,%d\n", handle_ns_, handle_); + assert_valid(); return handle_; } Handle take() { auto handle = get(); - DBG("Consuming handle %d\n", handle); + // DBG("Consuming handle %d,%d\n", handle_ns_, handle); + auto ns_handle = to_namespaced(handle, handle_ns_); + used_handles.erase(ns_handle); poisoned_ = true; return handle; } @@ -105,6 +119,8 @@ class host_api::HandleState { bool poisoned() const { return poisoned_; } }; +std::set host_api::HandleState::used_handles = std::set(); + namespace { // TODO: merge HandleOps into a WASIHandleState subclass of HandleState, and use that everywhere. @@ -256,10 +272,11 @@ void MonotonicClock::unsubscribe(const int32_t handle_id) { HttpHeaders::HttpHeaders(std::unique_ptr state) : HttpHeadersReadOnly(std::move(state)) {} -HttpHeaders::HttpHeaders() : HttpHeadersReadOnly(std::make_unique(wasi_http_0_2_0_types_constructor_fields().__handle)) { +static Resource::HandleNS HEADERS_HANDLE_NS = Resource::next_handle_ns("HttpHeaders"); +HttpHeaders::HttpHeaders() : HttpHeadersReadOnly(std::make_unique(wasi_http_0_2_0_types_constructor_fields().__handle, HEADERS_HANDLE_NS)) { } -Result HttpHeaders::FromEntries(const vector> &entries) { +Result HttpHeaders::FromEntries(vector>& entries) { std::vector pairs; pairs.reserve(entries.size()); @@ -271,16 +288,19 @@ Result HttpHeaders::FromEntries(const vector::err(154); + } - return Result::ok(new HttpHeaders(std::make_unique(ret.__handle))); + return Result::ok(new HttpHeaders(std::make_unique(ret.__handle, + HEADERS_HANDLE_NS))); } HttpHeaders::HttpHeaders(const HttpHeadersReadOnly &headers) : HttpHeadersReadOnly(nullptr) { Borrow borrow(headers.handle_state_.get()); auto handle = wasi_http_0_2_0_types_method_fields_clone(borrow); - this->handle_state_ = std::unique_ptr(new HandleState(handle.__handle)); + this->handle_state_ = std::unique_ptr(new HandleState(handle.__handle, HEADERS_HANDLE_NS)); } HttpHeaders *HttpHeadersReadOnly::clone() { @@ -289,7 +309,7 @@ HttpHeaders *HttpHeadersReadOnly::clone() { Result>> HttpHeadersReadOnly::entries() const { Result>> res; - ASSERT_VALID(); + handle_state_->assert_valid(); wasi_http_0_2_0_types_list_tuple2_field_key_field_value_t entries; Borrow borrow(this->handle_state_.get()); @@ -310,7 +330,7 @@ Result>> HttpHeadersReadOnly::entries() con Result> HttpHeadersReadOnly::names() const { Result> res; - ASSERT_VALID(); + handle_state_->assert_valid(); wasi_http_0_2_0_types_list_tuple2_field_key_field_value_t entries; Borrow borrow(this->handle_state_.get()); @@ -329,7 +349,7 @@ Result> HttpHeadersReadOnly::names() const { Result>> HttpHeadersReadOnly::get(string_view name) const { Result>> res; - ASSERT_VALID(); + handle_state_->assert_valid(); wasi_http_0_2_0_types_list_field_value_t values; auto hdr = string_view_to_world_string(name); @@ -352,7 +372,7 @@ Result>> HttpHeadersReadOnly::get(string_view name) } Result HttpHeadersReadOnly::has(string_view name) const { - ASSERT_VALID(); + handle_state_->assert_valid(); auto hdr = string_view_to_world_string(name); Borrow borrow(this->handle_state_.get()); @@ -360,7 +380,7 @@ Result HttpHeadersReadOnly::has(string_view name) const { } Result HttpHeaders::set(string_view name, string_view value) { - ASSERT_VALID(); + handle_state_->assert_valid(); auto hdr = from_string_view(name); auto val = from_string_view(value); wasi_http_0_2_0_types_list_field_value_t host_values{&val, 1}; @@ -375,7 +395,7 @@ Result HttpHeaders::set(string_view name, string_view value) { } Result HttpHeaders::append(string_view name, string_view value) { - ASSERT_VALID(); + handle_state_->assert_valid(); auto hdr = from_string_view(name); auto val = from_string_view(value); Borrow borrow(this->handle_state_.get()); @@ -397,7 +417,7 @@ Result HttpHeaders::append(string_view name, string_view value) { } Result HttpHeaders::remove(string_view name) { - ASSERT_VALID(); + handle_state_->assert_valid(); auto hdr = string_view_to_world_string(name); Borrow borrow(this->handle_state_.get()); @@ -447,6 +467,7 @@ bool write_to_outgoing_body(Borrow borrow, const uint8_t *ptr, con return wasi_io_0_2_0_streams_method_output_stream_write(borrow, &list, &err); } +static Resource::HandleNS OUT_BODY_HANDLE_NS = Resource::next_handle_ns("OutgoingBody"); class OutgoingBodyHandleState final : public HandleState { Handle stream_handle_; PollableHandle pollable_handle_; @@ -455,7 +476,7 @@ class OutgoingBodyHandleState final : public HandleState { public: explicit OutgoingBodyHandleState(const Handle handle) - : HandleState(handle), pollable_handle_(INVALID_POLLABLE_HANDLE) { + : HandleState(handle, OUT_BODY_HANDLE_NS), pollable_handle_(INVALID_POLLABLE_HANDLE) { const borrow_outgoing_body_t borrow = {handle}; own_output_stream_t stream{}; if (!wasi_http_0_2_0_types_method_outgoing_body_write(borrow, &stream)) { @@ -661,13 +682,13 @@ class BodyAppendTask final : public api::AsyncTask { }; Result HttpOutgoingBody::append(api::Engine *engine, HttpIncomingBody *other) { - ASSERT_VALID(); + handle_state_->assert_valid(); engine->queue_async_task(new BodyAppendTask(other, this)); return {}; } Result HttpOutgoingBody::close() { - ASSERT_VALID(); + handle_state_->assert_valid(); auto state = static_cast(handle_state_.get()); // A blocking flush is required here to ensure that all buffered contents are @@ -736,8 +757,9 @@ wasi_http_0_2_0_types_method_t http_method_to_host(string_view method_str) { HttpOutgoingRequest::HttpOutgoingRequest(std::unique_ptr state) { this->handle_state_ = std::move(state); } +static Resource::HandleNS OUT_REQUEST_HANDLE_NS = Resource::next_handle_ns("HttpOutgoingRequest"); HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional url_str, - HttpHeadersReadOnly *headers) { + std::unique_ptr headers) { bindings_string_t path_with_query; wasi_http_0_2_0_types_scheme_t scheme; bindings_string_t authority; @@ -771,7 +793,6 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< } Handle headers_handle = headers->handle_state_->take(); - DBG("HH: %d\n", headers_handle); auto handle = wasi_http_0_2_0_types_constructor_outgoing_request({headers_handle}); { @@ -792,16 +813,14 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< maybe_path_with_query); } - auto *state = new HandleState(handle.__handle); + auto *state = new HandleState(handle.__handle, OUT_REQUEST_HANDLE_NS); auto *resp = new HttpOutgoingRequest(std::unique_ptr(state)); - resp->headers_ = headers; - return resp; } Result HttpOutgoingRequest::method() { - ASSERT_VALID(); + handle_state_->assert_valid(); return Result::ok(method_); } @@ -812,7 +831,7 @@ Result HttpOutgoingRequest::headers() { } borrow_outgoing_request_t borrow(handle_state_->get()); auto res = wasi_http_0_2_0_types_method_outgoing_request_headers(borrow); - headers_ = new HttpHeadersReadOnly(std::unique_ptr(new HandleState(res.__handle))); + headers_ = new HttpHeadersReadOnly(std::make_unique(res.__handle, HEADERS_HANDLE_NS)); } return Result::ok(headers_); @@ -820,27 +839,32 @@ Result HttpOutgoingRequest::headers() { Result HttpOutgoingRequest::body() { typedef Result Res; - ASSERT_VALID(); if (!this->body_) { + handle_state_->assert_valid(); outgoing_body_t body; if (!wasi_http_0_2_0_types_method_outgoing_request_body( - wasi_http_0_2_0_types_borrow_outgoing_request({handle_state_->take()}), &body)) { + wasi_http_0_2_0_types_borrow_outgoing_request({handle_state_->get()}), &body)) { return Res::err(154); } - this->body_ = new HttpOutgoingBody(std::unique_ptr(new HandleState(body.__handle))); + body_ = new HttpOutgoingBody(std::unique_ptr(new OutgoingBodyHandleState(body.__handle))); } return Res::ok(body_); } +static Resource::HandleNS FUTURE_INCOMING_RESPONSE_HANDLE_NS = Resource::next_handle_ns("FutureHttpIncomingResponse"); Result HttpOutgoingRequest::send() { - ASSERT_VALID(); + typedef Result Res; + handle_state_->assert_valid(); future_incoming_response_t ret; wasi_http_0_2_0_outgoing_handler_error_code_t err; - wasi_http_0_2_0_outgoing_handler_handle({handle_state_->take()}, nullptr, &ret, &err); - auto res = new FutureHttpIncomingResponse(std::unique_ptr(new HandleState(ret.__handle))); + if (!wasi_http_0_2_0_outgoing_handler_handle({handle_state_->take()}, nullptr, &ret, &err)) { + return Res::err(154); + } + auto res = new FutureHttpIncomingResponse(std::make_unique(ret.__handle, FUTURE_INCOMING_RESPONSE_HANDLE_NS)); return Result::ok(res); } +static Resource::HandleNS IN_BODY_HANDLE_NS = Resource::next_handle_ns("IncomingBody"); class IncomingBodyHandleState final : public HandleState { Handle stream_handle_; PollableHandle pollable_handle_; @@ -849,7 +873,7 @@ class IncomingBodyHandleState final : public HandleState { public: explicit IncomingBodyHandleState(const Handle handle) - : HandleState(handle), pollable_handle_(INVALID_POLLABLE_HANDLE) { + : HandleState(handle, IN_BODY_HANDLE_NS), pollable_handle_(INVALID_POLLABLE_HANDLE) { const borrow_incoming_body_t borrow = {handle}; own_input_stream_t stream{}; if (!wasi_http_0_2_0_types_method_incoming_body_stream(borrow, &stream)) { @@ -859,6 +883,10 @@ class IncomingBodyHandleState final : public HandleState { } }; +void block_on_pollable_handle(PollableHandle handle) { + wasi_io_0_2_0_poll_method_pollable_block({handle}); +} + HttpIncomingBody::HttpIncomingBody(std::unique_ptr state) : Pollable() { handle_state_ = std::move(state); } Resource::~Resource() { @@ -867,6 +895,12 @@ Resource::~Resource() { } } +static Resource::HandleNS HANDLE_NS = 0; +Resource::HandleNS Resource::next_handle_ns(const char* ns_name) { + // DBG("Creating handle namespace %d with name %s\n", HANDLE_NS, ns_name); + return HANDLE_NS++; +} + bool Resource::valid() const { return this->handle_state_ != nullptr && this->handle_state_->valid(); } @@ -892,8 +926,8 @@ Result HttpIncomingBody::read(uint32_t chunk_size) Result HttpIncomingBody::close() { return {}; } Result HttpIncomingBody::subscribe() { - auto borrow = borrow_input_stream_t( - {static_cast(handle_state_.get())->stream_handle_}); + auto state = static_cast(handle_state_.get()); + auto borrow = borrow_input_stream_t({state->stream_handle_}); auto pollable = wasi_io_0_2_0_streams_method_input_stream_subscribe(borrow); return Result::ok(pollable.__handle); } @@ -910,6 +944,8 @@ FutureHttpIncomingResponse::FutureHttpIncomingResponse(std::unique_ptr> FutureHttpIncomingResponse::maybe_response() { typedef Result> Res; wasi_http_0_2_0_types_result_result_own_incoming_response_error_code_void_t res; @@ -926,7 +962,7 @@ Result> FutureHttpIncomingResponse::maybe_respo return Res::err(154); } - return Res::ok(new HttpIncomingResponse(std::unique_ptr(new HandleState(val.ok.__handle)))); + return Res::ok(new HttpIncomingResponse(std::make_unique(val.ok.__handle, IN_RESPONSE_HANDLE_NS))); } Result FutureHttpIncomingResponse::subscribe() { @@ -964,7 +1000,7 @@ Result HttpIncomingResponse::headers() { } auto res = wasi_http_0_2_0_types_method_incoming_response_headers( wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->get()})); - headers_ = new HttpHeadersReadOnly(std::unique_ptr(new HandleState(res.__handle))); + headers_ = new HttpHeadersReadOnly(std::make_unique(res.__handle, HEADERS_HANDLE_NS)); } return Result::ok(headers_); @@ -980,33 +1016,29 @@ Result HttpIncomingResponse::body() { wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->get()}), &body)) { return Result::err(154); } - body_ = new HttpIncomingBody(std::unique_ptr(new HandleState(body.__handle))); + body_ = new HttpIncomingBody(std::unique_ptr(new IncomingBodyHandleState(body.__handle))); } return Result::ok(body_); } HttpOutgoingResponse::HttpOutgoingResponse(std::unique_ptr state) { this->handle_state_ = std::move(state); } -// TODO: change this to take a unique_ptr to prevent reuse after consumption -HttpOutgoingResponse *HttpOutgoingResponse::make(const uint16_t status, HttpHeaders *headers) { +static Resource::HandleNS OUT_RESPONSE_HANDLE_NS = Resource::next_handle_ns("HttpOutgoingResponse"); +HttpOutgoingResponse *HttpOutgoingResponse::make(const uint16_t status, unique_ptr headers) { wasi_http_0_2_0_types_own_headers_t owned{headers->handle_state_->take()}; auto handle = wasi_http_0_2_0_types_constructor_outgoing_response(owned); auto borrow = wasi_http_0_2_0_types_borrow_outgoing_response(handle); - auto *state = new HandleState(handle.__handle); + auto *state = new HandleState(handle.__handle, OUT_RESPONSE_HANDLE_NS); auto *resp = new HttpOutgoingResponse(std::unique_ptr(state)); // Set the status if (status != 200) { - // TODO: handle success result - wasi_http_0_2_0_types_method_outgoing_response_set_status_code(borrow, status); + // The DOM implementation is expected to have validated the status code already. + MOZ_RELEASE_ASSERT(wasi_http_0_2_0_types_method_outgoing_response_set_status_code(borrow, status)); } - // TODO: ensure that the passed-in headers aren't used anywhere anymore. - resp->status_ = status; - resp->headers_ = headers; - return resp; } @@ -1017,7 +1049,7 @@ Result HttpOutgoingResponse::headers() { } borrow_outgoing_response_t borrow(handle_state_->get()); auto res = wasi_http_0_2_0_types_method_outgoing_response_headers(borrow); - headers_ = new HttpHeadersReadOnly(std::unique_ptr(new HandleState(res.__handle))); + headers_ = new HttpHeadersReadOnly(std::make_unique(res.__handle, HEADERS_HANDLE_NS)); } return Result::ok(headers_); @@ -1025,14 +1057,14 @@ Result HttpOutgoingResponse::headers() { Result HttpOutgoingResponse::body() { typedef Result Res; - ASSERT_VALID(); if (!this->body_) { + handle_state_->assert_valid(); outgoing_body_t body; if (!wasi_http_0_2_0_types_method_outgoing_response_body( wasi_http_0_2_0_types_borrow_outgoing_response({handle_state_->get()}), &body)) { return Res::err(154); } - this->body_ = new HttpOutgoingBody(std::unique_ptr(new OutgoingBodyHandleState(body.__handle))); + body_ = new HttpOutgoingBody(std::unique_ptr(new OutgoingBodyHandleState(body.__handle))); } return Res::ok(this->body_); } @@ -1067,7 +1099,7 @@ Result HttpIncomingRequest::headers() { } borrow_incoming_request_t borrow(handle_state_->get()); auto res = wasi_http_0_2_0_types_method_incoming_request_headers(borrow); - headers_ = new HttpHeadersReadOnly(std::unique_ptr(new HandleState(res.__handle))); + headers_ = new HttpHeadersReadOnly(std::make_unique(res.__handle, HEADERS_HANDLE_NS)); } return Result::ok(headers_); @@ -1099,9 +1131,6 @@ void host_api::HttpIncomingRequest::set_handler(RequestHandler handler) { } host_api::Result host_api::HttpOutgoingResponse::send() { - // Drop the headers that we eagerly grab in the factory function - wasi_http_0_2_0_types_fields_drop_own({this->headers_->handle_state_->take()}); - wasi_http_0_2_0_types_result_own_outgoing_response_error_code_t result; result.is_err = false; @@ -1112,10 +1141,13 @@ host_api::Result host_api::HttpOutgoingResponse::send() { return {}; } +static host_api::Resource::HandleNS RESPONSE_OUTPARAM_HANDLE_NS = + host_api::Resource::next_handle_ns("ResponseOutParam"); +static host_api::Resource::HandleNS IN_REQUEST_HANDLE_NS = host_api::Resource::next_handle_ns("HttpIncomingRequest"); void exports_wasi_http_incoming_handler(exports_wasi_http_incoming_request request_handle, -exports_wasi_http_response_outparam response_out) { - RESPONSE_OUT = new host_api::HandleState(response_out.__handle); - auto *request = new host_api::HttpIncomingRequest(std::unique_ptr(new host_api::HandleState(request_handle.__handle))); + exports_wasi_http_response_outparam response_out) { + RESPONSE_OUT = new host_api::HandleState(response_out.__handle, RESPONSE_OUTPARAM_HANDLE_NS); + auto *request = new host_api::HttpIncomingRequest(std::make_unique(request_handle.__handle, IN_REQUEST_HANDLE_NS)); auto res = REQUEST_HANDLER(request); MOZ_RELEASE_ASSERT(res); MOZ_RELEASE_ASSERT(!RESPONSE_OUT->valid()); diff --git a/include/host_api.h b/include/host_api.h index 83f4d1f2..5375756b 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -209,11 +209,12 @@ class Resource { protected: std::unique_ptr handle_state_ = nullptr; - // explicit Resource(HandleState *handle_state); - public: virtual ~Resource(); + typedef uint8_t HandleNS; + static HandleNS next_handle_ns(const char* ns_name); + /// Returns true if this resource handle has been initialized and is still valid. bool valid() const; }; @@ -229,6 +230,8 @@ class Pollable : public Resource { virtual void unsubscribe() = 0; }; +void block_on_pollable_handle(PollableHandle handle); + class HttpIncomingBody final : public Pollable { public: HttpIncomingBody() = delete; @@ -352,7 +355,7 @@ class HttpHeaders final : public HttpHeadersReadOnly { HttpHeaders(); explicit HttpHeaders(const HttpHeadersReadOnly &headers); - static Result FromEntries(const vector> &entries); + static Result FromEntries(vector>& entries); bool is_writable() override { return true; }; HttpHeaders* as_writable() override { @@ -435,8 +438,8 @@ class HttpOutgoingRequest final : public HttpRequest, public HttpOutgoingBodyOwn public: HttpOutgoingRequest() = delete; - static HttpOutgoingRequest *make(string_view method, optional url, - HttpHeadersReadOnly *headers); + static HttpOutgoingRequest *make(string_view method_str, optional url_str, + std::unique_ptr headers); bool is_incoming() override { return false; } bool is_request() override { return true; } @@ -476,7 +479,7 @@ class HttpOutgoingResponse final : public HttpResponse, public HttpOutgoingBodyO public: HttpOutgoingResponse() = delete; - static HttpOutgoingResponse *make(uint16_t status, HttpHeaders *headers); + static HttpOutgoingResponse *make(const uint16_t status, unique_ptr headers); bool is_incoming() override { return false; } bool is_request() override { return false; } From 2d68405b11c902f3065f563f703dffe50196fbfd Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 03:36:20 -0700 Subject: [PATCH 35/52] fix: release build on fetch rework (#54) fix: release build --- host-apis/wasi-0.2.0/host_api.cpp | 14 +++++++++++++- include/host_api.h | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 1322fed3..3d81ff74 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -1,6 +1,7 @@ #include "host_api.h" #include "bindings/bindings.h" +#include #include #include #ifdef DEBUG @@ -108,18 +109,29 @@ class host_api::HandleState { Handle take() { auto handle = get(); // DBG("Consuming handle %d,%d\n", handle_ns_, handle); +#ifdef DEBUG auto ns_handle = to_namespaced(handle, handle_ns_); used_handles.erase(ns_handle); poisoned_ = true; +#endif return handle; } - bool valid() const { return handle_ != UNINITIALIZED_HANDLE && !poisoned_; } + bool valid() const { +#ifdef DEBUG + if (posoned_) return false; +#endif + return handle_ != UNINITIALIZED_HANDLE; + } bool initialized() const { return handle_ != UNINITIALIZED_HANDLE;} +#ifdef DEBUG bool poisoned() const { return poisoned_; } +#endif }; +#ifdef DEBUG std::set host_api::HandleState::used_handles = std::set(); +#endif namespace { diff --git a/include/host_api.h b/include/host_api.h index 5375756b..85e8876b 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -335,6 +335,7 @@ class HttpHeadersReadOnly : public Resource { virtual bool is_writable() { return false; }; virtual HttpHeaders* as_writable() { MOZ_ASSERT_UNREACHABLE(); + return nullptr; }; Result>> entries() const; From 302730541124ad0ad9fd3a69e19e0eda0476bc01 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 15:38:45 -0700 Subject: [PATCH 36/52] feat: single-tick non-tracking event loop runner --- .../wasi-0.2.0-rc-2023-10-18/host_api.cpp | 17 ++++++++++ .../wasi-0.2.0-rc-2023-12-05/host_api.cpp | 14 ++++++++ host-apis/wasi-0.2.0/host_api.cpp | 13 +++++++ include/extension-api.h | 5 +++ runtime/event_loop.cpp | 34 ++++++++++++------- 5 files changed, 70 insertions(+), 13 deletions(-) diff --git a/host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp b/host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp index 732525fb..86e2d12a 100644 --- a/host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp +++ b/host-apis/wasi-0.2.0-rc-2023-10-18/host_api.cpp @@ -112,6 +112,23 @@ size_t api::AsyncTask::select(std::vector *tasks) { return ready_index; } +std::optional api::AsyncTask::ready(std::vector *tasks) { + auto count = tasks->size(); + vector> handles; + for (const auto task : *tasks) { + handles.emplace_back(task->id()); + } + auto list = list_borrow_pollable_t{ + reinterpret_cast::borrow *>(handles.data()), count}; + bindings_list_u32_t result{nullptr, 0}; + wasi_io_0_2_0_rc_2023_10_18_poll_poll_list(&list, &result); + MOZ_ASSERT(result.len > 0); + const auto ready_index = result.ptr[0]; + free(result.ptr); + + return ready_index; +} + namespace host_api { HostString::HostString(const char *c_str) { diff --git a/host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp b/host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp index 263794ed..f125b29b 100644 --- a/host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp +++ b/host-apis/wasi-0.2.0-rc-2023-12-05/host_api.cpp @@ -116,6 +116,20 @@ size_t api::AsyncTask::select(std::vector *tasks) { return ready_index; } +std::optional api::AsyncTask::ready(std::vector *tasks) { + auto count = tasks->size(); + vector> handles; + auto list = list_borrow_pollable_t{ + reinterpret_cast::borrow *>(handles.data()), count}; + bindings_list_u32_t result{nullptr, 0}; + wasi_io_0_2_0_rc_2023_10_18_poll_poll_list(&list, &result); + MOZ_ASSERT(result.len > 0); + const auto ready_index = result.ptr[0]; + free(result.ptr); + + return ready_index; +} + namespace host_api { HostString::HostString(const char *c_str) { diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 39b4f61a..d84b629f 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -113,6 +113,19 @@ size_t api::AsyncTask::select(std::vector *tasks) { return ready_index; } +std::optional api::AsyncTask::ready(std::vector *tasks) { + auto count = tasks->size(); + vector> handles; + for (size_t idx = 0; idx < count; ++idx) { + auto task = tasks->at(idx); + Borrow poll = (task->id()); + if (wasi_io_0_2_0_poll_method_pollable_ready(poll)) { + return idx; + } + } + return std::nullopt; +} + namespace host_api { HostString::HostString(const char *c_str) { diff --git a/include/extension-api.h b/include/extension-api.h index f6558b4b..c2df551b 100644 --- a/include/extension-api.h +++ b/include/extension-api.h @@ -133,6 +133,11 @@ class AsyncTask { * Select for the next available ready task, providing the oldest ready first. */ static size_t select(std::vector *handles); + + /** + * Non-blocking check for a ready task, providing the oldest ready first, if any. + */ + static std::optional ready(std::vector *handles); }; } // namespace api diff --git a/runtime/event_loop.cpp b/runtime/event_loop.cpp index ee8637e8..e31f4acd 100644 --- a/runtime/event_loop.cpp +++ b/runtime/event_loop.cpp @@ -67,31 +67,39 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { exit_event_loop(); return false; } - // if there is no interest in the event loop at all, just run one tick - if (interest_complete()) { - exit_event_loop(); - return true; - } const auto tasks = &queue.get().tasks; size_t tasks_size = tasks->size(); + if (tasks_size == 0) { exit_event_loop(); - MOZ_ASSERT(!interest_complete()); + if (interest_complete()) { + return true; + } fprintf(stderr, "event loop error - both task and job queues are empty, but expected " "operations did not resolve"); return false; } + std::optional task_idx; + // Select the next task to run according to event-loop semantics of oldest-first. - size_t task_idx = api::AsyncTask::select(tasks); + if (interest_complete()) { + // Perform a non-blocking select in the case of there being no event loop interest + // (we are thus only performing a "single tick", but must still progress work that is ready) + task_idx = api::AsyncTask::ready(tasks); + } else { + task_idx = std::optional{api::AsyncTask::select(tasks)}; + } - auto task = tasks->at(task_idx); - bool success = task->run(engine); - tasks->erase(tasks->begin() + task_idx); - if (!success) { - exit_event_loop(); - return false; + if (task_idx.has_value()) { + auto task = tasks->at(task_idx.value()); + bool success = task->run(engine); + tasks->erase(tasks->begin() + task_idx.value()); + if (!success) { + exit_event_loop(); + return false; + } } } } From 2e23c54f2a9a51cd846b4007fa9eb31bb04d4e10 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 15:43:31 -0700 Subject: [PATCH 37/52] fixup --- runtime/event_loop.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/runtime/event_loop.cpp b/runtime/event_loop.cpp index e31f4acd..691e5a26 100644 --- a/runtime/event_loop.cpp +++ b/runtime/event_loop.cpp @@ -81,25 +81,28 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { return false; } - std::optional task_idx; + size_t task_idx; // Select the next task to run according to event-loop semantics of oldest-first. if (interest_complete()) { // Perform a non-blocking select in the case of there being no event loop interest // (we are thus only performing a "single tick", but must still progress work that is ready) - task_idx = api::AsyncTask::ready(tasks); + std::optional maybe_task_idx = api::AsyncTask::ready(tasks); + // nothing ready -> single tick has completed + if (!maybe_task_idx.has_value()) { + return true; + } + task_idx = maybe_task_idx.value(); } else { - task_idx = std::optional{api::AsyncTask::select(tasks)}; + task_idx = api::AsyncTask::select(tasks); } - if (task_idx.has_value()) { - auto task = tasks->at(task_idx.value()); - bool success = task->run(engine); - tasks->erase(tasks->begin() + task_idx.value()); - if (!success) { - exit_event_loop(); - return false; - } + auto task = tasks->at(task_idx); + bool success = task->run(engine); + tasks->erase(tasks->begin() + task_idx); + if (!success) { + exit_event_loop(); + return false; } } } From 5bdb946f137e946206fb865cdba92b8fb0654416 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 15:52:24 -0700 Subject: [PATCH 38/52] fixup --- runtime/event_loop.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/event_loop.cpp b/runtime/event_loop.cpp index 691e5a26..ac693f65 100644 --- a/runtime/event_loop.cpp +++ b/runtime/event_loop.cpp @@ -90,6 +90,7 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { std::optional maybe_task_idx = api::AsyncTask::ready(tasks); // nothing ready -> single tick has completed if (!maybe_task_idx.has_value()) { + exit_event_loop(); return true; } task_idx = maybe_task_idx.value(); From 55566e9cef772ec404885bbf2e2cde53f05ec4d1 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 16:52:29 -0700 Subject: [PATCH 39/52] fix event loop interest handling --- builtins/web/fetch/fetch_event.cpp | 15 +++++++++++---- builtins/web/fetch/request-response.cpp | 8 +++++--- runtime/event_loop.cpp | 15 +++++++++++---- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/builtins/web/fetch/fetch_event.cpp b/builtins/web/fetch/fetch_event.cpp index 87724642..46af6b5a 100644 --- a/builtins/web/fetch/fetch_event.cpp +++ b/builtins/web/fetch/fetch_event.cpp @@ -29,6 +29,9 @@ void inc_pending_promise_count(JSObject *self) { auto count = JS::GetReservedSlot(self, static_cast(FetchEvent::Slots::PendingPromiseCount)) .toInt32(); + if (count == 0) { + ENGINE->incr_event_loop_interest(); + } count++; MOZ_ASSERT(count > 0); JS::SetReservedSlot(self, static_cast(FetchEvent::Slots::PendingPromiseCount), @@ -42,8 +45,9 @@ void dec_pending_promise_count(JSObject *self) { .toInt32(); MOZ_ASSERT(count > 0); count--; - if (count == 0) + if (count == 0) { ENGINE->decr_event_loop_interest(); + } JS::SetReservedSlot(self, static_cast(FetchEvent::Slots::PendingPromiseCount), JS::Int32Value(count)); } @@ -187,6 +191,10 @@ bool start_response(JSContext *cx, JS::HandleObject response_obj, bool streaming STREAMING_BODY = response->body().unwrap(); } + if (streaming) { + ENGINE->incr_event_loop_interest(); + } + return send_response(response, FetchEvent::instance(), streaming ? FetchEvent::State::responseStreaming : FetchEvent::State::responseDone); @@ -424,11 +432,13 @@ bool FetchEvent::is_dispatching(JSObject *self) { void FetchEvent::start_dispatching(JSObject *self) { MOZ_ASSERT(!is_dispatching(self)); + ENGINE->incr_event_loop_interest(); JS::SetReservedSlot(self, static_cast(Slots::Dispatch), JS::TrueValue()); } void FetchEvent::stop_dispatching(JSObject *self) { MOZ_ASSERT(is_dispatching(self)); + ENGINE->decr_event_loop_interest(); JS::SetReservedSlot(self, static_cast(Slots::Dispatch), JS::FalseValue()); } @@ -594,9 +604,6 @@ void exports_wasi_http_incoming_handler(exports_wasi_http_incoming_request reque dispatch_fetch_event(fetch_event, &total_compute); - // track fetch event interest, which when decremented ends the event loop - ENGINE->incr_event_loop_interest(); - bool success = ENGINE->run_event_loop(); if (JS_IsExceptionPending(ENGINE->cx())) { diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 36c66dfa..8e968689 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -938,6 +938,7 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject // `responseDone`. // TODO(TS): factor this out to remove dependency on fetch-event.h if (Response::is_instance(body_owner)) { + ENGINE->decr_event_loop_interest(); fetch_event::FetchEvent::set_state(fetch_event::FetchEvent::instance(), fetch_event::FetchEvent::State::responseDone); } @@ -1025,9 +1026,10 @@ bool RequestOrResponse::body_reader_catch_handler(JSContext *cx, JS::HandleObjec // `responseDone` is the right state: `respondedWithError` is for when sending // a response at all failed.) // TODO(TS): investigate why this is disabled. - // if (Response::is_instance(body_owner)) { - // FetchEvent::set_state(FetchEvent::instance(), FetchEvent::State::responseDone); - // } + if (Response::is_instance(body_owner)) { + ENGINE->decr_event_loop_interest(); + // FetchEvent::set_state(FetchEvent::instance(), FetchEvent::State::responseDone); + } return true; } diff --git a/runtime/event_loop.cpp b/runtime/event_loop.cpp index ac693f65..8bd419e5 100644 --- a/runtime/event_loop.cpp +++ b/runtime/event_loop.cpp @@ -59,10 +59,10 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { queue.get().event_loop_running = true; JSContext *cx = engine->cx(); - while (true) { - // Run a microtask checkpoint - js::RunJobs(cx); + // Run a microtask checkpoint + js::RunJobs(cx); + while (true) { if (JS_IsExceptionPending(cx)) { exit_event_loop(); return false; @@ -88,7 +88,6 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { // Perform a non-blocking select in the case of there being no event loop interest // (we are thus only performing a "single tick", but must still progress work that is ready) std::optional maybe_task_idx = api::AsyncTask::ready(tasks); - // nothing ready -> single tick has completed if (!maybe_task_idx.has_value()) { exit_event_loop(); return true; @@ -105,6 +104,14 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { exit_event_loop(); return false; } + + // Run a single microtask checkpoint after each async task processing + // to complete "one tick" + js::RunJobs(cx); + + if (interest_complete()) { + return true; + } } } From 4b3f9f5b21b6cede79e33c86fa1411f3d75ce1a5 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 16:55:00 -0700 Subject: [PATCH 40/52] simplify single tick --- runtime/event_loop.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/runtime/event_loop.cpp b/runtime/event_loop.cpp index 8bd419e5..acea5784 100644 --- a/runtime/event_loop.cpp +++ b/runtime/event_loop.cpp @@ -59,10 +59,10 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { queue.get().event_loop_running = true; JSContext *cx = engine->cx(); - // Run a microtask checkpoint - js::RunJobs(cx); - while (true) { + // Run a microtask checkpoint + js::RunJobs(cx); + if (JS_IsExceptionPending(cx)) { exit_event_loop(); return false; @@ -105,10 +105,6 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { return false; } - // Run a single microtask checkpoint after each async task processing - // to complete "one tick" - js::RunJobs(cx); - if (interest_complete()) { return true; } From 1d17acee10d04abf4784b873289962ec17edeef4 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 17:07:24 -0700 Subject: [PATCH 41/52] simpler loop --- runtime/event_loop.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/runtime/event_loop.cpp b/runtime/event_loop.cpp index acea5784..3b3022a0 100644 --- a/runtime/event_loop.cpp +++ b/runtime/event_loop.cpp @@ -59,7 +59,7 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { queue.get().event_loop_running = true; JSContext *cx = engine->cx(); - while (true) { + do { // Run a microtask checkpoint js::RunJobs(cx); @@ -104,11 +104,7 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { exit_event_loop(); return false; } - - if (interest_complete()) { - return true; - } - } + } while (!interest_complete()); } void EventLoop::init(JSContext *cx) { queue.init(cx); } From 6a8ae9e9d8468f0261c0b32514bb7e400d50aedd Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 29 May 2024 17:13:37 -0700 Subject: [PATCH 42/52] fixup return paths --- runtime/event_loop.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/runtime/event_loop.cpp b/runtime/event_loop.cpp index 3b3022a0..4ea4e204 100644 --- a/runtime/event_loop.cpp +++ b/runtime/event_loop.cpp @@ -72,10 +72,10 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { size_t tasks_size = tasks->size(); if (tasks_size == 0) { - exit_event_loop(); if (interest_complete()) { - return true; + break; } + exit_event_loop(); fprintf(stderr, "event loop error - both task and job queues are empty, but expected " "operations did not resolve"); return false; @@ -89,8 +89,7 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { // (we are thus only performing a "single tick", but must still progress work that is ready) std::optional maybe_task_idx = api::AsyncTask::ready(tasks); if (!maybe_task_idx.has_value()) { - exit_event_loop(); - return true; + break; } task_idx = maybe_task_idx.value(); } else { @@ -105,6 +104,9 @@ bool EventLoop::run_event_loop(api::Engine *engine, double total_compute) { return false; } } while (!interest_complete()); + + exit_event_loop(); + return true; } void EventLoop::init(JSContext *cx) { queue.init(cx); } From 2bad18a88af4ab17d2474e50d7fcaa73d528af33 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sat, 1 Jun 2024 21:57:48 +0200 Subject: [PATCH 43/52] Use a templated WASIHandle class to represent handles for WASI 0.2.0 This changes the implementation of the handle abstraction to make it more robust, and cleans it up at least somewhat. --- host-apis/wasi-0.2.0/host_api.cpp | 489 +++++++++++++++--------------- include/host_api.h | 6 +- 2 files changed, 249 insertions(+), 246 deletions(-) diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 3d81ff74..bf24858f 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -1,8 +1,6 @@ #include "host_api.h" #include "bindings/bindings.h" -#include -#include #include #ifdef DEBUG #include @@ -19,12 +17,6 @@ using std::vector; // pointer. static_assert(sizeof(uint32_t) == sizeof(void *)); -typedef wasi_http_0_2_0_types_own_incoming_request_t incoming_request_t; -typedef wasi_http_0_2_0_types_borrow_incoming_request_t borrow_incoming_request_t; -typedef wasi_http_0_2_0_types_own_incoming_response_t incoming_response_t; -typedef wasi_http_0_2_0_types_borrow_outgoing_request_t borrow_outgoing_request_t; -typedef wasi_http_0_2_0_types_borrow_outgoing_response_t borrow_outgoing_response_t; - typedef wasi_http_0_2_0_types_own_future_incoming_response_t future_incoming_response_t; typedef wasi_http_0_2_0_types_borrow_future_incoming_response_t borrow_future_incoming_response_t; @@ -34,173 +26,233 @@ typedef wasi_http_0_2_0_types_own_outgoing_body_t outgoing_body_t; using field_key = wasi_http_0_2_0_types_field_key_t; using field_value = wasi_http_0_2_0_types_field_value_t; -typedef wasi_http_0_2_0_types_borrow_incoming_body_t borrow_incoming_body_t; -typedef wasi_http_0_2_0_types_borrow_outgoing_body_t borrow_outgoing_body_t; - typedef wasi_io_0_2_0_poll_own_pollable_t own_pollable_t; typedef wasi_io_0_2_0_poll_borrow_pollable_t borrow_pollable_t; typedef wasi_io_0_2_0_poll_list_borrow_pollable_t list_borrow_pollable_t; -typedef wasi_io_0_2_0_streams_own_input_stream_t own_input_stream_t; -typedef wasi_io_0_2_0_streams_borrow_input_stream_t borrow_input_stream_t; - -typedef wasi_io_0_2_0_streams_own_output_stream_t own_output_stream_t; +#ifdef LOG_HANDLE_OPS +#define LOG_HANDLE_OP(...) fprintf(stderr, "%s", __PRETTY_FUNCTION__); fprintf(stderr, __VA_ARGS__) +#else +#define LOG_HANDLE_OP(...) +#endif /// The type of handles used by the host interface. typedef int32_t Handle; -constexpr Handle UNINITIALIZED_HANDLE = -1; +constexpr Handle POISONED_HANDLE = -1; class host_api::HandleState { - Handle handle_ = UNINITIALIZED_HANDLE; +protected: + HandleState() = default; -#ifdef DEBUG - uint8_t handle_ns_; - bool poisoned_ = false; +public: + virtual ~HandleState() = default; + virtual bool valid() const = 0; +}; + +template struct HandleOps {}; - static std::set used_handles; +template +class WASIHandle : public host_api::HandleState { +#ifdef DEBUG + static inline auto used_handles = std::set(); #endif - static int32_t to_namespaced(Handle handle, uint8_t handle_ns) { - return (handle_ns << 24) | handle; - } +protected: + Handle handle_; +#ifdef DEBUG + bool owned_; +#endif public: - HandleState() = delete; - HandleState(Handle handle, uint8_t handle_ns) : handle_{handle} { + using Borrowed = typename HandleOps::borrowed; + + explicit WASIHandle(typename HandleOps::owned handle) : handle_{handle.__handle} { + LOG_HANDLE_OP("Creating owned handle %d\n", handle.__handle); #ifdef DEBUG - handle_ns_ = handle_ns; - // DBG("Adding handle %d,%d\n", handle_ns_, handle); - // TODO: remove this, and replace all this with better things - MOZ_ASSERT(handle < 500000); - auto ns_handle = to_namespaced(handle, handle_ns); - MOZ_ASSERT(handle > UNINITIALIZED_HANDLE); - MOZ_ASSERT(!used_handles.contains(ns_handle)); - used_handles.insert(ns_handle); + owned_ = true; + MOZ_ASSERT(!used_handles.contains(handle.__handle)); + used_handles.insert(handle.__handle); #endif } - virtual ~HandleState() { - // TODO: support dropping handles for all handle types. Probably using a template class. - // DBG("Removing handle %d,%d\n", handle_ns_, handle_); + + explicit WASIHandle(typename HandleOps::borrowed handle) : handle_{handle.__handle} { + LOG_HANDLE_OP("Creating borrowed handle %d\n", handle.__handle); #ifdef DEBUG - if (!poisoned_) { - auto ns_handle = to_namespaced(handle_, handle_ns_); - MOZ_ASSERT(used_handles.contains(ns_handle)); - used_handles.erase(ns_handle); - } + owned_ = false; + MOZ_ASSERT(!used_handles.contains(handle.__handle)); + used_handles.insert(handle.__handle); #endif } - void assert_valid() const { + ~WASIHandle() override { #ifdef DEBUG - MOZ_ASSERT(initialized(), "Handle not initialized"); - if (poisoned()) { - fprintf(stderr, "Handle %d,%d is poisened", handle_ns_, handle_); fflush(stderr); - MOZ_ASSERT(false); + if (handle_ != POISONED_HANDLE) { + LOG_HANDLE_OP("Deleting (owned? %d) handle %d\n", owned_, handle_); + MOZ_ASSERT(used_handles.contains(handle_)); + used_handles.erase(handle_); } #endif } - Handle get() const { - // DBG("Getting handle %d,%d\n", handle_ns_, handle_); - assert_valid(); - return handle_; + static WASIHandle* cast(HandleState* handle) { + return reinterpret_cast*>(handle); } - Handle take() { - auto handle = get(); - // DBG("Consuming handle %d,%d\n", handle_ns_, handle); -#ifdef DEBUG - auto ns_handle = to_namespaced(handle, handle_ns_); - used_handles.erase(ns_handle); - poisoned_ = true; -#endif - return handle; + typename HandleOps::borrowed borrow(HandleState *handle) { + return cast(handle)->borrow(); } - bool valid() const { -#ifdef DEBUG - if (posoned_) return false; -#endif - return handle_ != UNINITIALIZED_HANDLE; + bool valid() const override { + bool valid = handle_ != POISONED_HANDLE; + MOZ_ASSERT_IF(valid, used_handles.contains(handle_)); + return valid; + } + + typename HandleOps::borrowed borrow() const { + MOZ_ASSERT(valid()); + LOG_HANDLE_OP("borrowing handle %d\n", handle_); + return {handle_}; } - bool initialized() const { return handle_ != UNINITIALIZED_HANDLE;} + + typename HandleOps::owned take() { + MOZ_ASSERT(valid()); + MOZ_ASSERT(owned_); + LOG_HANDLE_OP("taking handle %d\n", handle_); + typename HandleOps::owned handle = { handle_ }; #ifdef DEBUG - bool poisoned() const { return poisoned_; } + used_handles.erase(handle_); #endif + handle_ = POISONED_HANDLE; + return handle; + } }; -#ifdef DEBUG -std::set host_api::HandleState::used_handles = std::set(); -#endif +template +struct Borrow { + static constexpr typename HandleOps::borrowed invalid{std::numeric_limits::max()}; + typename HandleOps::borrowed handle_{invalid}; -namespace { + explicit Borrow(host_api::HandleState *handle) { + handle_ = WASIHandle::cast(handle)->borrow(); + } + + explicit Borrow(typename HandleOps::borrowed handle) { + handle_ = handle; + } -// TODO: merge HandleOps into a WASIHandleState subclass of HandleState, and use that everywhere. -/// This is the type contract for using the Own and Borrow templates. -template struct HandleOps {}; + explicit Borrow(typename HandleOps::owned handle) { + handle_ = {handle.__handle}; + } -/// A convenience wrapper for constructing a borrow. As we only create borrows of things we already -/// own, this wrapper will never explicitly drop borrows. -template class Borrow final { - static constexpr const typename HandleOps::borrow invalid{std::numeric_limits::max()}; - HandleOps::borrow handle{Borrow::invalid}; + operator typename HandleOps::borrowed() const { return handle_; } +}; -public: - Borrow() = default; +template <> struct HandleOps { + using owned = wasi_io_0_2_0_poll_own_pollable_t; + using borrowed = wasi_io_0_2_0_poll_borrow_pollable_t; +}; - // Construct a borrow from an owned handle. - Borrow(HandleOps::own handle) : handle{HandleOps::borrow_owned(handle)} {} +template <> struct HandleOps { + using owned = wasi_http_0_2_0_types_own_headers_t; + using borrowed = wasi_http_0_2_0_types_borrow_fields_t; +}; - // Construct a borrow from a raw `Handle` value. - Borrow(Handle handle) : Borrow{typename HandleOps::own{handle}} { - MOZ_ASSERT(valid()); - } +template <> struct HandleOps { + using owned = wasi_http_0_2_0_types_own_incoming_request_t; + using borrowed = wasi_http_0_2_0_types_borrow_incoming_request_t; +}; - // Convenience wrapper for constructing a borrow of a HandleState. - Borrow(host_api::HandleState *state) : Borrow{typename HandleOps::own{state->get()}} {} +template <> struct HandleOps { + using owned = wasi_http_0_2_0_types_own_outgoing_request_t; + using borrowed = wasi_http_0_2_0_types_borrow_outgoing_request_t; +}; - bool valid() const { return this->handle.__handle != Borrow::invalid.__handle; } +template <> struct HandleOps { + using owned = wasi_http_0_2_0_types_own_future_incoming_response_t; + using borrowed = wasi_http_0_2_0_types_borrow_future_incoming_response_t; +}; - operator bool() const { return this->valid(); } +template <> struct HandleOps { + using owned = wasi_http_0_2_0_types_own_incoming_response_t; + using borrowed = wasi_http_0_2_0_types_borrow_incoming_response_t; +}; - operator typename HandleOps::borrow() const { return this->handle; } +template <> struct HandleOps { + using owned = wasi_http_0_2_0_types_own_outgoing_response_t; + using borrowed = wasi_http_0_2_0_types_borrow_outgoing_response_t; }; -template <> struct HandleOps { - using own = wasi_http_0_2_0_types_own_fields_t; - using borrow = wasi_http_0_2_0_types_borrow_fields_t; +template <> struct HandleOps { + using owned = wasi_http_0_2_0_types_own_incoming_body_t; + using borrowed = wasi_http_0_2_0_types_borrow_incoming_body_t; +}; - static constexpr const auto borrow_owned = wasi_http_0_2_0_types_borrow_fields; +template <> struct HandleOps { + using owned = wasi_http_0_2_0_types_own_outgoing_body_t; + using borrowed = wasi_http_0_2_0_types_borrow_outgoing_body_t; }; struct OutputStream {}; - template <> struct HandleOps { - using own = wasi_io_0_2_0_streams_own_output_stream_t; - using borrow = wasi_io_0_2_0_streams_borrow_output_stream_t; + using owned = wasi_io_0_2_0_streams_own_output_stream_t; + using borrowed = wasi_io_0_2_0_streams_borrow_output_stream_t; +}; - static constexpr const auto borrow_owned = wasi_io_0_2_0_streams_borrow_output_stream; +struct InputStream {}; +template <> struct HandleOps { + using owned = wasi_io_0_2_0_streams_own_input_stream_t; + using borrowed = wasi_io_0_2_0_streams_borrow_input_stream_t; }; -struct Pollable {}; +class IncomingBodyHandle final : public WASIHandle { + HandleOps::owned stream_handle_; + PollableHandle pollable_handle_; + + friend host_api::HttpIncomingBody; -template <> struct HandleOps { - using own = wasi_io_0_2_0_poll_own_pollable_t; - using borrow = wasi_io_0_2_0_poll_borrow_pollable_t; +public: + explicit IncomingBodyHandle(HandleOps::owned handle) + : WASIHandle(handle), pollable_handle_(INVALID_POLLABLE_HANDLE) { + HandleOps::owned stream{}; + if (!wasi_http_0_2_0_types_method_incoming_body_stream(borrow(), &stream)) { + MOZ_ASSERT_UNREACHABLE("Getting a body's stream should never fail"); + } + stream_handle_ = stream; + } - static constexpr const auto borrow_owned = wasi_io_0_2_0_poll_borrow_pollable; + static IncomingBodyHandle* cast(HandleState* handle) { + return reinterpret_cast(handle); + } }; -} // namespace +class OutgoingBodyHandle final : public WASIHandle { + HandleOps::owned stream_handle_; + PollableHandle pollable_handle_; + + friend host_api::HttpOutgoingBody; + +public: + explicit OutgoingBodyHandle(HandleOps::owned handle) + : WASIHandle(handle), pollable_handle_(INVALID_POLLABLE_HANDLE) { + HandleOps::owned stream{}; + if (!wasi_http_0_2_0_types_method_outgoing_body_write(borrow(), &stream)) { + MOZ_ASSERT_UNREACHABLE("Getting a body's stream should never fail"); + } + stream_handle_ = stream; + } + + static OutgoingBodyHandle* cast(HandleState* handle) { + return reinterpret_cast(handle); + } +}; -size_t api::AsyncTask::select(std::vector *tasks) { +size_t api::AsyncTask::select(std::vector *tasks) { auto count = tasks->size(); - vector> handles; + vector::Borrowed> handles; for (const auto task : *tasks) { handles.emplace_back(task->id()); } - auto list = list_borrow_pollable_t{ - reinterpret_cast::borrow *>(handles.data()), count}; + auto list = list_borrow_pollable_t{ handles.data(), count}; wasi_io_0_2_0_poll_list_u32_t result{nullptr, 0}; wasi_io_0_2_0_poll_poll(&list, &result); MOZ_ASSERT(result.len > 0); @@ -236,7 +288,7 @@ template T from_string_view(std::string_view str) { auto string_view_to_world_string = from_string_view; -HostString scheme_to_string(const wasi_http_0_2_0_types_scheme_t scheme) { +HostString scheme_to_string(const wasi_http_0_2_0_types_scheme_t &scheme) { if (scheme.tag == WASI_HTTP_0_2_0_TYPES_SCHEME_HTTP) { return {"http:"}; } @@ -284,8 +336,8 @@ void MonotonicClock::unsubscribe(const int32_t handle_id) { HttpHeaders::HttpHeaders(std::unique_ptr state) : HttpHeadersReadOnly(std::move(state)) {} -static Resource::HandleNS HEADERS_HANDLE_NS = Resource::next_handle_ns("HttpHeaders"); -HttpHeaders::HttpHeaders() : HttpHeadersReadOnly(std::make_unique(wasi_http_0_2_0_types_constructor_fields().__handle, HEADERS_HANDLE_NS)) { +HttpHeaders::HttpHeaders() { + handle_state_ = std::make_unique>(wasi_http_0_2_0_types_constructor_fields()); } Result HttpHeaders::FromEntries(vector>& entries) { @@ -305,14 +357,13 @@ Result HttpHeaders::FromEntries(vector::err(154); } - return Result::ok(new HttpHeaders(std::make_unique(ret.__handle, - HEADERS_HANDLE_NS))); + return Result::ok(new HttpHeaders(std::unique_ptr(new WASIHandle(ret)))); } HttpHeaders::HttpHeaders(const HttpHeadersReadOnly &headers) : HttpHeadersReadOnly(nullptr) { Borrow borrow(headers.handle_state_.get()); auto handle = wasi_http_0_2_0_types_method_fields_clone(borrow); - this->handle_state_ = std::unique_ptr(new HandleState(handle.__handle, HEADERS_HANDLE_NS)); + this->handle_state_ = std::unique_ptr(new WASIHandle(handle)); } HttpHeaders *HttpHeadersReadOnly::clone() { @@ -321,7 +372,6 @@ HttpHeaders *HttpHeadersReadOnly::clone() { Result>> HttpHeadersReadOnly::entries() const { Result>> res; - handle_state_->assert_valid(); wasi_http_0_2_0_types_list_tuple2_field_key_field_value_t entries; Borrow borrow(this->handle_state_.get()); @@ -342,13 +392,13 @@ Result>> HttpHeadersReadOnly::entries() con Result> HttpHeadersReadOnly::names() const { Result> res; - handle_state_->assert_valid(); wasi_http_0_2_0_types_list_tuple2_field_key_field_value_t entries; Borrow borrow(this->handle_state_.get()); wasi_http_0_2_0_types_method_fields_entries(borrow, &entries); vector names; + names.reserve(entries.len); for (int i = 0; i < entries.len; i++) { names.emplace_back(bindings_string_to_host_string(entries.ptr[i].f0)); } @@ -361,7 +411,6 @@ Result> HttpHeadersReadOnly::names() const { Result>> HttpHeadersReadOnly::get(string_view name) const { Result>> res; - handle_state_->assert_valid(); wasi_http_0_2_0_types_list_field_value_t values; auto hdr = string_view_to_world_string(name); @@ -370,6 +419,7 @@ Result>> HttpHeadersReadOnly::get(string_view name) if (values.len > 0) { std::vector names; + names.reserve(values.len); for (int i = 0; i < values.len; i++) { names.emplace_back(to_host_string(values.ptr[i])); } @@ -384,15 +434,12 @@ Result>> HttpHeadersReadOnly::get(string_view name) } Result HttpHeadersReadOnly::has(string_view name) const { - handle_state_->assert_valid(); - auto hdr = string_view_to_world_string(name); Borrow borrow(this->handle_state_.get()); return Result::ok(wasi_http_0_2_0_types_method_fields_has(borrow, &hdr)); } Result HttpHeaders::set(string_view name, string_view value) { - handle_state_->assert_valid(); auto hdr = from_string_view(name); auto val = from_string_view(value); wasi_http_0_2_0_types_list_field_value_t host_values{&val, 1}; @@ -407,7 +454,6 @@ Result HttpHeaders::set(string_view name, string_view value) { } Result HttpHeaders::append(string_view name, string_view value) { - handle_state_->assert_valid(); auto hdr = from_string_view(name); auto val = from_string_view(value); Borrow borrow(this->handle_state_.get()); @@ -420,8 +466,9 @@ Result HttpHeaders::append(string_view name, string_view value) { case WASI_HTTP_0_2_0_TYPES_HEADER_ERROR_FORBIDDEN: return Result::err(154); case WASI_HTTP_0_2_0_TYPES_HEADER_ERROR_IMMUTABLE: - fprintf(stderr, "Headers %d should not be immutable", this->handle_state_->get()); - MOZ_ASSERT_UNREACHABLE(); + MOZ_ASSERT_UNREACHABLE("Headers should not be immutable"); + default: + MOZ_ASSERT_UNREACHABLE("Unknown header error type"); } } @@ -429,14 +476,14 @@ Result HttpHeaders::append(string_view name, string_view value) { } Result HttpHeaders::remove(string_view name) { - handle_state_->assert_valid(); auto hdr = string_view_to_world_string(name); Borrow borrow(this->handle_state_.get()); wasi_http_0_2_0_types_header_error_t err; - wasi_http_0_2_0_types_method_fields_delete(borrow, &hdr, &err); - - // TODO: handle `err` + if (!wasi_http_0_2_0_types_method_fields_delete(borrow, &hdr, &err)) { + // TODO: handle `err` + return Result::err(154); + } return {}; } @@ -447,7 +494,7 @@ string_view HttpRequestResponseBase::url() { return string_view(*_url); } - auto borrow = borrow_incoming_request_t{handle_state_->get()}; + Borrow borrow(handle_state_.get()); wasi_http_0_2_0_types_scheme_t scheme; bool success; @@ -479,25 +526,6 @@ bool write_to_outgoing_body(Borrow borrow, const uint8_t *ptr, con return wasi_io_0_2_0_streams_method_output_stream_write(borrow, &list, &err); } -static Resource::HandleNS OUT_BODY_HANDLE_NS = Resource::next_handle_ns("OutgoingBody"); -class OutgoingBodyHandleState final : public HandleState { - Handle stream_handle_; - PollableHandle pollable_handle_; - - friend HttpOutgoingBody; - -public: - explicit OutgoingBodyHandleState(const Handle handle) - : HandleState(handle, OUT_BODY_HANDLE_NS), pollable_handle_(INVALID_POLLABLE_HANDLE) { - const borrow_outgoing_body_t borrow = {handle}; - own_output_stream_t stream{}; - if (!wasi_http_0_2_0_types_method_outgoing_body_write(borrow, &stream)) { - MOZ_ASSERT_UNREACHABLE("Getting a body's stream should never fail"); - } - stream_handle_ = stream.__handle; - } -}; - HttpOutgoingBody::HttpOutgoingBody(std::unique_ptr state) : Pollable() { handle_state_ = std::move(state); } @@ -507,7 +535,7 @@ Result HttpOutgoingBody::capacity() { return Result::err(154); } - auto *state = static_cast(this->handle_state_.get()); + auto *state = static_cast(this->handle_state_.get()); Borrow borrow(state->stream_handle_); uint64_t capacity = 0; wasi_io_0_2_0_streams_stream_error_t err; @@ -526,7 +554,7 @@ Result HttpOutgoingBody::write(const uint8_t *bytes, size_t len) { auto capacity = res.unwrap(); auto bytes_to_write = std::min(len, static_cast(capacity)); - auto *state = static_cast(this->handle_state_.get()); + auto *state = static_cast(this->handle_state_.get()); Borrow borrow(state->stream_handle_); if (!write_to_outgoing_body(borrow, bytes, bytes_to_write)) { return Result::err(154); @@ -538,10 +566,10 @@ Result HttpOutgoingBody::write(const uint8_t *bytes, size_t len) { Result HttpOutgoingBody::write_all(const uint8_t *bytes, size_t len) { if (!valid()) { // TODO: proper error handling for all 154 error codes. - return Result::err({}); + return Result::err(154); } - auto *state = static_cast(handle_state_.get()); + auto *state = static_cast(handle_state_.get()); Borrow borrow(state->stream_handle_); while (len > 0) { @@ -642,7 +670,7 @@ class BodyAppendTask final : public api::AsyncTask { return true; } - auto offset = 0; + unsigned offset = 0; while (bytes.len - offset > 0) { // TODO: remove double checking of write-readiness // TODO: make this async by storing the remaining chunk in the task and marking it as @@ -694,15 +722,12 @@ class BodyAppendTask final : public api::AsyncTask { }; Result HttpOutgoingBody::append(api::Engine *engine, HttpIncomingBody *other) { - handle_state_->assert_valid(); engine->queue_async_task(new BodyAppendTask(other, this)); return {}; } Result HttpOutgoingBody::close() { - handle_state_->assert_valid(); - - auto state = static_cast(handle_state_.get()); + auto state = static_cast(handle_state_.get()); // A blocking flush is required here to ensure that all buffered contents are // actually written before finishing the body. Borrow borrow{state->stream_handle_}; @@ -728,7 +753,7 @@ Result HttpOutgoingBody::close() { return {}; } Result HttpOutgoingBody::subscribe() { - auto state = static_cast(handle_state_.get()); + auto state = static_cast(handle_state_.get()); if (state->pollable_handle_ == INVALID_POLLABLE_HANDLE) { Borrow borrow(state->stream_handle_); state->pollable_handle_ = wasi_io_0_2_0_streams_method_output_stream_subscribe(borrow).__handle; @@ -737,7 +762,7 @@ Result HttpOutgoingBody::subscribe() { } void HttpOutgoingBody::unsubscribe() { - auto state = static_cast(handle_state_.get()); + auto state = static_cast(handle_state_.get()); if (state->pollable_handle_ == INVALID_POLLABLE_HANDLE) { return; } @@ -769,7 +794,6 @@ wasi_http_0_2_0_types_method_t http_method_to_host(string_view method_str) { HttpOutgoingRequest::HttpOutgoingRequest(std::unique_ptr state) { this->handle_state_ = std::move(state); } -static Resource::HandleNS OUT_REQUEST_HANDLE_NS = Resource::next_handle_ns("HttpOutgoingRequest"); HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional url_str, std::unique_ptr headers) { bindings_string_t path_with_query; @@ -804,9 +828,9 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< maybe_path_with_query = &path_with_query; } - Handle headers_handle = headers->handle_state_->take(); + auto headers_handle = WASIHandle::cast(headers->handle_state_.get())->take(); auto handle = - wasi_http_0_2_0_types_constructor_outgoing_request({headers_handle}); + wasi_http_0_2_0_types_constructor_outgoing_request(headers_handle); { auto borrow = wasi_http_0_2_0_types_borrow_outgoing_request(handle); @@ -825,14 +849,13 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< maybe_path_with_query); } - auto *state = new HandleState(handle.__handle, OUT_REQUEST_HANDLE_NS); + auto *state = new WASIHandle(handle); auto *resp = new HttpOutgoingRequest(std::unique_ptr(state)); return resp; } Result HttpOutgoingRequest::method() { - handle_state_->assert_valid(); return Result::ok(method_); } @@ -841,9 +864,9 @@ Result HttpOutgoingRequest::headers() { if (!valid()) { return Result::err(154); } - borrow_outgoing_request_t borrow(handle_state_->get()); + Borrow borrow(handle_state_.get()); auto res = wasi_http_0_2_0_types_method_outgoing_request_headers(borrow); - headers_ = new HttpHeadersReadOnly(std::make_unique(res.__handle, HEADERS_HANDLE_NS)); + headers_ = new HttpHeadersReadOnly(std::unique_ptr(new WASIHandle(res))); } return Result::ok(headers_); @@ -852,49 +875,28 @@ Result HttpOutgoingRequest::headers() { Result HttpOutgoingRequest::body() { typedef Result Res; if (!this->body_) { - handle_state_->assert_valid(); outgoing_body_t body; - if (!wasi_http_0_2_0_types_method_outgoing_request_body( - wasi_http_0_2_0_types_borrow_outgoing_request({handle_state_->get()}), &body)) { + Borrow borrow(handle_state_.get()); + if (!wasi_http_0_2_0_types_method_outgoing_request_body(borrow, &body)) { return Res::err(154); } - body_ = new HttpOutgoingBody(std::unique_ptr(new OutgoingBodyHandleState(body.__handle))); + body_ = new HttpOutgoingBody(std::unique_ptr(new OutgoingBodyHandle(body))); } return Res::ok(body_); } -static Resource::HandleNS FUTURE_INCOMING_RESPONSE_HANDLE_NS = Resource::next_handle_ns("FutureHttpIncomingResponse"); Result HttpOutgoingRequest::send() { typedef Result Res; - handle_state_->assert_valid(); future_incoming_response_t ret; wasi_http_0_2_0_outgoing_handler_error_code_t err; - if (!wasi_http_0_2_0_outgoing_handler_handle({handle_state_->take()}, nullptr, &ret, &err)) { + auto request_handle = WASIHandle::cast(handle_state_.get())->take(); + if (!wasi_http_0_2_0_outgoing_handler_handle(request_handle, nullptr, &ret, &err)) { return Res::err(154); } - auto res = new FutureHttpIncomingResponse(std::make_unique(ret.__handle, FUTURE_INCOMING_RESPONSE_HANDLE_NS)); + auto res = new FutureHttpIncomingResponse(std::unique_ptr(new WASIHandle(ret))); return Result::ok(res); } -static Resource::HandleNS IN_BODY_HANDLE_NS = Resource::next_handle_ns("IncomingBody"); -class IncomingBodyHandleState final : public HandleState { - Handle stream_handle_; - PollableHandle pollable_handle_; - - friend HttpIncomingBody; - -public: - explicit IncomingBodyHandleState(const Handle handle) - : HandleState(handle, IN_BODY_HANDLE_NS), pollable_handle_(INVALID_POLLABLE_HANDLE) { - const borrow_incoming_body_t borrow = {handle}; - own_input_stream_t stream{}; - if (!wasi_http_0_2_0_types_method_incoming_body_stream(borrow, &stream)) { - MOZ_ASSERT_UNREACHABLE("Getting a body's stream should never fail"); - } - stream_handle_ = stream.__handle; - } -}; - void block_on_pollable_handle(PollableHandle handle) { wasi_io_0_2_0_poll_method_pollable_block({handle}); } @@ -903,16 +905,10 @@ HttpIncomingBody::HttpIncomingBody(std::unique_ptr state) : Pollabl Resource::~Resource() { if (handle_state_ != nullptr) { - delete handle_state_.release(); + handle_state_ = nullptr; } } -static Resource::HandleNS HANDLE_NS = 0; -Resource::HandleNS Resource::next_handle_ns(const char* ns_name) { - // DBG("Creating handle namespace %d with name %s\n", HANDLE_NS, ns_name); - return HANDLE_NS++; -} - bool Resource::valid() const { return this->handle_state_ != nullptr && this->handle_state_->valid(); } @@ -922,8 +918,8 @@ Result HttpIncomingBody::read(uint32_t chunk_size) wasi_io_0_2_0_streams_list_u8_t ret{}; wasi_io_0_2_0_streams_stream_error_t err{}; - auto borrow = borrow_input_stream_t( - {static_cast(handle_state_.get())->stream_handle_}); + auto body_handle = IncomingBodyHandle::cast(handle_state_.get()); + auto borrow = Borrow(body_handle->stream_handle_); bool success = wasi_io_0_2_0_streams_method_input_stream_read(borrow, chunk_size, &ret, &err); if (!success) { if (err.tag == WASI_IO_0_2_0_STREAMS_STREAM_ERROR_CLOSED) { @@ -938,13 +934,13 @@ Result HttpIncomingBody::read(uint32_t chunk_size) Result HttpIncomingBody::close() { return {}; } Result HttpIncomingBody::subscribe() { - auto state = static_cast(handle_state_.get()); - auto borrow = borrow_input_stream_t({state->stream_handle_}); + auto body_handle = IncomingBodyHandle::cast(handle_state_.get()); + auto borrow = Borrow(body_handle->stream_handle_); auto pollable = wasi_io_0_2_0_streams_method_input_stream_subscribe(borrow); return Result::ok(pollable.__handle); } void HttpIncomingBody::unsubscribe() { - auto state = static_cast(handle_state_.get()); + auto state = static_cast(handle_state_.get()); if (state->pollable_handle_ == INVALID_POLLABLE_HANDLE) { return; } @@ -956,12 +952,11 @@ FutureHttpIncomingResponse::FutureHttpIncomingResponse(std::unique_ptr> FutureHttpIncomingResponse::maybe_response() { typedef Result> Res; wasi_http_0_2_0_types_result_result_own_incoming_response_error_code_void_t res; - auto borrow = wasi_http_0_2_0_types_borrow_future_incoming_response({handle_state_->get()}); + Borrow borrow(handle_state_.get()); if (!wasi_http_0_2_0_types_method_future_incoming_response_get(borrow, &res)) { return Res::ok(std::nullopt); } @@ -974,11 +969,12 @@ Result> FutureHttpIncomingResponse::maybe_respo return Res::err(154); } - return Res::ok(new HttpIncomingResponse(std::make_unique(val.ok.__handle, IN_RESPONSE_HANDLE_NS))); + auto state = new WASIHandle(val.ok); + return Res::ok(new HttpIncomingResponse(std::unique_ptr(state))); } Result FutureHttpIncomingResponse::subscribe() { - auto borrow = wasi_http_0_2_0_types_borrow_future_incoming_response({handle_state_->get()}); + Borrow borrow(handle_state_.get()); auto pollable = wasi_http_0_2_0_types_method_future_incoming_response_subscribe(borrow); return Result::ok(pollable.__handle); } @@ -986,6 +982,10 @@ void FutureHttpIncomingResponse::unsubscribe() { // TODO: implement } +HttpHeadersReadOnly::HttpHeadersReadOnly() { + handle_state_ = nullptr; +} + HttpHeadersReadOnly::HttpHeadersReadOnly(std::unique_ptr state) { handle_state_ = std::move(state); } @@ -995,7 +995,7 @@ Result HttpIncomingResponse::status() { if (!valid()) { return Result::err(154); } - auto borrow = wasi_http_0_2_0_types_borrow_incoming_response_t({handle_state_->get()}); + auto borrow = Borrow(handle_state_.get()); status_ = wasi_http_0_2_0_types_method_incoming_response_status(borrow); } return Result::ok(status_); @@ -1010,9 +1010,10 @@ Result HttpIncomingResponse::headers() { if (!valid()) { return Result::err(154); } - auto res = wasi_http_0_2_0_types_method_incoming_response_headers( - wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->get()})); - headers_ = new HttpHeadersReadOnly(std::make_unique(res.__handle, HEADERS_HANDLE_NS)); + auto borrow = Borrow(handle_state_.get()); + auto res = wasi_http_0_2_0_types_method_incoming_response_headers(borrow); + auto state = new WASIHandle(res); + headers_ = new HttpHeadersReadOnly(std::unique_ptr(state)); } return Result::ok(headers_); @@ -1023,31 +1024,29 @@ Result HttpIncomingResponse::body() { if (!valid()) { return Result::err(154); } + auto borrow = Borrow(handle_state_.get()); incoming_body_t body; - if (!wasi_http_0_2_0_types_method_incoming_response_consume( - wasi_http_0_2_0_types_borrow_incoming_response({handle_state_->get()}), &body)) { + if (!wasi_http_0_2_0_types_method_incoming_response_consume(borrow, &body)) { return Result::err(154); } - body_ = new HttpIncomingBody(std::unique_ptr(new IncomingBodyHandleState(body.__handle))); + body_ = new HttpIncomingBody(std::unique_ptr(new IncomingBodyHandle(body))); } return Result::ok(body_); } HttpOutgoingResponse::HttpOutgoingResponse(std::unique_ptr state) { this->handle_state_ = std::move(state); } -static Resource::HandleNS OUT_RESPONSE_HANDLE_NS = Resource::next_handle_ns("HttpOutgoingResponse"); HttpOutgoingResponse *HttpOutgoingResponse::make(const uint16_t status, unique_ptr headers) { - wasi_http_0_2_0_types_own_headers_t owned{headers->handle_state_->take()}; - auto handle = wasi_http_0_2_0_types_constructor_outgoing_response(owned); - auto borrow = wasi_http_0_2_0_types_borrow_outgoing_response(handle); + auto owned_headers = WASIHandle::cast(headers->handle_state_.get())->take(); + auto handle = wasi_http_0_2_0_types_constructor_outgoing_response(owned_headers); - auto *state = new HandleState(handle.__handle, OUT_RESPONSE_HANDLE_NS); + auto *state = new WASIHandle(handle); auto *resp = new HttpOutgoingResponse(std::unique_ptr(state)); // Set the status if (status != 200) { // The DOM implementation is expected to have validated the status code already. - MOZ_RELEASE_ASSERT(wasi_http_0_2_0_types_method_outgoing_response_set_status_code(borrow, status)); + MOZ_RELEASE_ASSERT(wasi_http_0_2_0_types_method_outgoing_response_set_status_code(state->borrow(), status)); } resp->status_ = status; @@ -1059,9 +1058,10 @@ Result HttpOutgoingResponse::headers() { if (!valid()) { return Result::err(154); } - borrow_outgoing_response_t borrow(handle_state_->get()); + auto borrow = Borrow(handle_state_.get()); auto res = wasi_http_0_2_0_types_method_outgoing_response_headers(borrow); - headers_ = new HttpHeadersReadOnly(std::make_unique(res.__handle, HEADERS_HANDLE_NS)); + auto state = new WASIHandle(res); + headers_ = new HttpHeadersReadOnly(std::unique_ptr(state)); } return Result::ok(headers_); @@ -1070,13 +1070,12 @@ Result HttpOutgoingResponse::headers() { Result HttpOutgoingResponse::body() { typedef Result Res; if (!this->body_) { - handle_state_->assert_valid(); + auto borrow = Borrow(handle_state_.get()); outgoing_body_t body; - if (!wasi_http_0_2_0_types_method_outgoing_response_body( - wasi_http_0_2_0_types_borrow_outgoing_response({handle_state_->get()}), &body)) { + if (!wasi_http_0_2_0_types_method_outgoing_response_body(borrow, &body)) { return Res::err(154); } - body_ = new HttpOutgoingBody(std::unique_ptr(new OutgoingBodyHandleState(body.__handle))); + body_ = new HttpOutgoingBody(std::unique_ptr(new OutgoingBodyHandle(body))); } return Res::ok(this->body_); } @@ -1092,9 +1091,9 @@ Result HttpIncomingRequest::method() { return Result::err(154); } } + auto borrow = Borrow(handle_state_.get()); wasi_http_0_2_0_types_method_t method; - wasi_http_0_2_0_types_method_incoming_request_method( - borrow_incoming_request_t(handle_state_->get()), &method); + wasi_http_0_2_0_types_method_incoming_request_method(borrow, &method); if (method.tag != WASI_HTTP_0_2_0_TYPES_METHOD_OTHER) { method_ = std::string(http_method_names[method.tag], strlen(http_method_names[method.tag])); } else { @@ -1109,9 +1108,10 @@ Result HttpIncomingRequest::headers() { if (!valid()) { return Result::err(154); } - borrow_incoming_request_t borrow(handle_state_->get()); + auto borrow = Borrow(handle_state_.get()); auto res = wasi_http_0_2_0_types_method_incoming_request_headers(borrow); - headers_ = new HttpHeadersReadOnly(std::make_unique(res.__handle, HEADERS_HANDLE_NS)); + auto state = new WASIHandle(res); + headers_ = new HttpHeadersReadOnly(std::unique_ptr(state)); } return Result::ok(headers_); @@ -1122,12 +1122,12 @@ Result HttpIncomingRequest::body() { if (!valid()) { return Result::err(154); } + auto borrow = Borrow(handle_state_.get()); incoming_body_t body; - if (!wasi_http_0_2_0_types_method_incoming_request_consume( - borrow_incoming_request_t(handle_state_->get()), &body)) { + if (!wasi_http_0_2_0_types_method_incoming_request_consume(borrow, &body)) { return Result::err(154); } - body_ = new HttpIncomingBody(std::unique_ptr(new IncomingBodyHandleState(body.__handle))); + body_ = new HttpIncomingBody(std::unique_ptr(new IncomingBodyHandle(body))); } return Result::ok(body_); } @@ -1135,7 +1135,7 @@ Result HttpIncomingRequest::body() { } // namespace host_api static host_api::HttpIncomingRequest::RequestHandler REQUEST_HANDLER = nullptr; -static host_api::HandleState *RESPONSE_OUT = nullptr; +static exports_wasi_http_response_outparam RESPONSE_OUT; void host_api::HttpIncomingRequest::set_handler(RequestHandler handler) { MOZ_ASSERT(!REQUEST_HANDLER); @@ -1145,22 +1145,21 @@ void host_api::HttpIncomingRequest::set_handler(RequestHandler handler) { host_api::Result host_api::HttpOutgoingResponse::send() { wasi_http_0_2_0_types_result_own_outgoing_response_error_code_t result; + auto own = WASIHandle::cast(this->handle_state_.get())->take(); + result.is_err = false; - result.val.ok = {this->handle_state_->take()}; + result.val.ok = own; - wasi_http_0_2_0_types_static_response_outparam_set({RESPONSE_OUT->take()}, &result); + wasi_http_0_2_0_types_static_response_outparam_set(RESPONSE_OUT, &result); return {}; } -static host_api::Resource::HandleNS RESPONSE_OUTPARAM_HANDLE_NS = - host_api::Resource::next_handle_ns("ResponseOutParam"); -static host_api::Resource::HandleNS IN_REQUEST_HANDLE_NS = host_api::Resource::next_handle_ns("HttpIncomingRequest"); void exports_wasi_http_incoming_handler(exports_wasi_http_incoming_request request_handle, exports_wasi_http_response_outparam response_out) { - RESPONSE_OUT = new host_api::HandleState(response_out.__handle, RESPONSE_OUTPARAM_HANDLE_NS); - auto *request = new host_api::HttpIncomingRequest(std::make_unique(request_handle.__handle, IN_REQUEST_HANDLE_NS)); + RESPONSE_OUT = response_out; + auto state = new WASIHandle(request_handle); + auto *request = new host_api::HttpIncomingRequest(std::unique_ptr(state)); auto res = REQUEST_HANDLER(request); MOZ_RELEASE_ASSERT(res); - MOZ_RELEASE_ASSERT(!RESPONSE_OUT->valid()); } diff --git a/include/host_api.h b/include/host_api.h index 85e8876b..ee0e5601 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -325,8 +325,12 @@ class HttpHeadersReadOnly : public Resource { friend HttpOutgoingRequest; friend HttpHeaders; +protected: + // It's never valid to create an HttpHeadersReadOnly without a handle, + // but a subclass can create a handle and then assign it. + explicit HttpHeadersReadOnly(); + public: - HttpHeadersReadOnly() = delete; explicit HttpHeadersReadOnly(std::unique_ptr handle); HttpHeadersReadOnly(const HttpHeadersReadOnly &headers) = delete; From ec28998990b579cc9beef9fe1b571c46ec3a6459 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sun, 2 Jun 2024 16:19:22 +0200 Subject: [PATCH 44/52] Fix bugs in runtime-evaluation --- CMakeLists.txt | 2 +- include/extension-api.h | 2 ++ runtime/engine.cpp | 16 ++++++++++------ runtime/script_loader.cpp | 26 +++++++++++++++----------- runtime/script_loader.h | 2 +- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2c35fdf..98c3e9d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ function(componentize OUTPUT) add_custom_command( OUTPUT ${OUTPUT}.wasm WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMAND ${CMAKE_COMMAND} -E env "PATH=${WASM_TOOLS_DIR};${WIZER_DIR};$ENV{PATH}" ${RUNTIME_DIR}/componentize.sh ${SOURCES} ${OUTPUT}.wasm + COMMAND ${CMAKE_COMMAND} -E env "PATH=${WASM_TOOLS_DIR};${WIZER_DIR};$ENV{PATH}" ${RUNTIME_DIR}/componentize.sh ${SOURCES} -o ${OUTPUT}.wasm DEPENDS ${ARG_SOURCES} ${RUNTIME_DIR}/componentize.sh starling.wasm VERBATIM ) diff --git a/include/extension-api.h b/include/extension-api.h index 86192760..790a2f4c 100644 --- a/include/extension-api.h +++ b/include/extension-api.h @@ -67,6 +67,8 @@ class Engine { bool eval_toplevel(const char *path, MutableHandleValue result); bool eval_toplevel(JS::SourceText &source, const char *path, MutableHandleValue result); + + bool is_preinitializing(); bool toplevel_evaluated(); /** diff --git a/runtime/engine.cpp b/runtime/engine.cpp index 4d8529d4..a61c7192 100644 --- a/runtime/engine.cpp +++ b/runtime/engine.cpp @@ -201,6 +201,8 @@ bool fix_math_random(JSContext *cx, HandleObject global) { return JS_DefineFunctions(cx, math, funs); } +static api::Engine *ENGINE; + bool init_js() { JS_Init(); @@ -253,8 +255,9 @@ bool init_js() { // generating bytecode for functions. // https://searchfox.org/mozilla-central/rev/5b2d2863bd315f232a3f769f76e0eb16cdca7cb0/js/public/CompileOptions.h#571-574 opts->setForceFullParse(); - scriptLoader = new ScriptLoader(cx, opts); + scriptLoader = new ScriptLoader(ENGINE, opts); + // TODO: restore in a way that doesn't cause a dependency on the Performance builtin in the core runtime. // builtins::Performance::timeOrigin.emplace( // std::chrono::high_resolution_clock::now()); @@ -327,15 +330,13 @@ static void abort(JSContext *cx, const char *description) { exit(1); } -static api::Engine *ENGINE; - api::Engine::Engine() { // total_compute = 0; + ENGINE = this; bool result = init_js(); MOZ_RELEASE_ASSERT(result); JS::EnterRealm(cx(), global()); core::EventLoop::init(cx()); - ENGINE = this; } JSContext *api::Engine::cx() { return CONTEXT; } @@ -447,8 +448,10 @@ bool api::Engine::eval_toplevel(JS::SourceText &source, const // the shrinking GC causes them to be intermingled with other objects. I.e., // writes become more fragmented due to the shrinking GC. // https://github.com/fastly/js-compute-runtime/issues/224 - JS::PrepareForFullGC(cx); - JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API); + if (isWizening()) { + JS::PrepareForFullGC(cx); + JS::NonIncrementalGC(cx, JS::GCOptions::Normal, JS::GCReason::API); + } // Ignore the first GC, but then print all others, because ideally GCs // should be rare, and developers should know about them. @@ -460,6 +463,7 @@ bool api::Engine::eval_toplevel(JS::SourceText &source, const return true; } +bool api::Engine::is_preinitializing() { return isWizening(); } bool api::Engine::eval_toplevel(const char *path, MutableHandleValue result) { JS::SourceText source; diff --git a/runtime/script_loader.cpp b/runtime/script_loader.cpp index 06ca71ad..fa4a472e 100644 --- a/runtime/script_loader.cpp +++ b/runtime/script_loader.cpp @@ -9,7 +9,7 @@ #include #include -static JSContext* CONTEXT; +static api::Engine* ENGINE; static ScriptLoader* SCRIPT_LOADER; JS::PersistentRootedObject moduleRegistry; JS::PersistentRootedObject builtinModules; @@ -345,11 +345,12 @@ bool module_metadata_hook(JSContext* cx, HandleValue referencingPrivate, HandleO return true; } -ScriptLoader::ScriptLoader(JSContext *cx, JS::CompileOptions *opts) { +ScriptLoader::ScriptLoader(api::Engine* engine, JS::CompileOptions *opts) { MOZ_ASSERT(!SCRIPT_LOADER); + ENGINE = engine; SCRIPT_LOADER = this; - CONTEXT = cx; COMPILE_OPTS = opts; + JSContext* cx = engine->cx(); moduleRegistry.init(cx, JS::NewMapObject(cx)); builtinModules.init(cx, JS::NewMapObject(cx)); MOZ_RELEASE_ASSERT(moduleRegistry); @@ -360,21 +361,22 @@ ScriptLoader::ScriptLoader(JSContext *cx, JS::CompileOptions *opts) { } bool ScriptLoader::define_builtin_module(const char* id, HandleValue builtin) { - RootedString id_str(CONTEXT, JS_NewStringCopyZ(CONTEXT, id)); + JSContext* cx = ENGINE->cx(); + RootedString id_str(cx, JS_NewStringCopyZ(cx, id)); if (!id_str) { return false; } - RootedValue module_val(CONTEXT); - RootedValue id_val(CONTEXT, StringValue(id_str)); + RootedValue module_val(cx); + RootedValue id_val(cx, StringValue(id_str)); bool already_exists; - if (!MapHas(CONTEXT, builtinModules, id_val, &already_exists)) { + if (!MapHas(cx, builtinModules, id_val, &already_exists)) { return false; } if (already_exists) { fprintf(stderr, "Unable to define builtin %s, as it already exists", id); return false; } - if (!MapSet(CONTEXT, builtinModules, id_val, builtin)) { + if (!MapSet(cx, builtinModules, id_val, builtin)) { return false; } return true; @@ -444,7 +446,7 @@ bool ScriptLoader::load_script(JSContext *cx, const char *script_path, bool ScriptLoader::eval_top_level_script(const char *path, JS::SourceText &source, MutableHandleValue result, MutableHandleValue tla_promise) { - JSContext *cx = CONTEXT; + JSContext *cx = ENGINE->cx(); JS::CompileOptions opts(cx, *COMPILE_OPTS); opts.setFileAndLine(strip_base(path, BASE_PATH), 1); @@ -482,8 +484,10 @@ bool ScriptLoader::eval_top_level_script(const char *path, JS::SourceTextis_preinitializing()) { + JS::PrepareForFullGC(cx); + JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API); + } // Execute the top-level module script. if (!MODULE_MODE) { diff --git a/runtime/script_loader.h b/runtime/script_loader.h index 460b58db..e7022ef6 100644 --- a/runtime/script_loader.h +++ b/runtime/script_loader.h @@ -14,7 +14,7 @@ class ScriptLoader { public: - ScriptLoader(JSContext* cx, JS::CompileOptions* opts); + ScriptLoader(api::Engine* engine, JS::CompileOptions* opts); ~ScriptLoader(); bool define_builtin_module(const char* id, HandleValue builtin); From 429daecc349e7464297b1ecd783e814d34fe954b Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sun, 2 Jun 2024 16:59:46 +0200 Subject: [PATCH 45/52] Fix bugs in headers handling --- builtins/web/fetch/headers.cpp | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 2281190c..4b1373af 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -158,6 +158,11 @@ host_api::HostString normalize_header_value(JSContext *cx, HandleValue value_val } } + if (*value_changed) { + memmove(value_chars, value_chars + start, end - start); + value.len = end - start; + } + return value; } @@ -456,6 +461,25 @@ bool append_single_normalized_header_value(JSContext *cx, HandleObject self, return false; } + RootedValue entry(cx); + if (!MapGet(cx, entries, name_val, &entry)) { + return false; + } + + if (!entry.isUndefined()) { + RootedString entry_str(cx, JS::ToString(cx, entry)); + entry_str = JS_ConcatStrings(cx, entry_str, comma); + if (!entry_str) { + return false; + } + RootedString val_str(cx, value_val.toString()); + entry_str = JS_ConcatStrings(cx, entry_str, val_str); + if (!entry_str) { + return false; + } + value_val.setString(entry_str); + } + if (!MapSet(cx, entries, name_val, value_val)) { return false; } @@ -618,7 +642,7 @@ bool Headers::has(JSContext *cx, unsigned argc, JS::Value *vp) { NORMALIZE_NAME(args[0], "Headers.has") Mode mode = Headers::mode(self); - if (mode == Headers::Mode::Uninitialized) { + if (mode == Mode::Uninitialized) { args.rval().setBoolean(false); return true; } @@ -634,8 +658,13 @@ bool Headers::has(JSContext *cx, unsigned argc, JS::Value *vp) { if (!entries) { return false; } + + RootedValue name_val(cx); + if (!redecode_str_if_changed(cx, args[0], name_chars, name_changed, &name_val)) { + return false; + } bool has; - if (!MapHas(cx, entries, args[0], &has)) { + if (!MapHas(cx, entries, name_val, &has)) { return false; } args.rval().setBoolean(has); From a0781f0ca347b5cf0cb3af3060e332ce64e91e2e Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sun, 2 Jun 2024 21:20:12 +0200 Subject: [PATCH 46/52] Make the `extract body` spec operation work for outgoing requests and responses --- builtins/web/fetch/headers.cpp | 14 ++++++++++++-- builtins/web/fetch/headers.h | 1 + builtins/web/fetch/request-response.cpp | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index 4b1373af..edac89d8 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -523,8 +523,18 @@ void init_from_handle(JSObject* self, host_api::HttpHeadersReadOnly* handle) { SetReservedSlot(self, static_cast(Headers::Slots::Handle), PrivateValue(handle)); } +JSObject *Headers::create(JSContext *cx) { + JSObject* self = JS_NewObjectWithGivenProto(cx, &class_, proto_obj); + if (!self) { + return nullptr; + } + SetReservedSlot(self, static_cast(Slots::Mode), + JS::Int32Value(static_cast(Mode::Uninitialized))); + return self; +} + JSObject *Headers::create(JSContext *cx, host_api::HttpHeadersReadOnly *handle) { - RootedObject self(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj)); + RootedObject self(cx, create(cx)); if (!self) { return nullptr; } @@ -533,7 +543,7 @@ JSObject *Headers::create(JSContext *cx, host_api::HttpHeadersReadOnly *handle) } JSObject *Headers::create(JSContext *cx, HandleValue init_headers) { - RootedObject self(cx, JS_NewObjectWithGivenProto(cx, &class_, proto_obj)); + RootedObject self(cx, create(cx)); if (!self) { return nullptr; } diff --git a/builtins/web/fetch/headers.h b/builtins/web/fetch/headers.h index e9a946fb..9d7247ba 100644 --- a/builtins/web/fetch/headers.h +++ b/builtins/web/fetch/headers.h @@ -97,6 +97,7 @@ class Headers final : public BuiltinImpl { static bool init_class(JSContext *cx, HandleObject global); static bool constructor(JSContext *cx, unsigned argc, Value *vp); + static JSObject *create(JSContext *cx); static JSObject *create(JSContext *cx, HandleValue init_headers); static JSObject *create(JSContext *cx, HandleObject self, HandleValue init_headers); static JSObject *create(JSContext *cx, host_api::HttpHeadersReadOnly *handle); diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index d0654113..33c7698b 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -379,6 +379,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, chunk.setObject(*array); } + // Set a __proto__-less source so modifying Object.prototype doesn't change the behavior. RootedObject source(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); if (!source) { return false; @@ -468,7 +469,7 @@ JSObject *RequestOrResponse::headers(JSContext *cx, JS::HandleObject obj) { if (is_incoming(obj) && (handle = headers_handle(obj))) { headers = Headers::create(cx, handle); } else { - headers = Headers::create(cx, NullHandleValue); + headers = Headers::create(cx); } if (!headers) { return nullptr; From 7f4ba5a047a5e156ff99d1934bc33db807e68974 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sun, 2 Jun 2024 21:38:49 +0200 Subject: [PATCH 47/52] Lock request/response body when consuming it for `.text()`, `.arrayBuffer()`, or `.json()` --- builtins/web/fetch/request-response.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 33c7698b..eb10e1b3 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -806,7 +806,8 @@ template bool RequestOrResponse::bodyAll(JSContext *cx, JS::CallArgs args, JS::HandleObject self) { // TODO: mark body as consumed when operating on stream, too. if (body_used(self)) { - JS_ReportErrorASCII(cx, "Body has already been consumed"); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_RESPONSE_BODY_DISTURBED_OR_LOCKED); return ReturnPromiseRejectedWithPendingError(cx, args); } @@ -843,6 +844,7 @@ bool RequestOrResponse::bodyAll(JSContext *cx, JS::CallArgs args, JS::HandleObje return false; } + SetReservedSlot(self, static_cast(Slots::BodyUsed), JS::BooleanValue(true)); JS::RootedValue extra(cx, JS::ObjectValue(*stream)); if (!enqueue_internal_method(cx, self, extra)) { return ReturnPromiseRejectedWithPendingError(cx, args); From e2f3b377702d346d6940ec9b8352d2da4fd73227 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sun, 2 Jun 2024 22:18:35 +0200 Subject: [PATCH 48/52] Fix handling of various value types when extracting request/response bodies --- builtins/web/fetch/request-response.cpp | 26 +++++++++---------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index eb10e1b3..c49f1094 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -323,9 +323,6 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, char *buf = nullptr; size_t length = 0; - // Must be declared here to keep the buffer alive. - host_api::HostString text; - if (body_obj && JS_IsArrayBufferViewObject(body_obj)) { length = JS_GetArrayBufferViewByteLength(body_obj); buf = static_cast(js_malloc(length)); @@ -342,42 +339,37 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, if (!buffer) { return false; } + length = GetArrayBufferByteLength(buffer); } else if (body_obj && url::URLSearchParams::is_instance(body_obj)) { auto slice = url::URLSearchParams::serialize(cx, body_obj); buf = (char *)slice.data; length = slice.len; content_type = "application/x-www-form-urlencoded;charset=UTF-8"; } else { - text = core::encode(cx, body_val); + auto text = core::encode(cx, body_val); if (!text.ptr) { return false; } - buf = text.ptr.get(); + buf = text.ptr.release(); length = text.len; content_type = "text/plain;charset=UTF-8"; } - if (buf) { - MOZ_ASSERT(!buffer); + if (!buffer) { + MOZ_ASSERT_IF(length, buf); buffer = NewArrayBufferWithContents(cx, length, buf, JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory); - if (text.ptr) { - static_cast(text.ptr.release()); - } if (!buffer) { js_free(buf); return false; } - chunk.setObject(*buffer); } - if (buffer) { - RootedObject array(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, length)); - if (!array) { - return false; - } - chunk.setObject(*array); + RootedObject array(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, length)); + if (!array) { + return false; } + chunk.setObject(*array); // Set a __proto__-less source so modifying Object.prototype doesn't change the behavior. RootedObject source(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); From 2d7fc1b99d9bfb80efdf6f2b9811b377d48696cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Istv=C3=A1n=20B=C3=ADr=C3=B3?= Date: Mon, 3 Jun 2024 15:48:10 +0200 Subject: [PATCH 49/52] Revert "Merge pull request #1 from golemcloud/fetch-fixes" This reverts commit 62acc86458ae9b07f9f7429407bcda93c10edd2a, reversing changes made to 71bb6dcddd50f26de6a9070ef234f74a25410a0f. --- builtins/web/fetch/request-response.cpp | 28 +- builtins/web/fetch/request-response.h | 1 - builtins/web/streams/native-stream-source.cpp | 13 - builtins/web/streams/native-stream-source.h | 1 - host-apis/wasi-0.2.0/host_api.cpp | 19 +- tests/e2e/fetch-1/expect_serve_body.txt | 1 - tests/e2e/fetch-1/expect_serve_stderr.txt | 0 tests/e2e/fetch-1/expect_serve_stdout.txt | 1 - tests/e2e/fetch-1/fetch-1.js | 22 - tests/e2e/fetch-2/expect_serve_body.txt | 3502 ----------------- tests/e2e/fetch-2/expect_serve_stderr.txt | 0 tests/e2e/fetch-2/expect_serve_stdout.txt | 1 - tests/e2e/fetch-2/fetch-2.js | 31 - tests/e2e/fetch-3/expect_serve_body.txt | 1 - tests/e2e/fetch-3/expect_serve_stderr.txt | 0 tests/e2e/fetch-3/expect_serve_stdout.txt | 1 - tests/e2e/fetch-3/fetch-3.js | 33 - tests/test.sh | 2 +- tests/tests.cmake | 3 - 19 files changed, 11 insertions(+), 3649 deletions(-) delete mode 100644 tests/e2e/fetch-1/expect_serve_body.txt delete mode 100644 tests/e2e/fetch-1/expect_serve_stderr.txt delete mode 100644 tests/e2e/fetch-1/expect_serve_stdout.txt delete mode 100644 tests/e2e/fetch-1/fetch-1.js delete mode 100644 tests/e2e/fetch-2/expect_serve_body.txt delete mode 100644 tests/e2e/fetch-2/expect_serve_stderr.txt delete mode 100644 tests/e2e/fetch-2/expect_serve_stdout.txt delete mode 100644 tests/e2e/fetch-2/fetch-2.js delete mode 100644 tests/e2e/fetch-3/expect_serve_body.txt delete mode 100644 tests/e2e/fetch-3/expect_serve_stderr.txt delete mode 100644 tests/e2e/fetch-3/expect_serve_stdout.txt delete mode 100644 tests/e2e/fetch-3/fetch-3.js diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index c4dfdd84..36c66dfa 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -326,13 +326,6 @@ bool RequestOrResponse::body_unusable(JSContext *cx, JS::HandleObject body) { return disturbed || locked; } -bool RequestOrResponse::body_disturbed(JSContext *cx, JS::HandleObject body) { - MOZ_ASSERT(JS::IsReadableStream(body)); - bool disturbed; - MOZ_RELEASE_ASSERT(JS::ReadableStreamIsDisturbed(cx, body, &disturbed)); - return disturbed; -} - /** * Implementation of the `extract a body` algorithm at * https://fetch.spec.whatwg.org/#concept-bodyinit-extract @@ -423,8 +416,6 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, auto body = RequestOrResponse::outgoing_body_handle(self); auto write_res = body->write_all(reinterpret_cast(buf), length); - body->close(); - // Ensure that the NoGC is reset, so throwing an error in HANDLE_ERROR // succeeds. if (maybeNoGC.isSome()) { @@ -440,9 +431,6 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, // Step 36.3 of Request constructor / 8.4 of Response constructor. if (content_type) { JS::RootedObject headers(cx, RequestOrResponse::headers(cx, self)); - if (!headers) { - return false; - } if (!Headers::maybe_add(cx, headers, "content-type", content_type)) { return false; } @@ -763,7 +751,7 @@ bool RequestOrResponse::consume_content_stream_for_bodyAll(JSContext *cx, JS::Ha return false; } MOZ_ASSERT(JS::IsReadableStream(stream)); - if (RequestOrResponse::body_disturbed(cx, stream)) { + if (RequestOrResponse::body_unusable(cx, stream)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RESPONSE_BODY_DISTURBED_OR_LOCKED); JS::RootedObject result_promise(cx); @@ -772,9 +760,8 @@ bool RequestOrResponse::consume_content_stream_for_bodyAll(JSContext *cx, JS::Ha JS::SetReservedSlot(self, static_cast(Slots::BodyAllPromise), JS::UndefinedValue()); return RejectPromiseWithPendingError(cx, result_promise); } - - JS::RootedObject unwrappedReader(cx, streams::NativeStreamSource::get_locked_by_internal_reader(cx, stream)); - + JS::Rooted unwrappedReader( + cx, JS::ReadableStreamGetReader(cx, stream, JS::ReadableStreamReaderMode::Default)); if (!unwrappedReader) { return false; } @@ -1148,11 +1135,7 @@ JSObject *RequestOrResponse::create_body_stream(JSContext *cx, JS::HandleObject return nullptr; } - // If the body has already been used without being reified as a ReadableStream, - // lock the stream immediately. - if (body_used(owner)) { - MOZ_RELEASE_ASSERT(streams::NativeStreamSource::lock_stream(cx, body_stream)); - } + // TODO: immediately lock the stream if the owner's body is already used. JS_SetReservedSlot(owner, static_cast(Slots::BodyStream), JS::ObjectValue(*body_stream)); @@ -1710,8 +1693,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H // `init["headers"]` exists, create the request's `headers` from that, // otherwise create it from the `init` object's `headers`, or create a new, // empty one. - auto *headers_handle = new host_api::HttpHeaders(); - + auto *headers_handle = new host_api::HttpHeaders(); JS::RootedObject headers(cx); if (headers_val.isUndefined() && input_headers) { diff --git a/builtins/web/fetch/request-response.h b/builtins/web/fetch/request-response.h index f911762d..e1b2f1d5 100644 --- a/builtins/web/fetch/request-response.h +++ b/builtins/web/fetch/request-response.h @@ -42,7 +42,6 @@ class RequestOrResponse final { static JS::Value url(JSObject *obj); static void set_url(JSObject *obj, JS::Value url); static bool body_unusable(JSContext *cx, JS::HandleObject body); - static bool body_disturbed(JSContext *cx, JS::HandleObject body); static bool extract_body(JSContext *cx, JS::HandleObject self, JS::HandleValue body_val); /** diff --git a/builtins/web/streams/native-stream-source.cpp b/builtins/web/streams/native-stream-source.cpp index 0dc69cc0..fb8b56c4 100644 --- a/builtins/web/streams/native-stream-source.cpp +++ b/builtins/web/streams/native-stream-source.cpp @@ -103,19 +103,6 @@ bool NativeStreamSource::lock_stream(JSContext *cx, JS::HandleObject stream) { return true; } -JSObject *NativeStreamSource::get_locked_by_internal_reader(JSContext *cx, JS::HandleObject stream) { - - JS::RootedObject self(cx, get_stream_source(cx, stream)); - MOZ_ASSERT(is_instance(self)); - - bool locked; - JS::ReadableStreamIsLocked(cx, stream, &locked); - MOZ_ASSERT(locked); - - JS::RootedObject reader(cx, &JS::GetReservedSlot(self, Slots::InternalReader).toObject()); - return reader; -} - bool NativeStreamSource::start(JSContext *cx, unsigned argc, JS::Value *vp) { METHOD_HEADER(1) diff --git a/builtins/web/streams/native-stream-source.h b/builtins/web/streams/native-stream-source.h index e5352a79..d07ec7bd 100644 --- a/builtins/web/streams/native-stream-source.h +++ b/builtins/web/streams/native-stream-source.h @@ -47,7 +47,6 @@ class NativeStreamSource : public BuiltinNoConstructor { JS::HandleObject writable); static JSObject *piped_to_transform_stream(JSObject *self); static bool lock_stream(JSContext *cx, JS::HandleObject stream); - static JSObject *get_locked_by_internal_reader(JSContext *cx, JS::HandleObject stream); static bool start(JSContext *cx, unsigned argc, JS::Value *vp); static bool pull(JSContext *cx, unsigned argc, JS::Value *vp); static bool cancel(JSContext *cx, unsigned argc, JS::Value *vp); diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index 1f3e312e..39b4f61a 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -709,24 +709,21 @@ HttpOutgoingRequest *HttpOutgoingRequest::make(string_view method_str, optional< auto *state = new HandleState(handle.__handle); auto *resp = new HttpOutgoingRequest(state); + resp->headers_ = headers; + return resp; } Result HttpOutgoingRequest::method() { MOZ_ASSERT(valid()); + MOZ_ASSERT(headers_); return Result::ok(method_); } Result HttpOutgoingRequest::headers() { - typedef Result Res; MOZ_ASSERT(valid()); - - if (!this->headers_) { - auto res = wasi_http_0_2_0_types_method_outgoing_request_headers( - wasi_http_0_2_0_types_borrow_outgoing_request({handle_state_->handle})); - headers_ = new HttpHeaders(res.__handle); - } - return Res::ok(this->headers_); + MOZ_ASSERT(headers_); + return Result::ok(headers_); } Result HttpOutgoingRequest::body() { @@ -745,12 +742,6 @@ Result HttpOutgoingRequest::body() { Result HttpOutgoingRequest::send() { MOZ_ASSERT(valid()); - - if (this->headers_) { - wasi_http_0_2_0_types_fields_drop_borrow({this->headers_->handle_state_->handle}); - } - - future_incoming_response_t ret; wasi_http_0_2_0_outgoing_handler_error_code_t err; wasi_http_0_2_0_outgoing_handler_handle({handle_state_->handle}, nullptr, &ret, &err); diff --git a/tests/e2e/fetch-1/expect_serve_body.txt b/tests/e2e/fetch-1/expect_serve_body.txt deleted file mode 100644 index d9524d8f..00000000 --- a/tests/e2e/fetch-1/expect_serve_body.txt +++ /dev/null @@ -1 +0,0 @@ -[{"id":1,"name":"Leanne Graham","username":"Bret","email":"Sincere@april.biz","address":{"street":"Kulas Light","suite":"Apt. 556","city":"Gwenborough","zipcode":"92998-3874","geo":{"lat":"-37.3159","lng":"81.1496"}},"phone":"1-770-736-8031 x56442","website":"hildegard.org","company":{"name":"Romaguera-Crona","catchPhrase":"Multi-layered client-server neural-net","bs":"harness real-time e-markets"}},{"id":2,"name":"Ervin Howell","username":"Antonette","email":"Shanna@melissa.tv","address":{"street":"Victor Plains","suite":"Suite 879","city":"Wisokyburgh","zipcode":"90566-7771","geo":{"lat":"-43.9509","lng":"-34.4618"}},"phone":"010-692-6593 x09125","website":"anastasia.net","company":{"name":"Deckow-Crist","catchPhrase":"Proactive didactic contingency","bs":"synergize scalable supply-chains"}},{"id":3,"name":"Clementine Bauch","username":"Samantha","email":"Nathan@yesenia.net","address":{"street":"Douglas Extension","suite":"Suite 847","city":"McKenziehaven","zipcode":"59590-4157","geo":{"lat":"-68.6102","lng":"-47.0653"}},"phone":"1-463-123-4447","website":"ramiro.info","company":{"name":"Romaguera-Jacobson","catchPhrase":"Face to face bifurcated interface","bs":"e-enable strategic applications"}},{"id":4,"name":"Patricia Lebsack","username":"Karianne","email":"Julianne.OConner@kory.org","address":{"street":"Hoeger Mall","suite":"Apt. 692","city":"South Elvis","zipcode":"53919-4257","geo":{"lat":"29.4572","lng":"-164.2990"}},"phone":"493-170-9623 x156","website":"kale.biz","company":{"name":"Robel-Corkery","catchPhrase":"Multi-tiered zero tolerance productivity","bs":"transition cutting-edge web services"}},{"id":5,"name":"Chelsey Dietrich","username":"Kamren","email":"Lucio_Hettinger@annie.ca","address":{"street":"Skiles Walks","suite":"Suite 351","city":"Roscoeview","zipcode":"33263","geo":{"lat":"-31.8129","lng":"62.5342"}},"phone":"(254)954-1289","website":"demarco.info","company":{"name":"Keebler LLC","catchPhrase":"User-centric fault-tolerant solution","bs":"revolutionize end-to-end systems"}},{"id":6,"name":"Mrs. Dennis Schulist","username":"Leopoldo_Corkery","email":"Karley_Dach@jasper.info","address":{"street":"Norberto Crossing","suite":"Apt. 950","city":"South Christy","zipcode":"23505-1337","geo":{"lat":"-71.4197","lng":"71.7478"}},"phone":"1-477-935-8478 x6430","website":"ola.org","company":{"name":"Considine-Lockman","catchPhrase":"Synchronised bottom-line interface","bs":"e-enable innovative applications"}},{"id":7,"name":"Kurtis Weissnat","username":"Elwyn.Skiles","email":"Telly.Hoeger@billy.biz","address":{"street":"Rex Trail","suite":"Suite 280","city":"Howemouth","zipcode":"58804-1099","geo":{"lat":"24.8918","lng":"21.8984"}},"phone":"210.067.6132","website":"elvis.io","company":{"name":"Johns Group","catchPhrase":"Configurable multimedia task-force","bs":"generate enterprise e-tailers"}},{"id":8,"name":"Nicholas Runolfsdottir V","username":"Maxime_Nienow","email":"Sherwood@rosamond.me","address":{"street":"Ellsworth Summit","suite":"Suite 729","city":"Aliyaview","zipcode":"45169","geo":{"lat":"-14.3990","lng":"-120.7677"}},"phone":"586.493.6943 x140","website":"jacynthe.com","company":{"name":"Abernathy Group","catchPhrase":"Implemented secondary concept","bs":"e-enable extensible e-tailers"}},{"id":9,"name":"Glenna Reichert","username":"Delphine","email":"Chaim_McDermott@dana.io","address":{"street":"Dayna Park","suite":"Suite 449","city":"Bartholomebury","zipcode":"76495-3109","geo":{"lat":"24.6463","lng":"-168.8889"}},"phone":"(775)976-6794 x41206","website":"conrad.com","company":{"name":"Yost and Sons","catchPhrase":"Switchable contextually-based project","bs":"aggregate real-time technologies"}},{"id":10,"name":"Clementina DuBuque","username":"Moriah.Stanton","email":"Rey.Padberg@karina.biz","address":{"street":"Kattie Turnpike","suite":"Suite 198","city":"Lebsackbury","zipcode":"31428-2261","geo":{"lat":"-38.2386","lng":"57.2232"}},"phone":"024-648-3804","website":"ambrose.net","company":{"name":"Hoeger LLC","catchPhrase":"Centralized empowering task-force","bs":"target end-to-end models"}}] \ No newline at end of file diff --git a/tests/e2e/fetch-1/expect_serve_stderr.txt b/tests/e2e/fetch-1/expect_serve_stderr.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/e2e/fetch-1/expect_serve_stdout.txt b/tests/e2e/fetch-1/expect_serve_stdout.txt deleted file mode 100644 index 7bcdc8dc..00000000 --- a/tests/e2e/fetch-1/expect_serve_stdout.txt +++ /dev/null @@ -1 +0,0 @@ -stdout [0] :: Log: Successfully received response json body diff --git a/tests/e2e/fetch-1/fetch-1.js b/tests/e2e/fetch-1/fetch-1.js deleted file mode 100644 index a078227a..00000000 --- a/tests/e2e/fetch-1/fetch-1.js +++ /dev/null @@ -1,22 +0,0 @@ -async function main(event) { - let resolve, reject; - - try { - let responsePromise = new Promise(async (res, rej) => { - resolve = res; - reject = rej; - }); - event.respondWith(responsePromise); - - let response = await fetch("https://jsonplaceholder.typicode.com/users"); - let text = await response.json(); - - console.log("Successfully received response json body"); - - resolve(new Response(JSON.stringify(text), { headers: response.headers })); - } catch (e) { - console.log(`Error: ${e}. Stack: ${e.stack}`); - } -} - -addEventListener('fetch', main); diff --git a/tests/e2e/fetch-2/expect_serve_body.txt b/tests/e2e/fetch-2/expect_serve_body.txt deleted file mode 100644 index 0d7ba4cd..00000000 --- a/tests/e2e/fetch-2/expect_serve_body.txt +++ /dev/null @@ -1,3502 +0,0 @@ -[ - { - "postId": 1, - "id": 1, - "name": "id labore ex et quam laborum", - "email": "Eliseo@gardner.biz", - "body": "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium" - }, - { - "postId": 1, - "id": 2, - "name": "quo vero reiciendis velit similique earum", - "email": "Jayne_Kuhic@sydney.com", - "body": "est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et" - }, - { - "postId": 1, - "id": 3, - "name": "odio adipisci rerum aut animi", - "email": "Nikita@garfield.biz", - "body": "quia molestiae reprehenderit quasi aspernatur\naut expedita occaecati aliquam eveniet laudantium\nomnis quibusdam delectus saepe quia accusamus maiores nam est\ncum et ducimus et vero voluptates excepturi deleniti ratione" - }, - { - "postId": 1, - "id": 4, - "name": "alias odio sit", - "email": "Lew@alysha.tv", - "body": "non et atque\noccaecati deserunt quas accusantium unde odit nobis qui voluptatem\nquia voluptas consequuntur itaque dolor\net qui rerum deleniti ut occaecati" - }, - { - "postId": 1, - "id": 5, - "name": "vero eaque aliquid doloribus et culpa", - "email": "Hayden@althea.biz", - "body": "harum non quasi et ratione\ntempore iure ex voluptates in ratione\nharum architecto fugit inventore cupiditate\nvoluptates magni quo et" - }, - { - "postId": 2, - "id": 6, - "name": "et fugit eligendi deleniti quidem qui sint nihil autem", - "email": "Presley.Mueller@myrl.com", - "body": "doloribus at sed quis culpa deserunt consectetur qui praesentium\naccusamus fugiat dicta\nvoluptatem rerum ut voluptate autem\nvoluptatem repellendus aspernatur dolorem in" - }, - { - "postId": 2, - "id": 7, - "name": "repellat consequatur praesentium vel minus molestias voluptatum", - "email": "Dallas@ole.me", - "body": "maiores sed dolores similique labore et inventore et\nquasi temporibus esse sunt id et\neos voluptatem aliquam\naliquid ratione corporis molestiae mollitia quia et magnam dolor" - }, - { - "postId": 2, - "id": 8, - "name": "et omnis dolorem", - "email": "Mallory_Kunze@marie.org", - "body": "ut voluptatem corrupti velit\nad voluptatem maiores\net nisi velit vero accusamus maiores\nvoluptates quia aliquid ullam eaque" - }, - { - "postId": 2, - "id": 9, - "name": "provident id voluptas", - "email": "Meghan_Littel@rene.us", - "body": "sapiente assumenda molestiae atque\nadipisci laborum distinctio aperiam et ab ut omnis\net occaecati aspernatur odit sit rem expedita\nquas enim ipsam minus" - }, - { - "postId": 2, - "id": 10, - "name": "eaque et deleniti atque tenetur ut quo ut", - "email": "Carmen_Keeling@caroline.name", - "body": "voluptate iusto quis nobis reprehenderit ipsum amet nulla\nquia quas dolores velit et non\naut quia necessitatibus\nnostrum quaerat nulla et accusamus nisi facilis" - }, - { - "postId": 3, - "id": 11, - "name": "fugit labore quia mollitia quas deserunt nostrum sunt", - "email": "Veronica_Goodwin@timmothy.net", - "body": "ut dolorum nostrum id quia aut est\nfuga est inventore vel eligendi explicabo quis consectetur\naut occaecati repellat id natus quo est\nut blanditiis quia ut vel ut maiores ea" - }, - { - "postId": 3, - "id": 12, - "name": "modi ut eos dolores illum nam dolor", - "email": "Oswald.Vandervort@leanne.org", - "body": "expedita maiores dignissimos facilis\nipsum est rem est fugit velit sequi\neum odio dolores dolor totam\noccaecati ratione eius rem velit" - }, - { - "postId": 3, - "id": 13, - "name": "aut inventore non pariatur sit vitae voluptatem sapiente", - "email": "Kariane@jadyn.tv", - "body": "fuga eos qui dolor rerum\ninventore corporis exercitationem\ncorporis cupiditate et deserunt recusandae est sed quis culpa\neum maiores corporis et" - }, - { - "postId": 3, - "id": 14, - "name": "et officiis id praesentium hic aut ipsa dolorem repudiandae", - "email": "Nathan@solon.io", - "body": "vel quae voluptas qui exercitationem\nvoluptatibus unde sed\nminima et qui ipsam aspernatur\nexpedita magnam laudantium et et quaerat ut qui dolorum" - }, - { - "postId": 3, - "id": 15, - "name": "debitis magnam hic odit aut ullam nostrum tenetur", - "email": "Maynard.Hodkiewicz@roberta.com", - "body": "nihil ut voluptates blanditiis autem odio dicta rerum\nquisquam saepe et est\nsunt quasi nemo laudantium deserunt\nmolestias tempora quo quia" - }, - { - "postId": 4, - "id": 16, - "name": "perferendis temporibus delectus optio ea eum ratione dolorum", - "email": "Christine@ayana.info", - "body": "iste ut laborum aliquid velit facere itaque\nquo ut soluta dicta voluptate\nerror tempore aut et\nsequi reiciendis dignissimos expedita consequuntur libero sed fugiat facilis" - }, - { - "postId": 4, - "id": 17, - "name": "eos est animi quis", - "email": "Preston_Hudson@blaise.tv", - "body": "consequatur necessitatibus totam sed sit dolorum\nrecusandae quae odio excepturi voluptatum harum voluptas\nquisquam sit ad eveniet delectus\ndoloribus odio qui non labore" - }, - { - "postId": 4, - "id": 18, - "name": "aut et tenetur ducimus illum aut nulla ab", - "email": "Vincenza_Klocko@albertha.name", - "body": "veritatis voluptates necessitatibus maiores corrupti\nneque et exercitationem amet sit et\nullam velit sit magnam laborum\nmagni ut molestias" - }, - { - "postId": 4, - "id": 19, - "name": "sed impedit rerum quia et et inventore unde officiis", - "email": "Madelynn.Gorczany@darion.biz", - "body": "doloribus est illo sed minima aperiam\nut dignissimos accusantium tempore atque et aut molestiae\nmagni ut accusamus voluptatem quos ut voluptates\nquisquam porro sed architecto ut" - }, - { - "postId": 4, - "id": 20, - "name": "molestias expedita iste aliquid voluptates", - "email": "Mariana_Orn@preston.org", - "body": "qui harum consequatur fugiat\net eligendi perferendis at molestiae commodi ducimus\ndoloremque asperiores numquam qui\nut sit dignissimos reprehenderit tempore" - }, - { - "postId": 5, - "id": 21, - "name": "aliquid rerum mollitia qui a consectetur eum sed", - "email": "Noemie@marques.me", - "body": "deleniti aut sed molestias explicabo\ncommodi odio ratione nesciunt\nvoluptate doloremque est\nnam autem error delectus" - }, - { - "postId": 5, - "id": 22, - "name": "porro repellendus aut tempore quis hic", - "email": "Khalil@emile.co.uk", - "body": "qui ipsa animi nostrum praesentium voluptatibus odit\nqui non impedit cum qui nostrum aliquid fuga explicabo\nvoluptatem fugit earum voluptas exercitationem temporibus dignissimos distinctio\nesse inventore reprehenderit quidem ut incidunt nihil necessitatibus rerum" - }, - { - "postId": 5, - "id": 23, - "name": "quis tempora quidem nihil iste", - "email": "Sophia@arianna.co.uk", - "body": "voluptates provident repellendus iusto perspiciatis ex fugiat ut\nut dolor nam aliquid et expedita voluptate\nsunt vitae illo rerum in quos\nvel eligendi enim quae fugiat est" - }, - { - "postId": 5, - "id": 24, - "name": "in tempore eos beatae est", - "email": "Jeffery@juwan.us", - "body": "repudiandae repellat quia\nsequi est dolore explicabo nihil et\net sit et\net praesentium iste atque asperiores tenetur" - }, - { - "postId": 5, - "id": 25, - "name": "autem ab ea sit alias hic provident sit", - "email": "Isaias_Kuhic@jarrett.net", - "body": "sunt aut quae laboriosam sit ut impedit\nadipisci harum laborum totam deleniti voluptas odit rem ea\nnon iure distinctio ut velit doloribus\net non ex" - }, - { - "postId": 6, - "id": 26, - "name": "in deleniti sunt provident soluta ratione veniam quam praesentium", - "email": "Russel.Parker@kameron.io", - "body": "incidunt sapiente eaque dolor eos\nad est molestias\nquas sit et nihil exercitationem at cumque ullam\nnihil magnam et" - }, - { - "postId": 6, - "id": 27, - "name": "doloribus quibusdam molestiae amet illum", - "email": "Francesco.Gleason@nella.us", - "body": "nisi vel quas ut laborum ratione\nrerum magni eum\nunde et voluptatem saepe\nvoluptas corporis modi amet ipsam eos saepe porro" - }, - { - "postId": 6, - "id": 28, - "name": "quo voluptates voluptas nisi veritatis dignissimos dolores ut officiis", - "email": "Ronny@rosina.org", - "body": "voluptatem repellendus quo alias at laudantium\nmollitia quidem esse\ntemporibus consequuntur vitae rerum illum\nid corporis sit id" - }, - { - "postId": 6, - "id": 29, - "name": "eum distinctio amet dolor", - "email": "Jennings_Pouros@erica.biz", - "body": "tempora voluptatem est\nmagnam distinctio autem est dolorem\net ipsa molestiae odit rerum itaque corporis nihil nam\neaque rerum error" - }, - { - "postId": 6, - "id": 30, - "name": "quasi nulla ducimus facilis non voluptas aut", - "email": "Lurline@marvin.biz", - "body": "consequuntur quia voluptate assumenda et\nautem voluptatem reiciendis ipsum animi est provident\nearum aperiam sapiente ad vitae iste\naccusantium aperiam eius qui dolore voluptatem et" - }, - { - "postId": 7, - "id": 31, - "name": "ex velit ut cum eius odio ad placeat", - "email": "Buford@shaylee.biz", - "body": "quia incidunt ut\naliquid est ut rerum deleniti iure est\nipsum quia ea sint et\nvoluptatem quaerat eaque repudiandae eveniet aut" - }, - { - "postId": 7, - "id": 32, - "name": "dolorem architecto ut pariatur quae qui suscipit", - "email": "Maria@laurel.name", - "body": "nihil ea itaque libero illo\nofficiis quo quo dicta inventore consequatur voluptas voluptatem\ncorporis sed necessitatibus velit tempore\nrerum velit et temporibus" - }, - { - "postId": 7, - "id": 33, - "name": "voluptatum totam vel voluptate omnis", - "email": "Jaeden.Towne@arlene.tv", - "body": "fugit harum quae vero\nlibero unde tempore\nsoluta eaque culpa sequi quibusdam nulla id\net et necessitatibus" - }, - { - "postId": 7, - "id": 34, - "name": "omnis nemo sunt ab autem", - "email": "Ethelyn.Schneider@emelia.co.uk", - "body": "omnis temporibus quasi ab omnis\nfacilis et omnis illum quae quasi aut\nminus iure ex rem ut reprehenderit\nin non fugit" - }, - { - "postId": 7, - "id": 35, - "name": "repellendus sapiente omnis praesentium aliquam ipsum id molestiae omnis", - "email": "Georgianna@florence.io", - "body": "dolor mollitia quidem facere et\nvel est ut\nut repudiandae est quidem dolorem sed atque\nrem quia aut adipisci sunt" - }, - { - "postId": 8, - "id": 36, - "name": "sit et quis", - "email": "Raheem_Heaney@gretchen.biz", - "body": "aut vero est\ndolor non aut excepturi dignissimos illo nisi aut quas\naut magni quia nostrum provident magnam quas modi maxime\nvoluptatem et molestiae" - }, - { - "postId": 8, - "id": 37, - "name": "beatae veniam nemo rerum voluptate quam aspernatur", - "email": "Jacky@victoria.net", - "body": "qui rem amet aut\ncumque maiores earum ut quia sit nam esse qui\niusto aspernatur quis voluptas\ndolorem distinctio ex temporibus rem" - }, - { - "postId": 8, - "id": 38, - "name": "maiores dolores expedita", - "email": "Piper@linwood.us", - "body": "unde voluptatem qui dicta\nvel ad aut eos error consequatur voluptatem\nadipisci doloribus qui est sit aut\nvelit aut et ea ratione eveniet iure fuga" - }, - { - "postId": 8, - "id": 39, - "name": "necessitatibus ratione aut ut delectus quae ut", - "email": "Gaylord@russell.net", - "body": "atque consequatur dolorem sunt\nadipisci autem et\nvoluptatibus et quae necessitatibus rerum eaque aperiam nostrum nemo\neligendi sed et beatae et inventore" - }, - { - "postId": 8, - "id": 40, - "name": "non minima omnis deleniti pariatur facere quibusdam at", - "email": "Clare.Aufderhar@nicole.ca", - "body": "quod minus alias quos\nperferendis labore molestias quae ut ut corporis deserunt vitae\net quaerat ut et ullam unde asperiores\ncum voluptatem cumque" - }, - { - "postId": 9, - "id": 41, - "name": "voluptas deleniti ut", - "email": "Lucio@gladys.tv", - "body": "facere repudiandae vitae ea aut sed quo ut et\nfacere nihil ut voluptates in\nsaepe cupiditate accusantium numquam dolores\ninventore sint mollitia provident" - }, - { - "postId": 9, - "id": 42, - "name": "nam qui et", - "email": "Shemar@ewell.name", - "body": "aut culpa quaerat veritatis eos debitis\naut repellat eius explicabo et\nofficiis quo sint at magni ratione et iure\nincidunt quo sequi quia dolorum beatae qui" - }, - { - "postId": 9, - "id": 43, - "name": "molestias sint est voluptatem modi", - "email": "Jackeline@eva.tv", - "body": "voluptatem ut possimus laborum quae ut commodi delectus\nin et consequatur\nin voluptas beatae molestiae\nest rerum laborum et et velit sint ipsum dolorem" - }, - { - "postId": 9, - "id": 44, - "name": "hic molestiae et fuga ea maxime quod", - "email": "Marianna_Wilkinson@rupert.io", - "body": "qui sunt commodi\nsint vel optio vitae quis qui non distinctio\nid quasi modi dicta\neos nihil sit inventore est numquam officiis" - }, - { - "postId": 9, - "id": 45, - "name": "autem illo facilis", - "email": "Marcia@name.biz", - "body": "ipsum odio harum voluptatem sunt cumque et dolores\nnihil laboriosam neque commodi qui est\nquos numquam voluptatum\ncorporis quo in vitae similique cumque tempore" - }, - { - "postId": 10, - "id": 46, - "name": "dignissimos et deleniti voluptate et quod", - "email": "Jeremy.Harann@waino.me", - "body": "exercitationem et id quae cum omnis\nvoluptatibus accusantium et quidem\nut ipsam sint\ndoloremque illo ex atque necessitatibus sed" - }, - { - "postId": 10, - "id": 47, - "name": "rerum commodi est non dolor nesciunt ut", - "email": "Pearlie.Kling@sandy.com", - "body": "occaecati laudantium ratione non cumque\nearum quod non enim soluta nisi velit similique voluptatibus\nesse laudantium consequatur voluptatem rem eaque voluptatem aut ut\net sit quam" - }, - { - "postId": 10, - "id": 48, - "name": "consequatur animi dolorem saepe repellendus ut quo aut tenetur", - "email": "Manuela_Stehr@chelsie.tv", - "body": "illum et alias quidem magni voluptatum\nab soluta ea qui saepe corrupti hic et\ncum repellat esse\nest sint vel veritatis officia consequuntur cum" - }, - { - "postId": 10, - "id": 49, - "name": "rerum placeat quae minus iusto eligendi", - "email": "Camryn.Weimann@doris.io", - "body": "id est iure occaecati quam similique enim\nab repudiandae non\nillum expedita quam excepturi soluta qui placeat\nperspiciatis optio maiores non doloremque aut iusto sapiente" - }, - { - "postId": 10, - "id": 50, - "name": "dolorum soluta quidem ex quae occaecati dicta aut doloribus", - "email": "Kiana_Predovic@yasmin.io", - "body": "eum accusamus aut delectus\narchitecto blanditiis quia sunt\nrerum harum sit quos quia aspernatur vel corrupti inventore\nanimi dicta vel corporis" - }, - { - "postId": 11, - "id": 51, - "name": "molestias et odio ut commodi omnis ex", - "email": "Laurie@lincoln.us", - "body": "perferendis omnis esse\nvoluptate sit mollitia sed perferendis\nnemo nostrum qui\nvel quis nisi doloribus animi odio id quas" - }, - { - "postId": 11, - "id": 52, - "name": "esse autem dolorum", - "email": "Abigail.OConnell@june.org", - "body": "et enim voluptatem totam laudantium\nimpedit nam labore repellendus enim earum aut\nconsectetur mollitia fugit qui repellat expedita sunt\naut fugiat vel illo quos aspernatur ducimus" - }, - { - "postId": 11, - "id": 53, - "name": "maiores alias necessitatibus aut non", - "email": "Laverne_Price@scotty.info", - "body": "a at tempore\nmolestiae odit qui dolores molestias dolorem et\nlaboriosam repudiandae placeat quisquam\nautem aperiam consectetur maiores laboriosam nostrum" - }, - { - "postId": 11, - "id": 54, - "name": "culpa eius tempora sit consequatur neque iure deserunt", - "email": "Kenton_Vandervort@friedrich.com", - "body": "et ipsa rem ullam cum pariatur similique quia\ncum ipsam est sed aut inventore\nprovident sequi commodi enim inventore assumenda aut aut\ntempora possimus soluta quia consequatur modi illo" - }, - { - "postId": 11, - "id": 55, - "name": "quas pariatur quia a doloribus", - "email": "Hayden_Olson@marianna.me", - "body": "perferendis eaque labore laudantium ut molestiae soluta et\nvero odio non corrupti error pariatur consectetur et\nenim nam quia voluptatum non\nmollitia culpa facere voluptas suscipit veniam" - }, - { - "postId": 12, - "id": 56, - "name": "et dolorem corrupti sed molestias", - "email": "Vince_Crist@heidi.biz", - "body": "cum esse odio nihil reiciendis illum quaerat\nest facere quia\noccaecati sit totam fugiat in beatae\nut occaecati unde vitae nihil quidem consequatur" - }, - { - "postId": 12, - "id": 57, - "name": "qui quidem sed", - "email": "Darron.Nikolaus@eulah.me", - "body": "dolorem facere itaque fuga odit autem\nperferendis quisquam quis corrupti eius dicta\nrepudiandae error esse itaque aut\ncorrupti sint consequatur aliquid" - }, - { - "postId": 12, - "id": 58, - "name": "sint minus reiciendis qui perspiciatis id", - "email": "Ezra_Abshire@lyda.us", - "body": "veritatis qui nihil\nquia reprehenderit non ullam ea iusto\nconsectetur nam voluptas ut temporibus tempore provident error\neos et nisi et voluptate" - }, - { - "postId": 12, - "id": 59, - "name": "quis ducimus distinctio similique et illum minima ab libero", - "email": "Jameson@tony.info", - "body": "cumque molestiae officia aut fugiat nemo autem\nvero alias atque sed qui ratione quia\nrepellat vel earum\nea laudantium mollitia" - }, - { - "postId": 12, - "id": 60, - "name": "expedita libero quos cum commodi ad", - "email": "Americo@estrella.net", - "body": "error eum quia voluptates alias repudiandae\naccusantium veritatis maiores assumenda\nquod impedit animi tempore veritatis\nanimi et et officiis labore impedit blanditiis repudiandae" - }, - { - "postId": 13, - "id": 61, - "name": "quidem itaque dolores quod laborum aliquid molestiae", - "email": "Aurelio.Pfeffer@griffin.ca", - "body": "deserunt cumque laudantium\net et odit quia sint quia quidem\nquibusdam debitis fuga in tempora deleniti\nimpedit consequatur veniam reiciendis autem porro minima" - }, - { - "postId": 13, - "id": 62, - "name": "libero beatae consequuntur optio est hic", - "email": "Vesta_Crooks@dora.us", - "body": "tempore dolorum corrupti facilis\npraesentium sunt iste recusandae\nunde quisquam similique\nalias consequuntur voluptates velit" - }, - { - "postId": 13, - "id": 63, - "name": "occaecati dolor accusantium et quasi architecto aut eveniet fugiat", - "email": "Margarett_Klein@mike.biz", - "body": "aut eligendi et molestiae voluptatum tempora\nofficia nihil sit voluptatem aut deleniti\nquaerat consequatur eaque\nsapiente tempore commodi tenetur rerum qui quo" - }, - { - "postId": 13, - "id": 64, - "name": "consequatur aut ullam voluptas dolorum voluptatum sequi et", - "email": "Freida@brandt.tv", - "body": "sed illum quis\nut aut culpa labore aspernatur illo\ndolorem quia vitae ut aut quo repellendus est omnis\nesse at est debitis" - }, - { - "postId": 13, - "id": 65, - "name": "earum ea ratione numquam", - "email": "Mollie@agustina.name", - "body": "qui debitis vitae ratione\ntempora impedit aperiam porro molestiae placeat vero laboriosam recusandae\npraesentium consequatur facere et itaque quidem eveniet\nmagnam natus distinctio sapiente" - }, - { - "postId": 14, - "id": 66, - "name": "eius nam consequuntur", - "email": "Janice@alda.io", - "body": "necessitatibus libero occaecati\nvero inventore iste assumenda veritatis\nasperiores non sit et quia omnis facere nemo explicabo\nodit quo nobis porro" - }, - { - "postId": 14, - "id": 67, - "name": "omnis consequatur natus distinctio", - "email": "Dashawn@garry.com", - "body": "nulla quo itaque beatae ad\nofficiis animi aut exercitationem voluptatum dolorem doloremque ducimus in\nrecusandae officia consequuntur quas\naspernatur dolores est esse dolores sit illo laboriosam quaerat" - }, - { - "postId": 14, - "id": 68, - "name": "architecto ut deserunt consequatur cumque sapiente", - "email": "Devan.Nader@ettie.me", - "body": "sed magni accusantium numquam quidem omnis et voluptatem beatae\nquos fugit libero\nvel ipsa et nihil recusandae ea\niste nemo qui optio sit enim ut nostrum odit" - }, - { - "postId": 14, - "id": 69, - "name": "at aut ea iure accusantium voluptatum nihil ipsum", - "email": "Joana.Schoen@leora.co.uk", - "body": "omnis dolor autem qui est natus\nautem animi nemo voluptatum aut natus accusantium iure\ninventore sunt ea tenetur commodi suscipit facere architecto consequatur\ndolorem nihil veritatis consequuntur corporis" - }, - { - "postId": 14, - "id": 70, - "name": "eum magni accusantium labore aut cum et tenetur", - "email": "Minerva.Anderson@devonte.ca", - "body": "omnis aliquam praesentium ad voluptatem harum aperiam dolor autem\nhic asperiores quisquam ipsa necessitatibus suscipit\npraesentium rem deserunt et\nfacere repellendus aut sed fugit qui est" - }, - { - "postId": 15, - "id": 71, - "name": "vel pariatur perferendis vero ab aut voluptates labore", - "email": "Lavinia@lafayette.me", - "body": "mollitia magnam et\nipsum consequatur est expedita\naut rem ut ex doloremque est vitae est\ncumque velit recusandae numquam libero dolor fuga fugit a" - }, - { - "postId": 15, - "id": 72, - "name": "quia sunt dolor dolor suscipit expedita quis", - "email": "Sabrina.Marks@savanah.name", - "body": "quisquam voluptas ut\npariatur eos amet non\nreprehenderit voluptates numquam\nin est voluptatem dicta ipsa qui esse enim" - }, - { - "postId": 15, - "id": 73, - "name": "ut quia ipsa repellat sunt et sequi aut est", - "email": "Desmond_Graham@kailee.biz", - "body": "nam qui possimus deserunt\ninventore dignissimos nihil rerum ut consequatur vel architecto\ntenetur recusandae voluptate\nnumquam dignissimos aliquid ut reprehenderit voluptatibus" - }, - { - "postId": 15, - "id": 74, - "name": "ut non illum pariatur dolor", - "email": "Gussie_Kunde@sharon.biz", - "body": "non accusamus eum aut et est\naccusantium animi nesciunt distinctio ea quas quisquam\nsit ut voluptatem modi natus sint\nfacilis est qui molestias recusandae nemo" - }, - { - "postId": 15, - "id": 75, - "name": "minus laboriosam consequuntur", - "email": "Richard@chelsie.co.uk", - "body": "natus numquam enim asperiores doloremque ullam et\nest molestias doloribus cupiditate labore vitae aut voluptatem\nitaque quos quo consectetur nihil illum veniam\nnostrum voluptatum repudiandae ut" - }, - { - "postId": 16, - "id": 76, - "name": "porro ut soluta repellendus similique", - "email": "Gage_Turner@halle.name", - "body": "sunt qui consequatur similique recusandae repellendus voluptates eos et\nvero nostrum fugit magnam aliquam\nreprehenderit nobis voluptatem eos consectetur possimus\net perferendis qui ea fugiat sit doloremque" - }, - { - "postId": 16, - "id": 77, - "name": "dolores et quo omnis voluptates", - "email": "Alfred@sadye.biz", - "body": "eos ullam dolorem impedit labore mollitia\nrerum non dolores\nmolestiae dignissimos qui maxime sed voluptate consequatur\ndoloremque praesentium magnam odio iste quae totam aut" - }, - { - "postId": 16, - "id": 78, - "name": "voluptas voluptas voluptatibus blanditiis", - "email": "Catharine@jordyn.com", - "body": "qui adipisci eveniet excepturi iusto magni et\nenim ducimus asperiores blanditiis nemo\ncommodi nihil ex\nenim rerum vel nobis nostrum et non" - }, - { - "postId": 16, - "id": 79, - "name": "beatae ut ad quisquam sed repellendus et", - "email": "Esther_Ratke@shayna.biz", - "body": "et inventore sapiente sed\nsunt similique fugiat officia velit doloremque illo eligendi quas\nsed rerum in quidem perferendis facere molestias\ndolore dolor voluptas nam vel quia" - }, - { - "postId": 16, - "id": 80, - "name": "et cumque ad culpa ut eligendi non", - "email": "Evangeline@chad.net", - "body": "dignissimos itaque ab et tempore odio omnis voluptatem\nitaque perferendis rem repellendus tenetur nesciunt velit\nqui cupiditate recusandae\nquis debitis dolores aliquam nam" - }, - { - "postId": 17, - "id": 81, - "name": "aut non consequuntur dignissimos voluptatibus dolorem earum recusandae dolorem", - "email": "Newton.Kertzmann@anjali.io", - "body": "illum et voluptatem quis repellendus quidem repellat\nreprehenderit voluptas consequatur mollitia\npraesentium nisi quo quod et\noccaecati repellendus illo eius harum explicabo doloribus officia" - }, - { - "postId": 17, - "id": 82, - "name": "ea est non dolorum iste nihil est", - "email": "Caleb_Herzog@rosamond.net", - "body": "officiis dolorem voluptas aliquid eveniet tempora qui\nea temporibus labore accusamus sint\nut sunt necessitatibus voluptatum animi cumque\nat reiciendis voluptatem iure aliquid et qui dolores et" - }, - { - "postId": 17, - "id": 83, - "name": "nihil qui accusamus ratione et molestias et minus", - "email": "Sage_Mueller@candace.net", - "body": "et consequatur voluptates magnam fugit sunt repellendus nihil earum\nofficiis aut cupiditate\net distinctio laboriosam\nmolestiae sunt dolor explicabo recusandae" - }, - { - "postId": 17, - "id": 84, - "name": "quia voluptatibus magnam voluptatem optio vel perspiciatis", - "email": "Bernie.Bergnaum@lue.com", - "body": "ratione ut magni voluptas\nexplicabo natus quia consequatur nostrum aut\nomnis enim in qui illum\naut atque laboriosam aliquid blanditiis quisquam et laborum" - }, - { - "postId": 17, - "id": 85, - "name": "non voluptas cum est quis aut consectetur nam", - "email": "Alexzander_Davis@eduardo.name", - "body": "quisquam incidunt dolores sint qui doloribus provident\nvel cupiditate deleniti alias voluptatem placeat ad\nut dolorem illum unde iure quis libero neque\nea et distinctio id" - }, - { - "postId": 18, - "id": 86, - "name": "suscipit est sunt vel illum sint", - "email": "Jacquelyn@krista.info", - "body": "eum culpa debitis sint\neaque quia magni laudantium qui neque voluptas\nvoluptatem qui molestiae vel earum est ratione excepturi\nsit aut explicabo et repudiandae ut perspiciatis" - }, - { - "postId": 18, - "id": 87, - "name": "dolor asperiores autem et omnis quasi nobis", - "email": "Grover_Volkman@coty.tv", - "body": "assumenda corporis architecto repudiandae omnis qui et odit\nperferendis velit enim\net quia reiciendis sint\nquia voluptas quam deserunt facilis harum eligendi" - }, - { - "postId": 18, - "id": 88, - "name": "officiis aperiam odit sint est non", - "email": "Jovanny@abigale.ca", - "body": "laudantium corrupti atque exercitationem quae enim et veniam dicta\nautem perspiciatis sit dolores\nminima consectetur tenetur iste facere\namet est neque" - }, - { - "postId": 18, - "id": 89, - "name": "in voluptatum nostrum voluptas iure nisi rerum est placeat", - "email": "Isac_Schmeler@barton.com", - "body": "quibusdam rerum quia nostrum culpa\nculpa est natus impedit quo rem voluptate quos\nrerum culpa aut ut consectetur\nsunt esse laudantium voluptatibus cupiditate rerum" - }, - { - "postId": 18, - "id": 90, - "name": "eum voluptas dolores molestias odio amet repellendus", - "email": "Sandy.Erdman@sabina.info", - "body": "vitae cupiditate excepturi eum veniam laudantium aspernatur blanditiis\naspernatur quia ut assumenda et magni enim magnam\nin voluptate tempora\nnon qui voluptatem reprehenderit porro qui voluptatibus" - }, - { - "postId": 19, - "id": 91, - "name": "repellendus est laboriosam voluptas veritatis", - "email": "Alexandro@garry.io", - "body": "qui nisi at maxime deleniti quo\nex quas tenetur nam\ndeleniti aut asperiores deserunt cum ex eaque alias sit\net veniam ab consequatur molestiae" - }, - { - "postId": 19, - "id": 92, - "name": "repellendus aspernatur occaecati tempore blanditiis deleniti omnis qui distinctio", - "email": "Vickie_Schuster@harley.net", - "body": "nihil necessitatibus omnis asperiores nobis praesentium quia\nab debitis quo deleniti aut sequi commodi\nut perspiciatis quod est magnam aliquam modi\neum quos aliquid ea est" - }, - { - "postId": 19, - "id": 93, - "name": "mollitia dolor deleniti sed iure laudantium", - "email": "Roma_Doyle@alia.com", - "body": "ut quis et id repellat labore\nnobis itaque quae saepe est ullam aut\ndolor id ut quis\nsunt iure voluptates expedita voluptas doloribus modi saepe autem" - }, - { - "postId": 19, - "id": 94, - "name": "vero repudiandae voluptatem nobis", - "email": "Tatum_Marks@jaylon.name", - "body": "reiciendis delectus nulla quae voluptas nihil provident quia\nab corporis nesciunt blanditiis quibusdam et unde et\nmagni eligendi aperiam corrupti perspiciatis quasi\nneque iure voluptatibus mollitia" - }, - { - "postId": 19, - "id": 95, - "name": "voluptatem unde quos provident ad qui sit et excepturi", - "email": "Juston.Ruecker@scot.tv", - "body": "at ut tenetur rem\nut fuga quis ea magnam alias\naut tempore fugiat laboriosam porro quia iure qui\narchitecto est enim" - }, - { - "postId": 20, - "id": 96, - "name": "non sit ad culpa quis", - "email": "River.Grady@lavada.biz", - "body": "eum itaque quam\nlaboriosam sequi ullam quos nobis\nomnis dignissimos nam dolores\nfacere id suscipit aliquid culpa rerum quis" - }, - { - "postId": 20, - "id": 97, - "name": "reiciendis culpa omnis suscipit est", - "email": "Claudia@emilia.ca", - "body": "est ducimus voluptate saepe iusto repudiandae recusandae et\nsint fugit voluptas eum itaque\nodit ab eos voluptas molestiae necessitatibus earum possimus voluptatem\nquibusdam aut illo beatae qui delectus aut officia veritatis" - }, - { - "postId": 20, - "id": 98, - "name": "praesentium dolorem ea voluptate et", - "email": "Torrey@june.tv", - "body": "ex et expedita cum voluptatem\nvoluptatem ab expedita quis nihil\nesse quo nihil perferendis dolores velit ut culpa aut\ndolor maxime necessitatibus voluptatem" - }, - { - "postId": 20, - "id": 99, - "name": "laudantium delectus nam", - "email": "Hildegard.Aufderhar@howard.com", - "body": "aut quam consequatur sit et\nrepellat maiores laborum similique voluptatem necessitatibus nihil\net debitis nemo occaecati cupiditate\nmodi dolorum quia aut" - }, - { - "postId": 20, - "id": 100, - "name": "et sint quia dolor et est ea nulla cum", - "email": "Leone_Fay@orrin.com", - "body": "architecto dolorem ab explicabo et provident et\net eos illo omnis mollitia ex aliquam\natque ut ipsum nulla nihil\nquis voluptas aut debitis facilis" - }, - { - "postId": 21, - "id": 101, - "name": "perspiciatis magnam ut eum autem similique explicabo expedita", - "email": "Lura@rod.tv", - "body": "ut aut maxime officia sed aliquam et magni autem\nveniam repudiandae nostrum odio enim eum optio aut\nomnis illo quasi quibusdam inventore explicabo\nreprehenderit dolor saepe possimus molestiae" - }, - { - "postId": 21, - "id": 102, - "name": "officia ullam ut neque earum ipsa et fuga", - "email": "Lottie.Zieme@ruben.us", - "body": "aut dolorem quos ut non\naliquam unde iure minima quod ullam qui\nfugiat molestiae tempora voluptate vel labore\nsaepe animi et vitae numquam ipsa" - }, - { - "postId": 21, - "id": 103, - "name": "ipsum a ut", - "email": "Winona_Price@jevon.me", - "body": "totam eum fugiat repellendus\nquae beatae explicabo excepturi iusto et\nrepellat alias iure voluptates consequatur sequi minus\nsed maxime unde" - }, - { - "postId": 21, - "id": 104, - "name": "a assumenda totam", - "email": "Gabriel@oceane.biz", - "body": "qui aperiam labore animi magnam odit est\nut autem eaque ea magni quas voluptatem\ndoloribus vel voluptatem nostrum ut debitis enim quaerat\nut esse eveniet aut" - }, - { - "postId": 21, - "id": 105, - "name": "voluptatem repellat est", - "email": "Adolph.Ondricka@mozell.co.uk", - "body": "ut rerum illum error at inventore ab nobis molestiae\nipsa architecto temporibus non aliquam aspernatur omnis quidem aliquid\nconsequatur non et expedita cumque voluptates ipsam quia\nblanditiis libero itaque sed iusto at" - }, - { - "postId": 22, - "id": 106, - "name": "maiores placeat facere quam pariatur", - "email": "Allen@richard.biz", - "body": "dolores debitis voluptatem ab hic\nmagnam alias qui est sunt\net porro velit et repellendus occaecati est\nsequi quia odio deleniti illum" - }, - { - "postId": 22, - "id": 107, - "name": "in ipsam vel id impedit possimus eos voluptate", - "email": "Nicholaus@mikayla.ca", - "body": "eveniet fugit qui\nporro eaque dolores eos adipisci dolores ut\nfugit perferendis pariatur\nnumquam et repellat occaecati atque ipsum neque" - }, - { - "postId": 22, - "id": 108, - "name": "ut veritatis corporis placeat suscipit consequatur quaerat", - "email": "Kayla@susanna.org", - "body": "at a vel sequi nostrum\nharum nam nihil\ncumque aut in dolore rerum ipsa hic ratione\nrerum cum ratione provident labore ad quisquam repellendus a" - }, - { - "postId": 22, - "id": 109, - "name": "eveniet ut similique accusantium qui dignissimos", - "email": "Gideon@amina.name", - "body": "aliquid qui dolorem deserunt aperiam natus corporis eligendi neque\nat et sunt aut qui\nillum repellat qui excepturi laborum facilis aut omnis consequatur\net aut optio ipsa nisi enim" - }, - { - "postId": 22, - "id": 110, - "name": "sint est odit officiis similique aut corrupti quas autem", - "email": "Cassidy@maribel.io", - "body": "cum sequi in eligendi id eaque\ndolores accusamus dolorem eum est voluptatem quisquam tempore\nin voluptas enim voluptatem asperiores voluptatibus\neius quo quos quasi voluptas earum ut necessitatibus" - }, - { - "postId": 23, - "id": 111, - "name": "possimus facilis deleniti nemo atque voluptate", - "email": "Stefan.Crist@duane.ca", - "body": "ullam autem et\naccusantium quod sequi similique soluta explicabo ipsa\neius ratione quisquam sed et excepturi occaecati pariatur\nmolestiae ut reiciendis eum voluptatem sed" - }, - { - "postId": 23, - "id": 112, - "name": "dolore aut aspernatur est voluptate quia ipsam", - "email": "Aniyah.Ortiz@monte.me", - "body": "ut tempora deleniti quo molestiae eveniet provident earum occaecati\nest nesciunt ut pariatur ipsa voluptas voluptatem aperiam\nqui deleniti quibusdam voluptas molestiae facilis id iusto similique\ntempora aut qui" - }, - { - "postId": 23, - "id": 113, - "name": "sint quo debitis deleniti repellat", - "email": "Laverna@rico.biz", - "body": "voluptatem sint quia modi accusantium alias\nrecusandae rerum voluptatem aut sit et ut magnam\nvoluptas rerum odio quo labore voluptatem facere consequuntur\nut sit voluptatum hic distinctio" - }, - { - "postId": 23, - "id": 114, - "name": "optio et sunt non", - "email": "Derek@hildegard.net", - "body": "nihil labore qui\nquis dolor eveniet iste numquam\nporro velit incidunt\nlaboriosam asperiores aliquam facilis in et voluptas eveniet quasi" - }, - { - "postId": 23, - "id": 115, - "name": "occaecati dolorem eum in veniam quia quo reiciendis", - "email": "Tyrell@abdullah.ca", - "body": "laudantium tempore aut\nmaiores laborum fugit qui suscipit hic sint officiis corrupti\nofficiis eum optio cumque fuga sed voluptatibus similique\nsit consequuntur rerum commodi" - }, - { - "postId": 24, - "id": 116, - "name": "veritatis sit tempora quasi fuga aut dolorum", - "email": "Reyes@hailey.name", - "body": "quia voluptas qui assumenda nesciunt harum iusto\nest corrupti aperiam\nut aut unde maxime consequatur eligendi\nveniam modi id sint rem labore saepe minus" - }, - { - "postId": 24, - "id": 117, - "name": "incidunt quae optio quam corporis iste deleniti accusantium vero", - "email": "Danika.Dicki@mekhi.biz", - "body": "doloribus esse necessitatibus qui eos et ut est saepe\nsed rerum tempore est ut\nquisquam et eligendi accusantium\ncommodi non doloribus" - }, - { - "postId": 24, - "id": 118, - "name": "quisquam laborum reiciendis aut", - "email": "Alessandra.Nitzsche@stephania.us", - "body": "repudiandae aliquam maxime cupiditate consequatur id\nquas error repellendus\ntotam officia dolorem beatae natus cum exercitationem\nasperiores dolor ea" - }, - { - "postId": 24, - "id": 119, - "name": "minus pariatur odit", - "email": "Matteo@marquis.net", - "body": "et omnis consequatur ut\nin suscipit et voluptatem\nanimi at ut\ndolores quos aut numquam esse praesentium aut placeat nam" - }, - { - "postId": 24, - "id": 120, - "name": "harum error sit", - "email": "Joshua.Spinka@toby.io", - "body": "iusto sint recusandae placeat atque perferendis sit corporis molestiae\nrem dolor eius id delectus et qui\nsed dolorem reiciendis error ullam corporis delectus\nexplicabo mollitia odit laborum sed itaque deserunt rem dolorem" - }, - { - "postId": 25, - "id": 121, - "name": "deleniti quo corporis ullam magni praesentium molestiae", - "email": "Annabelle@cole.com", - "body": "soluta mollitia impedit cumque nostrum tempore aut placeat repellat\nenim adipisci dolores aut ut ratione laboriosam necessitatibus vel\net blanditiis est iste sapiente qui atque repellendus alias\nearum consequuntur quia quasi quia" - }, - { - "postId": 25, - "id": 122, - "name": "nihil tempora et reiciendis modi veniam", - "email": "Kacey@jamal.info", - "body": "doloribus veritatis a et quis corrupti incidunt est\nharum maiores impedit et beatae qui velit et aut\nporro sed dignissimos deserunt deleniti\net eveniet voluptas ipsa pariatur rem ducimus" - }, - { - "postId": 25, - "id": 123, - "name": "ad eos explicabo odio velit", - "email": "Mina@mallie.name", - "body": "nostrum perspiciatis doloribus\nexplicabo soluta id libero illo iste et\nab expedita error aliquam eum sint ipsum\nmodi possimus et" - }, - { - "postId": 25, - "id": 124, - "name": "nostrum suscipit aut consequatur magnam sunt fuga nihil", - "email": "Hudson.Blick@ruben.biz", - "body": "ut ut eius qui explicabo quis\niste autem nulla beatae tenetur enim\nassumenda explicabo consequatur harum exercitationem\nvelit itaque consectetur et possimus" - }, - { - "postId": 25, - "id": 125, - "name": "porro et voluptate et reprehenderit", - "email": "Domenic.Durgan@joaquin.name", - "body": "aut voluptas dolore autem\nreprehenderit expedita et nihil pariatur ea animi quo ullam\na ea officiis corporis\neius voluptatum cum mollitia dolore quaerat accusamus" - }, - { - "postId": 26, - "id": 126, - "name": "fuga tenetur id et qui labore delectus", - "email": "Alexie@alayna.org", - "body": "est qui ut tempore temporibus pariatur provident qui consequuntur\nlaboriosam porro dignissimos quos debitis id laborum et totam\naut eius sequi dolor maiores amet\nrerum voluptatibus quod ratione quos labore fuga sit" - }, - { - "postId": 26, - "id": 127, - "name": "consequatur harum magnam", - "email": "Haven_Barrows@brant.org", - "body": "omnis consequatur dignissimos iure rerum odio\nculpa laudantium quia voluptas enim est nisi\ndoloremque consequatur autem officiis necessitatibus beatae et\net itaque animi dolor praesentium" - }, - { - "postId": 26, - "id": 128, - "name": "labore architecto quaerat tempora voluptas consequuntur animi", - "email": "Marianne@maximo.us", - "body": "exercitationem eius aut ullam vero\nimpedit similique maiores ea et in culpa possimus omnis\neos labore autem quam repellendus dolores deserunt voluptatem\nnon ullam eos accusamus" - }, - { - "postId": 26, - "id": 129, - "name": "deleniti facere tempore et perspiciatis voluptas quis voluptatem", - "email": "Fanny@danial.com", - "body": "fugit minima voluptatem est aut sed explicabo\nharum dolores at qui eaque\nmagni velit ut et\nnam et ut sunt excepturi repellat non commodi" - }, - { - "postId": 26, - "id": 130, - "name": "quod est non quia doloribus quam deleniti libero", - "email": "Trevion_Kuphal@bernice.name", - "body": "dicta sit culpa molestiae quasi at voluptate eos\ndolorem perferendis accusamus rerum expedita ipsum quis qui\nquos est deserunt\nrerum fuga qui aliquam in consequatur aspernatur" - }, - { - "postId": 27, - "id": 131, - "name": "voluptas quasi sunt laboriosam", - "email": "Emmet@guy.biz", - "body": "rem magnam at voluptatem\naspernatur et et nostrum rerum\ndignissimos eum quibusdam\noptio quod dolores et" - }, - { - "postId": 27, - "id": 132, - "name": "unde tenetur vero eum iusto", - "email": "Megane.Fritsch@claude.name", - "body": "ullam harum consequatur est rerum est\nmagni tenetur aperiam et\nrepudiandae et reprehenderit dolorum enim voluptas impedit\neligendi quis necessitatibus in exercitationem voluptatem qui" - }, - { - "postId": 27, - "id": 133, - "name": "est adipisci laudantium amet rem asperiores", - "email": "Amya@durward.ca", - "body": "sunt quis iure molestias qui ipsa commodi dolore a\nodio qui debitis earum\nunde ut omnis\ndoloremque corrupti at repellendus earum eum" - }, - { - "postId": 27, - "id": 134, - "name": "reiciendis quo est vitae dignissimos libero ut officiis fugiat", - "email": "Jasen_Rempel@willis.org", - "body": "corrupti perspiciatis eligendi\net omnis tempora nobis dolores hic\ndolorum vitae odit\nreiciendis sunt odit qui" - }, - { - "postId": 27, - "id": 135, - "name": "inventore fugiat dignissimos", - "email": "Harmony@reggie.com", - "body": "sapiente nostrum dolorem odit a\nsed animi non architecto doloremque unde\nnam aut aut ut facilis\net ut autem fugit minima culpa inventore non" - }, - { - "postId": 28, - "id": 136, - "name": "et expedita est odit", - "email": "Rosanna_Kunze@guy.net", - "body": "cum natus qui dolorem dolorum nihil ut nam tempore\nmodi nesciunt ipsum hic\nrem sunt possimus earum magnam similique aspernatur sed\ntotam sed voluptatem iusto id iste qui" - }, - { - "postId": 28, - "id": 137, - "name": "saepe dolore qui tempore nihil perspiciatis omnis omnis magni", - "email": "Ressie.Boehm@flossie.com", - "body": "reiciendis maiores id\nvoluptas sapiente deserunt itaque\nut omnis sunt\nnecessitatibus quibusdam dolorem voluptatem harum error" - }, - { - "postId": 28, - "id": 138, - "name": "ea optio nesciunt officia velit enim facilis commodi", - "email": "Domenic.Wuckert@jazmyne.us", - "body": "dolorem suscipit adipisci ad cum totam quia fugiat\nvel quia dolores molestiae eos\nomnis officia quidem quaerat alias vel distinctio\nvero provident et corporis a quia ut" - }, - { - "postId": 28, - "id": 139, - "name": "ut pariatur voluptate possimus quasi", - "email": "Rhett.OKon@brian.info", - "body": "facilis cumque nostrum dignissimos\ndoloremque saepe quia adipisci sunt\ndicta dolorum quo esse\nculpa iste ut asperiores cum aperiam" - }, - { - "postId": 28, - "id": 140, - "name": "consectetur tempore eum consequuntur", - "email": "Mathias@richmond.info", - "body": "velit ipsa fugiat sit qui vel nesciunt sapiente\nrepudiandae perferendis nemo eos quos perspiciatis aperiam\ndoloremque incidunt nostrum temporibus corrupti repudiandae vitae non corporis\ncupiditate suscipit quod sed numquam excepturi enim labore" - }, - { - "postId": 29, - "id": 141, - "name": "dignissimos perspiciatis voluptate quos rem qui temporibus excepturi", - "email": "Ottis@lourdes.org", - "body": "et ullam id eligendi rem sit\noccaecati et delectus in nemo\naut veritatis deserunt aspernatur dolor enim voluptas quos consequatur\nmolestiae temporibus error" - }, - { - "postId": 29, - "id": 142, - "name": "cum dolore sit quisquam provident nostrum vitae", - "email": "Estel@newton.ca", - "body": "cumque voluptas quo eligendi sit\nnemo ut ut dolor et cupiditate aut\net voluptatem quia aut maiores quas accusantium expedita quia\nbeatae aut ad quis soluta id dolorum" - }, - { - "postId": 29, - "id": 143, - "name": "velit molestiae assumenda perferendis voluptas explicabo", - "email": "Bertha@erik.co.uk", - "body": "est quasi maiores nisi reiciendis enim\ndolores minus facilis laudantium dignissimos\nreiciendis et facere occaecati dolores et\npossimus et vel et aut ipsa ad" - }, - { - "postId": 29, - "id": 144, - "name": "earum ipsum ea quas qui molestiae omnis unde", - "email": "Joesph@matteo.info", - "body": "voluptatem unde consequatur natus nostrum vel ut\nconsequatur sequi doloremque omnis dolorem maxime\neaque sunt excepturi\nfuga qui illum et accusamus" - }, - { - "postId": 29, - "id": 145, - "name": "magni iusto sit", - "email": "Alva@cassandre.net", - "body": "assumenda nihil et\nsed nulla tempora porro iste possimus aut sit officia\ncumque totam quis tenetur qui sequi\ndelectus aut sunt" - }, - { - "postId": 30, - "id": 146, - "name": "est qui debitis", - "email": "Vivienne@willis.org", - "body": "possimus necessitatibus quis\net dicta omnis voluptatem ea est\nsuscipit eum soluta in quia corrupti hic iusto\nconsequatur est aut qui earum nisi officiis sed culpa" - }, - { - "postId": 30, - "id": 147, - "name": "reiciendis et consectetur officiis beatae corrupti aperiam", - "email": "Angelita@aliza.me", - "body": "nihil aspernatur consequatur voluptatem facere sed fugiat ullam\nbeatae accusamus et fuga maxime vero\nomnis necessitatibus quisquam ipsum consectetur incidunt repellat voluptas\nerror quo et ab magnam quisquam" - }, - { - "postId": 30, - "id": 148, - "name": "iusto reprehenderit voluptatem modi", - "email": "Timmothy_Okuneva@alyce.tv", - "body": "nemo corporis quidem eius aut dolores\nitaque rerum quo occaecati mollitia incidunt\nautem est saepe nulla nobis a id\ndolore facilis placeat molestias in fugiat aliquam excepturi" - }, - { - "postId": 30, - "id": 149, - "name": "optio dolorem et reiciendis et recusandae quidem", - "email": "Moriah_Welch@richmond.org", - "body": "veniam est distinctio\nnihil quia eos sed\ndistinctio hic ut sint ducimus debitis dolorem voluptatum assumenda\neveniet ea perspiciatis" - }, - { - "postId": 30, - "id": 150, - "name": "id saepe numquam est facilis sint enim voluptas voluptatem", - "email": "Ramiro_Kuhn@harmon.biz", - "body": "est non atque eligendi aspernatur quidem earum mollitia\nminima neque nam exercitationem provident eum\nmaxime quo et ut illum sequi aut fuga repudiandae\nsapiente sed ea distinctio molestias illum consequatur libero quidem" - }, - { - "postId": 31, - "id": 151, - "name": "ut quas facilis laborum voluptatum consequatur odio voluptate et", - "email": "Cary@taurean.biz", - "body": "quos eos sint voluptatibus similique iusto perferendis omnis voluptas\nearum nulla cumque\ndolorem consequatur officiis quis consequatur aspernatur nihil ullam et\nenim enim unde nihil labore non ducimus" - }, - { - "postId": 31, - "id": 152, - "name": "quod doloremque omnis", - "email": "Tillman_Koelpin@luisa.com", - "body": "itaque veritatis explicabo\nquis voluptatem mollitia soluta id non\ndoloribus nobis fuga provident\nnesciunt saepe molestiae praesentium laboriosam" - }, - { - "postId": 31, - "id": 153, - "name": "dolorum et dolorem optio in provident", - "email": "Aleen@tania.biz", - "body": "et cumque error pariatur\nquo doloribus corrupti voluptates ad voluptatem consequatur voluptas dolores\npariatur at quas iste repellat et sed quasi\nea maiores rerum aut earum" - }, - { - "postId": 31, - "id": 154, - "name": "odit illo optio ea modi in", - "email": "Durward@cindy.com", - "body": "quod magni consectetur\nquod doloremque velit autem ipsam nisi praesentium ut\nlaboriosam quod deleniti\npariatur aliquam sint excepturi a consectetur quas eos" - }, - { - "postId": 31, - "id": 155, - "name": "adipisci laboriosam repudiandae omnis veritatis in facere similique rem", - "email": "Lester@chauncey.ca", - "body": "animi asperiores modi et tenetur vel magni\nid iusto aliquid ad\nnihil dolorem dolorum aut veritatis voluptates\nomnis cupiditate incidunt" - }, - { - "postId": 32, - "id": 156, - "name": "pariatur omnis in", - "email": "Telly_Lynch@karl.co.uk", - "body": "dolorum voluptas laboriosam quisquam ab\ntotam beatae et aut aliquid optio assumenda\nvoluptas velit itaque quidem voluptatem tempore cupiditate\nin itaque sit molestiae minus dolores magni" - }, - { - "postId": 32, - "id": 157, - "name": "aut nobis et consequatur", - "email": "Makenzie@libbie.io", - "body": "voluptas quia quo ad\nipsum voluptatum provident ut ipsam velit dignissimos aut assumenda\nut officia laudantium\nquibusdam sed minima" - }, - { - "postId": 32, - "id": 158, - "name": "explicabo est molestiae aut", - "email": "Amiya@perry.us", - "body": "et qui ad vero quis\nquisquam omnis fuga et vel nihil minima eligendi nostrum\nsed deserunt rem voluptates autem\nquia blanditiis cum sed" - }, - { - "postId": 32, - "id": 159, - "name": "voluptas blanditiis deserunt quia quis", - "email": "Meghan@akeem.tv", - "body": "deserunt deleniti officiis architecto consequatur molestiae facere dolor\nvoluptatem velit eos fuga dolores\nsit quia est a deleniti hic dolor quisquam repudiandae\nvoluptas numquam voluptatem impedit" - }, - { - "postId": 32, - "id": 160, - "name": "sint fugit esse", - "email": "Mitchel.Williamson@fletcher.io", - "body": "non reprehenderit aut sed quos est ad voluptatum\nest ut est dignissimos ut dolores consequuntur\ndebitis aspernatur consequatur est\nporro nulla laboriosam repellendus et nesciunt est libero placeat" - }, - { - "postId": 33, - "id": 161, - "name": "nesciunt quidem veritatis alias odit nisi voluptatem non est", - "email": "Ashlee_Jast@emie.biz", - "body": "sunt totam blanditiis\neum quos fugit et ab rerum nemo\nut iusto architecto\nut et eligendi iure placeat omnis" - }, - { - "postId": 33, - "id": 162, - "name": "animi vitae qui aut corrupti neque culpa modi", - "email": "Antwan@lori.ca", - "body": "nulla impedit porro in sed\nvoluptatem qui voluptas et enim beatae\nnobis et sit ipsam aut\nvoluptatem voluptatibus blanditiis officia quod eos omnis earum dolorem" - }, - { - "postId": 33, - "id": 163, - "name": "omnis ducimus ab temporibus nobis porro natus deleniti", - "email": "Estelle@valentina.info", - "body": "molestiae dolorem quae rem neque sapiente voluptatum nesciunt cum\nid rerum at blanditiis est accusantium est\neos illo porro ad\nquod repellendus ad et labore fugit dolorum" - }, - { - "postId": 33, - "id": 164, - "name": "eius corrupti ea", - "email": "Haylie@gino.name", - "body": "beatae aut ut autem sit officia rerum nostrum\nprovident ratione sed dicta omnis alias commodi rerum expedita\nnon nobis sapiente consectetur odit unde quia\nvoluptas in nihil consequatur doloremque ullam dolorem cum" - }, - { - "postId": 33, - "id": 165, - "name": "quia commodi molestiae assumenda provident sit incidunt laudantium", - "email": "Blake_Spinka@robyn.info", - "body": "sed praesentium ducimus minima autem corporis debitis\naperiam eum sit pariatur\nimpedit placeat illo odio\nodit accusantium expedita quo rerum magnam" - }, - { - "postId": 34, - "id": 166, - "name": "sint alias molestiae qui dolor vel", - "email": "Aimee.Bins@braeden.ca", - "body": "nam quas eaque unde\ndolor blanditiis cumque eaque omnis qui\nrerum modi sint quae numquam exercitationem\natque aut praesentium ipsa sit neque qui sint aut" - }, - { - "postId": 34, - "id": 167, - "name": "ea nam iste est repudiandae", - "email": "Eloy@vladimir.com", - "body": "molestiae voluptatem qui\nid facere nostrum quasi asperiores rerum\nquisquam est repellendus\ndeleniti eos aut sed nemo non" - }, - { - "postId": 34, - "id": 168, - "name": "quis harum voluptatem et culpa", - "email": "Gabrielle@jada.co.uk", - "body": "cupiditate optio in quidem adipisci sit dolor id\net tenetur quo sit odit\naperiam illum optio magnam qui\nmolestiae eligendi harum optio dolores dolor quaerat nostrum" - }, - { - "postId": 34, - "id": 169, - "name": "dolor dolore similique tempore ducimus", - "email": "Lee@dawn.net", - "body": "unde non aliquid magni accusantium architecto et\nrerum autem eos explicabo et\nodio facilis sed\nrerum ex esse beatae quia" - }, - { - "postId": 34, - "id": 170, - "name": "cupiditate labore omnis consequatur", - "email": "Gideon.Hyatt@jalen.tv", - "body": "amet id deserunt ipsam\ncupiditate distinctio nulla voluptatem\ncum possimus voluptate\nipsum quidem laudantium quos nihil" - }, - { - "postId": 35, - "id": 171, - "name": "voluptatibus qui est et", - "email": "Gerda.Reynolds@ceasar.co.uk", - "body": "sed non beatae placeat qui libero nam eaque qui\nquia ut ad doloremque\nsequi unde quidem adipisci debitis autem velit\narchitecto aperiam eos nihil enim dolores veritatis omnis ex" - }, - { - "postId": 35, - "id": 172, - "name": "corporis ullam quo", - "email": "Ivah@brianne.net", - "body": "nemo ullam omnis sit\nlabore perferendis et eveniet nostrum\ndignissimos expedita iusto\noccaecati quia sit quibusdam" - }, - { - "postId": 35, - "id": 173, - "name": "nulla accusamus saepe debitis cum", - "email": "Ethyl_Bogan@candace.co.uk", - "body": "asperiores hic fugiat aut et temporibus mollitia quo quam\ncumque numquam labore qui illum vel provident quod\npariatur natus incidunt\nsunt error voluptatibus vel voluptas maiores est vero possimus" - }, - { - "postId": 35, - "id": 174, - "name": "iure praesentium ipsam", - "email": "Janelle_Guann@americo.info", - "body": "sit dolores consequatur qui voluptas sunt\nearum at natus alias excepturi dolores\nmaiores pariatur at reiciendis\ndolor esse optio" - }, - { - "postId": 35, - "id": 175, - "name": "autem totam velit officiis voluptates et ullam rem", - "email": "Alfonzo.Barton@kelley.co.uk", - "body": "culpa non ea\nperspiciatis exercitationem sed natus sit\nenim voluptatum ratione omnis consequuntur provident commodi omnis\nquae odio sit at tempora" - }, - { - "postId": 36, - "id": 176, - "name": "ipsam deleniti incidunt repudiandae voluptatem maxime provident non dolores", - "email": "Esther@ford.me", - "body": "quam culpa fugiat\nrerum impedit ratione vel ipsam rem\ncommodi qui rem eum\nitaque officiis omnis ad" - }, - { - "postId": 36, - "id": 177, - "name": "ab cupiditate blanditiis ea sunt", - "email": "Naomie_Cronin@rick.co.uk", - "body": "ut facilis sapiente\nquia repellat autem et aut delectus sint\nautem nulla debitis\nomnis consequuntur neque" - }, - { - "postId": 36, - "id": 178, - "name": "rerum ex quam enim sunt", - "email": "Darryl@reginald.us", - "body": "sit maxime fugit\nsequi culpa optio consequatur voluptatem dolor expedita\nenim iure eum reprehenderit doloremque aspernatur modi\nvoluptatem culpa nostrum quod atque rerum sint laboriosam et" - }, - { - "postId": 36, - "id": 179, - "name": "et iure ex rerum qui", - "email": "Thea@aurelio.org", - "body": "non saepe ipsa velit sunt\ntotam ipsum optio voluptatem\nnesciunt qui iste eum\net deleniti ullam" - }, - { - "postId": 36, - "id": 180, - "name": "autem tempora a iusto eum aut voluptatum", - "email": "Carolyn@eloisa.biz", - "body": "recusandae temporibus nihil non alias consequatur\nlibero voluptatem sed soluta accusamus\na qui reiciendis officiis ad\nquia laborum libero et rerum voluptas sed ut et" - }, - { - "postId": 37, - "id": 181, - "name": "similique ut et non laboriosam in eligendi et", - "email": "Milan.Schoen@cortney.io", - "body": "dolor iure corrupti\net eligendi nesciunt voluptatum autem\nconsequatur in sapiente\ndolor voluptas dolores natus iusto qui et in perferendis" - }, - { - "postId": 37, - "id": 182, - "name": "soluta corporis excepturi consequatur perspiciatis quia voluptatem", - "email": "Sabrina@raymond.biz", - "body": "voluptatum voluptatem nisi consequatur et\nomnis nobis odio neque ab enim veniam\nsit qui aperiam odit voluptatem facere\nnesciunt esse nemo" - }, - { - "postId": 37, - "id": 183, - "name": "praesentium quod quidem optio omnis qui", - "email": "Hildegard@alford.ca", - "body": "tempora soluta voluptas deserunt\nnon fugiat similique\nest natus enim eum error magni soluta\ndolores sit doloremque" - }, - { - "postId": 37, - "id": 184, - "name": "veritatis velit quasi quo et voluptates dolore", - "email": "Lowell.Pagac@omari.biz", - "body": "odio saepe ad error minima itaque\nomnis fuga corrupti qui eaque cupiditate eum\nvitae laborum rerum ut reprehenderit architecto sit debitis magnam\nqui corrupti cum quidem commodi facere corporis" - }, - { - "postId": 37, - "id": 185, - "name": "natus assumenda ut", - "email": "Vivianne@ima.us", - "body": "deleniti non et corrupti delectus ea cupiditate\nat nihil fuga rerum\ntemporibus doloribus unde sed alias\nducimus perspiciatis quia debitis fuga" - }, - { - "postId": 38, - "id": 186, - "name": "voluptas distinctio qui similique quasi voluptatem non sit", - "email": "Yasmin.Prohaska@hanna.co.uk", - "body": "asperiores eaque error sunt ut natus et omnis\nexpedita error iste vitae\nsit alias voluptas voluptatibus quia iusto quia ea\nenim facere est quam ex" - }, - { - "postId": 38, - "id": 187, - "name": "maiores iste dolor itaque nemo voluptas", - "email": "Ursula.Kirlin@eino.org", - "body": "et enim necessitatibus velit autem magni voluptas\nat maxime error sunt nobis sit\ndolor beatae harum rerum\ntenetur facere pariatur et perferendis voluptas maiores nihil eaque" - }, - { - "postId": 38, - "id": 188, - "name": "quisquam quod quia nihil animi minima facere odit est", - "email": "Nichole_Bartoletti@mozell.me", - "body": "quam magni adipisci totam\nut reprehenderit ut tempore non asperiores repellendus architecto aperiam\ndignissimos est aut reiciendis consectetur voluptate nihil culpa at\nmolestiae labore qui ea" - }, - { - "postId": 38, - "id": 189, - "name": "ut iusto asperiores delectus", - "email": "Lottie_Wyman@jasen.biz", - "body": "nostrum excepturi debitis cum\narchitecto iusto laudantium odit aut dolor voluptatem consectetur nulla\nmollitia beatae autem quasi nemo repellendus ut ea et\naut architecto odio cum quod optio" - }, - { - "postId": 38, - "id": 190, - "name": "dignissimos voluptatibus libero", - "email": "Dominique_Hermann@paige.ca", - "body": "laudantium vero similique eum\niure iste culpa praesentium\nmolestias doloremque alias et at doloribus\naperiam natus est illo quo ratione porro excepturi" - }, - { - "postId": 39, - "id": 191, - "name": "est perferendis eos dolores maxime rerum qui", - "email": "Eugene@mohammed.net", - "body": "sit vero aut voluptatem soluta corrupti dolor cum\nnulla ipsa accusamus aut suscipit ut dicta ut nemo\nut et ut sit voluptas modi\nillum suscipit ea debitis aut ullam harum" - }, - { - "postId": 39, - "id": 192, - "name": "sunt veritatis quisquam est et porro nesciunt excepturi a", - "email": "Janick@marty.me", - "body": "dolore velit autem perferendis hic\ntenetur quo rerum\nimpedit error sit eaque ut\nad in expedita et nesciunt sit aspernatur repudiandae" - }, - { - "postId": 39, - "id": 193, - "name": "quia velit nostrum eligendi voluptates", - "email": "Alena@deron.name", - "body": "laudantium consequatur sed adipisci a\nodit quia necessitatibus qui\nnumquam expedita est accusantium nostrum\noccaecati perspiciatis molestiae nostrum atque" - }, - { - "postId": 39, - "id": 194, - "name": "non ut sunt ut eius autem ipsa eos sapiente", - "email": "Alphonso_Rosenbaum@valentin.co.uk", - "body": "aut distinctio iusto autem sit libero deleniti\nest soluta non perferendis illo\neveniet corrupti est sint quae\nsed sunt voluptatem" - }, - { - "postId": 39, - "id": 195, - "name": "tempore vel accusantium qui quidem esse ut aut", - "email": "Frank@rosalind.name", - "body": "culpa voluptas quidem eos quis excepturi\nquasi corporis provident enim\nprovident velit ex occaecati deleniti\nid aspernatur fugiat eligendi" - }, - { - "postId": 40, - "id": 196, - "name": "totam vel saepe aut qui velit quis", - "email": "Jenifer_Lowe@reuben.ca", - "body": "eum laborum quidem omnis facere harum ducimus dolores quaerat\ncorporis quidem aliquid\nquod aut aut at dolorum aspernatur reiciendis\nexercitationem quasi consectetur asperiores vero blanditiis dolor" - }, - { - "postId": 40, - "id": 197, - "name": "non perspiciatis omnis facere rem", - "email": "Cecelia_Nitzsche@marty.com", - "body": "fugit ut laborum provident\nquos provident voluptatibus quam non\nsed accusantium explicabo dolore quia distinctio voluptatibus et\nconsequatur eos qui iure minus eaque praesentium" - }, - { - "postId": 40, - "id": 198, - "name": "quod vel enim sit quia ipsa quo dolores", - "email": "Christop_Friesen@jordan.me", - "body": "est veritatis mollitia atque quas et sint et dolor\net ut beatae aut\nmagni corporis dolores voluptatibus optio molestiae enim minus est\nreiciendis facere voluptate rem officia doloribus ut" - }, - { - "postId": 40, - "id": 199, - "name": "pariatur aspernatur nam atque quis", - "email": "Cooper_Boehm@damian.biz", - "body": "veniam eos ab voluptatem in fugiat ipsam quis\nofficiis non qui\nquia ut id voluptates et a molestiae commodi quam\ndolorem enim soluta impedit autem nulla" - }, - { - "postId": 40, - "id": 200, - "name": "aperiam et omnis totam", - "email": "Amir@kaitlyn.org", - "body": "facere maxime alias aspernatur ab quibusdam necessitatibus\nratione similique error enim\nsed minus et\net provident minima voluptatibus" - }, - { - "postId": 41, - "id": 201, - "name": "et adipisci aliquam a aperiam ut soluta", - "email": "Cleve@royal.us", - "body": "est officiis placeat\nid et iusto ut fugit numquam\neos aut voluptas ad quia tempore qui quibusdam doloremque\nrecusandae tempora qui" - }, - { - "postId": 41, - "id": 202, - "name": "blanditiis vel fuga odio qui", - "email": "Donnell@polly.net", - "body": "sequi expedita quibusdam enim ipsam\nbeatae ad eum placeat\nperspiciatis quis in nulla porro voluptas quia\nesse et quibusdam" - }, - { - "postId": 41, - "id": 203, - "name": "ab enim adipisci laudantium impedit qui sed", - "email": "Bonita@karl.biz", - "body": "eum voluptates id autem sequi qui omnis commodi\nveniam et laudantium aut\net molestias esse asperiores et quaerat\npariatur non officia voluptatibus" - }, - { - "postId": 41, - "id": 204, - "name": "autem voluptates voluptas nihil", - "email": "Shea@angelina.biz", - "body": "voluptatibus pariatur illo\nautem quia aut ullam laudantium quod laborum officia\ndicta sit consequatur quis delectus vel\nomnis laboriosam laborum vero ipsa voluptas" - }, - { - "postId": 41, - "id": 205, - "name": "et reiciendis ullam quae", - "email": "Omari@veronica.us", - "body": "voluptatem accusamus delectus natus quasi aliquid\nporro ab id ea aut consequatur dignissimos quod et\naspernatur sapiente cum corrupti\npariatur veritatis unde" - }, - { - "postId": 42, - "id": 206, - "name": "deserunt eveniet quam vitae velit", - "email": "Sophie@antoinette.ca", - "body": "nam iusto minus expedita numquam\net id quis\nvoluptatibus minima porro facilis dolores beatae aut sit\naut quia suscipit" - }, - { - "postId": 42, - "id": 207, - "name": "asperiores sed voluptate est", - "email": "Jessika@crystel.ca", - "body": "nulla quos harum commodi\naperiam qui et dignissimos\nreiciendis ut quia est corrupti itaque\nlaboriosam debitis suscipit" - }, - { - "postId": 42, - "id": 208, - "name": "excepturi aut libero incidunt doloremque at", - "email": "Cesar_Volkman@letitia.biz", - "body": "enim aut doloremque mollitia provident molestiae omnis esse excepturi\nofficiis tempora sequi molestiae veniam voluptatem\nrecusandae omnis temporibus et deleniti laborum sed ipsa\nmolestiae eum aut" - }, - { - "postId": 42, - "id": 209, - "name": "repudiandae consectetur dolore", - "email": "Maureen_Mueller@lance.us", - "body": "officiis qui eos voluptas laborum error\nsunt repellat quis nisi unde velit\ndelectus eum molestias tempora quia ut aut\nconsequatur cupiditate quis sint in eum voluptates" - }, - { - "postId": 42, - "id": 210, - "name": "quibusdam provident accusamus id aut totam eligendi", - "email": "Eriberto@geovany.ca", - "body": "praesentium odit quos et tempora eum voluptatem non\nnon aut eaque consectetur reprehenderit in qui eos nam\nnemo ea eos\nea nesciunt consequatur et" - }, - { - "postId": 43, - "id": 211, - "name": "rerum voluptate dolor", - "email": "Faustino.Keeling@morris.co.uk", - "body": "odio temporibus est ut a\naut commodi minima tempora eum\net fuga omnis alias deleniti facere totam unde\nimpedit voluptas et possimus consequatur necessitatibus qui velit" - }, - { - "postId": 43, - "id": 212, - "name": "et maiores sed temporibus cumque voluptatem sunt necessitatibus in", - "email": "Viola@aric.co.uk", - "body": "aut vero sint ut et voluptate\nsunt quod velit impedit quia\ncupiditate dicta magni ut\neos blanditiis assumenda pariatur nemo est tempore velit" - }, - { - "postId": 43, - "id": 213, - "name": "ratione architecto in est voluptatem quibusdam et", - "email": "Felton_Huel@terrell.biz", - "body": "at non dolore molestiae\nautem rerum id\ncum facilis sit necessitatibus accusamus quia officiis\nsint ea sit natus rerum est nemo perspiciatis ea" - }, - { - "postId": 43, - "id": 214, - "name": "dicta deserunt tempore", - "email": "Ferne_Bogan@angus.info", - "body": "nam nesciunt earum sequi dolorum\net fuga sint quae architecto\nin et et ipsam veniam ad voluptas rerum animi\nillum temporibus enim rerum est" - }, - { - "postId": 43, - "id": 215, - "name": "sint culpa cupiditate ut sit quas qui voluptas qui", - "email": "Amy@reymundo.org", - "body": "esse ab est deleniti dicta non\ninventore veritatis cupiditate\neligendi sequi excepturi assumenda a harum sint aut eaque\nrerum molestias id excepturi quidem aut" - }, - { - "postId": 44, - "id": 216, - "name": "voluptatem esse sint alias", - "email": "Jaylan.Mayert@norbert.biz", - "body": "minima quaerat voluptatibus iusto earum\nquia nihil et\nminus deleniti aspernatur voluptatibus tempore sit molestias\narchitecto velit id debitis" - }, - { - "postId": 44, - "id": 217, - "name": "eos velit velit esse autem minima voluptas", - "email": "Cristina.DAmore@destini.biz", - "body": "aperiam rerum aut provident cupiditate laboriosam\nenim placeat aut explicabo\nvoluptatum similique rerum eaque eligendi\nnobis occaecati perspiciatis sint ullam" - }, - { - "postId": 44, - "id": 218, - "name": "voluptatem qui deserunt dolorum in voluptates similique et qui", - "email": "Ettie_Bashirian@lambert.biz", - "body": "rem qui est\nfacilis qui voluptatem quis est veniam quam aspernatur dicta\ndolore id sapiente saepe consequatur\nenim qui impedit culpa ex qui voluptas dolor" - }, - { - "postId": 44, - "id": 219, - "name": "qui unde recusandae omnis ut explicabo neque magni veniam", - "email": "Lizeth@kellen.org", - "body": "est et dolores voluptas aut molestiae nam eos saepe\nexpedita eum ea tempore sit iure eveniet\niusto enim quos quo\nrepellendus laudantium eum fugiat aut et" - }, - { - "postId": 44, - "id": 220, - "name": "vel autem quia in modi velit", - "email": "Vladimir_Schumm@sharon.tv", - "body": "illum ea eum quia\ndoloremque modi ducimus voluptatum eaque aperiam accusamus eos quia\nsed rerum aperiam sunt quo\nea veritatis natus eos deserunt voluptas ea voluptate" - }, - { - "postId": 45, - "id": 221, - "name": "reprehenderit rem voluptatem voluptate recusandae dolore distinctio", - "email": "Madonna@will.com", - "body": "rerum possimus asperiores non dolores maiores tenetur ab\nsuscipit laudantium possimus ab iure\ndistinctio assumenda iste adipisci optio est sed eligendi\ntemporibus perferendis tempore soluta" - }, - { - "postId": 45, - "id": 222, - "name": "rerum aliquam ducimus repudiandae perferendis", - "email": "Cicero_Goldner@elenor.tv", - "body": "cum officiis impedit neque a sed dolorum accusamus eaque\nrepellat natus aut commodi sint fugit consequatur corporis\nvoluptatum dolorum sequi perspiciatis ut facilis\ndelectus pariatur consequatur at aut temporibus facere vitae" - }, - { - "postId": 45, - "id": 223, - "name": "accusantium aliquid consequuntur minus quae quis et autem", - "email": "Zella@jan.net", - "body": "maiores perspiciatis quo alias doloremque\nillum iusto possimus impedit\nvelit voluptatem assumenda possimus\nut nesciunt eum et deleniti molestias harum excepturi" - }, - { - "postId": 45, - "id": 224, - "name": "eum dolorum ratione vitae expedita", - "email": "Robin_Jacobi@verdie.net", - "body": "perferendis quae est velit ipsa autem adipisci ex rerum\nvoluptatem occaecati velit perferendis aut tenetur\ndeleniti eaque quasi suscipit\ndolorum nobis vel et aut est eos" - }, - { - "postId": 45, - "id": 225, - "name": "occaecati et corrupti expedita", - "email": "Lawson@demarco.co.uk", - "body": "doloribus illum tempora aliquam qui perspiciatis dolorem ratione velit\nfacere nobis et fugiat modi\nfugiat dolore at\nducimus voluptate porro qui architecto optio unde deleniti" - }, - { - "postId": 46, - "id": 226, - "name": "assumenda officia quam ex natus minima sint quia", - "email": "Benton@jayde.tv", - "body": "provident sed similique\nexplicabo fugiat quasi saepe voluptatem corrupti recusandae\nvoluptas repudiandae illum tenetur mollitia\nsint in enim earum est" - }, - { - "postId": 46, - "id": 227, - "name": "omnis error aut doloremque ipsum ducimus", - "email": "Melody@london.name", - "body": "est quo quod tempora fuga debitis\neum nihil nemo nisi consequatur sequi nesciunt dolorum et\ncumque maxime qui consequatur mollitia dicta iusto aut\nvero recusandae ut dolorem provident voluptatibus suscipit sint" - }, - { - "postId": 46, - "id": 228, - "name": "eaque expedita temporibus iure velit eligendi labore dignissimos molestiae", - "email": "Wyman.Swaniawski@marjorie.name", - "body": "quibusdam dolores eveniet qui minima\nmagni perspiciatis pariatur\nullam dolor sit ex molestiae in nulla unde rerum\nquibusdam deleniti nisi" - }, - { - "postId": 46, - "id": 229, - "name": "maxime veniam at", - "email": "Deborah@fletcher.co.uk", - "body": "unde aliquam ipsam eaque quia laboriosam exercitationem totam illo\nnon et dolore ipsa\nlaborum et sapiente fugit voluptatem\net debitis quia optio et minima et nostrum" - }, - { - "postId": 46, - "id": 230, - "name": "illo dolor corrupti quia pariatur in", - "email": "Dario@barton.info", - "body": "neque ullam eos amet\nratione architecto doloribus qui est nisi\noccaecati unde expedita perspiciatis animi tenetur minus eveniet aspernatur\neius nihil adipisci aut" - }, - { - "postId": 47, - "id": 231, - "name": "omnis minima dicta aliquam excepturi", - "email": "Kelton_McKenzie@danial.us", - "body": "veritatis laudantium laboriosam ut maxime sed voluptate\nconsequatur itaque occaecati voluptatum est\nut itaque aperiam eligendi at vel minus\ndicta tempora nihil pariatur libero est" - }, - { - "postId": 47, - "id": 232, - "name": "voluptatem excepturi sit et sed qui ipsum quam consequatur", - "email": "Itzel@fritz.io", - "body": "ullam modi consequatur officia dolor non explicabo et\neum minus dicta dolores blanditiis dolore\nnobis assumenda harum velit ullam et cupiditate\net commodi dolor harum et sed cum reprehenderit omnis" - }, - { - "postId": 47, - "id": 233, - "name": "qui dolores maxime autem enim repellendus culpa nostrum consequuntur", - "email": "Jacquelyn_Kutch@kaya.co.uk", - "body": "aperiam quo quis\nnobis error et culpa veritatis\nquae sapiente nobis architecto doloribus quia laboriosam\nest consequatur et recusandae est eius" - }, - { - "postId": 47, - "id": 234, - "name": "natus et necessitatibus animi", - "email": "Cheyanne.Schowalter@alycia.biz", - "body": "itaque voluptatem voluptas velit non est rerum incidunt\nvitae aut labore accusantium in atque\nrepudiandae quos necessitatibus\nautem ea excepturi" - }, - { - "postId": 47, - "id": 235, - "name": "odio sed accusantium iure repudiandae officiis ut autem illo", - "email": "Macey@abbie.org", - "body": "ea iusto laboriosam sit\nvoluptatibus magni ratione eum\net minus perferendis\neius rerum suscipit velit culpa ipsa ipsam aperiam est" - }, - { - "postId": 48, - "id": 236, - "name": "cupiditate rerum voluptate quo facere repudiandae", - "email": "Freeda.Kirlin@eddie.ca", - "body": "itaque error cupiditate asperiores ut aspernatur veniam qui\ndoloribus sit aliquid pariatur dicta deleniti qui alias dignissimos\nrecusandae eaque repellendus est et dolorem aut non\nexplicabo voluptas est beatae vel temporibus" - }, - { - "postId": 48, - "id": 237, - "name": "recusandae deserunt possimus voluptatibus ipsam iste consequatur consequatur", - "email": "Jennifer.Rowe@zoe.org", - "body": "aut culpa quis modi\nlibero hic dolore provident officiis placeat minima vero\net iste dolores aut voluptatem saepe unde\nvero temporibus sunt corrupti voluptate" - }, - { - "postId": 48, - "id": 238, - "name": "voluptatem nam ducimus non molestiae", - "email": "Providenci.Heller@lenna.info", - "body": "et nostrum cupiditate nobis facere et est illo\nconsequatur harum voluptatem totam\nmolestiae voluptas consequatur quibusdam aut\nmodi impedit necessitatibus et nisi nesciunt adipisci" - }, - { - "postId": 48, - "id": 239, - "name": "voluptatum debitis qui aut voluptas eos quibusdam et", - "email": "Emerald_Murazik@darrell.biz", - "body": "esse rem ut neque magni voluptatem id qui\naut ut vel autem non qui quam sit\nimpedit quis sit illum laborum\naut at vel eos nihil quo" - }, - { - "postId": 48, - "id": 240, - "name": "est dolorem est placeat provident non nihil", - "email": "Joseph@corrine.com", - "body": "necessitatibus nulla perferendis ad inventore earum delectus\nvitae illo sed perferendis\nofficiis quo eligendi voluptatem animi totam perspiciatis\nrepellat quam eum placeat est tempore facere" - }, - { - "postId": 49, - "id": 241, - "name": "reprehenderit inventore soluta ut aliquam", - "email": "Lemuel@willow.name", - "body": "quisquam asperiores voluptas\nmodi tempore officia quod hic dolor omnis asperiores\narchitecto aut vel odio quisquam sunt\ndeserunt soluta illum" - }, - { - "postId": 49, - "id": 242, - "name": "quis sit aut vero quo accusamus", - "email": "Sven@gudrun.info", - "body": "dolores minus sequi laudantium excepturi deserunt rerum voluptatem\npariatur harum natus sed dolore quis\nconsectetur quod inventore laboriosam et in dolor beatae rerum\nquia rerum qui recusandae quo officiis fugit" - }, - { - "postId": 49, - "id": 243, - "name": "quaerat natus illum", - "email": "Jennifer@shania.ca", - "body": "rem ut cumque tempore sed\naperiam unde tenetur ab maiores officiis alias\naut nemo veniam dolor est eum sunt a\nesse ratione deserunt et" - }, - { - "postId": 49, - "id": 244, - "name": "labore temporibus ipsa at blanditiis autem", - "email": "Eldora@madge.com", - "body": "est et itaque qui laboriosam dolor ut debitis\ncumque et et dolor\neaque enim et architecto\net quia reiciendis quis" - }, - { - "postId": 49, - "id": 245, - "name": "et rerum fuga blanditiis provident eligendi iste eos", - "email": "Litzy@kaylie.io", - "body": "vel nam nemo sed vitae\nrepellat necessitatibus dolores deserunt dolorum\nminima quae velit et nemo\nsit expedita nihil consequatur autem quia maiores" - }, - { - "postId": 50, - "id": 246, - "name": "magnam earum qui eaque sunt excepturi", - "email": "Jaycee.Turner@euna.name", - "body": "quia est sed eos animi optio dolorum\nconsequatur reiciendis exercitationem ipsum nihil omnis\nbeatae sed corporis enim quisquam\net blanditiis qui nihil" - }, - { - "postId": 50, - "id": 247, - "name": "vel aut blanditiis magni accusamus dolor soluta", - "email": "Wilbert@cheyenne.ca", - "body": "explicabo nam nihil atque sint aut\nqui qui rerum excepturi soluta quis et\net mollitia et voluptate minima nihil\nsed quaerat dolor earum tempore et non est voluptatem" - }, - { - "postId": 50, - "id": 248, - "name": "voluptatum sint dicta voluptas aut ut", - "email": "Rebecca_Hessel@edna.net", - "body": "assumenda aut quis repellendus\nnihil impedit cupiditate nemo\nsit sequi laudantium aut voluptas nam dolore magnam\nminima aspernatur vero sapiente" - }, - { - "postId": 50, - "id": 249, - "name": "quibusdam dignissimos aperiam sint commodi", - "email": "Christiana@lawrence.info", - "body": "non repudiandae dicta et commodi\nsint dolores facere eos nesciunt autem quia\nplaceat quaerat non culpa quasi dolores voluptas\ndolorum placeat non atque libero odit autem sunt" - }, - { - "postId": 50, - "id": 250, - "name": "perferendis magnam natus exercitationem eveniet sunt", - "email": "Samara@shaun.org", - "body": "doloremque quae ratione cumque\nexcepturi eligendi delectus maiores necessitatibus veniam\nfugiat exercitationem consectetur vel earum\nquia illo explicabo molestiae enim rem deserunt et omnis" - }, - { - "postId": 51, - "id": 251, - "name": "veritatis sint eius", - "email": "Ayden_Hickle@stephany.tv", - "body": "sit vero at voluptatem corporis adipisci\nerror sit aut nihil rerum doloremque dolorum ipsum\neum ut numquam sapiente ipsam nam blanditiis ut quasi\nfacilis optio rerum qui temporibus esse excepturi eaque" - }, - { - "postId": 51, - "id": 252, - "name": "qui alias beatae iusto omnis placeat recusandae ut", - "email": "Carissa.Krajcik@jean.name", - "body": "exercitationem quisquam sed\neius et cum reiciendis deleniti non\nperspiciatis aut voluptatum deserunt\nsint dignissimos est sed architecto sed" - }, - { - "postId": 51, - "id": 253, - "name": "voluptate ipsum corporis quis provident voluptatem eos asperiores", - "email": "Jayde@geovanny.io", - "body": "debitis dignissimos ut illum\nrerum voluptatem ut qui labore\noptio quaerat iure\niste consequuntur praesentium vero blanditiis quibusdam aut" - }, - { - "postId": 51, - "id": 254, - "name": "velit inventore et eius saepe", - "email": "Ardella@khalid.biz", - "body": "laboriosam voluptas aut quibusdam mollitia sunt non\ndolores illum fugiat ex vero nemo aperiam porro quam\nexpedita est vel voluptatem sit voluptas consequuntur quis eligendi\nomnis id nisi ducimus sapiente odit quam" - }, - { - "postId": 51, - "id": 255, - "name": "impedit et sapiente et tempore repellendus", - "email": "Delta_Welch@carleton.tv", - "body": "nihil esse aut\ndebitis nostrum mollitia similique minus aspernatur possimus\nomnis eaque non eveniet\nrerum qui iure laboriosam" - }, - { - "postId": 52, - "id": 256, - "name": "tempore distinctio quam", - "email": "Carlee_Heathcote@harley.tv", - "body": "nemo deleniti sunt praesentium quis quam repellendus\nconsequatur non est ex fugiat distinctio aliquam explicabo\naspernatur omnis debitis sint consequatur\nquo autem natus veritatis" - }, - { - "postId": 52, - "id": 257, - "name": "illum non quod vel voluptas quos", - "email": "Delpha_Cormier@raymond.org", - "body": "facere at voluptatem\nrepellendus omnis perspiciatis placeat aspernatur nobis blanditiis ut deleniti\nquis non cumque laborum sit id ratione vel sequi\nfacere doloremque beatae aut maxime non" - }, - { - "postId": 52, - "id": 258, - "name": "omnis quia fugit nisi officiis aspernatur occaecati et", - "email": "Glenna@caesar.org", - "body": "aut cum sint qui facere blanditiis magnam consequuntur perspiciatis\nharum impedit reprehenderit iste doloribus quia quo facere\net explicabo aut voluptate modi dolorem\nrem aut nobis ut ad voluptatum ipsum eos maxime" - }, - { - "postId": 52, - "id": 259, - "name": "animi minima ducimus tempore officiis qui", - "email": "Hoyt_Dickens@napoleon.ca", - "body": "itaque occaecati non aspernatur\nvelit repudiandae sit rerum esse quibusdam unde molestias\nexplicabo dolorem vero consequatur quis et libero\nvoluptatem totam vel sapiente autem dolorum consequuntur" - }, - { - "postId": 52, - "id": 260, - "name": "qui dolore delectus et omnis quia", - "email": "Wendell.Marvin@maegan.net", - "body": "aliquid molestias nemo aut est maxime\nlaboriosam et consequatur laudantium\ncommodi et corrupti\nharum quasi minima ratione sint magni sapiente ut" - }, - { - "postId": 53, - "id": 261, - "name": "aut veritatis quasi voluptatem enim dolor soluta temporibus", - "email": "Virgie@layne.org", - "body": "sapiente qui est quod\ndebitis qui est optio consequuntur\nalias hic amet ut non ad qui provident\nquia provident aspernatur corrupti occaecati" - }, - { - "postId": 53, - "id": 262, - "name": "ipsa aliquid laborum quidem recusandae dolorum quia", - "email": "Tia@kirsten.info", - "body": "similique harum iste ipsam non dolores facere esse\net beatae error necessitatibus laboriosam fugiat culpa esse occaecati\nut provident ut et dolorum nam\ndelectus impedit aut blanditiis fugiat est unde" - }, - { - "postId": 53, - "id": 263, - "name": "vitae voluptatem dolor iure quo non atque", - "email": "Marco@jennyfer.biz", - "body": "debitis dolore est\nut eos velit accusamus\nnon nobis ipsa nemo quas facilis quia hic\nofficia quam et possimus voluptas voluptatem quisquam tempore delectus" - }, - { - "postId": 53, - "id": 264, - "name": "cum ab voluptates aut", - "email": "Taya@milan.biz", - "body": "consectetur maiores ab est qui aliquid porro\nipsa labore incidunt\niste deserunt quia aperiam quis sit perferendis\net sint iste" - }, - { - "postId": 53, - "id": 265, - "name": "omnis incidunt est molestias", - "email": "Lenora@derrick.biz", - "body": "et quibusdam saepe labore delectus et earum quis perferendis\nlaborum soluta veritatis\nea veritatis quidem accusantium est aut porro rerum\nquia est consequatur voluptatem numquam laudantium repellendus" - }, - { - "postId": 54, - "id": 266, - "name": "eum enim provident atque eum", - "email": "Carolina.Auer@polly.co.uk", - "body": "non et voluptas\neaque atque esse qui molestias porro quam veniam voluptatibus\nminima ut velit velit ut architecto deleniti\nab sint deserunt possimus quas velit et eum" - }, - { - "postId": 54, - "id": 267, - "name": "ea commodi provident veritatis voluptatem voluptates aperiam", - "email": "Jaylan.Braun@lane.us", - "body": "magnam similique animi eos explicabo natus\nprovident cumque sit maxime velit\nveritatis fuga esse dolor hic nihil nesciunt assumenda\naliquid vero modi alias qui quia doloribus est" - }, - { - "postId": 54, - "id": 268, - "name": "eum et eos delectus", - "email": "Javier.Dicki@damien.org", - "body": "velit earum perspiciatis ea recusandae nihil consectetur ut\nmaxime repellendus doloribus\naperiam ut ex ratione quia esse magni\nducimus rerum vel rerum officiis suscipit nihil qui" - }, - { - "postId": 54, - "id": 269, - "name": "molestiae vitae pariatur", - "email": "Khalil_Sawayn@tanya.net", - "body": "quos sed unde repudiandae aut porro dignissimos qui\noccaecati sed alias enim\nvoluptates nesciunt sit ut adipisci est\nexpedita quae corrupti" - }, - { - "postId": 54, - "id": 270, - "name": "rerum adipisci et ut sit sit dolores", - "email": "Tom.Russel@pattie.org", - "body": "quas placeat repudiandae a delectus facere exercitationem consectetur\nfacilis quas sequi est mollitia\nest vero hic laudantium maiores\nquisquam itaque aut maxime ut cumque quia doloremque voluptatem" - }, - { - "postId": 55, - "id": 271, - "name": "et et repellat quasi non ea similique", - "email": "Ethelyn.Moore@gabe.info", - "body": "quae eaque reprehenderit\nsuscipit facilis ut tenetur blanditiis sint occaecati\naccusantium expedita sed nostrum\nrerum sunt nam qui placeat consequatur et" - }, - { - "postId": 55, - "id": 272, - "name": "repudiandae ut velit dignissimos enim rem dolores odit", - "email": "Evangeline_Kuvalis@santina.ca", - "body": "consequuntur molestiae aut distinctio illo qui est sequi reprehenderit\nhic accusamus et officiis reprehenderit culpa\nest et numquam et\neius ipsa rerum velit" - }, - { - "postId": 55, - "id": 273, - "name": "et aperiam autem inventore nisi nulla reiciendis velit", - "email": "Orland@larry.name", - "body": "asperiores et minus non\ndolor dolorem et sint et ipsam\net enim quia sequi\nsed beatae culpa architecto nisi minima" - }, - { - "postId": 55, - "id": 274, - "name": "et vero nostrum tempore", - "email": "Micaela.Powlowski@saul.me", - "body": "quos illo consectetur dolores\ncupiditate enim rerum dicta sequi totam\naspernatur sed praesentium\nipsum voluptates perspiciatis ipsa accusantium et et" - }, - { - "postId": 55, - "id": 275, - "name": "error nulla est laudantium similique ad", - "email": "Imelda_Klein@melany.biz", - "body": "error et quasi qui facilis enim eum adipisci iste\nad nostrum sint corporis quam velit necessitatibus a\neius doloribus optio ad qui molestiae\nquaerat dignissimos voluptatem culpa aliquam explicabo commodi natus" - }, - { - "postId": 56, - "id": 276, - "name": "inventore amet ut debitis ipsam reiciendis molestiae autem sed", - "email": "Matt.Moen@gilda.org", - "body": "dolores tempora totam quas maxime voluptatem voluptas excepturi\nrecusandae quis odio exercitationem in\nconsectetur sed aut\nexcepturi eligendi sint consectetur repellendus aperiam" - }, - { - "postId": 56, - "id": 277, - "name": "dolorem aut ipsum alias ex ea quidem nostrum", - "email": "Rocky_Ullrich@rowena.name", - "body": "nihil ratione aliquam recusandae ipsa sunt doloribus labore molestiae\nofficia cum aliquid repudiandae et error\ninventore minima optio repellat aut\nea et maxime alias voluptas eius" - }, - { - "postId": 56, - "id": 278, - "name": "est pariatur similique quod voluptas et consequuntur nam molestiae", - "email": "Natalia@caitlyn.ca", - "body": "corporis perferendis dolorum error quo rerum aut odio veritatis\nsit deleniti aut eligendi quam doloremque aut ipsam sint\ndoloribus id voluptatem esse reprehenderit molestiae quia voluptatem\nincidunt illo beatae nihil corporis eligendi iure quo" - }, - { - "postId": 56, - "id": 279, - "name": "voluptas nihil aut dolor adipisci non", - "email": "Edythe@general.org", - "body": "natus atque ipsum voluptatem et\nnecessitatibus atque quia asperiores animi odit ratione quos\nest repellendus sit aut repudiandae animi aut\ncum blanditiis repudiandae saepe laborum" - }, - { - "postId": 56, - "id": 280, - "name": "culpa minima non consequatur sit autem quas sed ipsam", - "email": "Aglae@gerardo.name", - "body": "perferendis fugit expedita cumque\nexercitationem animi fugit aut earum\nnihil quia illum eum dicta ut\nquam commodi optio" - }, - { - "postId": 57, - "id": 281, - "name": "consequatur voluptates sed voluptatem fuga", - "email": "Bridie@pearl.ca", - "body": "eius voluptatem minus\net aliquid perspiciatis sint unde ut\nsimilique odio ullam vitae quisquam hic ex consequatur aliquid\nab nihil explicabo sint maiores aut et dolores nostrum" - }, - { - "postId": 57, - "id": 282, - "name": "et vitae culpa corrupti", - "email": "Aglae_Goldner@madisyn.co.uk", - "body": "ea consequatur placeat\nquo omnis illum voluptatem\nvoluptatem fugit aut dolorum recusandae ut et\ntenetur officia voluptas" - }, - { - "postId": 57, - "id": 283, - "name": "iste molestiae aut hic perspiciatis sint", - "email": "Owen_Moore@jeremy.org", - "body": "perspiciatis omnis eum nihil et porro facilis fuga qui\ndeleniti id et velit adipisci fuga voluptatibus voluptatum\ndebitis tempore dolorem atque consequatur ea perspiciatis sed\nqui temporibus doloremque" - }, - { - "postId": 57, - "id": 284, - "name": "soluta omnis maiores animi veniam voluptas et totam repellendus", - "email": "Jarred@dangelo.name", - "body": "rem ut sed\nnon cumque corrupti vel nam rerum autem\nnobis dolorem necessitatibus fugit corporis\nquos sint distinctio ex et animi tempore" - }, - { - "postId": 57, - "id": 285, - "name": "non est sunt consequatur reiciendis", - "email": "Remington_Mohr@vincenza.me", - "body": "est accusamus facere\nreprehenderit corporis ad et est fugit iure nulla et\ndoloribus reiciendis quasi autem voluptas\nipsam labore et pariatur quia" - }, - { - "postId": 58, - "id": 286, - "name": "dolore dignissimos distinctio", - "email": "Marco.Langworth@zoie.org", - "body": "doloremque accusantium necessitatibus architecto ut provident\nquaerat iusto eius omnis\nfuga laborum harum totam sunt velit\naut neque corporis atque" - }, - { - "postId": 58, - "id": 287, - "name": "voluptas ad autem maxime iusto eos dolorem ducimus est", - "email": "Sedrick@mertie.tv", - "body": "voluptatem perspiciatis voluptatum quaerat\nodit voluptates iure\nquisquam magnam voluptates ut non qui\naliquam aut ut amet sit consequatur ut suscipit" - }, - { - "postId": 58, - "id": 288, - "name": "numquam eius voluptas quibusdam soluta exercitationem", - "email": "Caleigh@eleanore.org", - "body": "est sed illo omnis delectus aut\nlaboriosam possimus incidunt est sunt at\nnemo voluptas ex ipsam\nvoluptatibus inventore velit sit et numquam omnis accusamus in" - }, - { - "postId": 58, - "id": 289, - "name": "voluptatem aut harum aut corporis dignissimos occaecati sequi quod", - "email": "Paolo@cristopher.com", - "body": "occaecati tempora unde\nmaiores aliquid in\nquo libero sint quidem at est facilis ipsa facere\nnostrum atque harum" - }, - { - "postId": 58, - "id": 290, - "name": "suscipit debitis eveniet nobis atque commodi quisquam", - "email": "Juana_Stamm@helmer.com", - "body": "pariatur veniam repellat quisquam tempore autem et voluptatem itaque\nea impedit ex molestiae placeat hic harum mollitia dolorem\ninventore accusantium aut quae quia rerum autem numquam\nnulla culpa quasi dolor" - }, - { - "postId": 59, - "id": 291, - "name": "occaecati et dolorum", - "email": "Pascale@fleta.ca", - "body": "nisi dicta numquam dolor\nrerum sed quaerat et\nsed sequi doloribus libero quos temporibus\nblanditiis optio est tempore qui" - }, - { - "postId": 59, - "id": 292, - "name": "consequatur et facere similique beatae explicabo eligendi consequuntur", - "email": "Molly_Kertzmann@tristin.me", - "body": "eos officiis omnis ab laborum nulla saepe exercitationem recusandae\nvoluptate minima voluptatem sint\nsunt est consequuntur dolor voluptatem odit\nmaxime similique deserunt et quod" - }, - { - "postId": 59, - "id": 293, - "name": "qui sint hic", - "email": "Kailee.Larkin@amina.org", - "body": "fugiat dicta quasi voluptatibus ea aut est aspernatur sed\ncorrupti harum non omnis eos eaque quos ut\nquia et et nisi rerum voluptates possimus quis\nrecusandae aperiam quia esse" - }, - { - "postId": 59, - "id": 294, - "name": "autem totam necessitatibus sit sunt minima aut quis", - "email": "Earnest.Sanford@lane.us", - "body": "ut est veritatis animi quos\nnam sed dolor\nitaque omnis nostrum autem molestiae\naut optio tempora ad sapiente quae voluptatem perferendis repellat" - }, - { - "postId": 59, - "id": 295, - "name": "ullam dignissimos non aut dolore", - "email": "Abigail@trudie.com", - "body": "voluptatem est aspernatur consequatur vel facere\nut omnis tenetur non ea eos\nquibusdam error odio\natque consectetur voluptatem eligendi" - }, - { - "postId": 60, - "id": 296, - "name": "hic eum sed dolore velit cupiditate quisquam ut inventore", - "email": "Name.Walter@zoie.me", - "body": "quasi dolorem veniam dignissimos\natque voluptas iure et quidem fugit velit et\nquod magnam illum quia et ea est modi\naut quis dolores" - }, - { - "postId": 60, - "id": 297, - "name": "dignissimos dolor facere", - "email": "Norma@abraham.co.uk", - "body": "eos exercitationem est ut voluptas accusamus qui\nvelit rerum ut\ndolorem eaque omnis eligendi mollitia\natque ea architecto dolorum delectus accusamus" - }, - { - "postId": 60, - "id": 298, - "name": "ipsam ut labore voluptatem quis id velit sunt", - "email": "Norberto_Weimann@ford.tv", - "body": "molestiae accusantium a tempore occaecati qui sunt optio eos\nexercitationem quas eius voluptatem\nomnis quibusdam autem\nmolestiae odio dolor quam laboriosam mollitia in quibusdam sunt" - }, - { - "postId": 60, - "id": 299, - "name": "laborum asperiores nesciunt itaque", - "email": "Nelson@charlene.biz", - "body": "voluptatem omnis pariatur aut saepe enim qui\naut illo aut sed aperiam expedita debitis\ntempore animi dolorem\nut libero et eos unde ex" - }, - { - "postId": 60, - "id": 300, - "name": "in dolore iusto ex molestias vero", - "email": "Letha@liliane.ca", - "body": "dolorem fugit quidem animi quas quisquam reprehenderit\noccaecati et dolor laborum nemo sed quas unde deleniti\nfacere eligendi placeat aliquid aspernatur commodi sunt impedit\nneque corrupti alias molestiae magni tempora" - }, - { - "postId": 61, - "id": 301, - "name": "id voluptatibus voluptas occaecati quia sunt eveniet et eius", - "email": "Tiana@jeramie.tv", - "body": "dolore maxime saepe dolor asperiores cupiditate nisi nesciunt\nvitae tempora ducimus vel eos perferendis\nfuga sequi numquam blanditiis sit sed inventore et\nut possimus soluta voluptas nihil aliquid sed earum" - }, - { - "postId": 61, - "id": 302, - "name": "quia voluptatem sunt voluptate ut ipsa", - "email": "Lindsey@caitlyn.net", - "body": "fuga aut est delectus earum optio impedit qui excepturi\niusto consequatur deserunt soluta sunt\net autem neque\ndolor ut saepe dolores assumenda ipsa eligendi" - }, - { - "postId": 61, - "id": 303, - "name": "excepturi itaque laudantium reiciendis dolorem", - "email": "Gregory.Kutch@shawn.info", - "body": "sit nesciunt id vitae ut itaque sapiente\nneque in at consequuntur perspiciatis dicta consequatur velit\nfacilis iste ut error sed\nin sequi expedita autem" - }, - { - "postId": 61, - "id": 304, - "name": "voluptatem quidem animi sit est nemo non omnis molestiae", - "email": "Murphy.Kris@casimer.me", - "body": "minus ab quis nihil quia suscipit vel\nperspiciatis sunt unde\naut ullam quo laudantium deleniti modi\nrerum illo error occaecati vel officiis facere" - }, - { - "postId": 61, - "id": 305, - "name": "non cum consequatur at nihil aut fugiat delectus quia", - "email": "Isidro_Kiehn@cristina.org", - "body": "repellendus quae labore sunt ut praesentium fuga reiciendis quis\ncorporis aut quis dolor facere earum\nexercitationem enim sunt nihil asperiores expedita\neius nesciunt a sit sit" - }, - { - "postId": 62, - "id": 306, - "name": "omnis nisi libero", - "email": "Kenton_Carter@yolanda.co.uk", - "body": "ab veritatis aspernatur molestiae explicabo ea saepe molestias sequi\nbeatae aut perferendis quaerat aut omnis illo fugiat\nquisquam hic doloribus maiores itaque\nvoluptas amet dolorem blanditiis" - }, - { - "postId": 62, - "id": 307, - "name": "id ab commodi est quis culpa", - "email": "Amos_Rohan@mozelle.tv", - "body": "sit tenetur aut eum quasi reiciendis dignissimos non nulla\nrepellendus ut quisquam\nnumquam provident et repellendus eum nihil blanditiis\nbeatae iusto sed eius sit sed doloremque exercitationem nihil" - }, - { - "postId": 62, - "id": 308, - "name": "enim ut optio architecto dolores nemo quos", - "email": "Timothy_Heathcote@jose.name", - "body": "officiis ipsa exercitationem impedit dolorem repellat adipisci qui\natque illum sapiente omnis et\nnihil esse et eum facilis aut impedit\nmaxime ullam et dolorem" - }, - { - "postId": 62, - "id": 309, - "name": "maiores et quisquam", - "email": "Otilia.Daniel@elvie.io", - "body": "impedit qui nemo\nreprehenderit sequi praesentium ullam veniam quaerat optio qui error\naperiam qui quisquam dolor est blanditiis molestias rerum et\nquae quam eum odit ab quia est ut" - }, - { - "postId": 62, - "id": 310, - "name": "sed qui atque", - "email": "Toni@joesph.biz", - "body": "quae quis qui ab rerum non hic\nconsequatur earum facilis atque quas dolore fuga ipsam\nnihil velit quis\nrerum sit nam est nulla nihil qui excepturi et" - }, - { - "postId": 63, - "id": 311, - "name": "veritatis nulla consequatur sed cumque", - "email": "Brisa@carrie.us", - "body": "officia provident libero explicabo tempora velit eligendi mollitia similique\nrerum sit aut consequatur ullam tenetur qui est vero\nrerum est et explicabo\nsit sunt ea exercitationem molestiae" - }, - { - "postId": 63, - "id": 312, - "name": "libero et distinctio repudiandae voluptatem dolores", - "email": "Jasen.Kihn@devon.biz", - "body": "ipsa id eum dolorum et officiis\nsaepe eos voluptatem\nnesciunt quos voluptas temporibus dolores ad rerum\nnon voluptatem aut fugit" - }, - { - "postId": 63, - "id": 313, - "name": "quia enim et", - "email": "Efren.Konopelski@madisyn.us", - "body": "corporis quo magnam sunt rerum enim vel\nrepudiandae suscipit corrupti ut ab qui debitis quidem adipisci\ndistinctio voluptatibus vitae molestias incidunt laboriosam quia quidem facilis\nquia architecto libero illum ut dicta" - }, - { - "postId": 63, - "id": 314, - "name": "enim voluptatem quam", - "email": "Demetris.Bergnaum@fae.io", - "body": "sunt cupiditate commodi est pariatur incidunt quis\nsuscipit saepe magnam amet enim\nquod quis neque\net modi rerum asperiores consequatur reprehenderit maiores" - }, - { - "postId": 63, - "id": 315, - "name": "maxime nulla perspiciatis ad quo quae consequatur quas", - "email": "Luella.Pollich@gloria.org", - "body": "repudiandae dolores nam quas\net incidunt odio dicta eum vero dolor quidem\ndolorem quisquam cumque\nmolestiae neque maxime rerum deserunt nam sequi" - }, - { - "postId": 64, - "id": 316, - "name": "totam est minima modi sapiente nobis impedit", - "email": "Sister.Morissette@adelia.io", - "body": "consequatur qui doloribus et rerum\ndebitis cum dolorem voluptate qui fuga\nnecessitatibus quod temporibus non voluptates\naut saepe molestiae" - }, - { - "postId": 64, - "id": 317, - "name": "iusto pariatur ea", - "email": "Shyanne@rick.info", - "body": "quam iste aut molestiae nesciunt modi\natque quo laudantium vel tempora quam tenetur neque aut\net ipsum eum nostrum enim laboriosam officia eligendi\nlaboriosam libero ullam sit nulla voluptate in" - }, - { - "postId": 64, - "id": 318, - "name": "vitae porro aut ex est cumque", - "email": "Freeman.Dare@ada.name", - "body": "distinctio placeat nisi repellat animi\nsed praesentium voluptatem\nplaceat eos blanditiis deleniti natus eveniet dolorum quia tempore\npariatur illum dolor aspernatur ratione tenetur autem ipsum fugit" - }, - { - "postId": 64, - "id": 319, - "name": "et eos praesentium porro voluptatibus quas quidem explicabo est", - "email": "Donnell@orland.org", - "body": "occaecati quia ipsa id fugit sunt velit iure adipisci\nullam inventore quidem dolorem adipisci optio quia et\nquis explicabo omnis ipsa quo ut voluptatem aliquam inventore\nminima aut tempore excepturi similique" - }, - { - "postId": 64, - "id": 320, - "name": "fugiat eos commodi consequatur vel qui quasi", - "email": "Robin@gaylord.biz", - "body": "nihil consequatur dolorem voluptatem non molestiae\nodit eum animi\nipsum omnis ut quasi\nvoluptas sed et et qui est aut" - }, - { - "postId": 65, - "id": 321, - "name": "rem ducimus ipsam ut est vero distinctio et", - "email": "Danyka_Stark@jedidiah.name", - "body": "ea necessitatibus eum nesciunt corporis\nminus in quisquam iste recusandae\nqui nobis deleniti asperiores non laboriosam sunt molestiae dolore\ndistinctio qui officiis tempora dolorem ea" - }, - { - "postId": 65, - "id": 322, - "name": "ipsam et commodi", - "email": "Margarita@casper.io", - "body": "id molestiae doloribus\nomnis atque eius sunt aperiam\ntenetur quia natus nihil sunt veritatis recusandae quia\ncorporis quam omnis veniam voluptas amet quidem illo deleniti" - }, - { - "postId": 65, - "id": 323, - "name": "et aut non illo cumque pariatur laboriosam", - "email": "Carlo@cortney.net", - "body": "explicabo dicta quas cum quis rerum dignissimos et\nmagnam sit mollitia est dolor voluptas sed\nipsum et tenetur recusandae\nquod facilis nulla amet deserunt" - }, - { - "postId": 65, - "id": 324, - "name": "ut ut architecto vero est ipsam", - "email": "Mina@nikita.tv", - "body": "ipsam eum ea distinctio\nducimus saepe eos quaerat molestiae\ncorporis aut officia qui ut perferendis\nitaque possimus incidunt aut quis" - }, - { - "postId": 65, - "id": 325, - "name": "odit sit numquam rerum porro dolorem", - "email": "Violette@naomi.tv", - "body": "qui vero recusandae\nporro esse sint doloribus impedit voluptatem commodi\nasperiores laudantium ut dolores\npraesentium distinctio magnam voluptatum aut" - }, - { - "postId": 66, - "id": 326, - "name": "voluptatem laborum incidunt accusamus", - "email": "Lauren.Hodkiewicz@jarret.info", - "body": "perspiciatis vero nulla aut consequatur fuga earum aut\nnemo suscipit totam vitae qui at omnis aut\nincidunt optio dolorem voluptatem vel\nassumenda vero id explicabo deleniti sit corrupti sit" - }, - { - "postId": 66, - "id": 327, - "name": "quisquam necessitatibus commodi iure eum", - "email": "Ernestina@piper.biz", - "body": "consequatur ut aut placeat harum\nquia perspiciatis unde doloribus quae non\nut modi ad unde ducimus omnis nobis voluptatem atque\nmagnam reiciendis dolorem et qui explicabo" - }, - { - "postId": 66, - "id": 328, - "name": "temporibus ut vero quas", - "email": "Hettie_Morar@wiley.info", - "body": "molestiae minima aut rerum nesciunt\nvitae iusto consequatur architecto assumenda dolorum\nnon doloremque tempora possimus qui mollitia omnis\nvitae odit sed" - }, - { - "postId": 66, - "id": 329, - "name": "quasi beatae sapiente voluptates quo temporibus", - "email": "Corbin.Hilll@modesto.biz", - "body": "nulla corrupti delectus est cupiditate explicabo facere\nsapiente quo id quis illo culpa\nut aut sit error magni atque asperiores soluta\naut cumque voluptatem occaecati omnis aliquid" - }, - { - "postId": 66, - "id": 330, - "name": "illo ab quae deleniti", - "email": "Kenyatta@renee.io", - "body": "dolores tenetur rerum et aliquam\nculpa officiis ea rem neque modi quaerat deserunt\nmolestias minus nesciunt iusto impedit enim laborum perferendis\nvelit minima itaque voluptatem fugiat" - }, - { - "postId": 67, - "id": 331, - "name": "nemo cum est officia maiores sint sunt a", - "email": "Don@cameron.co.uk", - "body": "maxime incidunt velit quam vel fugit nostrum veritatis\net ipsam nisi voluptatem voluptas cumque tempora velit et\net quisquam error\nmaiores fugit qui dolor sit doloribus" - }, - { - "postId": 67, - "id": 332, - "name": "dicta vero voluptas hic dolorem", - "email": "Jovan@aaliyah.tv", - "body": "voluptas iste deleniti\nest itaque vel ea incidunt quia voluptates sapiente repellat\naut consectetur vel neque tempora esse similique sed\na qui nobis voluptate hic eligendi doloribus molestiae et" - }, - { - "postId": 67, - "id": 333, - "name": "soluta dicta pariatur reiciendis", - "email": "Jeanie.McGlynn@enoch.ca", - "body": "et dolor error doloremque\nodio quo sunt quod\nest ipsam assumenda in veniam illum rerum deleniti expedita\nvoluptate hic nostrum sed dolor et qui" - }, - { - "postId": 67, - "id": 334, - "name": "et adipisci laboriosam est modi", - "email": "Garett_Gusikowski@abigale.me", - "body": "et voluptatibus est et aperiam quaerat voluptate eius quo\nnihil voluptas doloribus et ea tempore\nlabore non dolorem\noptio consequatur est id quia magni voluptas enim" - }, - { - "postId": 67, - "id": 335, - "name": "voluptatem accusantium beatae veniam voluptatem quo culpa deleniti", - "email": "Doug@alana.co.uk", - "body": "hic et et aspernatur voluptates voluptas ut ut id\nexcepturi eligendi aspernatur nulla dicta ab\nsuscipit quis distinctio nihil\ntemporibus unde quasi expedita et inventore consequatur rerum ab" - }, - { - "postId": 68, - "id": 336, - "name": "eveniet eligendi nisi sunt a error blanditiis et ea", - "email": "Yoshiko@viviane.name", - "body": "similique autem voluptatem ab et et\nodio animi repellendus libero voluptas voluptas quia\nlibero facere saepe nobis\nconsequatur et qui non hic ea maxime nihil" - }, - { - "postId": 68, - "id": 337, - "name": "beatae esse tenetur aut est", - "email": "Micaela_Bins@mertie.us", - "body": "est ut aut ut omnis distinctio\nillum quisquam pariatur qui aspernatur vitae\ndolor explicabo architecto veritatis ipsa et aut est molestiae\nducimus et sapiente error sed omnis" - }, - { - "postId": 68, - "id": 338, - "name": "qui sit quo est ipsam minima neque nobis", - "email": "Loy@gillian.me", - "body": "maiores totam quo atque\nexplicabo perferendis iste facilis odio ab eius consequatur\nsit praesentium ea vitae optio minus\nvoluptate necessitatibus omnis itaque omnis qui" - }, - { - "postId": 68, - "id": 339, - "name": "accusantium et sit nihil quibusdam voluptatum provident est qui", - "email": "Tyrel@hunter.net", - "body": "dicta dolorem veniam ipsa harum minus sequi\nomnis quia voluptatem autem\nest optio doloribus repellendus id commodi quas exercitationem eum\net eum dignissimos accusamus est eaque doloremque" - }, - { - "postId": 68, - "id": 340, - "name": "rerum et quae tenetur soluta voluptatem tempore laborum enim", - "email": "Otilia.Schuppe@randal.com", - "body": "est aut consequatur error illo ut\nenim nihil fuga\nsuscipit inventore officiis iure earum pariatur temporibus in\naperiam qui quod vel necessitatibus velit eos exercitationem culpa" - }, - { - "postId": 69, - "id": 341, - "name": "sunt ut voluptatem cupiditate maxime dolores eligendi", - "email": "April@larissa.co.uk", - "body": "iure ea ea neque est\nesse ab sed hic et ullam sed sequi a\nnon hic tenetur sunt enim ea\nlaudantium ea natus" - }, - { - "postId": 69, - "id": 342, - "name": "corporis at consequuntur consequatur", - "email": "Glenna_Waters@retha.me", - "body": "quis beatae qui\nsequi dicta quas et dolor\nnon enim aspernatur excepturi aut rerum asperiores\naliquid animi nulla ea tempore esse" - }, - { - "postId": 69, - "id": 343, - "name": "repellat sed consequatur suscipit aliquam", - "email": "Cordelia.Oberbrunner@peyton.com", - "body": "ea alias eos et corrupti\nvoluptatem ab incidunt\nvoluptatibus voluptas ea nesciunt\ntotam corporis dolor recusandae voluptas harum" - }, - { - "postId": 69, - "id": 344, - "name": "blanditiis rerum voluptatem quaerat modi saepe ratione assumenda qui", - "email": "Zander@santino.net", - "body": "iusto nihil quae rerum laborum recusandae voluptatem et necessitatibus\nut deserunt cumque qui qui\nnon et et eos adipisci cupiditate dolor sed voluptates\nmaiores commodi eveniet consequuntur" - }, - { - "postId": 69, - "id": 345, - "name": "ut deleniti autem ullam quod provident ducimus enim explicabo", - "email": "Camila_Runolfsdottir@tressa.tv", - "body": "omnis et fugit eos sint saepe ipsam unde est\ndolores sit sit assumenda laboriosam\ndolor deleniti voluptatem id nesciunt et\nplaceat dolorem cumque laboriosam sunt non" - }, - { - "postId": 70, - "id": 346, - "name": "beatae in fuga assumenda dolorem accusantium blanditiis mollitia", - "email": "Kirstin@tina.info", - "body": "quas non magnam\nquia veritatis assumenda reiciendis\nsimilique dolores est ab\npraesentium fuga ut" - }, - { - "postId": 70, - "id": 347, - "name": "tenetur id delectus recusandae voluptates quo aut", - "email": "Anthony.Koepp@savannah.tv", - "body": "consectetur illo corporis sit labore optio quod\nqui occaecati aut sequi quia\nofficiis quia aut odio quo ad\nrerum tenetur aut quasi veniam" - }, - { - "postId": 70, - "id": 348, - "name": "molestias natus autem quae sint qui", - "email": "Bradley.Lang@marilyne.tv", - "body": "perferendis dignissimos soluta ut provident sit et\ndelectus ratione ad sapiente qui excepturi error qui quo\nquo illo commodi\nrerum maxime voluptas voluptatem" - }, - { - "postId": 70, - "id": 349, - "name": "odio maiores a porro dolorum ut pariatur inventore", - "email": "Loren@aric.biz", - "body": "dicta impedit non\net laborum laudantium qui eaque et beatae suscipit\nsequi magnam rem dolorem non quia vel adipisci\ncorrupti officiis laudantium impedit" - }, - { - "postId": 70, - "id": 350, - "name": "eius quia pariatur", - "email": "Arjun@natalie.ca", - "body": "eaque rerum tempore distinctio\nconsequatur fugiat veniam et incidunt ut ut et\nconsequatur blanditiis magnam\ndoloremque voluptate ut architecto facere in dolorem et aut" - }, - { - "postId": 71, - "id": 351, - "name": "quia ex perspiciatis sunt voluptatem quidem", - "email": "Solon.Goldner@judah.org", - "body": "quo nisi impedit velit repellendus esse itaque ut saepe\nvoluptatibus occaecati ab eaque dolores\nmaxime alias velit ducimus placeat sit laudantium quia\ncorrupti doloremque ut" - }, - { - "postId": 71, - "id": 352, - "name": "sit ipsam voluptatem velit", - "email": "Nina@osbaldo.name", - "body": "dolorem eius voluptatem vitae aliquid unde labore est\nmolestiae labore dolorem beatae voluptatem soluta eum eos dolore\net ea quasi aut doloribus sint\nvel suscipit tempora delectus soluta" - }, - { - "postId": 71, - "id": 353, - "name": "consequatur reprehenderit similique vitae dolor debitis", - "email": "Madaline@marlin.org", - "body": "nemo aut laborum expedita nisi sed illum\nab asperiores provident\na sunt recusandae ut rerum itaque est voluptatibus nihil\nesse ipsum et repellendus nobis rerum voluptas et" - }, - { - "postId": 71, - "id": 354, - "name": "eligendi tempora eum deserunt", - "email": "Mike.Kozey@gladyce.us", - "body": "delectus est consequatur\nat excepturi asperiores dolor nesciunt ad\nid non aut ad ut\nnon et voluptatem" - }, - { - "postId": 71, - "id": 355, - "name": "reiciendis ad ea", - "email": "Orval.Treutel@arnold.me", - "body": "vel cumque labore vitae quisquam magnam sequi ut\nmolestiae dolores vel minus aut\nquas repellat nostrum fugit molestiae veritatis sequi\nvel quidem in molestiae id doloribus sed" - }, - { - "postId": 72, - "id": 356, - "name": "qui vel id qui est", - "email": "Trent@samir.net", - "body": "fugiat dolore voluptas tempore\naspernatur quibusdam rem iste sit fugiat nesciunt consequatur\ndolor sed odit similique minima corporis quae in adipisci\nimpedit dolores et cupiditate accusantium perferendis dignissimos error" - }, - { - "postId": 72, - "id": 357, - "name": "consectetur totam fugit et occaecati minima aliquid hic adipisci", - "email": "Ashleigh@annette.ca", - "body": "et eos est quis quia molestiae est\nquasi est quos omnis\naut et sit consectetur ex molestiae\nest sed accusamus asperiores" - }, - { - "postId": 72, - "id": 358, - "name": "accusantium natus ex et consequuntur neque", - "email": "Douglas@anabel.org", - "body": "unde ad id nemo ipsam dolorem autem quaerat\nperspiciatis voluptas corrupti laborum rerum est architecto\neius quos aut et voluptate voluptatem atque necessitatibus\nvoluptate fugiat aut iusto et atque" - }, - { - "postId": 72, - "id": 359, - "name": "esse quia quidem quisquam consequatur nisi deleniti", - "email": "Lowell@mossie.com", - "body": "et explicabo voluptatem ut est nihil culpa et\nveritatis repellendus ipsum velit qui eligendi maxime voluptatem est\ndicta rerum et et nemo quia\neveniet aspernatur nostrum molestiae mollitia ut dolores rem fugiat" - }, - { - "postId": 72, - "id": 360, - "name": "rerum tempore facilis ut quod sit", - "email": "Jacquelyn@kristian.net", - "body": "sit et aut recusandae\ncorrupti nisi vero eius nulla voluptates\nvoluptatem placeat est commodi impedit odio omnis\nsimilique debitis et in molestiae omnis sed non magni" - }, - { - "postId": 73, - "id": 361, - "name": "voluptates qui et corporis", - "email": "Antwon@domenico.me", - "body": "cum ad porro fuga sequi dolores\nipsa error magni itaque labore accusamus\ncorporis odit consequatur quis debitis\ncum et voluptas facilis incidunt ut itaque dolores error" - }, - { - "postId": 73, - "id": 362, - "name": "quia qui quia qui", - "email": "Kenyon@retha.me", - "body": "excepturi omnis occaecati officiis enim saepe id\nnon quo et dignissimos voluptates ipsum\nmolestias facere dolorem qui iure similique corrupti\nneque ducimus sit alias dolor earum autem doloribus consequatur" - }, - { - "postId": 73, - "id": 363, - "name": "nihil consequatur quibusdam", - "email": "Ben@elouise.net", - "body": "est magni totam est\net enim nam voluptas veritatis est\nsint doloremque incidunt et cum a\net eligendi nobis ratione delectus" - }, - { - "postId": 73, - "id": 364, - "name": "vel architecto assumenda et maiores", - "email": "Madisen.Hauck@barney.co.uk", - "body": "architecto quo enim ad et reprehenderit\nlaboriosam quia commodi officia iusto\ndolorem totam consequuntur cupiditate\nveritatis voluptates aspernatur earum saepe et sed consequatur" - }, - { - "postId": 73, - "id": 365, - "name": "aliquam officiis omnis", - "email": "Dock.Parker@roy.biz", - "body": "modi sed aut quidem quisquam optio est\naut facilis sit quia quis facere quod\nfugiat recusandae ex et quisquam ipsum sed sit\nexercitationem quia recusandae dolorem quasi iusto ipsa qui et" - }, - { - "postId": 74, - "id": 366, - "name": "aperiam ut deserunt minus quo dicta nisi", - "email": "Pablo.Ritchie@tyrique.co.uk", - "body": "explicabo perspiciatis quae sit qui nulla incidunt facilis\nrepudiandae perspiciatis voluptate expedita sunt consectetur quasi\nid occaecati nesciunt dolorem aliquid perspiciatis repellat inventore esse\nut possimus exercitationem facere modi" - }, - { - "postId": 74, - "id": 367, - "name": "praesentium eos quam eius optio eveniet", - "email": "Sebastian_Gaylord@freda.org", - "body": "nostrum modi et et dolores maxime facere\nalias doloribus eaque expedita et similique voluptatum magnam est\nomnis eos voluptatem\net unde fugit voluptatem asperiores" - }, - { - "postId": 74, - "id": 368, - "name": "fugiat aliquid sint", - "email": "Lazaro@nadia.ca", - "body": "est dolor eveniet\nest minus eveniet recusandae\niure quo aut eos ut sed ipsa\nharum earum aut nesciunt non dolores" - }, - { - "postId": 74, - "id": 369, - "name": "qui incidunt vel iusto eligendi amet quia qui", - "email": "Jessy.Boyle@vernice.biz", - "body": "qui fugit accusamus\net quo minus cumque hic adipisci\nodio blanditiis omnis et est\narchitecto et facilis inventore quasi provident quaerat ex rem" - }, - { - "postId": 74, - "id": 370, - "name": "libero vero voluptatum sed facilis quos aut reprehenderit ad", - "email": "Mitchel@hal.co.uk", - "body": "beatae hic est et deserunt eius\ncorrupti quam ut commodi sit modi est corporis animi\nharum ut est\naperiam non fugit excepturi quo tenetur totam" - }, - { - "postId": 75, - "id": 371, - "name": "ut quia sequi sed eius voluptas", - "email": "Lindsay@kiley.name", - "body": "est dicta totam architecto et minus id aut non\nut et fugit eaque culpa modi repellendus\naliquid qui veritatis doloribus aut consequatur voluptas sequi accusantium\nvoluptas occaecati saepe reprehenderit ut" - }, - { - "postId": 75, - "id": 372, - "name": "qui cumque eos consequatur fuga ut", - "email": "Erika.Murazik@jorge.me", - "body": "aut illum est asperiores\nrerum laboriosam quis sit dolores magni minima fuga atque\nomnis at et quibusdam earum rem\nearum distinctio autem et enim dolore eos" - }, - { - "postId": 75, - "id": 373, - "name": "nemo voluptatum quo qui atque", - "email": "Olin@edmund.ca", - "body": "iure aliquid qui sit\nexcepturi dolorem rerum possimus suscipit atque nisi\nlabore ut aut nihil voluptatum ut aliquid praesentium\nassumenda tempore dolor velit ratione et corrupti" - }, - { - "postId": 75, - "id": 374, - "name": "quam exercitationem alias totam", - "email": "Lacey@novella.biz", - "body": "eligendi et consequuntur dolor nihil quaerat doloremque ut\ndignissimos sunt veniam non ratione esse\ndebitis omnis similique maxime dolores tempora laborum rerum adipisci\nreiciendis explicabo error quidem quo necessitatibus voluptatibus vitae ipsum" - }, - { - "postId": 75, - "id": 375, - "name": "similique doloribus odit quas magnam omnis dolorem dolore et", - "email": "Sister@miller.net", - "body": "non ea sed reprehenderit reiciendis eaque et neque adipisci\nipsa architecto deserunt ratione nesciunt tempore similique occaecati non\nhic vitae sit neque\nrerum quod dolorem ratione esse aperiam necessitatibus" - }, - { - "postId": 76, - "id": 376, - "name": "dolorem qui architecto provident", - "email": "Raphaelle@miller.com", - "body": "sint qui aut aspernatur necessitatibus\nlaboriosam autem occaecati nostrum non\nofficiis consequuntur odit\net itaque quam placeat aut molestiae saepe veniam provident" - }, - { - "postId": 76, - "id": 377, - "name": "nemo hic sapiente placeat quidem omnis", - "email": "Jaren.Schiller@augusta.org", - "body": "sint fugit et\nid et saepe non molestiae sit earum doloremque\ndolorem nemo earum dignissimos ipsa soluta deleniti quos\nquis numquam ducimus sed corporis dolores sed quisquam suscipit" - }, - { - "postId": 76, - "id": 378, - "name": "vitae aut perspiciatis quia enim voluptas", - "email": "Nikko_Reynolds@harry.me", - "body": "est molestiae non fugiat voluptatem quo porro\naut praesentium ipsam aspernatur perferendis fuga\nofficia sit ut\naspernatur rerum est" - }, - { - "postId": 76, - "id": 379, - "name": "est qui quos exercitationem", - "email": "Afton.Medhurst@mina.info", - "body": "laboriosam quia animi ut\nquasi aut enim sequi numquam similique fugiat voluptatum non\nsed velit quod nisi id quidem\nmagni in eveniet hic" - }, - { - "postId": 76, - "id": 380, - "name": "similique fugiat tenetur ea incidunt numquam", - "email": "Wilson.Nikolaus@fredrick.org", - "body": "voluptatum quis ipsa voluptatem saepe est\nillum error repellat eaque dolor quae qui\neum rerum sunt quam illo\nvoluptates fuga possimus nemo nihil distinctio" - }, - { - "postId": 77, - "id": 381, - "name": "sint porro optio voluptatem", - "email": "Tomasa@lee.us", - "body": "consequatur possimus sit itaque distinctio fugit aut quod\nexplicabo exercitationem voluptas labore rerum\nporro ut in voluptas maiores tempora accusantium\nvoluptatum et sapiente sit quas quis et veniam" - }, - { - "postId": 77, - "id": 382, - "name": "eius itaque ut ipsa quia quis labore", - "email": "Ally_Kassulke@rashad.ca", - "body": "eaque eius delectus molestias suscipit nulla quisquam\ntotam vel quos et autem sed\neligendi et pariatur earum molestias magnam autem\nplaceat culpa est et qui commodi illo et" - }, - { - "postId": 77, - "id": 383, - "name": "provident voluptas perferendis quibusdam libero", - "email": "Reagan_Ziemann@ross.io", - "body": "qui quaerat id repellendus aut qui\nmaiores quidem consequatur dignissimos deleniti deserunt eveniet libero a\nrepellat ducimus quia aut dignissimos numquam molestiae\nconsequatur sit impedit nostrum et sunt iure" - }, - { - "postId": 77, - "id": 384, - "name": "et et voluptas et eligendi distinctio accusantium temporibus enim", - "email": "Angelita@sally.org", - "body": "blanditiis dolor sint nulla cum quidem aliquid voluptatem\nperferendis dolor consequatur voluptas et ut quisquam tempora tenetur\nmaxime minus animi qui id\neum accusantium et optio et blanditiis maxime" - }, - { - "postId": 77, - "id": 385, - "name": "qui voluptates molestias necessitatibus eos odio quo minima", - "email": "Lonzo@lorena.org", - "body": "sit fugiat est autem quia ipsam error ab\nvoluptatem sed ab labore molestiae qui debitis exercitationem\nnon et sunt officia illo possimus iste tenetur est\ndolores voluptas ad aspernatur nihil" - }, - { - "postId": 78, - "id": 386, - "name": "temporibus minus debitis deleniti repellat unde eveniet", - "email": "Alexandre@derrick.co.uk", - "body": "et dicta dolores sit\nrepudiandae id harum temporibus\nvoluptas quia blanditiis numquam a enim quae\nquisquam assumenda nam doloribus vel temporibus distinctio eveniet dolores" - }, - { - "postId": 78, - "id": 387, - "name": "magnam nihil delectus dolor natus ab ea et", - "email": "Judd@lucinda.ca", - "body": "qui recusandae veniam sed voluptatem ullam facilis consequatur\nnumquam ut quod aut et\nnon alias ex quam aut quasi ipsum praesentium\nut aspernatur sit" - }, - { - "postId": 78, - "id": 388, - "name": "laudantium quibusdam blanditiis pariatur non vero deleniti a perferendis", - "email": "Eleanora@karson.net", - "body": "facilis et totam\nvoluptatibus est optio cum\nfacilis qui aut blanditiis rerum voluptatem accusamus\net omnis quasi sint" - }, - { - "postId": 78, - "id": 389, - "name": "excepturi nam cum molestiae et totam voluptatem nisi", - "email": "Enrico_Feil@liana.biz", - "body": "dolore nihil perferendis\ndolor hic repudiandae iste\ndoloribus labore quaerat et molestiae dolores sit excepturi sint\net eum et aut" - }, - { - "postId": 78, - "id": 390, - "name": "temporibus aut et", - "email": "Beverly@perry.org", - "body": "dolor ratione ab repellendus aut quia reiciendis sed\nest alias ex\nodio voluptatem velit odit tempora nihil optio aperiam blanditiis\nlabore porro id velit dolor veritatis" - }, - { - "postId": 79, - "id": 391, - "name": "sed ratione nesciunt odit expedita", - "email": "Corene_Mante@rory.com", - "body": "aut repellat tenetur delectus eaque est nihil consequatur quae\ndeleniti assumenda voluptates sit sit cupiditate maiores\nautem suscipit sint tenetur dolor tempore\ndolorem dolorum alias adipisci" - }, - { - "postId": 79, - "id": 392, - "name": "rerum officiis qui quaerat omnis dolorem iure est repudiandae", - "email": "Emily_Flatley@ephraim.name", - "body": "aut aut ea ut repudiandae ea assumenda laboriosam\nsunt qui laboriosam dicta omnis sit corporis\nvoluptas eos amet quam quisquam officiis facilis laborum\nvoluptatibus accusantium ab aliquid sed id doloremque" - }, - { - "postId": 79, - "id": 393, - "name": "illo quis nostrum accusantium architecto et aliquam ratione", - "email": "Donna@frederik.com", - "body": "et quia explicabo\nut hic commodi quas provident aliquam nihil\nvitae in voluptatem commodi\nvero velit optio omnis accusamus corrupti voluptatem" - }, - { - "postId": 79, - "id": 394, - "name": "reprehenderit eos voluptatem ut", - "email": "Kyleigh@ruben.org", - "body": "voluptatem quisquam pariatur voluptatum qui quaerat\net minus ea aliquam ullam dolorem consequatur\nratione at ad nemo aperiam excepturi deleniti\nqui numquam quis hic nostrum tempora quidem" - }, - { - "postId": 79, - "id": 395, - "name": "excepturi esse laborum ut qui culpa", - "email": "Noemy.Hammes@lisette.net", - "body": "esse vel reiciendis nobis inventore vero est\nfugit inventore ea quo consequatur aut\nautem deserunt ratione et repellendus nihil quam\nquidem iure est nihil mollitia" - }, - { - "postId": 80, - "id": 396, - "name": "qui eos vitae possimus reprehenderit voluptatem voluptatem repellendus", - "email": "Margarett_Jenkins@harley.us", - "body": "perferendis veritatis saepe voluptatem\neum voluptas quis\nsed occaecati nostrum\nfugit animi omnis ratione molestias" - }, - { - "postId": 80, - "id": 397, - "name": "quasi exercitationem molestias dolore non non sed est", - "email": "Dexter.Pacocha@lauren.biz", - "body": "ut nisi sunt perspiciatis qui doloribus quas\nvelit molestiae atque corrupti corporis voluptatem\nvel ratione aperiam tempore est eos\nquia a voluptas" - }, - { - "postId": 80, - "id": 398, - "name": "labore consequuntur vel qui", - "email": "Gennaro@jaunita.co.uk", - "body": "libero atque accusamus blanditiis minima eveniet corporis est aliquid\ndolores asperiores neque quibusdam quaerat error esse non\nqui et adipisci\nmagni illo hic qui qui dignissimos earum" - }, - { - "postId": 80, - "id": 399, - "name": "sunt ut eos", - "email": "Jaycee@aimee.us", - "body": "corrupti ut et eveniet culpa\nveritatis eos sequi fugiat commodi consequuntur\nipsa totam voluptatem perferendis ducimus aut exercitationem magni\neos mollitia quia" - }, - { - "postId": 80, - "id": 400, - "name": "quia aut consequatur sunt iste aliquam impedit sit", - "email": "Brennon@carmela.tv", - "body": "natus iure velit impedit sed officiis sint\nmolestiae non beatae\nillo consequatur minima\nsed ratione est tenetur" - }, - { - "postId": 81, - "id": 401, - "name": "cum voluptate sint voluptas veritatis", - "email": "Vella.Mayer@colten.net", - "body": "sit delectus recusandae qui\net cupiditate sed ipsum culpa et fugiat ab\nillo dignissimos quo est repellat dolorum neque\nvoluptates sed sapiente ab aut rerum enim sint voluptatum" - }, - { - "postId": 81, - "id": 402, - "name": "ut eos mollitia eum eius", - "email": "Caleb_Dach@kathleen.us", - "body": "et nisi fugit totam\nmaiores voluptatibus quis ipsa sunt debitis assumenda\nullam non quasi numquam ut dolores modi recusandae\nut molestias magni est voluptas quibusdam corporis eius" - }, - { - "postId": 81, - "id": 403, - "name": "architecto voluptatum eos blanditiis aliquam debitis beatae nesciunt dolorum", - "email": "Patience_Bahringer@dameon.biz", - "body": "et a et perspiciatis\nautem expedita maiores dignissimos labore minus molestiae enim\net ipsam ea et\nperspiciatis veritatis debitis maxime" - }, - { - "postId": 81, - "id": 404, - "name": "officia qui ut explicabo eos fugit", - "email": "Destinee.Simonis@jose.tv", - "body": "modi est et eveniet facilis explicabo\nvoluptatem saepe quo et sint quas quia corporis\npariatur voluptatibus est iste fugiat delectus animi rerum\ndoloribus est enim" - }, - { - "postId": 81, - "id": 405, - "name": "incidunt commodi voluptatem maiores asperiores blanditiis omnis ratione", - "email": "Keshaun@brown.biz", - "body": "aut aut sit cupiditate maxime praesentium occaecati cumque\nvero sint sit aliquam porro quo consequuntur ut\nnumquam qui maxime voluptas est consequatur ullam\ntenetur commodi qui consectetur distinctio eligendi atque" - }, - { - "postId": 82, - "id": 406, - "name": "sint eaque rerum voluptas fugiat quia qui", - "email": "Merle.Schultz@marcel.org", - "body": "dicta in quam tenetur\nsed molestiae a sit est aut quia autem aut\nnam voluptatem reiciendis corporis voluptatem\nsapiente est id quia explicabo enim tempora asperiores" - }, - { - "postId": 82, - "id": 407, - "name": "eius tempora sint reprehenderit", - "email": "Malvina_Fay@louie.name", - "body": "totam ad quia non vero dolor laudantium sed temporibus\nquia aperiam corrupti sint accusantium eligendi\naliquam rerum voluptatem delectus numquam nihil\nsoluta qui sequi nisi voluptatum eaque voluptas animi ipsam" - }, - { - "postId": 82, - "id": 408, - "name": "non excepturi enim est sapiente numquam repudiandae illo", - "email": "Domenick_Douglas@gabe.us", - "body": "suscipit quidem fugiat consequatur\nquo sequi nesciunt\naliquam ratione possimus\nvoluptatem sit quia repellendus libero excepturi ut temporibus" - }, - { - "postId": 82, - "id": 409, - "name": "dicta dolor voluptate vel praesentium", - "email": "Isaac@allene.net", - "body": "provident illo quis dolor distinctio laborum eius enim\nsuscipit quia error enim eos consequuntur\nest incidunt adipisci beatae tenetur excepturi in labore commodi\nfugiat omnis in et at nam accusamus et" - }, - { - "postId": 82, - "id": 410, - "name": "et dolore hic a cupiditate beatae natus iusto soluta", - "email": "Marianna.Pacocha@george.net", - "body": "in consequatur corporis qui a et magni eum illum\ncorrupti veniam debitis ab iure harum\nenim ut assumenda cum adipisci veritatis et veniam\nrem eius expedita quos corrupti incidunt" - }, - { - "postId": 83, - "id": 411, - "name": "hic rem eligendi tenetur ipsum dolore maxime eum", - "email": "Sister_Barton@lela.com", - "body": "nam voluptatem ex aut voluptatem mollitia sit sapiente\nqui hic ut\nqui natus in iste et magnam dolores et fugit\nea sint ut minima quas eum nobis at reprehenderit" - }, - { - "postId": 83, - "id": 412, - "name": "quaerat et quia accusamus provident earum cumque", - "email": "Autumn.Lebsack@kasandra.ca", - "body": "veniam non culpa aut voluptas rem eum officiis\nmollitia placeat eos cum\nconsequatur eos commodi dolorem\nanimi maiores qui" - }, - { - "postId": 83, - "id": 413, - "name": "atque porro quo voluptas", - "email": "Irma.OKon@arden.me", - "body": "consequatur harum est omnis\nqui recusandae qui voluptatem et distinctio sint eaque\ndolores quo dolorem asperiores\naperiam non quis asperiores aut praesentium" - }, - { - "postId": 83, - "id": 414, - "name": "ut qui voluptatem hic maxime", - "email": "Alaina_Hammes@carter.info", - "body": "molestias debitis magni illo sint officiis ut quia\nsed tenetur dolorem soluta\nvoluptatem fugiat voluptas molestiae magnam fuga\nsimilique enim illum voluptas aspernatur officia" - }, - { - "postId": 83, - "id": 415, - "name": "rerum consequatur ut et voluptate harum amet accusantium est", - "email": "Alia@addison.org", - "body": "iure vitae accusamus tenetur autem ipsa deleniti\nsunt laudantium ut beatae repellendus non eos\nut consequuntur repudiandae ducimus enim\nreiciendis rem explicabo magni dolore" - }, - { - "postId": 84, - "id": 416, - "name": "neque nemo consequatur ea fugit aut esse suscipit dolore", - "email": "Aurelie_McKenzie@providenci.biz", - "body": "enim velit consequatur excepturi corporis voluptatem nostrum\nnesciunt alias perspiciatis corporis\nneque at eius porro sapiente ratione maiores natus\nfacere molestiae vel explicabo voluptas unde" - }, - { - "postId": 84, - "id": 417, - "name": "quia reiciendis nobis minima quia et saepe", - "email": "May_Steuber@virgil.net", - "body": "et vitae consectetur ut voluptatem\net et eveniet sit\nincidunt tenetur voluptatem\nprovident occaecati exercitationem neque placeat" - }, - { - "postId": 84, - "id": 418, - "name": "nesciunt voluptates amet sint et delectus et dolore culpa", - "email": "Tessie@emilie.co.uk", - "body": "animi est eveniet officiis qui\naperiam dolore occaecati enim aut reiciendis\nanimi ad sint labore blanditiis adipisci voluptatibus eius error\nomnis rerum facere architecto occaecati rerum" - }, - { - "postId": 84, - "id": 419, - "name": "omnis voluptate dolorem similique totam", - "email": "Priscilla@colten.org", - "body": "cum neque recusandae occaecati aliquam reprehenderit ullam saepe veniam\nquasi ea provident tenetur architecto ad\ncupiditate molestiae mollitia molestias debitis eveniet doloremque voluptatem aut\ndolore consequatur nihil facere et" - }, - { - "postId": 84, - "id": 420, - "name": "aut recusandae a sit voluptas explicabo nam et", - "email": "Aylin@abigale.me", - "body": "voluptas cum eum minima rem\ndolorem et nemo repellendus voluptatem sit\nrepudiandae nulla qui recusandae nobis\nblanditiis perspiciatis dolor ipsam reprehenderit odio" - }, - { - "postId": 85, - "id": 421, - "name": "non eligendi ipsam provident", - "email": "Holden@kenny.io", - "body": "voluptate libero corrupti facere totam eaque consequatur nemo\nenim aliquid exercitationem nulla dignissimos illo\nest amet non iure\namet sed dolore non ipsam magni" - }, - { - "postId": 85, - "id": 422, - "name": "sit molestiae corporis", - "email": "Guillermo_Dare@dorothea.tv", - "body": "ducimus ut ut fugiat nesciunt labore\ndeleniti non et aut voluptatum quidem consectetur\nincidunt voluptas accusantium\nquo fuga eaque quisquam et et sapiente aut" - }, - { - "postId": 85, - "id": 423, - "name": "assumenda iure a", - "email": "Oscar@pearline.com", - "body": "rerum laborum voluptas ipsa enim praesentium\nquisquam aliquid perspiciatis eveniet id est est facilis\natque aut facere\nprovident reiciendis libero atque est" - }, - { - "postId": 85, - "id": 424, - "name": "molestiae dolores itaque dicta earum eligendi dolores", - "email": "Jonathon_Feest@maxime.io", - "body": "ducimus hic ea velit\ndolorum soluta voluptas similique rerum\ndolorum sint maxime et vel\nvoluptatum nesciunt et id consequatur earum sed" - }, - { - "postId": 85, - "id": 425, - "name": "cumque expedita consequatur qui", - "email": "Micah_Wolf@lennie.co.uk", - "body": "labore necessitatibus et eum quas id ut\ndoloribus aspernatur nostrum sapiente quo aut quia\neos rerum voluptatem\nnumquam minima soluta velit recusandae ut" - }, - { - "postId": 86, - "id": 426, - "name": "deleniti tempora non quia et aut", - "email": "Shany@daisha.biz", - "body": "reiciendis consequatur sunt atque quisquam ut sed iure\nconsequatur laboriosam dicta odio\nquas cumque iure blanditiis ad sed ullam dignissimos\nsunt et exercitationem saepe" - }, - { - "postId": 86, - "id": 427, - "name": "delectus illum minus odit", - "email": "Drew_Lemke@alexis.net", - "body": "in laborum et distinctio nobis maxime\nmaxime id commodi eaque enim amet qui autem\ndebitis et porro eum dicta sapiente iusto nulla sunt\nvoluptate excepturi sint dolorem voluptatem quae explicabo id" - }, - { - "postId": 86, - "id": 428, - "name": "voluptas dolores dolor nisi voluptatem ratione rerum", - "email": "Karina.Donnelly@liam.com", - "body": "excepturi quos omnis aliquam voluptatem iste\nsit unde ab quam ipsa delectus culpa rerum\ncum delectus impedit ut qui modi\nasperiores qui sapiente quia facilis in iure" - }, - { - "postId": 86, - "id": 429, - "name": "sed omnis dolore aperiam", - "email": "Ahmed_Runolfsson@claire.name", - "body": "ab voluptatem nobis unde\ndoloribus aut fugiat\nconsequuntur laboriosam minima inventore sint quis\ndelectus hic et enim sit optio consequuntur" - }, - { - "postId": 86, - "id": 430, - "name": "sint ullam alias et at et", - "email": "Marilou_Halvorson@kane.io", - "body": "debitis ut maiores ut harum sed voluptas\nquae amet eligendi quo quidem odit atque quisquam animi\nut autem est corporis et\nsed tempora facere corrupti magnam" - }, - { - "postId": 87, - "id": 431, - "name": "velit incidunt ut accusantium odit maiores quaerat", - "email": "Bernie.Schoen@seamus.co.uk", - "body": "voluptas minus fugiat vel\nest quos soluta et veniam quia incidunt unde ut\nlaborum odio in eligendi distinctio odit repellat\nnesciunt consequatur blanditiis cupiditate consequatur at et" - }, - { - "postId": 87, - "id": 432, - "name": "quod quia nihil nisi perferendis laborum blanditiis tempora eos", - "email": "Joesph@darryl.net", - "body": "quam et et harum\nplaceat minus neque quae magni inventore saepe deleniti quisquam\nsuscipit dolorum error aliquid dolores\ndignissimos dolorem autem natus iste molestiae est id impedit" - }, - { - "postId": 87, - "id": 433, - "name": "qui ea voluptatem reiciendis enim enim nisi aut", - "email": "Timmothy.Corwin@augustus.co.uk", - "body": "voluptatem minus asperiores quasi\nperspiciatis et aut blanditiis illo deserunt molestiae ab aperiam\nex minima sed omnis at\net repellat aut incidunt" - }, - { - "postId": 87, - "id": 434, - "name": "doloremque eligendi quas voluptatem non quos ex", - "email": "Julien_OHara@vance.io", - "body": "ex eum at culpa quam aliquam\ncupiditate et id dolorem sint quasi et quos et\nomnis et qui minus est quisquam non qui rerum\nquas molestiae tempore veniam" - }, - { - "postId": 87, - "id": 435, - "name": "id voluptatum nulla maiores ipsa eos", - "email": "Susan.Bartell@euna.org", - "body": "reprehenderit molestias sit nemo quas culpa dolorem exercitationem\neos est voluptatem dolores est fugiat dolorem\neos aut quia et amet et beatae harum vitae\nofficia quia animi dicta magnam accusantium" - }, - { - "postId": 88, - "id": 436, - "name": "ea illo ab et maiores eaque non nobis", - "email": "Selena.Quigley@johan.co.uk", - "body": "sit non facilis commodi sapiente officiis aut facere libero\nsed voluptatum eligendi veniam velit explicabo\nsint laborum sunt reprehenderit dolore id nobis accusamus\nfugit voluptatem magni dolor qui dolores ipsa" - }, - { - "postId": 88, - "id": 437, - "name": "magni asperiores in cupiditate", - "email": "Clifton_Boehm@jacynthe.io", - "body": "suscipit ab qui eos et commodi\nquas ad maiores repellat laboriosam voluptatem exercitationem\nquibusdam ullam ratione nulla\nquia iste error dolorem consequatur beatae temporibus fugit" - }, - { - "postId": 88, - "id": 438, - "name": "ullam autem aliquam", - "email": "Lizzie_Bartell@diamond.net", - "body": "voluptas aspernatur eveniet\nquod id numquam dolores quia perspiciatis eum\net delectus quia occaecati adipisci nihil velit accusamus esse\nminus aspernatur repudiandae" - }, - { - "postId": 88, - "id": 439, - "name": "voluptates quasi minus dolorem itaque nemo", - "email": "Yasmeen@golda.ca", - "body": "cupiditate laborum sit reprehenderit ratione est ad\ncorporis rem pariatur enim et omnis dicta\nnobis molestias quo corporis et nihil\nsed et impedit aut quisquam natus expedita voluptate at" - }, - { - "postId": 88, - "id": 440, - "name": "adipisci sapiente libero beatae quas eveniet", - "email": "Adolf.Russel@clark.ca", - "body": "ut nam ut asperiores quis\nexercitationem aspernatur eligendi autem repellendus\nest repudiandae quisquam rerum ad explicabo suscipit deserunt eius\nalias aliquid eos pariatur rerum magnam provident iusto" - }, - { - "postId": 89, - "id": 441, - "name": "nisi qui voluptates recusandae voluptas assumenda et", - "email": "Elian@matilda.me", - "body": "illum qui quis optio\nquasi eius dolores et non numquam et\nqui necessitatibus itaque magnam mollitia earum et\nnisi voluptate eum accusamus ea" - }, - { - "postId": 89, - "id": 442, - "name": "sed molestias sit voluptatibus sit aut alias sunt inventore", - "email": "Salma@francis.net", - "body": "velit ut incidunt accusantium\nsuscipit animi officia iusto\nnemo omnis sunt nobis repellendus corporis\nconsequatur distinctio dolorem" - }, - { - "postId": 89, - "id": 443, - "name": "illum pariatur aliquam esse nisi voluptas quisquam ea", - "email": "Orlando_Dickinson@vern.org", - "body": "reiciendis et distinctio qui totam non aperiam voluptas\nveniam in dolorem pariatur itaque\nvoluptas adipisci velit\nqui voluptates voluptas ut ullam veritatis repudiandae" - }, - { - "postId": 89, - "id": 444, - "name": "incidunt aut qui quis est sit corporis pariatur qui", - "email": "Elda@orval.name", - "body": "eligendi labore aut non modi vel facere qui\naccusamus qui maxime aperiam\ntotam et non ut repudiandae eum corrupti pariatur quia\nnecessitatibus et adipisci ipsa consequuntur enim et nihil vero" - }, - { - "postId": 89, - "id": 445, - "name": "temporibus adipisci eveniet libero ullam", - "email": "Dennis@karley.net", - "body": "est consequuntur cumque\nquo dolorem at ut dolores\nconsequatur quia voluptates reiciendis\nvel rerum id et" - }, - { - "postId": 90, - "id": 446, - "name": "dicta excepturi aut est dolor ab dolores rerum", - "email": "Jedediah@mason.io", - "body": "cum fugit earum vel et nulla et voluptatem\net ipsam aut\net nihil officia nemo eveniet accusamus\nnulla aut impedit veritatis praesentium" - }, - { - "postId": 90, - "id": 447, - "name": "molestiae qui quod quo", - "email": "Maryam@jack.name", - "body": "rerum omnis voluptatem harum aliquid voluptas accusamus\neius dicta animi\nodio non quidem voluptas tenetur\nnostrum deserunt laudantium culpa dolorum" - }, - { - "postId": 90, - "id": 448, - "name": "pariatur consequatur sit commodi aliquam", - "email": "Rick@carlos.tv", - "body": "odio maxime beatae ab labore rerum\nalias ipsa nam est nostrum\net debitis aut\nab molestias assumenda eaque repudiandae" - }, - { - "postId": 90, - "id": 449, - "name": "sunt quia soluta quae sit deleniti dolor ullam veniam", - "email": "Vallie@jerrod.net", - "body": "dolor at accusantium eveniet\nin voluptatem quam et fugiat et quasi dolores\nsunt eligendi voluptatum id voluptas vitae\nquibusdam iure eum perspiciatis" - }, - { - "postId": 90, - "id": 450, - "name": "dolorem corporis facilis et", - "email": "Adolph.Hayes@isobel.biz", - "body": "et provident quo necessitatibus harum excepturi\nsed est ut sed est doloremque labore quod\nquia optio explicabo adipisci magnam doloribus\nveritatis illo aut est inventore" - }, - { - "postId": 91, - "id": 451, - "name": "maiores ut dolores quo sapiente nisi", - "email": "Duane_Dach@demario.us", - "body": "dolor veritatis ipsum accusamus quae voluptates sint voluptatum et\nharum saepe dolorem enim\nexpedita placeat qui quidem aut et et est\nminus odit qui possimus qui saepe" - }, - { - "postId": 91, - "id": 452, - "name": "quia excepturi in harum repellat consequuntur est vel qui", - "email": "General@schuyler.org", - "body": "ratione sequi sed\nearum nam aut sunt\nquam cum qui\nsimilique consequatur et est" - }, - { - "postId": 91, - "id": 453, - "name": "doloremque ut est eaque", - "email": "Stephania_Stanton@demond.biz", - "body": "quidem nisi reprehenderit eligendi fugiat et et\nsapiente adipisci natus nulla similique et est\nesse ea accusantium sunt\ndeleniti molestiae perferendis quam animi similique ut" - }, - { - "postId": 91, - "id": 454, - "name": "magni quos voluptatibus earum et inventore suscipit", - "email": "Reinhold.Schiller@kelly.info", - "body": "consequatur accusamus maiores dolorem impedit repellendus voluptas rerum eum\nquam quia error voluptatem et\ndignissimos fugit qui\net facilis necessitatibus dignissimos consequatur iusto nihil possimus" - }, - { - "postId": 91, - "id": 455, - "name": "assumenda qui et aspernatur", - "email": "Royce@jaiden.co.uk", - "body": "animi qui nostrum rerum velit\nvoluptates sit in laborum dolorum omnis ut omnis\nea optio quia necessitatibus delectus molestias sapiente perferendis\ndolores vel excepturi expedita" - }, - { - "postId": 92, - "id": 456, - "name": "quod voluptatem qui qui sit sed maiores fugit", - "email": "Cassie@diana.org", - "body": "sunt ipsam illum consequuntur\nquasi enim possimus unde qui beatae quo eligendi\nvel quia asperiores est quae voluptate\naperiam et iste perspiciatis" - }, - { - "postId": 92, - "id": 457, - "name": "ipsa animi saepe veritatis voluptatibus ad amet id aut", - "email": "Jena.OKeefe@adonis.net", - "body": "incidunt itaque enim pariatur quibusdam voluptatibus blanditiis sint\nerror laborum voluptas sed officiis molestiae nostrum\ntemporibus culpa aliquam sit\nconsectetur dolores tempore id accusantium dignissimos vel" - }, - { - "postId": 92, - "id": 458, - "name": "fugiat consectetur saepe dicta", - "email": "Magdalen@holly.io", - "body": "eos hic deserunt necessitatibus sed id ut esse nam\nhic eveniet vitae corrupti mollitia doloremque sit ratione\ndeleniti perspiciatis numquam est sapiente quaerat\nest est sit" - }, - { - "postId": 92, - "id": 459, - "name": "nesciunt numquam alias doloremque minus ipsam optio", - "email": "Nyah@otho.com", - "body": "veniam natus aut vero et aliquam doloremque\nalias cupiditate non est\ntempore et non vel error placeat est quisquam ea\nnon dolore aliquid non fuga expedita dicta ut quos" - }, - { - "postId": 92, - "id": 460, - "name": "eum fugit omnis optio", - "email": "Kara_Stokes@connie.co.uk", - "body": "qui qui deserunt expedita at\nprovident sequi veritatis sit qui nam tempora mollitia ratione\ncorporis vitae rerum pariatur unde deleniti ut eos ad\naut non quae nisi saepe" - }, - { - "postId": 93, - "id": 461, - "name": "perferendis nobis praesentium accusantium culpa et et", - "email": "Conner@daron.info", - "body": "eos quidem temporibus eum\nest ipsa sunt illum a facere\nomnis suscipit dolorem voluptatem incidunt\ntenetur deleniti aspernatur at quis" - }, - { - "postId": 93, - "id": 462, - "name": "assumenda quia sint", - "email": "Nathanael@jada.org", - "body": "adipisci et accusantium hic deserunt voluptates consequatur omnis\nquod dolorem voluptatibus quis velit laboriosam mollitia illo et\niure aliquam dolorem nesciunt laborum\naperiam labore repellat et maxime itaque" - }, - { - "postId": 93, - "id": 463, - "name": "cupiditate quidem corporis totam tenetur rem nesciunt et", - "email": "Nicklaus@talon.io", - "body": "voluptate officiis nihil laudantium sint autem adipisci\naspernatur voluptas debitis nam omnis ut non eligendi\naliquam vel commodi velit officiis laboriosam corporis\nquas aliquid aperiam autem" - }, - { - "postId": 93, - "id": 464, - "name": "quisquam quaerat rerum dolor asperiores doloremque", - "email": "Jerald@laura.io", - "body": "consequatur aliquam illum quis\nfacere vel voluptatem rem sint atque\nin nam autem impedit dolores enim\nsoluta rem adipisci odit sint voluptas aliquam" - }, - { - "postId": 93, - "id": 465, - "name": "est sunt est nesciunt distinctio quaerat reprehenderit in vero", - "email": "Jamey_Dare@johnny.org", - "body": "ex corrupti ut pariatur voluptas illo labore non voluptates\nvoluptas sint et est impedit cum\nin fugiat cumque eum id rerum error\nut rerum voluptates facilis molestiae et labore voluptatem corrupti" - }, - { - "postId": 94, - "id": 466, - "name": "impedit autem distinctio omnis ipsam voluptas eaque", - "email": "Brant@yasmin.co.uk", - "body": "aut dignissimos eos facere velit totam\neaque aut voluptas ex similique ut ipsa est\nvoluptates ut tempora\nquis commodi officia et consequatur cumque delectus" - }, - { - "postId": 94, - "id": 467, - "name": "voluptas unde perferendis ut eaque dicta", - "email": "Adrianna_Howell@molly.io", - "body": "deleniti fuga hic autem\nsed rerum non voluptate sit totam consequuntur illo\nquasi quod aut ducimus dolore distinctio molestias\nnon velit quis debitis cumque voluptas" - }, - { - "postId": 94, - "id": 468, - "name": "nam praesentium est ipsa libero aut", - "email": "Amiya.Morar@emma.tv", - "body": "facilis repellendus inventore aperiam corrupti saepe culpa velit\ndolores sint ut\naut quis voluptates iure et a\nneque harum quia similique sunt eum voluptatem a" - }, - { - "postId": 94, - "id": 469, - "name": "vel eum quia esse sapiente", - "email": "Destany@bailey.info", - "body": "dolor unde numquam distinctio\nducimus eum hic rerum non expedita\ndolores et dignissimos rerum\nperspiciatis et porro est minus" - }, - { - "postId": 94, - "id": 470, - "name": "deleniti vitae alias distinctio dignissimos ab accusantium pariatur dicta", - "email": "Katarina.Wolff@joel.io", - "body": "molestias incidunt eaque\nnumquam reprehenderit rerum ut ex ad\nomnis porro maiores quaerat harum nihil non quasi ea\nasperiores quisquam sunt fugiat eos natus iure adipisci" - }, - { - "postId": 95, - "id": 471, - "name": "nihil ad debitis rerum optio est cumque sed voluptates", - "email": "Pearline@veda.ca", - "body": "quia non dolor\ncorporis consectetur velit eos quis\nincidunt ut eos nesciunt repellendus voluptas voluptate sint neque\ndoloribus est minima autem quis velit illo ea neque" - }, - { - "postId": 95, - "id": 472, - "name": "aspernatur ex dolor optio", - "email": "Belle.Braun@otis.name", - "body": "et necessitatibus earum qui velit id explicabo harum optio\ndolor dolores reprehenderit in\na itaque odit esse et et id\npossimus est ut consequuntur velit autem iure ut" - }, - { - "postId": 95, - "id": 473, - "name": "quaerat et excepturi autem animi fuga", - "email": "Eliane@libby.net", - "body": "quod corrupti eum quisquam rerum accusantium tempora\nreprehenderit qui voluptate et sunt optio et\niusto nihil amet omnis labore cumque quo\nsaepe omnis aut quia consectetur" - }, - { - "postId": 95, - "id": 474, - "name": "natus consequatur deleniti ipsum delectus", - "email": "Trey.Harber@christop.biz", - "body": "tempora sint qui iste itaque non neque qui suscipit\nenim quas rerum totam impedit\nesse nulla praesentium natus explicabo doloremque atque maxime\nmollitia impedit dolorem occaecati officia in provident eos" - }, - { - "postId": 95, - "id": 475, - "name": "cumque consequuntur excepturi consequatur consequatur est", - "email": "Kailyn@ivory.info", - "body": "ut in nostrum\nut et incidunt et minus nulla perferendis libero delectus\nnulla nemo deleniti\ndeleniti facere autem vero velit non molestiae assumenda" - }, - { - "postId": 96, - "id": 476, - "name": "quia hic adipisci modi fuga aperiam", - "email": "Amely.Kunde@rodrigo.co.uk", - "body": "officia quas aut culpa eum\neaque quia rem unde ea quae reiciendis omnis\nexcepturi nemo est vel sequi accusantium tenetur at earum\net rerum quisquam temporibus cupiditate" - }, - { - "postId": 96, - "id": 477, - "name": "ut occaecati non", - "email": "Thaddeus.Halvorson@ruthe.ca", - "body": "nulla veniam quo consequuntur ullam\nautem nisi error aut facere distinctio rerum quia tempore\nvelit distinctio occaecati ducimus\nratione similique doloribus" - }, - { - "postId": 96, - "id": 478, - "name": "quo error dignissimos numquam qui nam fugit voluptates et", - "email": "Hannah@emma.ca", - "body": "non similique illo\nquia et rem placeat reprehenderit voluptas\nvelit officiis fugit blanditiis nihil\nab deserunt ullam" - }, - { - "postId": 96, - "id": 479, - "name": "distinctio minima error aspernatur reiciendis inventore quo", - "email": "Maryam.Mann@thelma.info", - "body": "totam explicabo harum quam impedit sunt\ndoloremque consectetur id et minima eos incidunt quibusdam omnis\nsaepe maiores officiis eligendi alias sint est aut cumque\ndebitis cumque hic aut ut dolorum" - }, - { - "postId": 96, - "id": 480, - "name": "accusantium quo error repudiandae", - "email": "Michel@keira.us", - "body": "tenetur qui ut\narchitecto officiis voluptatem velit eos molestias incidunt eum dolorum\ndistinctio quam et\nsequi consequatur nihil voluptates animi" - }, - { - "postId": 97, - "id": 481, - "name": "recusandae dolor similique autem saepe voluptate aut vel sit", - "email": "Domenick@russell.ca", - "body": "dignissimos nobis vitae corporis delectus eligendi et ut ut\namet laudantium neque\net quia cupiditate debitis aliquid\ndolorem aspernatur libero aut autem quo et" - }, - { - "postId": 97, - "id": 482, - "name": "placeat eveniet sunt ut quis", - "email": "Chanelle@samson.me", - "body": "aliquid natus voluptas doloremque fugiat ratione adipisci\nunde eum facilis enim omnis ipsum nobis nihil praesentium\nut blanditiis voluptatem veniam\ntenetur fugit et distinctio aspernatur" - }, - { - "postId": 97, - "id": 483, - "name": "a ipsa nihil sed impedit", - "email": "Hermann.Kunde@rosina.us", - "body": "quos aut rerum nihil est et\ndolores commodi voluptas voluptatem excepturi et\net expedita dignissimos atque aut reprehenderit\nquis quo soluta" - }, - { - "postId": 97, - "id": 484, - "name": "hic inventore sint aut", - "email": "Olen@bryce.net", - "body": "vel libero quo sit vitae\nid nesciunt ipsam non a aut enim itaque totam\nillum est cupiditate sit\nnam exercitationem magnam veniam" - }, - { - "postId": 97, - "id": 485, - "name": "enim asperiores illum", - "email": "Lorenza.Carter@consuelo.ca", - "body": "soluta quia porro mollitia eos accusamus\nvoluptatem illo perferendis earum quia\nquo sed ipsam in omnis cum earum tempore eos\nvoluptatem illum doloremque corporis ipsam facere" - }, - { - "postId": 98, - "id": 486, - "name": "et aut qui eaque porro quo quis velit rerum", - "email": "Lamont@georgiana.biz", - "body": "iste maxime et molestiae\nqui aliquam doloremque earum beatae repellat\nin aut eum libero eos itaque pariatur exercitationem\nvel quam non" - }, - { - "postId": 98, - "id": 487, - "name": "sunt omnis aliquam labore eveniet", - "email": "Colin_Gutkowski@muriel.net", - "body": "sint delectus nesciunt ipsum et aliquid et libero\naut suscipit et molestiae nemo pariatur sequi\nrepudiandae ea placeat neque quas eveniet\nmollitia quae laboriosam" - }, - { - "postId": 98, - "id": 488, - "name": "quo neque dolorem dolorum non incidunt", - "email": "Albert@johnny.biz", - "body": "aut sunt recusandae laboriosam omnis asperiores et\nnulla ipsum rerum quis doloremque rerum optio mollitia provident\nsed iste aut id\nnumquam repudiandae veritatis" - }, - { - "postId": 98, - "id": 489, - "name": "aut quia et corporis voluptas quisquam voluptatem", - "email": "Hilma.Kutch@ottilie.info", - "body": "et dolorem sit\nreprehenderit sapiente occaecati iusto sit impedit nobis ut quia\nmaiores debitis pariatur nostrum et aut\nassumenda error qui deserunt laborum quaerat et" - }, - { - "postId": 98, - "id": 490, - "name": "et eum provident maxime beatae minus et doloremque perspiciatis", - "email": "Donnie@alfreda.biz", - "body": "minus nihil sunt dolor\nipsum a illum quis\nquasi officiis cupiditate architecto sit consequatur ut\net sed quasi quam doloremque" - }, - { - "postId": 99, - "id": 491, - "name": "eos enim odio", - "email": "Maxwell@adeline.me", - "body": "natus commodi debitis cum ex rerum alias quis\nmaxime fugiat fugit sapiente distinctio nostrum tempora\npossimus quod vero itaque enim accusantium perferendis\nfugit ut eum labore accusantium voluptas" - }, - { - "postId": 99, - "id": 492, - "name": "consequatur alias ab fuga tenetur maiores modi", - "email": "Amina@emmet.org", - "body": "iure deleniti aut consequatur necessitatibus\nid atque voluptas mollitia\nvoluptates doloremque dolorem\nrepudiandae hic enim laboriosam consequatur velit minus" - }, - { - "postId": 99, - "id": 493, - "name": "ut praesentium sit eos rerum tempora", - "email": "Gilda@jacques.org", - "body": "est eos doloremque autem\nsimilique sint fuga atque voluptate est\nminus tempore quia asperiores aliquam et corporis voluptatem\nconsequatur et eum illo aut qui molestiae et amet" - }, - { - "postId": 99, - "id": 494, - "name": "molestias facere soluta mollitia totam dolorem commodi itaque", - "email": "Kadin@walter.io", - "body": "est illum quia alias ipsam minus\nut quod vero aut magni harum quis\nab minima voluptates nemo non sint quis\ndistinctio officia ea et maxime" - }, - { - "postId": 99, - "id": 495, - "name": "dolor ut ut aut molestiae esse et tempora numquam", - "email": "Alice_Considine@daren.com", - "body": "pariatur occaecati ea autem at quis et dolorem similique\npariatur ipsa hic et saepe itaque cumque repellendus vel\net quibusdam qui aut nemo et illo\nqui non quod officiis aspernatur qui optio" - }, - { - "postId": 100, - "id": 496, - "name": "et occaecati asperiores quas voluptas ipsam nostrum", - "email": "Zola@lizzie.com", - "body": "neque unde voluptatem iure\nodio excepturi ipsam ad id\nipsa sed expedita error quam\nvoluptatem tempora necessitatibus suscipit culpa veniam porro iste vel" - }, - { - "postId": 100, - "id": 497, - "name": "doloribus dolores ut dolores occaecati", - "email": "Dolly@mandy.co.uk", - "body": "non dolor consequatur\nlaboriosam ut deserunt autem odit\nlibero dolore non nesciunt qui\naut est consequatur quo dolorem" - }, - { - "postId": 100, - "id": 498, - "name": "dolores minus aut libero", - "email": "Davion@eldora.net", - "body": "aliquam pariatur suscipit fugiat eos sunt\noptio voluptatem eveniet rerum dignissimos\nquia aut beatae\nmodi consequatur qui rerum sint veritatis deserunt est" - }, - { - "postId": 100, - "id": 499, - "name": "excepturi sunt cum a et rerum quo voluptatibus quia", - "email": "Wilburn_Labadie@araceli.name", - "body": "et necessitatibus tempora ipsum quaerat inventore est quasi quidem\nea repudiandae laborum omnis ab reprehenderit ut\nratione sit numquam culpa a rem\natque aut et" - }, - { - "postId": 100, - "id": 500, - "name": "ex eaque eum natus", - "email": "Emma@joanny.ca", - "body": "perspiciatis quis doloremque\nveniam nisi eos velit sed\nid totam inventore voluptatem laborum et eveniet\naut aut aut maxime quia temporibus ut omnis" - } -] \ No newline at end of file diff --git a/tests/e2e/fetch-2/expect_serve_stderr.txt b/tests/e2e/fetch-2/expect_serve_stderr.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/e2e/fetch-2/expect_serve_stdout.txt b/tests/e2e/fetch-2/expect_serve_stdout.txt deleted file mode 100644 index 9b511128..00000000 --- a/tests/e2e/fetch-2/expect_serve_stdout.txt +++ /dev/null @@ -1 +0,0 @@ -stdout [0] :: Log: Successfully received response body diff --git a/tests/e2e/fetch-2/fetch-2.js b/tests/e2e/fetch-2/fetch-2.js deleted file mode 100644 index ea4ad0c1..00000000 --- a/tests/e2e/fetch-2/fetch-2.js +++ /dev/null @@ -1,31 +0,0 @@ - -async function main(event) { - let resolve, reject; - - try { - const responsePromise = new Promise(async (res, rej) => { - resolve = res; - reject = rej; - }); - event.respondWith(responsePromise); - - const response = await fetch("https://jsonplaceholder.typicode.com/comments"); - const reader = response.body.getReader(); - - let bodyChunks = [] ; - let chunk; - while (!(chunk = await reader.read()).done) { - for (let i = 0; i < chunk.value.length; i++) { - bodyChunks.push(chunk.value[i]); - } - } - const bodyText = new TextDecoder().decode(new Uint8Array(bodyChunks)); - console.log("Successfully received response body"); - - resolve(new Response(bodyText, { headers: response.headers })); - } catch (e) { - console.log(`Error: ${e}. Stack: ${e.stack}`); - } -} - -addEventListener('fetch', main); diff --git a/tests/e2e/fetch-3/expect_serve_body.txt b/tests/e2e/fetch-3/expect_serve_body.txt deleted file mode 100644 index 76088acf..00000000 --- a/tests/e2e/fetch-3/expect_serve_body.txt +++ /dev/null @@ -1 +0,0 @@ -{"method":"POST","parsedBody":{"hello":"world"}} \ No newline at end of file diff --git a/tests/e2e/fetch-3/expect_serve_stderr.txt b/tests/e2e/fetch-3/expect_serve_stderr.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/e2e/fetch-3/expect_serve_stdout.txt b/tests/e2e/fetch-3/expect_serve_stdout.txt deleted file mode 100644 index 7bcdc8dc..00000000 --- a/tests/e2e/fetch-3/expect_serve_stdout.txt +++ /dev/null @@ -1 +0,0 @@ -stdout [0] :: Log: Successfully received response json body diff --git a/tests/e2e/fetch-3/fetch-3.js b/tests/e2e/fetch-3/fetch-3.js deleted file mode 100644 index 6390b508..00000000 --- a/tests/e2e/fetch-3/fetch-3.js +++ /dev/null @@ -1,33 +0,0 @@ -async function main(event) { - let resolve, reject; - - try { - let responsePromise = new Promise(async (res, rej) => { - resolve = res; - reject = rej; - }); - event.respondWith(responsePromise); - - let response = await fetch("https://echo.free.beeceptor.com", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({hello: "world"}) - }) - - let responseJson = await response.json(); - - let result = { - method: responseJson.method, - parsedBody: responseJson.parsedBody, - }; - - console.log("Successfully received response json body"); - resolve(new Response(JSON.stringify(result))); - } catch (e) { - console.log(`Error: ${e}. Stack: ${e.stack}`); - } -} - -addEventListener('fetch', main); diff --git a/tests/test.sh b/tests/test.sh index 353cd8f8..426b6c86 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -70,7 +70,7 @@ if [ -z "$test_component" ]; then fi fi -WASMTIME_BACKTRACE_DETAILS=1 WASMTIME_LOG=wasmtime_wasi $wasmtime serve -S common --addr 0.0.0.0:0 "$test_component" 1> "$stdout_log" 2> "$stderr_log" & +$wasmtime serve -S common --addr 0.0.0.0:0 "$test_component" 1> "$stdout_log" 2> "$stderr_log" & wasmtime_pid="$!" function cleanup { diff --git a/tests/tests.cmake b/tests/tests.cmake index dff060b3..04d21644 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -30,9 +30,6 @@ function(test_integration TEST_NAME) endfunction() test_e2e(smoke) -test_e2e(fetch-1) -test_e2e(fetch-2) -test_e2e(fetch-3) test_e2e(tla) test_e2e(syntax-err) test_e2e(tla-err) From 4820fdff5a4cdd98461f0a24331dc934fcf580a8 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Mon, 3 Jun 2024 16:39:41 +0200 Subject: [PATCH 50/52] Remove some outdated comments and TODOs --- builtins/web/fetch/request-response.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index c49f1094..b0880930 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -270,11 +270,6 @@ bool RequestOrResponse::body_unusable(JSContext *cx, JS::HandleObject body) { * Implementation of the `extract a body` algorithm at * https://fetch.spec.whatwg.org/#concept-bodyinit-extract * - * Note: our implementation is somewhat different from what the spec describes - * in that we immediately write all non-streaming body types to the host instead - * of creating a stream for them. We don't have threads, so there's nothing "in - * parallel" to be had anyway. - * * Note: also includes the steps applying the `Content-Type` header from the * Request and Response constructors in step 36 and 8 of those, respectively. */ @@ -2468,16 +2463,6 @@ bool Response::constructor(JSContext *cx, unsigned argc, JS::Value *vp) { // be consumed by the content creating it, so we're lenient about its format. // 3. Set `this`’s `response` to a new `response`. - // TODO(performance): consider not creating a host-side representation for responses - // eagerly. Some applications create Response objects purely for internal use, - // e.g. to represent cache entries. While that's perhaps not ideal to begin - // with, it exists, so we should handle it in a good way, and not be - // superfluously slow. - // https://github.com/fastly/js-compute-runtime/issues/219 - // TODO(performance): enable creating Response objects during the init phase, and only - // creating the host-side representation when processing requests. - // https://github.com/fastly/js-compute-runtime/issues/220 - // 5. (Reordered) Set `this`’s `response`’s `status` to `init`["status"]. // 7. (Reordered) If `init`["headers"] `exists`, then `fill` `this`’s `headers` with From f992afe735a86fdee0906712de09c97f0c71a3b5 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Mon, 3 Jun 2024 17:24:04 +0200 Subject: [PATCH 51/52] Ensure that the `ResponseFutureTask` is enqueued for streaming requests --- builtins/web/fetch/fetch-api.cpp | 64 ++----------------------- builtins/web/fetch/request-response.cpp | 17 +++++-- builtins/web/fetch/request-response.h | 59 +++++++++++++++++++++++ 3 files changed, 76 insertions(+), 64 deletions(-) diff --git a/builtins/web/fetch/fetch-api.cpp b/builtins/web/fetch/fetch-api.cpp index 520de251..45dc10b5 100644 --- a/builtins/web/fetch/fetch-api.cpp +++ b/builtins/web/fetch/fetch-api.cpp @@ -11,64 +11,6 @@ namespace builtins::web::fetch { static api::Engine *ENGINE; -class ResponseFutureTask final : public api::AsyncTask { - Heap request_; - host_api::FutureHttpIncomingResponse *future_; - -public: - explicit ResponseFutureTask(const HandleObject request, - host_api::FutureHttpIncomingResponse *future) - : request_(request), future_(future) { - auto res = future->subscribe(); - MOZ_ASSERT(!res.is_err(), "Subscribing to a future should never fail"); - handle_ = res.unwrap(); - } - - [[nodiscard]] bool run(api::Engine *engine) override { - // MOZ_ASSERT(ready()); - JSContext *cx = engine->cx(); - - const RootedObject request(cx, request_); - RootedObject response_promise(cx, Request::response_promise(request)); - - auto res = future_->maybe_response(); - if (res.is_err()) { - JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource."); - return RejectPromiseWithPendingError(cx, response_promise); - } - - auto maybe_response = res.unwrap(); - MOZ_ASSERT(maybe_response.has_value()); - auto response = maybe_response.value(); - RootedObject response_obj( - cx, JS_NewObjectWithGivenProto(cx, &Response::class_, Response::proto_obj)); - if (!response_obj) { - return false; - } - - response_obj = Response::create_incoming(cx, response_obj, response); - if (!response_obj) { - return false; - } - - RequestOrResponse::set_url(response_obj, RequestOrResponse::url(request)); - RootedValue response_val(cx, ObjectValue(*response_obj)); - if (!ResolvePromise(cx, response_promise, response_val)) { - return false; - } - - return cancel(engine); - } - - [[nodiscard]] bool cancel(api::Engine *engine) override { - // TODO(TS): implement - handle_ = -1; - return true; - } - - void trace(JSTracer *trc) override { TraceEdge(trc, &request_, "Request for response future"); } -}; - // TODO: throw in all Request methods/getters that rely on host calls once a // request has been sent. The host won't let us act on them anymore anyway. /** @@ -158,8 +100,10 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { ENGINE->queue_async_task(new ResponseFutureTask(request_obj, pending_handle)); } - JS::SetReservedSlot(request_obj, static_cast(Request::Slots::ResponsePromise), - ObjectValue(*response_promise)); + SetReservedSlot(request_obj, static_cast(Request::Slots::ResponsePromise), + ObjectValue(*response_promise)); + SetReservedSlot(request_obj, static_cast(Request::Slots::PendingResponseHandle), + PrivateValue(pending_handle)); args.rval().setObject(*response_promise); return true; diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index b0880930..349b4f55 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -906,6 +906,12 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject return false; if (done_val.toBoolean()) { + auto res = body->close(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + // The only response we ever send is the one passed to // `FetchEvent#respondWith` to send to the client. As such, we can be // certain that if we have a response here, we can advance the FetchState to @@ -916,10 +922,13 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject fetch_event::FetchEvent::State::responseDone); } - auto res = body->close(); - if (auto *err = res.to_err()) { - HANDLE_ERROR(cx, *err); - return false; + if (Request::is_instance(body_owner)) { + auto pending_handle = + static_cast( + GetReservedSlot(body_owner, + static_cast(Request::Slots::PendingResponseHandle)) + .toPrivate()); + ENGINE->queue_async_task(new ResponseFutureTask(body_owner, pending_handle)); } return true; diff --git a/builtins/web/fetch/request-response.h b/builtins/web/fetch/request-response.h index b7af49ee..13ae3357 100644 --- a/builtins/web/fetch/request-response.h +++ b/builtins/web/fetch/request-response.h @@ -139,6 +139,7 @@ class Request final : public BuiltinImpl { URL = static_cast(RequestOrResponse::Slots::URL), Method = static_cast(RequestOrResponse::Slots::Count), ResponsePromise, + PendingResponseHandle, Count, }; @@ -217,6 +218,64 @@ class Response final : public BuiltinImpl { static void set_status_message_from_code(JSContext *cx, JSObject *obj, uint16_t code); }; +class ResponseFutureTask final : public api::AsyncTask { + Heap request_; + host_api::FutureHttpIncomingResponse *future_; + +public: + explicit ResponseFutureTask(const HandleObject request, + host_api::FutureHttpIncomingResponse *future) + : request_(request), future_(future) { + auto res = future->subscribe(); + MOZ_ASSERT(!res.is_err(), "Subscribing to a future should never fail"); + handle_ = res.unwrap(); + } + + [[nodiscard]] bool run(api::Engine *engine) override { + // MOZ_ASSERT(ready()); + JSContext *cx = engine->cx(); + + const RootedObject request(cx, request_); + RootedObject response_promise(cx, Request::response_promise(request)); + + auto res = future_->maybe_response(); + if (res.is_err()) { + JS_ReportErrorUTF8(cx, "NetworkError when attempting to fetch resource."); + return RejectPromiseWithPendingError(cx, response_promise); + } + + auto maybe_response = res.unwrap(); + MOZ_ASSERT(maybe_response.has_value()); + auto response = maybe_response.value(); + RootedObject response_obj( + cx, JS_NewObjectWithGivenProto(cx, &Response::class_, Response::proto_obj)); + if (!response_obj) { + return false; + } + + response_obj = Response::create_incoming(cx, response_obj, response); + if (!response_obj) { + return false; + } + + RequestOrResponse::set_url(response_obj, RequestOrResponse::url(request)); + RootedValue response_val(cx, ObjectValue(*response_obj)); + if (!ResolvePromise(cx, response_promise, response_val)) { + return false; + } + + return cancel(engine); + } + + [[nodiscard]] bool cancel(api::Engine *engine) override { + // TODO(TS): implement + handle_ = -1; + return true; + } + + void trace(JSTracer *trc) override { TraceEdge(trc, &request_, "Request for response future"); } +}; + } // namespace fetch } // namespace web } // namespace builtins From f40bafb1f6432153a4f88f71768d76f1e4119349 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Mon, 3 Jun 2024 19:11:48 -0400 Subject: [PATCH 52/52] Fix streaming of outgoing bodies and setting their content-length header --- builtins/web/fetch/fetch_event.cpp | 4 +- builtins/web/fetch/headers.cpp | 6 +- builtins/web/fetch/headers.h | 3 +- builtins/web/fetch/request-response.cpp | 102 +++++++++++++----------- builtins/web/fetch/request-response.h | 5 -- host-apis/wasi-0.2.0/host_api.cpp | 43 +++++++--- include/extension-api.h | 3 + include/host_api.h | 3 +- 8 files changed, 98 insertions(+), 71 deletions(-) diff --git a/builtins/web/fetch/fetch_event.cpp b/builtins/web/fetch/fetch_event.cpp index d1fbb10a..c9f6e752 100644 --- a/builtins/web/fetch/fetch_event.cpp +++ b/builtins/web/fetch/fetch_event.cpp @@ -186,7 +186,9 @@ bool start_response(JSContext *cx, JS::HandleObject response_obj, bool streaming auto *source_body = static_cast(existing_handle)->body().unwrap(); auto *dest_body = response->body().unwrap(); - auto res = dest_body->append(ENGINE, source_body); + // TODO: check if we should add a callback here and do something in response to body + // streaming being finished. + auto res = dest_body->append(ENGINE, source_body, nullptr, nullptr); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; diff --git a/builtins/web/fetch/headers.cpp b/builtins/web/fetch/headers.cpp index edac89d8..18533c69 100644 --- a/builtins/web/fetch/headers.cpp +++ b/builtins/web/fetch/headers.cpp @@ -694,7 +694,7 @@ bool Headers::append(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -bool Headers::set_if_undefined(JSContext *cx, JS::HandleObject self, const char *name, const char *value) { +bool Headers::set_if_undefined(JSContext *cx, HandleObject self, string_view name, string_view value) { if (!prepare_for_entries_modification(cx, self)) { return false; } @@ -717,7 +717,7 @@ bool Headers::set_if_undefined(JSContext *cx, JS::HandleObject self, const char MOZ_ASSERT(mode(self) == Mode::ContentOnly); RootedObject entries(cx, get_entries(cx, self)); - RootedString name_str(cx, JS_NewStringCopyZ(cx, name)); + RootedString name_str(cx, core::decode(cx, name)); if (!name_str) { return false; } @@ -731,7 +731,7 @@ bool Headers::set_if_undefined(JSContext *cx, JS::HandleObject self, const char return true; } - RootedString value_str(cx, JS_NewStringCopyZ(cx, value)); + RootedString value_str(cx, core::decode(cx, value)); if (!value_str) { return false; } diff --git a/builtins/web/fetch/headers.h b/builtins/web/fetch/headers.h index 9d7247ba..2a29876d 100644 --- a/builtins/web/fetch/headers.h +++ b/builtins/web/fetch/headers.h @@ -70,7 +70,8 @@ class Headers final : public BuiltinImpl { * Adds the given header name/value to `self`'s list of headers iff `self` * doesn't already contain a header with that name. */ - static bool set_if_undefined(JSContext *cx, JS::HandleObject self, const char *name, const char *value); + static bool set_if_undefined(JSContext *cx, JS::HandleObject self, string_view name, + string_view value); /// Appends a value for a header name. // diff --git a/builtins/web/fetch/request-response.cpp b/builtins/web/fetch/request-response.cpp index 349b4f55..2d6bb117 100644 --- a/builtins/web/fetch/request-response.cpp +++ b/builtins/web/fetch/request-response.cpp @@ -280,6 +280,7 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, MOZ_ASSERT(!body_val.isNullOrUndefined()); const char *content_type = nullptr; + mozilla::Maybe content_length; // We currently support five types of body inputs: // - byte sequence @@ -381,7 +382,6 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, MOZ_ASSERT(ReadableStreamIsDisturbed(cx, body_stream, &disturbed)); MOZ_ASSERT(!disturbed); - if (!ReadableStreamEnqueue(cx, body_stream, chunk) || !ReadableStreamClose(cx, body_stream)) { return false; @@ -389,15 +389,24 @@ bool RequestOrResponse::extract_body(JSContext *cx, JS::HandleObject self, JS_SetReservedSlot(self, static_cast(Slots::BodyStream), ObjectValue(*body_stream)); + content_length.emplace(length); } - // Step 36.3 of Request constructor / 8.4 of Response constructor. - if (content_type) { + if (content_type || content_length.isSome()) { JS::RootedObject headers(cx, RequestOrResponse::headers(cx, self)); if (!headers) { return false; } - if (!Headers::set_if_undefined(cx, headers, "content-type", content_type)) { + + if (content_length.isSome()) { + auto length_str = std::to_string(content_length.value()); + if (!Headers::set_if_undefined(cx, headers, "content-length", length_str)) { + return false; + } + } + + // Step 36.3 of Request constructor / 8.4 of Response constructor. + if (content_type && !Headers::set_if_undefined(cx, headers, "content-type", content_type)) { return false; } } @@ -428,12 +437,42 @@ unique_ptr RequestOrResponse::headers_clone(JSContext* cx return unique_ptr(res.unwrap()->clone()); } +bool finish_outgoing_body_streaming(JSContext* cx, HandleObject body_owner) { + // The only response we ever send is the one passed to + // `FetchEvent#respondWith` to send to the client. As such, we can be + // certain that if we have a response here, we can advance the FetchState to + // `responseDone`. + // TODO(TS): factor this out to remove dependency on fetch-event.h + auto body = RequestOrResponse::outgoing_body_handle(body_owner); + auto res = body->close(); + if (auto *err = res.to_err()) { + HANDLE_ERROR(cx, *err); + return false; + } + + if (Response::is_instance(body_owner)) { + fetch_event::FetchEvent::set_state(fetch_event::FetchEvent::instance(), + fetch_event::FetchEvent::State::responseDone); + } + + if (Request::is_instance(body_owner)) { + auto pending_handle = static_cast( + GetReservedSlot(body_owner, static_cast(Request::Slots::PendingResponseHandle)) + .toPrivate()); + SetReservedSlot(body_owner, static_cast(Request::Slots::PendingResponseHandle), + PrivateValue(nullptr)); + ENGINE->queue_async_task(new ResponseFutureTask(body_owner, pending_handle)); + } + + return true; +} + bool RequestOrResponse::append_body(JSContext *cx, JS::HandleObject self, JS::HandleObject source) { MOZ_ASSERT(!body_used(source)); MOZ_ASSERT(!body_used(self)); host_api::HttpIncomingBody *source_body = incoming_body_handle(source); host_api::HttpOutgoingBody *dest_body = outgoing_body_handle(self); - auto res = dest_body->append(ENGINE, source_body); + auto res = dest_body->append(ENGINE, source_body, finish_outgoing_body_streaming, self); if (auto *err = res.to_err()) { HANDLE_ERROR(cx, *err); return false; @@ -888,14 +927,14 @@ bool RequestOrResponse::body_source_cancel_algorithm(JSContext *cx, JS::CallArgs return true; } -bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject body_owner, - JS::HandleValue extra, JS::CallArgs args) { +bool reader_for_outgoing_body_then_handler(JSContext *cx, JS::HandleObject body_owner, + JS::HandleValue extra, JS::CallArgs args) { + ENGINE->dump_value(RequestOrResponse::url(body_owner)); JS::RootedObject then_handler(cx, &args.callee()); // The reader is stored in the catch handler, which we need here as well. // So we get that first, then the reader. JS::RootedObject catch_handler(cx, &extra.toObject()); JS::RootedObject reader(cx, &js::GetFunctionNativeReserved(catch_handler, 1).toObject()); - auto body = outgoing_body_handle(body_owner); // We're guaranteed to work with a native ReadableStreamDefaultReader here, // which in turn is guaranteed to vend {done: bool, value: any} objects to @@ -906,32 +945,7 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject return false; if (done_val.toBoolean()) { - auto res = body->close(); - if (auto *err = res.to_err()) { - HANDLE_ERROR(cx, *err); - return false; - } - - // The only response we ever send is the one passed to - // `FetchEvent#respondWith` to send to the client. As such, we can be - // certain that if we have a response here, we can advance the FetchState to - // `responseDone`. - // TODO(TS): factor this out to remove dependency on fetch-event.h - if (Response::is_instance(body_owner)) { - fetch_event::FetchEvent::set_state(fetch_event::FetchEvent::instance(), - fetch_event::FetchEvent::State::responseDone); - } - - if (Request::is_instance(body_owner)) { - auto pending_handle = - static_cast( - GetReservedSlot(body_owner, - static_cast(Request::Slots::PendingResponseHandle)) - .toPrivate()); - ENGINE->queue_async_task(new ResponseFutureTask(body_owner, pending_handle)); - } - - return true; + return finish_outgoing_body_streaming(cx, body_owner); } JS::RootedValue val(cx); @@ -943,16 +957,10 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject // reject the request promise if (Request::is_instance(body_owner)) { JS::RootedObject response_promise(cx, Request::response_promise(body_owner)); - JS::RootedValue exn(cx); // TODO: this should be a TypeError, but I'm not sure how to make that work JS_ReportErrorUTF8(cx, "TypeError"); - if (!JS_GetPendingException(cx, &exn)) { - return false; - } - JS_ClearPendingException(cx); - - return JS::RejectPromise(cx, response_promise, exn); + return RejectPromiseWithPendingError(cx, response_promise); } // TODO: should we also create a rejected promise if a response reads something that's not a @@ -971,6 +979,7 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject uint8_t *bytes = JS_GetUint8ArrayData(array, &is_shared, nogc); size_t length = JS_GetTypedArrayByteLength(array); // TODO: change this to write in chunks, respecting backpressure. + auto body = RequestOrResponse::outgoing_body_handle(body_owner); res = body->write_all(bytes, length); } @@ -980,6 +989,7 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject return false; } + // Read the next chunk. JS::RootedObject promise(cx, JS::ReadableStreamDefaultReaderRead(cx, reader)); if (!promise) { @@ -989,8 +999,8 @@ bool RequestOrResponse::body_reader_then_handler(JSContext *cx, JS::HandleObject return JS::AddPromiseReactions(cx, promise, then_handler, catch_handler); } -bool RequestOrResponse::body_reader_catch_handler(JSContext *cx, JS::HandleObject body_owner, - JS::HandleValue extra, JS::CallArgs args) { +bool reader_for_outgoing_body_catch_handler(JSContext *cx, JS::HandleObject body_owner, + JS::HandleValue extra, JS::CallArgs args) { // TODO: check if this should create a rejected promise instead, so an // in-content handler for unhandled rejections could deal with it. The body // stream errored during the streaming send. Not much we can do, but at least @@ -1041,8 +1051,6 @@ bool RequestOrResponse::maybe_stream_body(JSContext *cx, JS::HandleObject body_o // If the body stream is backed by an HTTP body handle, we can directly pipe // that handle into the body we're about to send. - // TODO: check if this case can still be hit. Given that we're not creating outgoing responses - // anymore until sending them, it probably can't. if (streams::NativeStreamSource::stream_is_body(cx, stream)) { MOZ_ASSERT(!is_incoming(body_owner)); // First, directly append the source's body to the target's and lock the stream. @@ -1070,13 +1078,13 @@ bool RequestOrResponse::maybe_stream_body(JSContext *cx, JS::HandleObject body_o // perform another operation in this way. JS::RootedObject catch_handler(cx); JS::RootedValue extra(cx, JS::ObjectValue(*reader)); - catch_handler = create_internal_method(cx, body_owner, extra); + catch_handler = create_internal_method(cx, body_owner, extra); if (!catch_handler) return false; JS::RootedObject then_handler(cx); extra.setObject(*catch_handler); - then_handler = create_internal_method(cx, body_owner, extra); + then_handler = create_internal_method(cx, body_owner, extra); if (!then_handler) return false; diff --git a/builtins/web/fetch/request-response.h b/builtins/web/fetch/request-response.h index 13ae3357..f7220c3a 100644 --- a/builtins/web/fetch/request-response.h +++ b/builtins/web/fetch/request-response.h @@ -91,11 +91,6 @@ class RequestOrResponse final { JS::HandleValue reason); static bool body_source_pull_algorithm(JSContext *cx, JS::CallArgs args, JS::HandleObject source, JS::HandleObject body_owner, JS::HandleObject controller); - static bool body_reader_then_handler(JSContext *cx, JS::HandleObject body_owner, - JS::HandleValue extra, JS::CallArgs args); - - static bool body_reader_catch_handler(JSContext *cx, JS::HandleObject body_owner, - JS::HandleValue extra, JS::CallArgs args); /** * Ensures that the given |body_owner|'s body is properly streamed, if it diff --git a/host-apis/wasi-0.2.0/host_api.cpp b/host-apis/wasi-0.2.0/host_api.cpp index bf24858f..7cd326ec 100644 --- a/host-apis/wasi-0.2.0/host_api.cpp +++ b/host-apis/wasi-0.2.0/host_api.cpp @@ -604,16 +604,30 @@ class BodyAppendTask final : public api::AsyncTask { HttpOutgoingBody *outgoing_body_; PollableHandle incoming_pollable_; PollableHandle outgoing_pollable_; + + api::TaskCompletionCallback cb_; + Heap cb_receiver_; State state_; - void set_state(const State state) { + void set_state(JSContext* cx, const State state) { MOZ_ASSERT(state_ != State::Done); state_ = state; + if (state == State::Done && cb_) { + RootedObject receiver(cx, cb_receiver_); + cb_(cx, receiver); + cb_ = nullptr; + cb_receiver_ = nullptr; + } } public: - explicit BodyAppendTask(HttpIncomingBody *incoming_body, HttpOutgoingBody *outgoing_body) - : incoming_body_(incoming_body), outgoing_body_(outgoing_body) { + explicit BodyAppendTask(api::Engine *engine, + HttpIncomingBody *incoming_body, + HttpOutgoingBody *outgoing_body, + api::TaskCompletionCallback completion_callback, + HandleObject callback_receiver) + : incoming_body_(incoming_body), outgoing_body_(outgoing_body), + cb_(completion_callback) { auto res = incoming_body_->subscribe(); MOZ_ASSERT(!res.is_err()); incoming_pollable_ = res.unwrap(); @@ -622,7 +636,9 @@ class BodyAppendTask final : public api::AsyncTask { MOZ_ASSERT(!res.is_err()); outgoing_pollable_ = res.unwrap(); - state_ = State::BlockedOnBoth; + cb_receiver_ = callback_receiver; + + set_state(engine->cx(), State::BlockedOnBoth); } [[nodiscard]] bool run(api::Engine *engine) override { @@ -633,10 +649,10 @@ class BodyAppendTask final : public api::AsyncTask { MOZ_ASSERT(!res.is_err()); auto [done, _] = std::move(res.unwrap()); if (done) { - set_state(State::Done); + set_state(engine->cx(), State::Done); return true; } - set_state(State::BlockedOnOutgoing); + set_state(engine->cx(), State::BlockedOnOutgoing); } uint64_t capacity = 0; @@ -647,7 +663,7 @@ class BodyAppendTask final : public api::AsyncTask { } capacity = res.unwrap(); if (capacity > 0) { - set_state(State::Ready); + set_state(engine->cx(), State::Ready); } else { engine->queue_async_task(this); return true; @@ -665,7 +681,7 @@ class BodyAppendTask final : public api::AsyncTask { } auto [done, bytes] = std::move(res.unwrap()); if (bytes.len == 0 && !done) { - set_state(State::BlockedOnIncoming); + set_state(engine->cx(), State::BlockedOnIncoming); engine->queue_async_task(this); return true; } @@ -684,7 +700,7 @@ class BodyAppendTask final : public api::AsyncTask { } if (done) { - set_state(State::Done); + set_state(engine->cx(), State::Done); return true; } @@ -696,7 +712,7 @@ class BodyAppendTask final : public api::AsyncTask { capacity = capacity_res.unwrap(); } while (capacity > 0); - set_state(State::BlockedOnOutgoing); + set_state(engine->cx(), State::BlockedOnOutgoing); engine->queue_async_task(this); return true; } @@ -717,12 +733,13 @@ class BodyAppendTask final : public api::AsyncTask { } void trace(JSTracer *trc) override { - // Nothing to trace. + JS::TraceEdge(trc, &cb_receiver_, "BodyAppendTask completion callback receiver"); } }; -Result HttpOutgoingBody::append(api::Engine *engine, HttpIncomingBody *other) { - engine->queue_async_task(new BodyAppendTask(other, this)); +Result HttpOutgoingBody::append(api::Engine *engine, HttpIncomingBody *other, + api::TaskCompletionCallback callback, HandleObject callback_receiver) { + engine->queue_async_task(new BodyAppendTask(engine, other, this, callback, callback_receiver)); return {}; } diff --git a/include/extension-api.h b/include/extension-api.h index 790a2f4c..ca58f069 100644 --- a/include/extension-api.h +++ b/include/extension-api.h @@ -120,6 +120,9 @@ class Engine { void dump_promise_rejection(HandleValue reason, HandleObject promise, FILE *fp); }; + +typedef bool (*TaskCompletionCallback)(JSContext* cx, HandleObject receiver); + class AsyncTask { protected: PollableHandle handle_ = -1; diff --git a/include/host_api.h b/include/host_api.h index ee0e5601..9364969e 100644 --- a/include/host_api.h +++ b/include/host_api.h @@ -281,7 +281,8 @@ class HttpOutgoingBody final : public Pollable { Result write_all(const uint8_t *bytes, size_t len); /// Append an HttpIncomingBody to this one. - Result append(api::Engine *engine, HttpIncomingBody *incoming); + Result append(api::Engine *engine, HttpIncomingBody *other, + api::TaskCompletionCallback callback, HandleObject callback_receiver); /// Close this handle, and reset internal state to invalid. Result close();