Skip to content

Commit e7e6f7c

Browse files
authored
js: pass request object as function argument & return response object (#1604)
1 parent 955033f commit e7e6f7c

6 files changed

Lines changed: 172 additions & 85 deletions

File tree

src/apps/js_generic/js_generic.cpp

Lines changed: 93 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,8 @@ namespace ccfapp
433433
JS_SetOpaque(tables_, &args.tx);
434434
JS_SetPropertyStr(ctx, global_obj, "tables", tables_);
435435

436+
auto request = JS_NewObject(ctx);
437+
436438
auto headers = JS_NewObject(ctx);
437439
for (auto& [header_name, header_value] :
438440
args.rpc_ctx->get_request_headers())
@@ -443,17 +445,29 @@ namespace ccfapp
443445
header_name.c_str(),
444446
JS_NewStringLen(ctx, header_value.c_str(), header_value.size()));
445447
}
446-
JS_SetPropertyStr(ctx, global_obj, "headers", headers);
448+
JS_SetPropertyStr(ctx, request, "headers", headers);
447449

448450
const auto& request_query = args.rpc_ctx->get_request_query();
449451
auto query_str =
450452
JS_NewStringLen(ctx, request_query.c_str(), request_query.size());
451-
JS_SetPropertyStr(ctx, global_obj, "query", query_str);
453+
JS_SetPropertyStr(ctx, request, "query", query_str);
454+
455+
auto params = JS_NewObject(ctx);
456+
for (auto& [param_name, param_value] :
457+
args.rpc_ctx->get_request_path_params())
458+
{
459+
JS_SetPropertyStr(
460+
ctx,
461+
params,
462+
param_name.c_str(),
463+
JS_NewStringLen(ctx, param_value.c_str(), param_value.size()));
464+
}
465+
JS_SetPropertyStr(ctx, request, "params", params);
452466

453467
const auto& request_body = args.rpc_ctx->get_request_body();
454468
auto body_ = JS_NewObjectClass(ctx, body_class_id);
455469
JS_SetOpaque(body_, (void*)&request_body);
456-
JS_SetPropertyStr(ctx, global_obj, "body", body_);
470+
JS_SetPropertyStr(ctx, request, "body", body_);
457471

458472
JS_FreeValue(ctx, global_obj);
459473

@@ -507,9 +521,10 @@ namespace ccfapp
507521
}
508522

509523
// Call exported function
510-
int argc = 0;
511-
JSValueConst* argv = nullptr;
524+
int argc = 1;
525+
JSValueConst* argv = (JSValueConst*)&request;
512526
auto val = JS_Call(ctx, export_func, JS_UNDEFINED, argc, argv);
527+
JS_FreeValue(ctx, request);
513528
JS_FreeValue(ctx, export_func);
514529

515530
if (JS_IsException(val))
@@ -520,13 +535,22 @@ namespace ccfapp
520535
return;
521536
}
522537

