Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,7 @@ jobs:
echo "Docker image: ${{ env.DOCKER_IMAGE_NAME }}@${{ needs.docker.outputs.docker_image_digest }}"

docker run -d --name router -p 4000:4000 \
-e HIVE__SUPERGRAPH__SOURCE="file" \
-e HIVE__SUPERGRAPH__PATH="/app/supergraph.graphql" \
-e SUPERGRAPH_FILE_PATH="/app/supergraph.graphql" \
-v ./bench/supergraph.graphql:/app/supergraph.graphql \
${{ env.DOCKER_IMAGE_NAME }}@${{ needs.docker.outputs.docker_image_digest }}

Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ jobs:
- name: Run router
run: ./target/release/hive_router & sleep 5
env:
HIVE__SUPERGRAPH__SOURCE: file
HIVE__SUPERGRAPH__PATH: bench/supergraph.graphql
SUPERGRAPH_FILE_PATH: bench/supergraph.graphql
- name: Run k6 benchmark
run: k6 run bench/k6.js

Expand Down
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ supergraph:
Alternativly, you can use environment variables to configure the router:

```env
HIVE__SUPERGRAPH__SOURCE=file
HIVE__SUPERGRAPH__PATH=./supergraph.graphql
SUPERGRAPH_FILE_PATH=./supergraph.graphql
```

Then, run the router:
Expand All @@ -58,8 +57,7 @@ The router image is being published to [Docker to GitHub Container Registry]().
```bash
docker run \
-p 4000:4000 \
-e HIVE__SUPERGRAPH__SOURCE="file" \
-e HIVE__SUPERGRAPH__PATH="/app/supergraph.graphql" \
-e SUPERGRAPH_FILE_PATH="/app/supergraph.graphql" \
-v ./my-supergraph.graphql:/app/supergraph.graphql \
ghcr.io/graphql-hive/router:latest
```
Expand Down
2 changes: 1 addition & 1 deletion audits/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"test:federation-all": "graphql-federation-audit test --run-script=\"./run-router.sh\" --graphql=\"http://localhost:4000/graphql\" --healthcheck=\"http://localhost:4000/health\" --junit",
"test:federation-single": "graphql-federation-audit test-suite --run-script=\"./run-router.sh\" --graphql=\"http://localhost:4000/graphql\" --healthcheck=\"http://localhost:4000/health\" --write=\"federation-audit-results.txt\"",
"start:test-router": "cd .. && HIVE__SUPERGRAPH__SOURCE=file HIVE__SUPERGRAPH__PATH=lib/query-planner/fixture/spotify-supergraph.graphql cargo router",
"start:test-router": "cd .. && SUPERGRAPH_FILE_PATH=lib/query-planner/fixture/spotify-supergraph.graphql cargo router",
"test:graphql-over-http": "node --test graphql-over-http.test.js",
"test-junit:graphql-over-http": "node --test --test-reporter=junit --test-reporter-destination=\"./reports/graphql-over-http.xml\" graphql-over-http.test.js",
"ci:test:graphql-over-http": "start-server-and-test start:test-router http://localhost:4000 test-junit:graphql-over-http"
Expand Down
3 changes: 1 addition & 2 deletions audits/run-router.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ echo "running router..."

cd ..

export HIVE__SUPERGRAPH__SOURCE="file"
export HIVE__SUPERGRAPH__PATH="audits/fed-audit-supergraph.graphql"
export SUPERGRAPH_FILE_PATH="audits/fed-audit-supergraph.graphql"
cargo router
3 changes: 1 addition & 2 deletions bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
```
cargo subgraphs

export HIVE__SUPERGRAPH__SOURCE="file"
export HIVE__SUPERGRAPH__PATH="bench/supergraph.graphql"
export SUPERGRAPH_FILE_PATH="bench/supergraph.graphql"
cargo router
```

Expand Down
9 changes: 8 additions & 1 deletion bin/router/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,12 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;

