Skip to content

Commit

Permalink
feat(data-proxy): add status endpoints
Browse files Browse the repository at this point in the history
Closes: #18
  • Loading branch information
Thomasvdam committed Oct 7, 2024
1 parent 0041f80 commit 9cfd626
Show file tree
Hide file tree
Showing 10 changed files with 434 additions and 39 deletions.
102 changes: 76 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,19 @@ The config file allows you to configure multiple routes:
"upstreamUrl": "https://myapi.com/eth-usd",
// Default is GET
"headers": {
"x-api-key": "some-api-key",
},
"x-api-key": "some-api-key"
}
},
{
"path": "/btc-usd",
"upstreamUrl": "https://myapi.com/btc-usd",
// Allows for multiple method setting
"method": ["GET", "HEAD"],
"headers": {
"x-api-key": "some-api-key",
},
},
],
"x-api-key": "some-api-key"
}
}
]
}
```

Expand All @@ -105,10 +105,10 @@ The config.json has support for using variable routes by using `:varName`:
"headers": {
"x-api-key": "some-api-key",
// Can also be injected in the header
"x-custom": "{:coinA}",
},
},
],
"x-custom": "{:coinA}"
}
}
]
}
```

Expand All @@ -126,10 +126,10 @@ If you don't want to expose all API info you can use `jsonPath` to reduce the re
// Calling the API http://localhost:5384/proxy/planets/1 will only return "Tatooine" and omit the rest
"jsonPath": "$.name",
"headers": {
"x-api-key": "some-api-key",
},
},
],
"x-api-key": "some-api-key"
}
}
]
}
```

Expand All @@ -147,10 +147,10 @@ By default the data proxy node will only forward the `content-type` as a respons
// Now the API will also return the server header from SWApi
"forwardRepsonseHeaders": ["content-type", "server"],
"headers": {
"x-api-key": "some-api-key",
},
},
],
"x-api-key": "some-api-key"
}
}
]
}
```

Expand All @@ -167,10 +167,10 @@ Sometimes you don't want to expose your API key in a config file, or you have mu
"path": "/*",
"upstreamUrl": "https://swapi.dev/api/{*}",
"headers": {
"x-api-key": "{$SECRET_API_KEY}",
},
},
],
"x-api-key": "{$SECRET_API_KEY}"
}
}
]
}
```

Expand All @@ -187,9 +187,59 @@ The Data Proxy node has support for wildcard routes, which allows you to quickly
"path": "/*",
"upstreamUrl": "https://swapi.dev/api/{*}",
"headers": {
"x-api-key": "some-api-key",
},
},
],
"x-api-key": "some-api-key"
}
}
]
}
```

## Status endpoints

The Data Proxy node has support for exposing status information through some endpoints. This can be used to monitor the health of the node and the number of requests it has processed.

The status endpoint has two routes:

- `/<statusEndpointsRoot>/health`
Returns a JSON object with the following structure:

```json
{
"status": "healthy",
"metrics": {
"uptime": "P0Y0M1DT2H3M4S", // ISO 8601 duration since the node was started
"requests": 1024, // Number of requests processed
"errors": 13 // Number of errors that occurred
}
}
```

- `/<statusEndpointsRoot>/pubkey`
Returns the public key of the node.
```json
{
"pubkey": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"
}
```

### Configuration

The status endpoints can be configured in the config file:

```json
{
// Other config...
"statusEndpoints": {
"root": "status",
// Optional
"apiKey": {
"header": "x-api-key",
"secret": "some-secret"
}
}
}
```

- `root`: Root path for the status endpoints. Defaults to `status`.
- `apiKey`: Optionally secure the status endpoints with an API key. The `header` attribute is the header key that needs to be set, and `secret` is the value that it needs to be set to.
The `statusEndpoints.apiKey.secret` attribute supports the `{$MY_ENV_VARIABLE}` syntax for injecting a value from the environment during start up.
Binary file modified bun.lockb
Binary file not shown.
19 changes: 10 additions & 9 deletions workspace/data-proxy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
"msw": "^2.3.5"
},
"dependencies": {
"big.js": "6.2.1",
"@commander-js/extra-typings": "^12.1.0",
"@cosmjs/crypto": "^0.32.4",
"@seda-protocol/data-proxy-sdk": "workspace:*",
"@seda-protocol/utils": "^1.0.0",
"@cosmjs/crypto": "^0.32.4",
"secp256k1": "^5.0.0",
"big.js": "6.2.1",
"commander": "^12.1.0",
"date-fns": "^4.1.0",
"elysia": "^1.1.6",
"winston": "^3.14.2",
"winston-daily-rotate-file": "^5.0.0",
"true-myth": "^8.0.0",
"jsonpath-plus": "^9.0.0",
"commander": "^12.1.0",
"@commander-js/extra-typings": "^12.1.0",
"valibot": "0.37.0"
"secp256k1": "^5.0.0",
"true-myth": "^8.0.0",
"valibot": "0.37.0",
"winston": "^3.14.2",
"winston-daily-rotate-file": "^5.0.0"
}
}
40 changes: 38 additions & 2 deletions workspace/data-proxy/src/config-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ const RouteSchema = v.object({
const ConfigSchema = v.object({
routeGroup: v.optional(v.string(), DEFAULT_PROXY_ROUTE_GROUP),
routes: v.array(RouteSchema),
statusEndpoints: v.optional(
v.object({
root: v.string(),
apiKey: v.optional(
v.object({
header: v.string(),
secret: v.string(),
}),
),
}),
{
root: "status",
},
),
});

export type Route = v.InferOutput<typeof RouteSchema>;
Expand All @@ -37,12 +51,34 @@ export function getHttpMethods(
return [configuredMethod];
}

const pathRegex = new RegExp(/{(:[^}]+)}/g, "g");
const envVariablesRegex = new RegExp(/{(\$[^}]+)}/g, "g");

export function parseConfig(input: unknown): Result<Config, string> {
const config = v.parse(ConfigSchema, input);

if (config.statusEndpoints.apiKey) {
const statusApiSecretEnvMatches =
config.statusEndpoints.apiKey.secret.matchAll(envVariablesRegex);

for (const match of statusApiSecretEnvMatches) {
const envKey = match[1].replace("$", "");
const envVariable = process.env[envKey];

if (!envVariable) {
return Result.err(
`Status endpoint API key secret required ${envKey} but was not available in the environment`,
);
}

config.statusEndpoints.apiKey.secret = replaceParams(
config.statusEndpoints.apiKey.secret,
{},
);
}
}

for (const route of config.routes) {
const pathRegex = new RegExp(/{(:[^}]+)}/g, "g");
const envVariablesRegex = new RegExp(/{(\$[^}]+)}/g, "g");
const urlMatches = route.upstreamUrl.matchAll(pathRegex);

// Content type should always be forwarded to the client
Expand Down
Loading

0 comments on commit 9cfd626

Please sign in to comment.