523-
// Handle return value
524-
std::string response_content_type;
538+
// Handle return value: {body, headers, statusCode}
539+
if (!JS_IsObject(val))
540+
{
541+
args.rpc_ctx->set_response_status(HTTP_STATUS_INTERNAL_SERVER_ERROR);
542+
args.rpc_ctx->set_response_body(
543+
"Invalid endpoint function return value");
544+
return;
545+
}
546+
547+
// Response body (also sets a default response content-type header)
548+
auto response_body_js = JS_GetPropertyStr(ctx, val, "body");
525549
std::vector<uint8_t> response_body;
526550
size_t buf_size;
527551
size_t buf_offset;
528-
JSValue typed_array_buffer =
529-
JS_GetTypedArrayBuffer(ctx, val, &buf_offset, &buf_size, nullptr);
552+
JSValue typed_array_buffer = JS_GetTypedArrayBuffer(
553+
ctx, response_body_js, &buf_offset, &buf_size, nullptr);
530554
uint8_t* array_buffer;
531555
if (!JS_IsException(typed_array_buffer))
532556
{
@@ -538,26 +562,33 @@ namespace ccfapp
538562
}
539563
else
540564
{
541-
array_buffer = JS_GetArrayBuffer(ctx, &buf_size, val);
565+
array_buffer = JS_GetArrayBuffer(ctx, &buf_size, response_body_js);
542566
}
543567
if (array_buffer)
544568
{
545-
response_content_type = http::headervalues::contenttype::OCTET_STREAM;
569+
args.rpc_ctx->set_response_header(
570+
http::headers::CONTENT_TYPE,
571+
http::headervalues::contenttype::OCTET_STREAM);
546572
response_body =
547573
std::vector<uint8_t>(array_buffer, array_buffer + buf_size);
548574
}
549575
else
550576
{
551577
const char* cstr = nullptr;
552-
if (JS_IsString(val))
578+
if (JS_IsString(response_body_js))
553579
{
554-
response_content_type = http::headervalues::contenttype::TEXT;
555-
cstr = JS_ToCString(ctx, val);
580+
args.rpc_ctx->set_response_header(
581+
http::headers::CONTENT_TYPE,
582+
http::headervalues::contenttype::TEXT);
583+
cstr = JS_ToCString(ctx, response_body_js);
556584
}
557585
else
558586
{
559-
response_content_type = http::headervalues::contenttype::JSON;
560-
JSValue rval = JS_JSONStringify(ctx, val, JS_NULL, JS_NULL);
587+
args.rpc_ctx->set_response_header(
588+
http::headers::CONTENT_TYPE,
589+
http::headervalues::contenttype::JSON);
590+
JSValue rval =
591+
JS_JSONStringify(ctx, response_body_js, JS_NULL, JS_NULL);
561592
cstr = JS_ToCString(ctx, rval);
562593
JS_FreeValue(ctx, rval);
563594
}
@@ -566,16 +597,58 @@ namespace ccfapp
566597

567598
response_body = std::vector<uint8_t>(str.begin(), str.end());
568599
}
600+
JS_FreeValue(ctx, response_body_js);
601+
args.rpc_ctx->set_response_body(std::move(response_body));
602+
603+
// Response headers
604+
auto response_headers_js = JS_GetPropertyStr(ctx, val, "headers");
605+
if (JS_IsObject(response_headers_js))
606+
{
607+
uint32_t prop_count = 0;
608+
JSPropertyEnum* props = nullptr;
609+
JS_GetOwnPropertyNames(
610+
ctx,
611+
&props,
612+
&prop_count,
613+
response_headers_js,
614+
JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY);
615+
for (size_t i = 0; i < prop_count; i++)
616+
{
617+
auto prop_name = props[i].atom;
618+
auto prop_name_cstr = JS_AtomToCString(ctx, prop_name);
619+
auto prop_val = JS_GetProperty(ctx, response_headers_js, prop_name);
620+
auto prop_val_cstr = JS_ToCString(ctx, prop_val);
621+
if (!prop_val_cstr)
622+
{
623+
args.rpc_ctx->set_response_status(
624+
HTTP_STATUS_INTERNAL_SERVER_ERROR);
625+
args.rpc_ctx->set_response_body("Invalid header value type");
626+
return;
627+
}
628+
args.rpc_ctx->set_response_header(prop_name_cstr, prop_val_cstr);
629+
JS_FreeCString(ctx, prop_name_cstr);
630+
JS_FreeCString(ctx, prop_val_cstr);
631+
JS_FreeValue(ctx, prop_val);
632+
}
633+
js_free(ctx, props);
634+
}
635+
JS_FreeValue(ctx, response_headers_js);
636+
637+
// Response status code
638+
int response_status_code = HTTP_STATUS_OK;
639+
auto status_code_js = JS_GetPropertyStr(ctx, val, "statusCode");
640+
if (JS_VALUE_GET_TAG(status_code_js) == JS_TAG_INT)
641+
{
642+
response_status_code = JS_VALUE_GET_INT(status_code_js);
643+
}
644+
JS_FreeValue(ctx, status_code_js);
645+
args.rpc_ctx->set_response_status(response_status_code);
569646

570647
JS_FreeValue(ctx, val);
571648

572649
JS_FreeContext(ctx);
573650
JS_FreeRuntime(rt);
574651

575-
args.rpc_ctx->set_response_status(HTTP_STATUS_OK);
576-
args.rpc_ctx->set_response_body(std::move(response_body));
577-
args.rpc_ctx->set_response_header(
578-
http::headers::CONTENT_TYPE, response_content_type);
579652
return;
580653
};
581654

src/apps/logging/logging_js.lua

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33