#[ntex::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
router_entrypoint().await
match router_entrypoint().await {
Ok(_) => Ok(()),
Err(err) => {
eprintln!("Failed to start Hive Router:\n {}", err);

Err(err)
}
}
}
10 changes: 7 additions & 3 deletions bin/router/src/pipeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ pub async fn graphql_request_handler(
schema_state: &Arc<SchemaState>,
) -> web::HttpResponse {
if req.method() == Method::GET && req.accepts_content_type(*TEXT_HTML_CONTENT_TYPE) {
return web::HttpResponse::Ok()
.header(CONTENT_TYPE, *TEXT_HTML_CONTENT_TYPE)
.body(GRAPHIQL_HTML);
if shared_state.router_config.graphiql.enabled {
return web::HttpResponse::Ok()
.header(CONTENT_TYPE, *TEXT_HTML_CONTENT_TYPE)
.body(GRAPHIQL_HTML);
} else {
return web::HttpResponse::NotFound().into();
}
}

if let Some(jwt) = &shared_state.jwt_auth_runtime {
Expand Down
3 changes: 1 addition & 2 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ Use the following command to run the Docker image locally:

```bash
docker run -p 4000:4000 \
-e HIVE__SUPERGRAPH__SOURCE="file" \
-e HIVE__SUPERGRAPH__PATH="/app/supergraph.graphql" \
-e SUPERGRAPH_FILE_PATH="/app/supergraph.graphql" \
-v ./bench/supergraph.graphql:/app/supergraph.graphql \
<ID_OF_THE_LOCAL_IMAGE>
```
41 changes: 33 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
|----|----|-----------|--------|
|[**cors**](#cors)|`object`|Configuration for CORS (Cross-Origin Resource Sharing).<br/>Default: `{"allow_any_origin":false,"allow_credentials":false,"enabled":false,"policies":[]}`<br/>|yes|
|[**csrf**](#csrf)|`object`|Configuration for CSRF prevention.<br/>Default: `{"enabled":false,"required_headers":[]}`<br/>||
|[**graphiql**](#graphiql)|`object`|Configuration for the GraphiQL interface.<br/>Default: `{"enabled":true}`<br/>||
|[**headers**](#headers)|`object`|Configuration for the headers.<br/>Default: `{}`<br/>||
|[**http**](#http)|`object`|Configuration for the HTTP server/listener.<br/>Default: `{"host":"0.0.0.0","port":4000}`<br/>||
|[**jwt**](#jwt)|`object`, `null`|Configuration for JWT authentication plugin.<br/>|yes|
Expand Down Expand Up @@ -36,6 +37,8 @@ csrf:
enabled: true
required_headers:
- x-csrf-token
graphiql:
enabled: true
headers:
all:
request:
Expand Down Expand Up @@ -328,6 +331,26 @@ A valid HTTP header name, according to RFC 7230.

**Item Type:** `string`
**Item Pattern:** `^[A-Za-z0-9!#$%&'*+\-.^_\`\|~]+$`
<a name="graphiql"></a>
## graphiql: object

Configuration for the GraphiQL interface.


**Properties**

|Name|Type|Description|Required|
|----|----|-----------|--------|
|**enabled**|`boolean`|Enables/disables the GraphiQL interface. By default, the GraphiQL interface is enabled.<br/><br/>You can override this setting by setting the `GRAPHIQL_ENABLED` environment variable to `true` or `false`.<br/>Default: `true`<br/>||

**Additional Properties:** not allowed
**Example**

```yaml
enabled: true

```

<a name="headers"></a>
## headers: object

Expand Down Expand Up @@ -1304,8 +1327,8 @@ Configuration for the HTTP server/listener.

|Name|Type|Description|Required|
|----|----|-----------|--------|
|**host**|`string`|The host address to bind the HTTP server to.<br/>Default: `"0.0.0.0"`<br/>||
|**port**|`integer`|The port to bind the HTTP server to.<br/><br/>If you are running the router inside a Docker container, please ensure that the port is exposed correctly using `-p <host_port>:<container_port>` flag.<br/>Default: `4000`<br/>Format: `"uint16"`<br/>Minimum: `0`<br/>Maximum: `65535`<br/>||
|**host**|`string`|The host address to bind the HTTP server to.<br/><br/>Can also be set via the `HOST` environment variable.<br/>Default: `"0.0.0.0"`<br/>||
|**port**|`integer`|The port to bind the HTTP server to.<br/><br/>Can also be set via the `PORT` environment variable.<br/><br/>If you are running the router inside a Docker container, please ensure that the port is exposed correctly using `-p <host_port>:<container_port>` flag.<br/>Default: `4000`<br/>Format: `"uint16"`<br/>Minimum: `0`<br/>Maximum: `65535`<br/>||

**Additional Properties:** not allowed
**Example**
Expand Down Expand Up @@ -1499,9 +1522,9 @@ The router is configured to be mostly silent (`info`) level, and will print only

|Name|Type|Description|Required|
|----|----|-----------|--------|
|**filter**|`string`, `null`|||
|**format**|`string`|Default: `"json"`<br/>Enum: `"pretty-tree"`, `"pretty-compact"`, `"json"`<br/>||
|**level**|`string`|Default: `"info"`<br/>Enum: `"trace"`, `"debug"`, `"info"`, `"warn"`, `"error"`<br/>||
|**filter**|`string`, `null`|The filter to apply to log messages.<br/><br/>Can also be set via the `LOG_FILTER` environment variable.<br/>||
|**format**|`string`|The format of the log messages.<br/><br/>Can also be set via the `LOG_FORMAT` environment variable.<br/>Default: `"json"`<br/>Enum: `"pretty-tree"`, `"pretty-compact"`, `"json"`<br/>||
|**level**|`string`|The level of logging to use.<br/><br/>Can also be set via the `LOG_LEVEL` environment variable.<br/>Default: `"info"`<br/>Enum: `"trace"`, `"debug"`, `"info"`, `"warn"`, `"error"`<br/>||

**Additional Properties:** not allowed
**Example**
Expand Down Expand Up @@ -1593,7 +1616,7 @@ The path can be either absolute or relative to the router's working directory.

|Name|Type|Description|Required|
|----|----|-----------|--------|
|**path**|`string`|Format: `"path"`<br/>|yes|
|**path**|`string`|The path to the supergraph file.<br/><br/>Can also be set using the `SUPERGRAPH_FILE_PATH` environment variable.<br/>Format: `"path"`<br/>|yes|
|[**poll\_interval**](#option1poll_interval)|`object`, `null`|Optional interval at which the file should be polled for changes.<br/>|yes|
|**source**|`string`|Constant Value: `"file"`<br/>|yes|

Expand All @@ -1615,8 +1638,8 @@ Loads a supergraph from Hive Console CDN.

|Name|Type|Description|Required|
|----|----|-----------|--------|
|**endpoint**|`string`|The CDN endpoint from Hive Console target.<br/>|yes|
|**key**|`string`|The CDN Access Token with from the Hive Console target.<br/>|yes|
|**endpoint**|`string`|The CDN endpoint from Hive Console target.<br/><br/>Can also be set using the `HIVE_CDN_ENDPOINT` environment variable.<br/>|yes|
|**key**|`string`|The CDN Access Token with from the Hive Console target.<br/><br/>Can also be set using the `HIVE_CDN_KEY` environment variable.<br/>|yes|
|[**poll\_interval**](#option2poll_interval)|`object`|Interval at which the Hive Console should be polled for changes.<br/>Default: `"10s"`<br/>|yes|
|[**retry\_policy**](#option2retry_policy)|`object`|Interval at which the Hive Console should be polled for changes.<br/>Default: `{"max_retries":10}`<br/>|yes|
|**source**|`string`|Constant Value: `"hive"`<br/>|yes|
Expand Down Expand Up @@ -1660,6 +1683,8 @@ If not provided, the file will only be loaded once when the router starts.

Interval at which the Hive Console should be polled for changes.

Can also be set using the `HIVE_CDN_POLL_INTERVAL` environment variable.


**Properties**

Expand Down
2 changes: 2 additions & 0 deletions lib/router-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ thiserror = { workspace = true }
http = { workspace = true }
jsonwebtoken = { workspace = true }
retry-policies = { workspace = true}
tracing = { workspace = true }

schemars = "1.0.4"
humantime-serde = "1.1.1"
config = { version = "0.15.14", features = ["yaml", "json", "json5"] }
envconfig = "0.11.0"
109 changes: 109 additions & 0 deletions lib/router-config/src/env_overrides.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use config::{builder::BuilderState, ConfigBuilder, ConfigError};
use envconfig::Envconfig;
use tracing::debug;

use crate::log::{LogFormat, LogLevel};

#[derive(Envconfig)]
pub struct EnvVarOverrides {
// Logger overrides
#[envconfig(from = "LOG_LEVEL")]
pub log_level: Option<LogLevel>,
#[envconfig(from = "LOG_FORMAT")]
pub log_format: Option<LogFormat>,
#[envconfig(from = "LOG_FILTER")]
pub log_filter: Option<String>,

// GraphiQL overrides
#[envconfig(from = "GRAPHIQL_ENABLED")]
pub graphiql_enabled: Option<bool>,

// HTTP overrides
#[envconfig(from = "PORT")]
pub http_port: Option<u64>,
#[envconfig(from = "HOST")]
pub http_host: Option<String>,

// Supergraph overrides
#[envconfig(from = "SUPERGRAPH_FILE_PATH")]
pub supergraph_file_path: Option<String>,
#[envconfig(from = "HIVE_CDN_ENDPOINT")]
pub hive_console_cdn_endpoint: Option<String>,
#[envconfig(from = "HIVE_CDN_KEY")]
pub hive_console_cdn_key: Option<String>,
#[envconfig(from = "HIVE_CDN_POLL_INTERVAL")]
pub hive_console_cdn_poll_interval: Option<String>,
}

#[derive(Debug, thiserror::Error)]
pub enum EnvVarOverridesError {
#[error("Failed to override configuration: {0}")]
FailedToOverrideConfig(#[from] ConfigError),
#[error("Cannot override supergraph source due to conflict: SUPERGRAPH_FILE_PATH and HIVE_CDN_ENDPOINT cannot be used together")]
ConflictingSupergraphSource,
#[error("Missing required environment variable: {0}")]
MissingRequiredEnvVar(&'static str),
}

impl EnvVarOverrides {
pub fn apply_overrides<T: BuilderState>(
mut self,
mut config: ConfigBuilder<T>,
) -> Result<ConfigBuilder<T>, EnvVarOverridesError> {
if let Some(log_level) = self.log_level.take() {
debug!("[config-override] 'log.level' = {:?}", log_level);
config = config.set_override("log.level", log_level.as_str())?;
}
if let Some(log_format) = self.log_format.take() {
debug!("[config-override] 'log.format' = {:?}", log_format);
config = config.set_override("log.format", log_format.as_str())?;
}
if let Some(log_filter) = self.log_filter.take() {
debug!("[config-override] 'log.filter' = {:?}", log_filter);
config = config.set_override("log.filter", log_filter)?;
}

if let Some(http_port) = self.http_port.take() {
debug!("[config-override] 'http.port' = {}", http_port);
config = config.set_override("http.port", http_port)?;
}

if let Some(http_host) = self.http_host.take() {
debug!("[config-override] 'http.host' = {}", http_host);
config = config.set_override("http.host", http_host)?;
}

if self.supergraph_file_path.is_some() && self.hive_console_cdn_endpoint.is_some() {
return Err(EnvVarOverridesError::ConflictingSupergraphSource);
}

if let Some(supergraph_file_path) = self.supergraph_file_path.take() {
config = config.set_override("supergraph.source", "file")?;
config = config.set_override("supergraph.path", supergraph_file_path)?;
}

if let Some(hive_console_cdn_endpoint) = self.hive_console_cdn_endpoint.take() {
config = config.set_override("supergraph.source", "hive")?;
config = config.set_override("supergraph.endpoint", hive_console_cdn_endpoint)?;

if let Some(hive_console_cdn_key) = self.hive_console_cdn_key.take() {
config = config.set_override("supergraph.key", hive_console_cdn_key)?;
} else {
return Err(EnvVarOverridesError::MissingRequiredEnvVar("HIVE_CDN_KEY"));
}

if let Some(hive_console_cdn_poll_interval) = self.hive_console_cdn_poll_interval.take()
{
config = config
.set_override("supergraph.poll_interval", hive_console_cdn_poll_interval)?;
}
}

// GraphiQL overrides
if let Some(graphiql_enabled) = self.graphiql_enabled.take() {
config = config.set_override("graphiql.enabled", graphiql_enabled)?;
}

Ok(config)
}
}
24 changes: 24 additions & 0 deletions lib/router-config/src/graphiql.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
#[serde(deny_unknown_fields)]
pub struct GraphiQLConfig {
/// Enables/disables the GraphiQL interface. By default, the GraphiQL interface is enabled.
///
/// You can override this setting by setting the `GRAPHIQL_ENABLED` environment variable to `true` or `false`.
#[serde(default = "default_graphiql_enabled")]
pub enabled: bool,
}

fn default_graphiql_enabled() -> bool {
true
}

impl Default for GraphiQLConfig {
fn default() -> Self {
Self {
enabled: default_graphiql_enabled(),
}
}
}
Loading
Loading