in src/apps/js_v8/js_v8_base.cpp [69:298]
void do_execute_request(
const ccf::endpoints::EndpointProperties& props,
ccf::endpoints::EndpointContext& endpoint_ctx,
ccf::historical::StatePtr historical_state)
{
// Isolates are re-used across requests
thread_local V8Isolate isolate;
// Each request is executed in a new context.
// A context is used in a browser to separate different
// origins of a page, for example iframes.
// No state is shared between contexts, except when
// explicitly allowed through security tokens, which
// are not used here.
V8Context ctx(isolate);
// Make sure handles are cleaned up at request end
v8::HandleScope handle_scope(isolate);
// Run finalizers at the end of the request
// no matter whether a context is re-used or not.
V8Context::FinalizerScope finalizer_scope(ctx);
// set a callback that loads modules from the KV
ctx.set_module_load_callback(
ccf::v8_kv_module_load_callback, &endpoint_ctx.tx);
v8::Local<v8::Context> context = ctx.get_context();
v8::TryCatch try_catch(isolate);
// Populate globals
v8::Local<v8::Value> console_global =
v8_tmpl::ConsoleGlobal::wrap(context);
ctx.install_global("console", console_global);
v8_tmpl::TxContext txctx{&endpoint_ctx.tx, v8_tmpl::TxAccess::APP};
v8::Local<v8::Value> ccf_global = v8_tmpl::CCFGlobal::wrap(
context,
&txctx,
&historical_state,
this,
&node_context.get_historical_state(),
endpoint_ctx.rpc_ctx.get());
ctx.install_global("ccf", ccf_global);
// Call exported function
v8::Local<v8::Value> request =
ccf::v8_tmpl::Request::wrap(context, &endpoint_ctx, this);
std::vector<v8::Local<v8::Value>> args{request};
v8::Local<v8::Value> val =
ctx.run(props.js_module, props.js_function, args);
if (val.IsEmpty())
{
v8_util::report_exception(isolate, &try_catch);
auto exception_str =
v8_util::get_exception_message(isolate, &try_catch);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
std::move(exception_str));
return;
}
// Handle return value: {body, headers, statusCode}
if (!val->IsObject())
{
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)
v8::Local<v8::Object> obj = val.As<v8::Object>();
v8::Local<v8::Value> response_body_js;
if (!obj->Get(context, v8_util::to_v8_str(isolate, "body"))
.ToLocal(&response_body_js))
{
v8_util::report_exception(isolate, &try_catch);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (cannot access body).");
return;
}
if (!response_body_js->IsUndefined())
{
std::vector<uint8_t> response_body;
size_t buf_size;
size_t buf_offset;
v8::Local<v8::ArrayBuffer> array_buffer;
if (response_body_js->IsArrayBufferView())
{
auto view = response_body_js.As<v8::ArrayBufferView>();
buf_offset = view->ByteOffset();
buf_size = view->ByteLength();
array_buffer = view->Buffer();
}
else if (response_body_js->IsArrayBuffer())
{
array_buffer = response_body_js.As<v8::ArrayBuffer>();
buf_offset = 0;
buf_size = array_buffer->ByteLength();
}
if (!array_buffer.IsEmpty())
{
uint8_t* buf = (uint8_t*)array_buffer->GetBackingStore()->Data();
buf += buf_offset;
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::OCTET_STREAM);
response_body = std::vector<uint8_t>(buf, buf + buf_size);
}
else
{
std::string str;
if (response_body_js->IsString())
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::TEXT);
v8::Local<v8::String> str_val = response_body_js.As<v8::String>();
str = v8_util::to_str(isolate, str_val);
}
else
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::JSON);
v8::Local<v8::String> json;
if (!v8::JSON::Stringify(context, response_body_js).ToLocal(&json))
{
v8_util::report_exception(isolate, &try_catch);
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 = v8_util::to_str(isolate, json);
}
response_body = std::vector<uint8_t>(str.begin(), str.end());
}
endpoint_ctx.rpc_ctx->set_response_body(std::move(response_body));
}
// Response headers
v8::Local<v8::Value> response_headers_js;
if (!obj->Get(context, v8_util::to_v8_str(isolate, "headers"))
.ToLocal(&response_headers_js))
{
v8_util::report_exception(isolate, &try_catch);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (cannot access headers).");
return;
}
if (!response_headers_js->IsNullOrUndefined())
{
if (!response_headers_js->IsObject())
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (headers is not an "
"object).");
return;
}
v8::Local<v8::Object> headers_obj =
response_headers_js.As<v8::Object>();
v8::Local<v8::Array> headers_arr =
headers_obj->GetOwnPropertyNames(context).ToLocalChecked();
for (uint32_t i = 0; i < headers_arr->Length(); i++)
{
v8::Local<v8::Value> key =
headers_arr->Get(context, i).ToLocalChecked();
v8::Local<v8::Value> val =
headers_obj->Get(context, key).ToLocalChecked();
if (!key->IsString() || !val->IsString())
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (header key/value "
"type).");
return;
}
std::string key_str = v8_util::to_str(isolate, key.As<v8::String>());
std::string val_str = v8_util::to_str(isolate, val.As<v8::String>());
endpoint_ctx.rpc_ctx->set_response_header(key_str, val_str);
}
}
// Response status code
int response_status_code = HTTP_STATUS_OK;
v8::Local<v8::Value> status_code_js;
if (!obj->Get(context, v8_util::to_v8_str(isolate, "statusCode"))
.ToLocal(&status_code_js))
{
v8_util::report_exception(isolate, &try_catch);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (cannot access statusCode).");
return;
}
if (!status_code_js->IsNullOrUndefined())
{
v8::Local<v8::Uint32> status_code;
if (
!status_code_js->IsNumber() ||
!status_code_js->ToUint32(context).ToLocal(&status_code))
{
if (try_catch.HasCaught())
v8_util::report_exception(isolate, &try_catch);
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 = status_code->Value();
}
endpoint_ctx.rpc_ctx->set_response_status(response_status_code);
}