44
return {
55
["GET log/private"] = [[
6-
export default function()
6+
export default function(request)
77
{
8-
const elements = query.split("&");
8+
const elements = request.query.split("&");
99
for (const kv of elements) {
1010
const [k, v] = kv.split("=");
1111
if (k == "id") {
1212
try
1313
{
14-
return {msg: tables.data.get(JSON.parse(v).toString())};
14+
return { body: {msg: tables.data.get(JSON.parse(v).toString())} };
1515
}
1616
catch (err)
1717
{
18-
return {error: err.message}
18+
return { body: {error: err.message} };
1919
}
2020
}
2121
}
@@ -24,19 +24,19 @@ return {
2424
]],
2525

2626
["GET log/public"] = [[
27-
export default function()
27+
export default function(request)
2828
{
29-
const elements = query.split("&");
29+
const elements = request.query.split("&");
3030
for (const kv of elements) {
3131
const [k, v] = kv.split("=");
3232
if (k == "id") {
3333
try
3434
{
35-
return {msg: tables.data.get(JSON.parse(v).toString())};
35+
return { body: {msg: tables.data.get(JSON.parse(v).toString())} };
3636
}
3737
catch (err)
3838
{
39-
return {error: err.message}
39+
return { body: {error: err.message} }
4040
}
4141
}
4242
}
@@ -45,45 +45,45 @@ return {
4545
]],
4646

4747
["POST log/private"] = [[
48-
export default function()
48+
export default function(request)
4949
{
50-
let params = body.json();
50+
let params = request.body.json();
5151
tables.data.put(params.id.toString(), params.msg);
52-
return true;
52+
return { body: true };
5353
}
5454
]],
5555

5656
["POST log/public"] = [[
57-
export default function()
57+
export default function(request)
5858
{
59-
let params = body.json();
59+
let params = request.body.json();
6060
tables.data.put(params.id.toString(), params.msg);
61-
return true;
61+
return { body: true };
6262
}
6363
]],
6464

6565
["DELETE log/public"] = [[
66-
export default function()
66+
export default function(request)
6767
{
68-
const elements = query.split("&");
68+
const elements = request.query.split("&");
6969
for (const kv of elements) {
7070
const [k, v] = kv.split("=");
7171
if (k == "id") {
72-
return tables.data.remove(JSON.parse(v).toString());
72+
return { body: tables.data.remove(JSON.parse(v).toString()) };
7373
}
7474
}
7575
throw "Could not find 'id' in query";
7676
}
7777
]],
7878

7979
["DELETE log/private"] = [[
80-
export default function()
80+
export default function(request)
8181
{
82-
const elements = query.split("&");
82+
const elements = request.query.split("&");
8383
for (const kv of elements) {
8484
const [k, v] = kv.split("=");
8585
if (k == "id") {
86-
return tables.data.remove(JSON.parse(v).toString());
86+
return { body: tables.data.remove(JSON.parse(v).toString()) };
8787
}
8888
}
8989
throw "Could not find 'id' in query";

tests/content_types.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,47 @@
1212
APP_SCRIPT = """
1313
return {
1414
["POST text"] = [[
15-
export default function()
15+
export default function(request)
1616
{
17-
if (headers['content-type'] !== 'text/plain')
18-
throw new Error('unexpected content-type: ' + headers['content-type']);
19-
const text = body.text();
17+
if (request.headers['content-type'] !== 'text/plain')
18+
throw new Error('unexpected content-type: ' + request.headers['content-type']);
19+
const text = request.body.text();
2020
if (text !== 'text')
2121
throw new Error('unexpected body: ' + text);
22-
return 'text';
22+
return { body: 'text' };
2323
}
2424
]],
2525
["POST json"] = [[
26-
export default function()
26+
export default function(request)
2727
{
28-
if (headers['content-type'] !== 'application/json')
29-
throw new Error('unexpected content type: ' + headers['content-type']);
30-
const obj = body.json();
28+
if (request.headers['content-type'] !== 'application/json')
29+
throw new Error('unexpected content type: ' + request.headers['content-type']);
30+
const obj = request.body.json();
3131
if (obj.foo !== 'bar')
3232
throw new Error('unexpected body: ' + obj);
33-
return { foo: 'bar' };
33+
return { body: { foo: 'bar' } };
3434
}
3535
]],
3636
["POST binary"] = [[
37-
export default function()
37+
export default function(request)
3838
{
39-
if (headers['content-type'] !== 'application/octet-stream')
40-
throw new Error('unexpected content type: ' + headers['content-type']);
41-
const buf = body.arrayBuffer();
39+
if (request.headers['content-type'] !== 'application/octet-stream')
40+
throw new Error('unexpected content type: ' + request.headers['content-type']);
41+
const buf = request.body.arrayBuffer();
4242
if (buf.byteLength !== 42)
4343
throw new Error(`unexpected body size: ${buf.byteLength}`);
44-
return new ArrayBuffer(42);
44+
return { body: new ArrayBuffer(42) };
4545
}
4646
]],
4747
["POST custom"] = [[
48-
export default function()
48+
export default function(request)
4949
{
50-
if (headers['content-type'] !== 'foo/bar')
51-
throw new Error('unexpected content type: ' + headers['content-type']);
52-
const text = body.text();
50+
if (request.headers['content-type'] !== 'foo/bar')
51+
throw new Error('unexpected content type: ' + request.headers['content-type']);
52+
const text = request.body.text();
5353
if (text !== 'text')
5454
throw new Error('unexpected body: ' + text);
55-
return 'text';
55+
return { body: 'text' };
5656
}
5757
]]
5858
}

tests/custom_authorization.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
APP_SCRIPT = f"""
1414
return {{
1515
["GET custom_auth"] = [[
16-
export default function()
16+
export default function(request)
1717
{{
1818
// Header names become lower-case
19-
const auth = headers['authorization'];
20-
return auth === '{AUTH_VALUE}';
19+
const auth = request.headers['authorization'];
20+
return {{ body: auth === '{AUTH_VALUE}' }};
2121
}}
2222
]]
2323
}}

0 commit comments

Comments
 (0)