Skip to content

Commit 538ed9c

Browse files
JakeChampionGuy Bedford
andauthored
feat: Add FetchEvent.server object which contains information about the server which received the incoming HTTP request from the client. (#855)
Co-authored-by: Guy Bedford <[email protected]>
1 parent 8271571 commit 538ed9c

34 files changed

+756
-635
lines changed

documentation/docs/globals/FetchEvent/FetchEvent.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ It provides the [`event.respondWith()`](./prototype/respondWith.mdx) method, whi
2929
- : An ArrayBuffer containing the raw client certificate in the mutual TLS handshake message. It is in PEM format. Returns an empty ArrayBuffer if this is not mTLS or available.
3030
- `FetchEvent.client.tlsClientHello` _**readonly**_
3131
- : An ArrayBuffer containing the raw bytes sent by the client in the TLS ClientHello message.
32+
- `FetchEvent.server` _**readonly**_
33+
- : Information about the server receiving the request for the Fastly Compute service.
34+
- `FetchEvent.server.address` _**readonly**_
35+
- : A string representation of the IPv4 or IPv6 address of the server which received the request.
3236

3337
## Instance methods
3438

integration-tests/js-compute/fixtures/app/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import "./response-json.js"
4343
import "./response-redirect.js"
4444
import "./response.js"
4545
import "./secret-store.js"
46+
import "./server.js"
4647
import "./tee.js"
4748
import "./timers.js"
4849
import "./transform-stream.js"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { pass, assert } from "./assertions.js";
2+
import { routes, isRunningLocally } from "./routes.js";
3+
4+
let error;
5+
routes.set("/server/address", event => {
6+
error = assert(typeof event.server.address, 'string', 'typeof event.server.address')
7+
if (error) { return error }
8+
9+
if (isRunningLocally()) {
10+
error = assert(event.server.address, '127.0.0.1', 'event.server.address')
11+
if (error) { return error }
12+
}
13+
return pass('ok')
14+
});

integration-tests/js-compute/fixtures/app/tests.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8219,5 +8219,16 @@
82198219
"status": 200,
82208220
"body": "ok"
82218221
}
8222+
},
8223+
"GET /server/address": {
8224+
"environments": ["compute", "viceroy"],
8225+
"downstream_request": {
8226+
"method": "GET",
8227+
"pathname": "/server/address"
8228+
},
8229+
"downstream_response": {
8230+
"status": 200,
8231+
"body": "ok"
8232+
}
82228233
}
82238234
}

runtime/fastly/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.27)
22

33
include("../StarlingMonkey/cmake/add_as_subproject.cmake")
44

5-
add_builtin(fastly::runtime SRC handler.cpp host-api/component/fastly_world_adapter.cpp)
5+
add_builtin(fastly::runtime SRC handler.cpp common/ip_octets_to_js_string.cpp host-api/component/fastly_world_adapter.cpp)
66
add_builtin(fastly::cache_simple SRC builtins/cache-simple.cpp DEPENDENCIES OpenSSL)
77
add_builtin(fastly::fastly SRC builtins/fastly.cpp)
88
add_builtin(fastly::backend SRC builtins/backend.cpp)

runtime/fastly/builtins/fastly.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "js/Conversions.h"
1212
#include "js/JSON.h"
1313
#include "logger.h"
14+
#include <arpa/inet.h>
1415

1516
using builtins::web::url::URL;
1617
using builtins::web::url::URLSearchParams;

runtime/fastly/builtins/fetch-event.cpp

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "../../StarlingMonkey/builtins/web/performance.h"
33
#include "../../StarlingMonkey/builtins/web/url.h"
44
#include "../../StarlingMonkey/builtins/web/worker-location.h"
5+
#include "../common/ip_octets_to_js_string.h"
56
#include "../host-api/fastly.h"
67
#include "../host-api/host_api_fastly.h"
78
#include "./fetch/request-response.h"
@@ -10,7 +11,6 @@
1011
#include "host_api.h"
1112
#include "js/JSON.h"
1213
#include "openssl/evp.h"
13-
#include <arpa/inet.h>
1414

