in src/apps/js_generic/js_generic_base.cpp [233:434]
void do_execute_request(
const ccf::endpoints::EndpointProperties& props,
ccf::endpoints::EndpointContext& endpoint_ctx,
kv::Tx* historical_tx,
const std::optional<ccf::TxID>& transaction_id,
ccf::TxReceiptPtr receipt)
{
js::Runtime rt;
rt.add_ccf_classdefs();
JS_SetModuleLoaderFunc(
rt, nullptr, js::js_app_module_loader, &endpoint_ctx.tx);
js::Context ctx(rt);
js::TxContext txctx{&endpoint_ctx.tx, js::TxAccess::APP};
js::TxContext historical_txctx{historical_tx, js::TxAccess::APP};
js::register_request_body_class(ctx);
js::populate_global(
&txctx,
historical_tx ? &historical_txctx : nullptr,
endpoint_ctx.rpc_ctx.get(),
transaction_id,
receipt,
nullptr,
&context.get_node_state(),
nullptr,
&context.get_historical_state(),
this,
ctx);
js::JSWrappedValue export_func;
try
{
auto module_val =
js::load_app_module(ctx, props.js_module.c_str(), &endpoint_ctx.tx);
export_func =
ctx.function(module_val, props.js_function, props.js_module);
}
catch (const std::exception& exc)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
exc.what());
return;
}
// Call exported function
auto request = create_request_obj(endpoint_ctx, ctx);
auto val = ctx.call(export_func, {request});
if (JS_IsException(val))
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Exception thrown while executing.");
return;
}
// Handle return value: {body, headers, statusCode}
if (!JS_IsObject(val))
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (not an object).");
return;
}
// Response body (also sets a default response content-type header)
{
auto response_body_js = val["body"];
if (!JS_IsUndefined(response_body_js))
{
std::vector<uint8_t> response_body;
size_t buf_size;
size_t buf_offset;
auto typed_array_buffer = ctx.get_typed_array_buffer(
response_body_js, &buf_offset, &buf_size, nullptr);
uint8_t* array_buffer;
if (!JS_IsException(typed_array_buffer))
{
size_t buf_size_total;
array_buffer =
JS_GetArrayBuffer(ctx, &buf_size_total, typed_array_buffer);
array_buffer += buf_offset;
}
else
{
array_buffer = JS_GetArrayBuffer(ctx, &buf_size, response_body_js);
}
if (array_buffer)
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::OCTET_STREAM);
response_body =
std::vector<uint8_t>(array_buffer, array_buffer + buf_size);
}
else
{
std::optional<std::string> str;
if (JS_IsString(response_body_js))
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::TEXT);
str = ctx.to_str(response_body_js);
}
else
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::JSON);
auto rval = ctx.json_stringify(response_body_js);
if (JS_IsException(rval))
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (error during JSON "
"conversion of body).");
return;
}
str = ctx.to_str(rval);
}
if (!str)
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (error during string "
"conversion of body).");
return;
}
response_body = std::vector<uint8_t>(str->begin(), str->end());
}
endpoint_ctx.rpc_ctx->set_response_body(std::move(response_body));
}
}
// Response headers
{
auto response_headers_js = val["headers"];
if (JS_IsObject(response_headers_js))
{
js::JSWrappedPropertyEnum prop_enum(ctx, response_headers_js);
for (size_t i = 0; i < prop_enum.size(); i++)
{
auto prop_name = prop_enum[i];
auto prop_name_str = ctx.to_str(prop_name);
if (!prop_name_str)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (header type).");
return;
}
auto prop_val = response_headers_js.get_property(prop_name);
auto prop_val_str = ctx.to_str(prop_val);
if (!prop_val_str)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (header value type).");
return;
}
endpoint_ctx.rpc_ctx->set_response_header(
*prop_name_str, *prop_val_str);
}
}
}
// Response status code
{
int response_status_code = HTTP_STATUS_OK;
auto status_code_js = ctx(JS_GetPropertyStr(ctx, val, "statusCode"));
if (!JS_IsUndefined(status_code_js) && !JS_IsNull(status_code_js))
{
if (JS_VALUE_GET_TAG(status_code_js.val) != JS_TAG_INT)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (status code type).");
return;
}
response_status_code = JS_VALUE_GET_INT(status_code_js.val);
}
endpoint_ctx.rpc_ctx->set_response_status(response_status_code);
}
return;
}