1515
#include <iostream>
1616
#include <memory>
@@ -29,7 +29,7 @@ namespace fastly::fetch_event {
2929

3030
namespace {
3131

32-
JSString *address(JSObject *obj) {
32+
JSString *client_address(JSObject *obj) {
3333
JS::Value val = JS::GetReservedSlot(obj, static_cast<uint32_t>(ClientInfo::Slots::Address));
3434
return val.isString() ? val.toString() : nullptr;
3535
}
@@ -60,44 +60,16 @@ JSString *protocol(JSObject *obj) {
6060
return val.isString() ? val.toString() : nullptr;
6161
}
6262

63-
static JSString *retrieve_address(JSContext *cx, JS::HandleObject self) {
63+
static JSString *retrieve_client_address(JSContext *cx, JS::HandleObject self) {
6464
auto res = host_api::HttpReq::downstream_client_ip_addr();
6565
if (auto *err = res.to_err()) {
6666
HANDLE_ERROR(cx, *err);
6767
return nullptr;
6868
}
6969

70-
auto octets = std::move(res.unwrap());
71-
char address_chars[INET6_ADDRSTRLEN];
72-
int addr_family = 0;
73-
socklen_t size = 0;
74-
75-
switch (octets.len) {
76-
case 0: {
77-
// No address to be had, leave `address` as a nullptr.
78-
break;
79-
}
80-
case 4: {
81-
addr_family = AF_INET;
82-
size = INET_ADDRSTRLEN;
83-
break;
84-
}
85-
case 16: {
86-
addr_family = AF_INET6;
87-
size = INET6_ADDRSTRLEN;
88-
break;
89-
}
90-
}
91-
92-
JS::RootedString address(cx);
93-
if (octets.len > 0) {
94-
// TODO: do we need to do error handling here, or can we depend on the
95-
// host giving us a valid address?
96-
inet_ntop(addr_family, octets.begin(), address_chars, size);
97-
address = JS_NewStringCopyZ(cx, address_chars);
98-
if (!address) {
99-
return nullptr;
100-
}
70+
JS::RootedString address(cx, common::ip_octets_to_js_string(cx, std::move(res.unwrap())));
71+
if (!address) {
72+
return nullptr;
10173
}
10274

10375
JS::SetReservedSlot(self, static_cast<uint32_t>(ClientInfo::Slots::Address),
@@ -106,9 +78,9 @@ static JSString *retrieve_address(JSContext *cx, JS::HandleObject self) {
10678
}
10779

10880
JSString *retrieve_geo_info(JSContext *cx, JS::HandleObject self) {
109-
JS::RootedString address_str(cx, address(self));
81+
JS::RootedString address_str(cx, client_address(self));
11082
if (!address_str) {
111-
address_str = retrieve_address(cx, self);
83+
address_str = retrieve_client_address(cx, self);
11284
if (!address_str)
11385
return nullptr;
11486
}
@@ -127,9 +99,9 @@ JSString *retrieve_geo_info(JSContext *cx, JS::HandleObject self) {
12799
bool ClientInfo::address_get(JSContext *cx, unsigned argc, JS::Value *vp) {
128100
METHOD_HEADER(0);
129101

130-
JS::RootedString address_str(cx, address(self));
102+
JS::RootedString address_str(cx, client_address(self));
131103
if (!address_str) {
132-
address_str = retrieve_address(cx, self);
104+
address_str = retrieve_client_address(cx, self);
133105
if (!address_str)
134106
return false;
135107
}
@@ -305,6 +277,67 @@ JSObject *ClientInfo::create(JSContext *cx) {
305277

306278
namespace {
307279

280+
JSString *server_address(JSObject *obj) {
281+
JS::Value val = JS::GetReservedSlot(obj, static_cast<uint32_t>(ServerInfo::Slots::Address));
282+
return val.isString() ? val.toString() : nullptr;
283+
}
284+
285+
static JSString *retrieve_server_address(JSContext *cx, JS::HandleObject self) {
286+
auto res = host_api::HttpReq::downstream_server_ip_addr();
287+
if (auto *err = res.to_err()) {
288+
HANDLE_ERROR(cx, *err);
289+
return nullptr;
290+
}
291+
292+
JS::RootedString address(cx, common::ip_octets_to_js_string(cx, std::move(res.unwrap())));
293+
if (!address) {
294+
return nullptr;
295+
}
296+
297+
JS::SetReservedSlot(self, static_cast<uint32_t>(ServerInfo::Slots::Address),
298+
JS::StringValue(address));
299+
return address;
300+
}
301+
302+
} // namespace
303+
304+
bool ServerInfo::address_get(JSContext *cx, unsigned argc, JS::Value *vp) {
305+
METHOD_HEADER(0);
306+
307+
JS::RootedString address_str(cx, server_address(self));
308+
if (!address_str) {
309+
address_str = retrieve_server_address(cx, self);
310+
if (!address_str)
311+
return false;
312+
}
313+
314+
args.rval().setString(address_str);
315+
return true;
316+
}
317+
318+
const JSFunctionSpec ServerInfo::static_methods[] = {
319+
JS_FS_END,
320+
};
321+
322+
const JSPropertySpec ServerInfo::static_properties[] = {
323+
JS_PS_END,
324+
};
325+
326+
const JSFunctionSpec ServerInfo::methods[] = {
327+
JS_FS_END,
328+
};
329+
330+
const JSPropertySpec ServerInfo::properties[] = {
331+
JS_PSG("address", address_get, JSPROP_ENUMERATE),
332+
JS_PS_END,
333+
};
334+
335+
JSObject *ServerInfo::create(JSContext *cx) {
336+
return JS_NewObjectWithGivenProto(cx, &class_, proto_obj);
337+
}
338+
339+
namespace {
340+
308341
api::Engine *ENGINE;
309342

310343
PersistentRooted<JSObject *> INSTANCE;
@@ -373,6 +406,25 @@ bool FetchEvent::client_get(JSContext *cx, unsigned argc, JS::Value *vp) {
373406
return true;
374407
}
375408

409+
bool FetchEvent::server_get(JSContext *cx, unsigned argc, JS::Value *vp) {
410+
METHOD_HEADER(0)
411+
412+
JS::RootedValue serverInfo(cx,
413+
JS::GetReservedSlot(self, static_cast<uint32_t>(Slots::ServerInfo)));
414+
415+
if (serverInfo.isUndefined()) {
416+
JS::RootedObject obj(cx, ServerInfo::create(cx));
417+
if (!obj) {
418+
return false;
419+
}
420+
serverInfo.setObject(*obj);
421+
JS::SetReservedSlot(self, static_cast<uint32_t>(Slots::ServerInfo), serverInfo);
422+
}
423+
424+
args.rval().set(serverInfo);
425+
return true;
426+
}
427+
376428
void dispatch_fetch_event(HandleObject event, double *total_compute) {
377429
MOZ_ASSERT(FetchEvent::is_instance(event));
378430
auto pre_handler = system_clock::now();
@@ -729,6 +781,7 @@ const JSFunctionSpec FetchEvent::methods[] = {
729781
const JSPropertySpec FetchEvent::properties[] = {
730782
JS_PSG("client", client_get, JSPROP_ENUMERATE),
731783
JS_PSG("request", request_get, JSPROP_ENUMERATE),
784+
JS_PSG("server", server_get, JSPROP_ENUMERATE),
732785
JS_PS_END,
733786
};
734787

@@ -858,6 +911,10 @@ bool install(api::Engine *engine) {
858911
return false;
859912
}
860913

914+
if (!ServerInfo::init_class(engine->cx(), engine->global())) {
915+
return false;
916+
}
917+
861918
return true;
862919
}
863920

runtime/fastly/builtins/fetch-event.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,31 @@ class ClientInfo final : public builtins::BuiltinNoConstructor<ClientInfo> {
3838
static JSObject *create(JSContext *cx);
3939
};
4040

41+
class ServerInfo final : public builtins::BuiltinNoConstructor<ServerInfo> {
42+
static bool address_get(JSContext *cx, unsigned argc, JS::Value *vp);
43+
44+
public:
45+
static constexpr const char *class_name = "ServerInfo";
46+
47+
enum class Slots {
48+
Address,
49+
Count,
50+
};
51+
static const JSFunctionSpec static_methods[];
52+
static const JSPropertySpec static_properties[];
53+
static const JSFunctionSpec methods[];
54+
static const JSPropertySpec properties[];
55+
56+
static JSObject *create(JSContext *cx);
57+
};
58+
4159
void dispatch_fetch_event(HandleObject event, double *total_compute);
4260

4361
class FetchEvent final : public builtins::BuiltinNoConstructor<FetchEvent> {
4462
static bool respondWith(JSContext *cx, unsigned argc, JS::Value *vp);
4563
static bool client_get(JSContext *cx, unsigned argc, JS::Value *vp);
4664
static bool request_get(JSContext *cx, unsigned argc, JS::Value *vp);
65+
static bool server_get(JSContext *cx, unsigned argc, JS::Value *vp);
4766
static bool waitUntil(JSContext *cx, unsigned argc, JS::Value *vp);
4867

4968
public:
@@ -64,6 +83,7 @@ class FetchEvent final : public builtins::BuiltinNoConstructor<FetchEvent> {
6483
PendingPromiseCount,
6584
DecPendingPromiseCountFunc,
6685
ClientInfo,
86+
ServerInfo,
6787
Count
6888
};
6989

runtime/fastly/builtins/fetch/request-response.cpp

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "../../../StarlingMonkey/builtins/web/url.h"
77
#include "../../../StarlingMonkey/builtins/web/worker-location.h"
88
#include "../../../StarlingMonkey/runtime/encode.h"
9+
#include "../../common/ip_octets_to_js_string.h"
910
#include "../cache-core.h"
1011
#include "../cache-override.h"
1112
#include "../cache-simple.h"
@@ -22,7 +23,6 @@
2223
#include "js/Stream.h"
2324
#include "picosha2.h"
2425
#include <algorithm>
25-
#include <arpa/inet.h>
2626
#include <vector>
2727

2828
#pragma clang diagnostic push
@@ -2535,26 +2535,13 @@ bool Response::ip_get(JSContext *cx, unsigned argc, JS::Value *vp) {
25352535
args.rval().setUndefined();
25362536
return true;
25372537
}
2538-
if (ret->len == 4) {
2539-
char *out = (char *)malloc(INET_ADDRSTRLEN);
2540-
inet_ntop(AF_INET, ret->ptr.get(), out, 16);
2541-
JS::RootedString text(
2542-
cx, JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(out, strnlen(out, INET_ADDRSTRLEN))));
2543-
if (!text) {
2544-
return false;
2545-
}
2546-
args.rval().setString(text);
2547-
} else {
2548-
MOZ_ASSERT(ret->len == 16);
2549-
char *out = (char *)malloc(INET6_ADDRSTRLEN);
2550-
inet_ntop(AF_INET6, ret->ptr.get(), out, 16);
2551-
JS::RootedString text(
2552-
cx, JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(out, strnlen(out, INET6_ADDRSTRLEN))));
2553-
if (!text) {
2554-
return false;
2555-
}
2556-
args.rval().setString(text);
2538+
2539+
JS::RootedString address(cx, common::ip_octets_to_js_string(cx, std::move(*ret)));
2540+
if (!address) {
2541+
return false;
25572542
}
2543+
args.rval().setString(address);
2544+
25582545
return true;
25592546
}
25602547

0 commit comments

Comments
 (0)