@rivetkit/engine 1.0.0 → 2.2.1-pr.4600.b74ff3b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +38 -0
- package/artifacts/config-schema.json +1140 -0
- package/artifacts/errors/actor.kv_storage_quota_exceeded.json +5 -0
- package/artifacts/errors/actor.no_runner_config_configured.json +5 -0
- package/artifacts/errors/guard.actor_runner_failed.json +5 -0
- package/artifacts/errors/guard.invalid_request.json +5 -0
- package/artifacts/errors/guard.invalid_request_body.json +5 -0
- package/artifacts/errors/guard.invalid_response_body.json +5 -0
- package/artifacts/errors/guard.missing_query_parameter.json +5 -0
- package/artifacts/errors/guard.query_ambiguous_runner_configs.json +5 -0
- package/artifacts/errors/guard.query_duplicate_param.json +5 -0
- package/artifacts/errors/guard.query_empty_actor_name.json +5 -0
- package/artifacts/errors/guard.query_get_disallowed_params.json +5 -0
- package/artifacts/errors/guard.query_invalid_base64_input.json +5 -0
- package/artifacts/errors/guard.query_invalid_cbor_input.json +5 -0
- package/artifacts/errors/guard.query_invalid_params.json +5 -0
- package/artifacts/errors/guard.query_invalid_percent_encoding.json +5 -0
- package/artifacts/errors/guard.query_missing_runner_name.json +5 -0
- package/artifacts/errors/guard.query_no_runner_configs.json +5 -0
- package/artifacts/errors/guard.query_param_missing_equals.json +5 -0
- package/artifacts/errors/guard.query_path_token_syntax.json +5 -0
- package/artifacts/errors/guard.query_unknown_param.json +5 -0
- package/artifacts/errors/guard.request_body_too_large.json +5 -0
- package/artifacts/errors/guard.response_body_too_large.json +5 -0
- package/artifacts/errors/serverless_runner_pool.failed_to_fetch_metadata.json +5 -0
- package/artifacts/errors/serverless_runner_pool.not_found.json +5 -0
- package/artifacts/errors/{api.rate_limited.json → test.api_rate_limited.json} +2 -2
- package/artifacts/errors/{namespace.invalid_name.json → test.namespace_invalid_name.json} +2 -2
- package/artifacts/errors/ws.going_away.json +5 -0
- package/artifacts/openapi.json +458 -6
- package/docker/builder-base/linux-gnu.Dockerfile +21 -0
- package/docker/builder-base/linux-musl.Dockerfile +53 -0
- package/docker/builder-base/osxcross.Dockerfile +42 -0
- package/docker/builder-base/windows-mingw.Dockerfile +41 -0
- package/docker/builder-base/windows-msvc.Dockerfile +25 -0
- package/docker/dev/docker-compose.yml +43 -18
- package/docker/dev/grafana/dashboards/api.json +1077 -1239
- package/docker/dev/grafana/dashboards/cache.json +911 -1074
- package/docker/dev/grafana/dashboards/epoxy.json +1606 -0
- package/docker/dev/grafana/dashboards/futures.json +242 -229
- package/docker/dev/grafana/dashboards/gasoline.json +2663 -2476
- package/docker/dev/grafana/dashboards/guard.json +1433 -1273
- package/docker/dev/grafana/dashboards/operation.json +871 -0
- package/docker/dev/grafana/dashboards/pegboard.json +1274 -0
- package/docker/dev/grafana/dashboards/tokio.json +930 -1004
- package/docker/dev/grafana/dashboards/traces.json +35 -13
- package/docker/dev/grafana/provisioning/datasources/datasources.yaml +8 -0
- package/docker/{dev-multinode/otel-collector-server → dev/otel-collector}/config.yaml +18 -13
- package/docker/dev/prometheus/prometheus.yml +4 -0
- package/docker/dev/rivet-engine/config.jsonc +9 -16
- package/docker/dev-host/docker-compose.yml +38 -16
- package/docker/dev-host/grafana/dashboards/api.json +1077 -1239
- package/docker/dev-host/grafana/dashboards/cache.json +911 -1074
- package/docker/dev-host/grafana/dashboards/epoxy.json +1606 -0
- package/docker/dev-host/grafana/dashboards/futures.json +242 -229
- package/docker/dev-host/grafana/dashboards/gasoline.json +2663 -2476
- package/docker/dev-host/grafana/dashboards/guard.json +1433 -1273
- package/docker/dev-host/grafana/dashboards/operation.json +871 -0
- package/docker/dev-host/grafana/dashboards/pegboard.json +1274 -0
- package/docker/dev-host/grafana/dashboards/tokio.json +930 -1004
- package/docker/dev-host/grafana/dashboards/traces.json +35 -13
- package/docker/dev-host/grafana/provisioning/datasources/datasources.yaml +8 -0
- package/docker/dev-host/{otel-collector-server → otel-collector}/config.yaml +18 -13
- package/docker/dev-host/prometheus/prometheus.yml +4 -0
- package/docker/dev-host/rivet-engine/config.jsonc +9 -16
- package/docker/dev-multidc/core/grafana/dashboards/api.json +1077 -1239
- package/docker/dev-multidc/core/grafana/dashboards/cache.json +911 -1074
- package/docker/dev-multidc/core/grafana/dashboards/epoxy.json +1606 -0
- package/docker/dev-multidc/core/grafana/dashboards/futures.json +242 -229
- package/docker/dev-multidc/core/grafana/dashboards/gasoline.json +2663 -2476
- package/docker/dev-multidc/core/grafana/dashboards/guard.json +1433 -1273
- package/docker/dev-multidc/core/grafana/dashboards/operation.json +871 -0
- package/docker/dev-multidc/core/grafana/dashboards/pegboard.json +1274 -0
- package/docker/dev-multidc/core/grafana/dashboards/tokio.json +930 -1004
- package/docker/dev-multidc/core/grafana/dashboards/traces.json +35 -13
- package/docker/dev-multidc/core/grafana/provisioning/datasources/datasources.yaml +8 -0
- package/docker/dev-multidc/core/prometheus/prometheus.yml +4 -0
- package/docker/dev-multidc/datacenters/dc-a/{otel-collector-server → otel-collector}/config.yaml +18 -13
- package/docker/dev-multidc/datacenters/dc-a/rivet-engine/config.jsonc +23 -22
- package/docker/{dev-multidc-multinode/datacenters/dc-b/otel-collector-server → dev-multidc/datacenters/dc-b/otel-collector}/config.yaml +18 -13
- package/docker/dev-multidc/datacenters/dc-b/rivet-engine/config.jsonc +23 -22
- package/docker/dev-multidc/datacenters/dc-c/{otel-collector-server → otel-collector}/config.yaml +18 -13
- package/docker/dev-multidc/datacenters/dc-c/rivet-engine/config.jsonc +23 -22
- package/docker/dev-multidc/docker-compose.yml +71 -64
- package/docker/dev-multidc-multinode/core/grafana/dashboards/api.json +1077 -1239
- package/docker/dev-multidc-multinode/core/grafana/dashboards/cache.json +911 -1074
- package/docker/dev-multidc-multinode/core/grafana/dashboards/epoxy.json +1606 -0
- package/docker/dev-multidc-multinode/core/grafana/dashboards/futures.json +242 -229
- package/docker/dev-multidc-multinode/core/grafana/dashboards/gasoline.json +2663 -2476
- package/docker/dev-multidc-multinode/core/grafana/dashboards/guard.json +1433 -1273
- package/docker/dev-multidc-multinode/core/grafana/dashboards/operation.json +871 -0
- package/docker/dev-multidc-multinode/core/grafana/dashboards/pegboard.json +1274 -0
- package/docker/dev-multidc-multinode/core/grafana/dashboards/tokio.json +930 -1004
- package/docker/dev-multidc-multinode/core/grafana/dashboards/traces.json +35 -13
- package/docker/dev-multidc-multinode/core/grafana/provisioning/datasources/datasources.yaml +8 -0
- package/docker/dev-multidc-multinode/core/prometheus/prometheus.yml +4 -0
- package/docker/dev-multidc-multinode/datacenters/dc-a/{otel-collector-server → otel-collector}/config.yaml +28 -13
- package/docker/dev-multidc-multinode/datacenters/dc-a/rivet-engine/0/config.jsonc +23 -22
- package/docker/dev-multidc-multinode/datacenters/dc-a/rivet-engine/1/config.jsonc +23 -22
- package/docker/dev-multidc-multinode/datacenters/dc-a/rivet-engine/2/config.jsonc +23 -22
- package/docker/{dev-multidc/datacenters/dc-b/otel-collector-server → dev-multidc-multinode/datacenters/dc-b/otel-collector}/config.yaml +28 -13
- package/docker/dev-multidc-multinode/datacenters/dc-b/rivet-engine/0/config.jsonc +23 -22
- package/docker/dev-multidc-multinode/datacenters/dc-b/rivet-engine/1/config.jsonc +23 -22
- package/docker/dev-multidc-multinode/datacenters/dc-b/rivet-engine/2/config.jsonc +23 -22
- package/docker/dev-multidc-multinode/datacenters/dc-c/{otel-collector-server → otel-collector}/config.yaml +28 -13
- package/docker/dev-multidc-multinode/datacenters/dc-c/rivet-engine/0/config.jsonc +23 -22
- package/docker/dev-multidc-multinode/datacenters/dc-c/rivet-engine/1/config.jsonc +23 -22
- package/docker/dev-multidc-multinode/datacenters/dc-c/rivet-engine/2/config.jsonc +23 -22
- package/docker/dev-multidc-multinode/docker-compose.yml +113 -88
- package/docker/dev-multinode/docker-compose.yml +57 -26
- package/docker/dev-multinode/grafana/dashboards/api.json +1077 -1239
- package/docker/dev-multinode/grafana/dashboards/cache.json +911 -1074
- package/docker/dev-multinode/grafana/dashboards/epoxy.json +1606 -0
- package/docker/dev-multinode/grafana/dashboards/futures.json +242 -229
- package/docker/dev-multinode/grafana/dashboards/gasoline.json +2663 -2476
- package/docker/dev-multinode/grafana/dashboards/guard.json +1433 -1273
- package/docker/dev-multinode/grafana/dashboards/operation.json +871 -0
- package/docker/dev-multinode/grafana/dashboards/pegboard.json +1274 -0
- package/docker/dev-multinode/grafana/dashboards/tokio.json +930 -1004
- package/docker/dev-multinode/grafana/dashboards/traces.json +35 -13
- package/docker/dev-multinode/grafana/provisioning/datasources/datasources.yaml +8 -0
- package/docker/{dev/otel-collector-server → dev-multinode/otel-collector}/config.yaml +28 -13
- package/docker/dev-multinode/prometheus/prometheus.yml +4 -0
- package/docker/dev-multinode/rivet-engine/0/config.jsonc +9 -16
- package/docker/dev-multinode/rivet-engine/1/config.jsonc +9 -16
- package/docker/dev-multinode/rivet-engine/2/config.jsonc +9 -16
- package/docker/engine/linux-aarch64.Dockerfile +9 -49
- package/docker/engine/linux-x86_64.Dockerfile +7 -57
- package/docker/engine/macos-aarch64.Dockerfile +8 -54
- package/docker/engine/macos-x86_64.Dockerfile +9 -55
- package/docker/engine/windows.Dockerfile +5 -53
- package/docker/template/grafana-dashboards/api.json +1077 -1239
- package/docker/template/grafana-dashboards/cache.json +911 -1074
- package/docker/template/grafana-dashboards/epoxy.json +1606 -0
- package/docker/template/grafana-dashboards/futures.json +242 -229
- package/docker/template/grafana-dashboards/gasoline.json +2663 -2476
- package/docker/template/grafana-dashboards/guard.json +1433 -1273
- package/docker/template/grafana-dashboards/operation.json +871 -0
- package/docker/template/grafana-dashboards/pegboard.json +1274 -0
- package/docker/template/grafana-dashboards/tokio.json +930 -1004
- package/docker/template/grafana-dashboards/traces.json +35 -13
- package/docker/template/node_modules/.bin/js-yaml +4 -4
- package/docker/template/node_modules/.bin/tsc +4 -4
- package/docker/template/node_modules/.bin/tsserver +4 -4
- package/docker/template/node_modules/.bin/tsx +4 -4
- package/docker/template/src/docker-compose.ts +42 -29
- package/docker/template/src/main.ts +4 -4
- package/docker/template/src/services/core/grafana.ts +14 -1
- package/docker/template/src/services/core/prometheus.ts +20 -0
- package/docker/template/src/services/edge/{otel-collector-server.ts → otel-collector.ts} +55 -24
- package/docker/template/src/services/edge/rivet-engine.ts +4 -16
- package/docker/template/src/services/edge/runner.ts +2 -3
- package/docker/universal/Dockerfile +5 -3
- package/package.json +2 -5
- package/packages/api-builder/src/global_context.rs +1 -1
- package/packages/api-builder/src/metrics.rs +28 -24
- package/packages/api-builder/src/middleware.rs +30 -48
- package/packages/api-builder/src/router.rs +13 -1
- package/packages/api-peer/Cargo.toml +7 -9
- package/packages/api-peer/src/actors/delete.rs +56 -57
- package/packages/api-peer/src/actors/get_or_create.rs +139 -0
- package/packages/api-peer/src/actors/kv_get.rs +40 -28
- package/packages/api-peer/src/actors/list.rs +31 -14
- package/packages/api-peer/src/actors/list_names.rs +6 -6
- package/packages/api-peer/src/actors/mod.rs +3 -0
- package/packages/api-peer/src/actors/reschedule.rs +55 -0
- package/packages/api-peer/src/actors/sleep.rs +55 -0
- package/packages/api-peer/src/envoys.rs +57 -0
- package/packages/api-peer/src/internal.rs +441 -24
- package/packages/api-peer/src/lib.rs +2 -1
- package/packages/api-peer/src/namespaces.rs +24 -9
- package/packages/api-peer/src/router.rs +31 -7
- package/packages/api-peer/src/runner_configs.rs +66 -19
- package/packages/api-peer/src/runners.rs +30 -32
- package/packages/api-public/Cargo.toml +2 -0
- package/packages/api-public/src/actors/create.rs +8 -17
- package/packages/api-public/src/actors/delete.rs +11 -35
- package/packages/api-public/src/actors/get_or_create.rs +23 -95
- package/packages/api-public/src/actors/kv_get.rs +12 -29
- package/packages/api-public/src/actors/list.rs +56 -78
- package/packages/api-public/src/actors/list_names.rs +15 -14
- package/packages/api-public/src/actors/mod.rs +2 -0
- package/packages/api-public/src/actors/reschedule.rs +65 -0
- package/packages/api-public/src/actors/sleep.rs +64 -0
- package/packages/api-public/src/actors/utils.rs +12 -60
- package/packages/api-public/src/ctx.rs +14 -6
- package/packages/api-public/src/datacenters.rs +5 -5
- package/packages/api-public/src/envoys.rs +57 -0
- package/packages/api-public/src/errors.rs +0 -7
- package/packages/api-public/src/health.rs +51 -44
- package/packages/api-public/src/lib.rs +2 -1
- package/packages/api-public/src/metadata.rs +44 -14
- package/packages/api-public/src/namespaces.rs +11 -11
- package/packages/api-public/src/router.rs +22 -5
- package/packages/api-public/src/runner_configs/delete.rs +13 -10
- package/packages/api-public/src/runner_configs/list.rs +5 -2
- package/packages/api-public/src/runner_configs/refresh_metadata.rs +1 -1
- package/packages/api-public/src/runner_configs/serverless_health_check.rs +2 -2
- package/packages/api-public/src/runner_configs/upsert.rs +12 -9
- package/packages/api-public/src/runner_configs/utils.rs +35 -175
- package/packages/api-public/src/runners.rs +17 -45
- package/packages/{dump-openapi → api-public-openapi-gen}/Cargo.toml +1 -1
- package/packages/api-types/src/actors/create.rs +1 -0
- package/packages/api-types/src/actors/delete.rs +20 -0
- package/packages/api-types/src/actors/get_or_create.rs +30 -0
- package/packages/api-types/src/actors/kv_get.rs +25 -0
- package/packages/api-types/src/actors/list.rs +8 -1
- package/packages/api-types/src/actors/mod.rs +5 -0
- package/packages/api-types/src/actors/reschedule.rs +26 -0
- package/packages/api-types/src/actors/sleep.rs +26 -0
- package/packages/api-types/src/datacenters/list.rs +2 -2
- package/packages/api-types/src/envoys/list.rs +24 -0
- package/packages/api-types/src/envoys/mod.rs +1 -0
- package/packages/api-types/src/lib.rs +1 -0
- package/packages/api-types/src/namespaces/list.rs +4 -0
- package/packages/api-types/src/namespaces/runner_configs.rs +23 -2
- package/packages/api-types/src/runner_configs/list.rs +6 -1
- package/packages/api-types/src/runner_configs/mod.rs +12 -0
- package/packages/api-types/src/runners/list.rs +4 -0
- package/packages/api-types/src/runners/list_names.rs +21 -0
- package/packages/api-types/src/runners/mod.rs +1 -0
- package/packages/api-util/src/lib.rs +44 -21
- package/packages/bootstrap/Cargo.toml +7 -4
- package/packages/bootstrap/src/backfill.rs +53 -0
- package/packages/bootstrap/src/lib.rs +43 -7
- package/packages/cache/Cargo.toml +3 -1
- package/packages/cache/src/driver.rs +43 -151
- package/packages/cache/src/getter_ctx.rs +48 -70
- package/packages/cache/src/inner.rs +28 -18
- package/packages/cache/src/key.rs +17 -3
- package/packages/cache/src/lib.rs +0 -2
- package/packages/cache/src/metrics.rs +43 -31
- package/packages/cache/src/req_config.rs +219 -156
- package/packages/cache/tests/fetch.rs +91 -0
- package/packages/cache/tests/in_flight.rs +361 -0
- package/packages/cache/tests/ttl.rs +314 -0
- package/packages/cache-purge/src/lib.rs +1 -1
- package/packages/config/Cargo.toml +1 -0
- package/packages/config/src/config/cache.rs +10 -3
- package/packages/config/src/config/clickhouse.rs +0 -30
- package/packages/config/src/config/{db.rs → db/mod.rs} +3 -18
- package/packages/config/src/config/db/postgres.rs +59 -0
- package/packages/config/src/config/guard.rs +19 -0
- package/packages/config/src/config/metrics.rs +22 -0
- package/packages/config/src/config/mod.rs +44 -10
- package/packages/config/src/config/pegboard.rs +242 -16
- package/packages/config/src/config/pubsub.rs +11 -0
- package/packages/config/src/config/runtime.rs +58 -0
- package/packages/config/src/config/telemetry.rs +1 -0
- package/packages/config/src/config/topology.rs +78 -19
- package/packages/config/src/defaults.rs +3 -0
- package/packages/config/src/lib.rs +10 -1
- package/packages/config-schema-gen/Cargo.toml +11 -0
- package/packages/config-schema-gen/build.rs +26 -0
- package/packages/config-schema-gen/src/lib.rs +2 -0
- package/packages/engine/Cargo.toml +11 -2
- package/packages/engine/src/commands/db/mod.rs +0 -10
- package/packages/engine/src/commands/epoxy.rs +395 -0
- package/packages/engine/src/commands/mod.rs +1 -1
- package/packages/engine/src/commands/start.rs +43 -63
- package/packages/engine/src/commands/udb/cli.rs +148 -4
- package/packages/engine/src/commands/wf/mod.rs +83 -12
- package/packages/engine/src/commands/wf/signal.rs +38 -0
- package/packages/engine/src/lib.rs +6 -3
- package/packages/engine/src/main.rs +1 -1
- package/packages/engine/src/run_config.rs +6 -7
- package/packages/engine/src/util/db.rs +1 -25
- package/packages/engine/src/util/wf/mod.rs +39 -5
- package/packages/engine/tests/common/actors.rs +50 -332
- package/packages/engine/tests/common/api/mod.rs +7 -0
- package/packages/engine/tests/common/api/peer.rs +364 -0
- package/packages/engine/tests/common/api/public.rs +473 -0
- package/packages/engine/tests/common/ctx.rs +15 -3
- package/packages/engine/tests/common/mod.rs +8 -5
- package/packages/engine/tests/common/test_envoy.rs +87 -0
- package/packages/engine/tests/common/test_helpers.rs +218 -130
- package/packages/engine/tests/common/test_runner.rs +273 -0
- package/packages/engine/tests/envoy/actors_lifecycle.rs +1277 -0
- package/packages/engine/tests/envoy/mod.rs +1 -0
- package/packages/engine/tests/mod.rs +3 -0
- package/packages/engine/tests/runner/actors_alarm.rs +1453 -0
- package/packages/engine/tests/runner/actors_kv_crud.rs +996 -0
- package/packages/engine/tests/runner/actors_kv_delete_range.rs +126 -0
- package/packages/engine/tests/runner/actors_kv_drop.rs +255 -0
- package/packages/engine/tests/runner/actors_kv_list.rs +1061 -0
- package/packages/engine/tests/runner/actors_kv_misc.rs +882 -0
- package/packages/engine/tests/runner/actors_lifecycle.rs +1284 -0
- package/packages/engine/tests/runner/actors_scheduling_errors.rs +1005 -0
- package/packages/engine/tests/runner/api_actors_create.rs +422 -0
- package/packages/engine/tests/runner/api_actors_delete.rs +487 -0
- package/packages/engine/tests/runner/api_actors_get_or_create.rs +634 -0
- package/packages/engine/tests/runner/api_actors_list.rs +1771 -0
- package/packages/engine/tests/runner/api_actors_list_names.rs +691 -0
- package/packages/engine/tests/runner/api_namespaces_create.rs +428 -0
- package/packages/engine/tests/runner/api_namespaces_list.rs +760 -0
- package/packages/engine/tests/runner/api_runner_configs_list.rs +646 -0
- package/packages/engine/tests/runner/api_runner_configs_upsert.rs +651 -0
- package/packages/engine/tests/runner/api_runners_list.rs +166 -0
- package/packages/engine/tests/runner/api_runners_list_names.rs +386 -0
- package/packages/engine/tests/runner/mod.rs +20 -0
- package/packages/engine/tests/runner/runner_drain_on_version.rs +620 -0
- package/packages/env/Cargo.toml +0 -4
- package/packages/env/src/lib.rs +0 -18
- package/packages/epoxy/Cargo.toml +3 -2
- package/packages/epoxy/README.md +554 -93
- package/packages/epoxy/src/consts.rs +4 -36
- package/packages/epoxy/src/http_client.rs +59 -26
- package/packages/epoxy/src/http_routes.rs +73 -10
- package/packages/epoxy/src/keys/keys.rs +260 -11
- package/packages/epoxy/src/keys/mod.rs +11 -1
- package/packages/epoxy/src/keys/replica.rs +5 -260
- package/packages/epoxy/src/lib.rs +2 -1
- package/packages/epoxy/src/metrics.rs +118 -0
- package/packages/epoxy/src/ops/kv/get_local.rs +15 -24
- package/packages/epoxy/src/ops/kv/get_optimistic.rs +102 -64
- package/packages/epoxy/src/ops/kv/mod.rs +1 -0
- package/packages/epoxy/src/ops/kv/purge_local.rs +18 -9
- package/packages/epoxy/src/ops/kv/read_value.rs +92 -0
- package/packages/epoxy/src/ops/mod.rs +0 -1
- package/packages/epoxy/src/ops/propose.rs +1079 -194
- package/packages/epoxy/src/replica/ballot.rs +162 -102
- package/packages/epoxy/src/replica/changelog.rs +147 -0
- package/packages/epoxy/src/replica/commit_kv.rs +69 -66
- package/packages/epoxy/src/replica/message_request.rs +33 -48
- package/packages/epoxy/src/replica/messages/accept.rs +82 -41
- package/packages/epoxy/src/replica/messages/commit.rs +21 -33
- package/packages/epoxy/src/replica/messages/mod.rs +0 -8
- package/packages/epoxy/src/replica/messages/prepare.rs +68 -69
- package/packages/epoxy/src/replica/mod.rs +1 -6
- package/packages/epoxy/src/replica/update_config.rs +3 -1
- package/packages/epoxy/src/types.rs +30 -54
- package/packages/epoxy/src/utils.rs +149 -16
- package/packages/epoxy/src/workflows/backfill.rs +233 -0
- package/packages/epoxy/src/workflows/coordinator/mod.rs +33 -7
- package/packages/epoxy/src/workflows/coordinator/reconfigure.rs +44 -0
- package/packages/epoxy/src/workflows/coordinator/replica_status_change.rs +4 -3
- package/packages/epoxy/src/workflows/mod.rs +1 -1
- package/packages/epoxy/src/workflows/replica/mod.rs +4 -6
- package/packages/epoxy/src/workflows/replica/setup.rs +130 -771
- package/packages/epoxy/tests/backfill.rs +65 -0
- package/packages/epoxy/tests/backfill_snapshot.rs +233 -0
- package/packages/epoxy/tests/common/mod.rs +77 -21
- package/packages/epoxy/tests/common/utils.rs +366 -10
- package/packages/epoxy/tests/consensus_regressions.rs +285 -0
- package/packages/epoxy/tests/kv.rs +128 -167
- package/packages/epoxy/tests/kv_get_optimistic.rs +257 -157
- package/packages/epoxy/tests/migration.rs +75 -0
- package/packages/epoxy/tests/proposal.rs +133 -28
- package/packages/epoxy/tests/reconfigure.rs +92 -474
- package/packages/error/tests/basic.rs +8 -8
- package/packages/gasoline/Cargo.toml +1 -0
- package/packages/gasoline/src/builder/common/message.rs +19 -47
- package/packages/gasoline/src/builder/common/signal.rs +37 -21
- package/packages/gasoline/src/builder/common/workflow.rs +19 -15
- package/packages/gasoline/src/builder/workflow/lupe.rs +295 -0
- package/packages/gasoline/src/builder/workflow/message.rs +24 -47
- package/packages/gasoline/src/builder/workflow/mod.rs +1 -0
- package/packages/gasoline/src/builder/workflow/signal.rs +68 -22
- package/packages/gasoline/src/builder/workflow/sub_workflow.rs +6 -15
- package/packages/gasoline/src/ctx/activity.rs +46 -6
- package/packages/gasoline/src/ctx/common.rs +26 -23
- package/packages/gasoline/src/ctx/listen.rs +33 -50
- package/packages/gasoline/src/ctx/message.rs +76 -64
- package/packages/gasoline/src/ctx/operation.rs +15 -5
- package/packages/gasoline/src/ctx/standalone.rs +32 -4
- package/packages/gasoline/src/ctx/test.rs +31 -6
- package/packages/gasoline/src/ctx/versioned_workflow.rs +33 -7
- package/packages/gasoline/src/ctx/workflow.rs +194 -384
- package/packages/gasoline/src/db/debug.rs +49 -9
- package/packages/gasoline/src/db/kv/debug.rs +905 -15
- package/packages/gasoline/src/db/kv/keys/history.rs +434 -9
- package/packages/gasoline/src/db/kv/keys/metric.rs +70 -47
- package/packages/gasoline/src/db/kv/keys/signal.rs +19 -3
- package/packages/gasoline/src/db/kv/keys/workflow.rs +349 -3
- package/packages/gasoline/src/db/kv/mod.rs +975 -514
- package/packages/gasoline/src/db/kv/system.rs +155 -18
- package/packages/gasoline/src/db/mod.rs +29 -7
- package/packages/gasoline/src/error.rs +26 -21
- package/packages/gasoline/src/executable.rs +3 -1
- package/packages/gasoline/src/history/cursor.rs +436 -336
- package/packages/gasoline/src/history/event.rs +24 -15
- package/packages/gasoline/src/listen.rs +2 -14
- package/packages/gasoline/src/message.rs +1 -1
- package/packages/gasoline/src/metrics.rs +260 -143
- package/packages/gasoline/src/prelude.rs +1 -1
- package/packages/gasoline/src/registry.rs +6 -2
- package/packages/gasoline/src/signal.rs +34 -31
- package/packages/gasoline/src/utils/mod.rs +1 -18
- package/packages/gasoline/src/utils/topic.rs +35 -0
- package/packages/gasoline/src/worker.rs +71 -14
- package/packages/gasoline/src/workflow.rs +13 -0
- package/packages/gasoline/tests/workflows/eviction_test.rs +2 -2
- package/packages/gasoline-macros/src/lib.rs +74 -12
- package/packages/gasoline-runtime/Cargo.toml +18 -0
- package/packages/gasoline-runtime/src/lib.rs +12 -0
- package/packages/gasoline-runtime/src/workflows/mod.rs +1 -0
- package/packages/gasoline-runtime/src/workflows/pruner.rs +55 -0
- package/packages/guard/Cargo.toml +16 -8
- package/packages/guard/src/cache/mod.rs +63 -43
- package/packages/guard/src/cache/pegboard_gateway.rs +144 -0
- package/packages/guard/src/errors.rs +105 -0
- package/packages/guard/src/lib.rs +5 -15
- package/packages/guard/src/metrics.rs +12 -0
- package/packages/guard/src/routing/actor_path.rs +409 -0
- package/packages/guard/src/routing/api_public.rs +6 -14
- package/packages/guard/src/routing/envoy.rs +98 -0
- package/packages/guard/src/routing/mod.rs +152 -206
- package/packages/guard/src/routing/pegboard_gateway/mod.rs +572 -0
- package/packages/guard/src/routing/pegboard_gateway/resolve_actor_query.rs +236 -0
- package/packages/guard/src/routing/runner.rs +24 -54
- package/packages/guard/src/routing/ws_health.rs +61 -0
- package/packages/guard/src/shared_state.rs +11 -2
- package/packages/guard/tests/parse_actor_path.rs +418 -165
- package/packages/guard-core/Cargo.toml +3 -10
- package/packages/guard-core/src/custom_serve.rs +4 -10
- package/packages/guard-core/src/errors.rs +20 -4
- package/packages/guard-core/src/lib.rs +6 -4
- package/packages/guard-core/src/metrics.rs +66 -53
- package/packages/guard-core/src/proxy_service.rs +618 -1520
- package/packages/guard-core/src/request_context.rs +149 -169
- package/packages/guard-core/src/response_body.rs +65 -0
- package/packages/guard-core/src/route.rs +76 -0
- package/packages/guard-core/src/server.rs +60 -26
- package/packages/guard-core/src/task_group.rs +4 -0
- package/packages/guard-core/src/utils.rs +296 -0
- package/packages/guard-core/src/websocket_handle.rs +3 -3
- package/packages/guard-core/tests/common/mod.rs +0 -1
- package/packages/guard-core/tests/custom_serve.rs +4 -6
- package/packages/guard-core/tests/simple_websocket.rs +19 -19
- package/packages/guard-core/tests/streaming_response.rs +4 -9
- package/packages/metrics/Cargo.toml +3 -2
- package/packages/metrics/src/buckets.rs +5 -11
- package/packages/metrics/src/lib.rs +6 -3
- package/packages/metrics/src/providers.rs +2 -42
- package/packages/metrics/src/registry.rs +7 -0
- package/packages/metrics/src/server.rs +57 -0
- package/packages/namespace/Cargo.toml +0 -3
- package/packages/namespace/src/keys/metric.rs +301 -0
- package/packages/namespace/src/keys/mod.rs +1 -1
- package/packages/namespace/src/ops/get_global.rs +7 -4
- package/packages/namespace/src/ops/get_local.rs +32 -16
- package/packages/namespace/src/ops/mod.rs +0 -1
- package/packages/namespace/src/ops/resolve_for_name_global.rs +7 -4
- package/packages/namespace/src/ops/resolve_for_name_local.rs +39 -19
- package/packages/namespace/src/workflows/namespace.rs +3 -3
- package/packages/pegboard/Cargo.toml +22 -0
- package/packages/pegboard/src/actor_kv/entry.rs +47 -0
- package/packages/pegboard/src/actor_kv/metrics.rs +19 -0
- package/packages/pegboard/src/actor_kv/mod.rs +530 -0
- package/packages/pegboard/src/actor_kv/preload.rs +363 -0
- package/packages/{actor-kv/src → pegboard/src/actor_kv}/utils.rs +36 -35
- package/packages/pegboard/src/errors.rs +39 -5
- package/packages/pegboard/src/keys/actor.rs +285 -2
- package/packages/{actor-kv/src/entry.rs → pegboard/src/keys/actor_kv.rs} +73 -39
- package/packages/pegboard/src/keys/backfill.rs +49 -0
- package/packages/pegboard/src/keys/envoy.rs +1070 -0
- package/packages/pegboard/src/keys/epoxy/ns.rs +1 -1
- package/packages/pegboard/src/keys/mod.rs +4 -6
- package/packages/pegboard/src/keys/ns.rs +493 -14
- package/packages/pegboard/src/keys/runner.rs +281 -0
- package/packages/{namespace → pegboard}/src/keys/runner_config.rs +53 -0
- package/packages/pegboard/src/lib.rs +15 -2
- package/packages/pegboard/src/metrics.rs +57 -16
- package/packages/pegboard/src/ops/actor/create.rs +123 -53
- package/packages/pegboard/src/ops/actor/get.rs +14 -45
- package/packages/pegboard/src/ops/actor/get_for_gateway.rs +16 -0
- package/packages/pegboard/src/ops/actor/get_for_key.rs +3 -0
- package/packages/pegboard/src/ops/actor/get_for_kv.rs +43 -0
- package/packages/pegboard/src/ops/actor/get_for_runner.rs +99 -0
- package/packages/pegboard/src/ops/actor/get_reservation_for_key.rs +1 -0
- package/packages/pegboard/src/ops/actor/list_for_ns.rs +10 -38
- package/packages/pegboard/src/ops/actor/list_names.rs +3 -3
- package/packages/pegboard/src/ops/actor/mod.rs +3 -1
- package/packages/pegboard/src/ops/actor/util.rs +263 -0
- package/packages/pegboard/src/ops/envoy/drain.rs +101 -0
- package/packages/pegboard/src/ops/envoy/evict_actors.rs +54 -0
- package/packages/pegboard/src/ops/envoy/expire.rs +92 -0
- package/packages/pegboard/src/ops/envoy/get.rs +135 -0
- package/packages/pegboard/src/ops/envoy/list.rs +131 -0
- package/packages/pegboard/src/ops/envoy/mod.rs +6 -0
- package/packages/pegboard/src/ops/envoy/update_ping.rs +92 -0
- package/packages/pegboard/src/ops/mod.rs +3 -0
- package/packages/pegboard/src/ops/runner/drain.rs +110 -0
- package/packages/pegboard/src/ops/runner/list_names.rs +3 -3
- package/packages/pegboard/src/ops/runner/list_runner_config_enabled_dcs.rs +199 -0
- package/packages/pegboard/src/ops/runner/list_runner_config_epoxy_replica_ids.rs +51 -0
- package/packages/pegboard/src/ops/runner/mod.rs +3 -1
- package/packages/pegboard/src/ops/runner/update_alloc_idx.rs +17 -5
- package/packages/{namespace → pegboard}/src/ops/runner_config/delete.rs +18 -9
- package/packages/pegboard/src/ops/runner_config/ensure_normal_if_missing.rs +62 -0
- package/packages/{namespace → pegboard}/src/ops/runner_config/get.rs +15 -5
- package/packages/pegboard/src/ops/runner_config/get_error.rs +146 -0
- package/packages/{namespace → pegboard}/src/ops/runner_config/list.rs +13 -12
- package/packages/pegboard/src/ops/runner_config/mod.rs +7 -0
- package/packages/pegboard/src/ops/runner_config/refresh_metadata.rs +124 -0
- package/packages/pegboard/src/ops/runner_config/upsert.rs +206 -0
- package/packages/pegboard/src/ops/serverless_metadata/fetch.rs +223 -0
- package/packages/pegboard/src/ops/serverless_metadata/mod.rs +1 -0
- package/packages/pegboard/src/pubsub_subjects.rs +52 -0
- package/packages/pegboard/src/utils.rs +36 -2
- package/packages/pegboard/src/workflows/actor/destroy.rs +135 -99
- package/packages/pegboard/src/workflows/actor/keys.rs +59 -5
- package/packages/pegboard/src/workflows/actor/metrics.rs +345 -0
- package/packages/pegboard/src/workflows/actor/mod.rs +848 -204
- package/packages/pegboard/src/workflows/actor/runtime.rs +785 -212
- package/packages/pegboard/src/workflows/actor/setup.rs +61 -0
- package/packages/pegboard/src/workflows/actor2/keys.rs +337 -0
- package/packages/pegboard/src/workflows/actor2/metrics.rs +334 -0
- package/packages/pegboard/src/workflows/actor2/mod.rs +1251 -0
- package/packages/pegboard/src/workflows/actor2/runtime.rs +1005 -0
- package/packages/pegboard/src/workflows/actor_runner_name_selector_backfill.rs +266 -0
- package/packages/pegboard/src/workflows/metrics_aggregator.rs +282 -0
- package/packages/pegboard/src/workflows/mod.rs +8 -0
- package/packages/pegboard/src/workflows/runner.rs +62 -56
- package/packages/pegboard/src/workflows/runner2.rs +978 -0
- package/packages/pegboard/src/workflows/runner_pool.rs +298 -0
- package/packages/pegboard/src/workflows/runner_pool_error_tracker.rs +173 -0
- package/packages/pegboard/src/workflows/runner_pool_metadata_poller.rs +237 -0
- package/packages/pegboard/src/workflows/serverless/backfill.rs +120 -0
- package/packages/pegboard/src/workflows/serverless/conn.rs +702 -0
- package/packages/pegboard/src/workflows/serverless/mod.rs +3 -0
- package/packages/pegboard/src/workflows/serverless/receiver.rs +87 -0
- package/packages/pegboard/tests/actor_v1_pre_migration.rs +77 -0
- package/packages/{actor-kv/tests/list_edge_cases.rs → pegboard/tests/kv_list_edge_cases.rs} +74 -59
- package/packages/{actor-kv → pegboard}/tests/kv_operations.rs +77 -48
- package/packages/pegboard-envoy/Cargo.toml +43 -0
- package/packages/pegboard-envoy/src/actor_event_demuxer.rs +165 -0
- package/packages/pegboard-envoy/src/conn.rs +417 -0
- package/packages/pegboard-envoy/src/errors.rs +38 -0
- package/packages/pegboard-envoy/src/lib.rs +250 -0
- package/packages/pegboard-envoy/src/metrics.rs +44 -0
- package/packages/pegboard-envoy/src/ping_task.rs +61 -0
- package/packages/pegboard-envoy/src/tunnel_to_ws_task.rs +183 -0
- package/packages/pegboard-envoy/src/utils.rs +68 -0
- package/packages/pegboard-envoy/src/ws_to_tunnel_task.rs +536 -0
- package/packages/pegboard-envoy/tests/support/ws_to_tunnel_task.rs +82 -0
- package/packages/pegboard-gateway/Cargo.toml +2 -0
- package/packages/pegboard-gateway/src/keepalive_task.rs +1 -1
- package/packages/pegboard-gateway/src/lib.rs +506 -128
- package/packages/pegboard-gateway/src/metrics.rs +7 -11
- package/packages/pegboard-gateway/src/metrics_task.rs +80 -0
- package/packages/pegboard-gateway/src/ping_task.rs +9 -2
- package/packages/pegboard-gateway/src/shared_state.rs +110 -74
- package/packages/pegboard-gateway/src/tunnel_to_ws_task.rs +21 -7
- package/packages/pegboard-gateway/src/ws_to_tunnel_task.rs +12 -6
- package/packages/pegboard-gateway2/Cargo.toml +37 -0
- package/packages/pegboard-gateway2/src/keepalive_task.rs +61 -0
- package/packages/pegboard-gateway2/src/lib.rs +1044 -0
- package/packages/pegboard-gateway2/src/metrics.rs +10 -0
- package/packages/pegboard-gateway2/src/metrics_task.rs +80 -0
- package/packages/pegboard-gateway2/src/ping_task.rs +30 -0
- package/packages/pegboard-gateway2/src/shared_state.rs +601 -0
- package/packages/pegboard-gateway2/src/tunnel_to_ws_task.rs +99 -0
- package/packages/pegboard-gateway2/src/ws_to_tunnel_task.rs +71 -0
- package/packages/{pegboard-serverless → pegboard-outbound}/Cargo.toml +9 -9
- package/packages/pegboard-outbound/src/lib.rs +487 -0
- package/packages/pegboard-outbound/src/metrics.rs +17 -0
- package/packages/pegboard-runner/Cargo.toml +11 -5
- package/packages/pegboard-runner/src/actor_event_demuxer.rs +163 -0
- package/packages/pegboard-runner/src/conn.rs +358 -122
- package/packages/pegboard-runner/src/errors.rs +5 -0
- package/packages/pegboard-runner/src/lib.rs +62 -36
- package/packages/pegboard-runner/src/metrics.rs +44 -0
- package/packages/pegboard-runner/src/ping_task.rs +60 -13
- package/packages/pegboard-runner/src/tunnel_to_ws_task.rs +249 -110
- package/packages/pegboard-runner/src/ws_to_tunnel_task.rs +738 -113
- package/packages/pegboard-runner/tests/support/ws_to_tunnel_task.rs +150 -0
- package/packages/pools/Cargo.toml +1 -2
- package/packages/pools/src/db/clickhouse.rs +7 -6
- package/packages/pools/src/db/udb.rs +16 -3
- package/packages/pools/src/db/ups.rs +27 -5
- package/packages/pools/src/error.rs +0 -3
- package/packages/pools/src/lib.rs +0 -2
- package/packages/pools/src/metrics.rs +33 -28
- package/packages/pools/src/pools.rs +15 -39
- package/packages/pools/src/prelude.rs +1 -1
- package/packages/postgres-util/Cargo.toml +13 -0
- package/packages/postgres-util/src/lib.rs +84 -0
- package/packages/runner-protocol/build.rs +157 -0
- package/packages/runner-protocol/src/lib.rs +16 -0
- package/packages/runner-protocol/src/util.rs +14 -0
- package/packages/runner-protocol/src/versioned.rs +4345 -0
- package/packages/runtime/src/lib.rs +46 -46
- package/packages/runtime/src/metrics.rs +39 -30
- package/packages/runtime/src/term_signal.rs +25 -12
- package/packages/runtime/src/traces.rs +5 -8
- package/packages/service-manager/src/lib.rs +66 -15
- package/packages/test-deps/src/datacenter.rs +22 -8
- package/packages/test-deps/src/lib.rs +47 -25
- package/packages/test-deps-docker/src/database.rs +45 -36
- package/packages/test-snapshot-gen/Cargo.toml +39 -0
- package/packages/test-snapshot-gen/snapshots/.gitkeep +0 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/metadata.json +3 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-1/000008.log +0 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-1/000009.sst +3 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-1/CURRENT +3 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-1/MANIFEST-000005 +3 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-1/OPTIONS-000007 +3 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-2/000008.log +0 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-2/000009.sst +3 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-2/CURRENT +3 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-2/MANIFEST-000005 +3 -0
- package/packages/test-snapshot-gen/snapshots/epoxy-v1/replica-2/OPTIONS-000007 +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/metadata.json +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-1/000008.log +0 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-1/000009.sst +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-1/CURRENT +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-1/MANIFEST-000005 +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-1/OPTIONS-000007 +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-2/000008.log +0 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-2/000009.sst +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-2/CURRENT +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-2/MANIFEST-000005 +3 -0
- package/packages/test-snapshot-gen/snapshots/pb-actor-v1-pre-migration/replica-2/OPTIONS-000007 +3 -0
- package/packages/test-snapshot-gen/src/lib.rs +328 -0
- package/packages/test-snapshot-gen/src/main.rs +145 -0
- package/packages/test-snapshot-gen/src/scenarios/epoxy_keys.rs +60 -0
- package/packages/test-snapshot-gen/src/scenarios/mod.rs +27 -0
- package/packages/test-snapshot-gen/src/scenarios/pb_actor_v1_pre_migration.rs +56 -0
- package/packages/test-snapshot-gen/src/test_cluster.rs +234 -0
- package/packages/tracing-reconfigure/src/lib.rs +1 -1
- package/packages/tracing-utils/src/lib.rs +12 -20
- package/packages/types/src/actor/error.rs +59 -0
- package/packages/types/src/actor/mod.rs +2 -0
- package/packages/types/src/actors.rs +5 -0
- package/packages/types/src/envoys.rs +21 -0
- package/packages/types/src/keys/backfill.rs +5 -0
- package/packages/types/src/keys/mod.rs +1 -0
- package/packages/types/src/lib.rs +2 -1
- package/packages/types/src/runner_configs.rs +43 -14
- package/packages/universaldb/Cargo.toml +4 -0
- package/packages/universaldb/src/database.rs +50 -5
- package/packages/universaldb/src/driver/mod.rs +12 -2
- package/packages/universaldb/src/driver/postgres/database.rs +88 -27
- package/packages/universaldb/src/driver/postgres/mod.rs +1 -1
- package/packages/universaldb/src/driver/postgres/transaction.rs +4 -7
- package/packages/universaldb/src/driver/postgres/transaction_task.rs +30 -52
- package/packages/universaldb/src/driver/rocksdb/database.rs +13 -7
- package/packages/universaldb/src/driver/rocksdb/transaction_conflict_tracker.rs +5 -5
- package/packages/universaldb/src/driver/rocksdb/transaction_task.rs +2 -1
- package/packages/universaldb/src/metrics.rs +39 -23
- package/packages/universaldb/src/prelude.rs +1 -1
- package/packages/universaldb/src/transaction.rs +9 -2
- package/packages/universaldb/src/utils/cherry_pick.rs +46 -46
- package/packages/universaldb/src/utils/keys.rs +21 -2
- package/packages/universaldb/src/utils/mod.rs +8 -0
- package/packages/universaldb/src/utils/subspace.rs +9 -4
- package/packages/universaldb/tests/integration.rs +5 -3
- package/packages/universaldb/tests/integration_gas.rs +5 -3
- package/packages/universaldb/tests/rocksdb.rs +152 -19
- package/packages/universalpubsub/Cargo.toml +8 -2
- package/packages/universalpubsub/benches/simple.rs +28 -8
- package/packages/universalpubsub/src/chunking.rs +27 -5
- package/packages/universalpubsub/src/driver/memory/mod.rs +131 -20
- package/packages/universalpubsub/src/driver/mod.rs +5 -0
- package/packages/universalpubsub/src/driver/nats/mod.rs +8 -0
- package/packages/universalpubsub/src/driver/postgres/mod.rs +505 -96
- package/packages/universalpubsub/src/lib.rs +3 -0
- package/packages/universalpubsub/src/metrics.rs +60 -0
- package/packages/universalpubsub/src/pubsub.rs +227 -87
- package/packages/universalpubsub/src/subject.rs +32 -0
- package/packages/universalpubsub/tests/chunking.rs +298 -0
- package/packages/universalpubsub/tests/integration.rs +148 -7
- package/packages/universalpubsub/tests/reconnect.rs +8 -6
- package/packages/util/Cargo.toml +1 -3
- package/packages/util/build.rs +6 -0
- package/packages/util/src/lib.rs +7 -2
- package/packages/util/src/metric.rs +1 -0
- package/packages/util/src/serde.rs +1 -516
- package/packages/{internal → util-serde}/Cargo.toml +4 -5
- package/packages/util-serde/src/lib.rs +517 -0
- package/packages/workflow-worker/Cargo.toml +4 -4
- package/packages/workflow-worker/src/lib.rs +3 -2
- package/sdks/go/api-full/client/client.go +17 -4
- package/sdks/go/api-full/metadata/client.go +50 -0
- package/sdks/go/api-full/namespaces/client.go +3 -0
- package/sdks/go/api-full/namespaces.go +6 -4
- package/sdks/go/api-full/runners/client.go +3 -0
- package/sdks/go/api-full/runners.go +8 -6
- package/sdks/go/api-full/types.go +107 -23
- package/sdks/rust/api-full/rust/.openapi-generator/FILES +6 -0
- package/sdks/rust/api-full/rust/Cargo.toml +1 -1
- package/sdks/rust/api-full/rust/README.md +5 -2
- package/sdks/rust/api-full/rust/docs/Actor.md +1 -0
- package/sdks/rust/api-full/rust/docs/ActorsDeleteApi.md +1 -1
- package/sdks/rust/api-full/rust/docs/ActorsKvGetApi.md +2 -1
- package/sdks/rust/api-full/rust/docs/ActorsListApi.md +3 -2
- package/sdks/rust/api-full/rust/docs/MetadataApi.md +34 -0
- package/sdks/rust/api-full/rust/docs/MetadataGetResponse.md +18 -0
- package/sdks/rust/api-full/rust/docs/NamespacesApi.md +3 -2
- package/sdks/rust/api-full/rust/docs/RunnerConfig.md +1 -0
- package/sdks/rust/api-full/rust/docs/RunnerConfigKindOneOf1Serverless.md +1 -0
- package/sdks/rust/api-full/rust/docs/RunnerConfigResponse.md +15 -0
- package/sdks/rust/api-full/rust/docs/RunnerConfigsListApi.md +3 -2
- package/sdks/rust/api-full/rust/docs/RunnerConfigsListResponseRunnerConfigsValue.md +1 -1
- package/sdks/rust/api-full/rust/docs/RunnersApi.md +3 -2
- package/sdks/rust/api-full/rust/src/apis/actors_create_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/actors_delete_api.rs +3 -5
- package/sdks/rust/api-full/rust/src/apis/actors_get_or_create_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/actors_kv_get_api.rs +4 -2
- package/sdks/rust/api-full/rust/src/apis/actors_list_api.rs +9 -2
- package/sdks/rust/api-full/rust/src/apis/actors_list_names_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/configuration.rs +2 -2
- package/sdks/rust/api-full/rust/src/apis/datacenters_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/health_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/metadata_api.rs +62 -0
- package/sdks/rust/api-full/rust/src/apis/mod.rs +1 -0
- package/sdks/rust/api-full/rust/src/apis/namespaces_api.rs +9 -2
- package/sdks/rust/api-full/rust/src/apis/runner_configs_delete_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/runner_configs_list_api.rs +9 -2
- package/sdks/rust/api-full/rust/src/apis/runner_configs_refresh_metadata_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/runner_configs_serverless_health_check_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/runner_configs_upsert_api.rs +1 -1
- package/sdks/rust/api-full/rust/src/apis/runners_api.rs +9 -2
- package/sdks/rust/api-full/rust/src/models/actor.rs +5 -1
- package/sdks/rust/api-full/rust/src/models/actor_name.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/actors_create_request.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/actors_create_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/actors_get_or_create_request.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/actors_get_or_create_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/actors_kv_get_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/actors_list_names_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/actors_list_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/crash_policy.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/datacenter.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/datacenter_health.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/datacenters_list_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/health_fanout_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/health_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/health_status.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/metadata_get_response.rs +48 -0
- package/sdks/rust/api-full/rust/src/models/mod.rs +4 -0
- package/sdks/rust/api-full/rust/src/models/namespace.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/namespace_list_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/namespaces_create_request.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/namespaces_create_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/pagination.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_config.rs +4 -1
- package/sdks/rust/api-full/rust/src/models/runner_config_kind.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1_serverless.rs +5 -1
- package/sdks/rust/api-full/rust/src/models/runner_config_response.rs +39 -0
- package/sdks/rust/api-full/rust/src/models/runner_config_variant.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_list_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_list_response_runner_configs_value.rs +3 -3
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_request.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1_failure.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_success.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_1.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_2.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3_non_success_status.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4_invalid_response_json.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5_invalid_response_schema.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_upsert_request_body.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runner_configs_upsert_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runners_list_names_response.rs +1 -1
- package/sdks/rust/api-full/rust/src/models/runners_list_response.rs +1 -1
- package/sdks/rust/api-full/src/apis/actors_api.rs +8 -4
- package/sdks/rust/api-full/src/apis/ns_api.rs +8 -4
- package/sdks/rust/data/src/converted.rs +7 -4
- package/sdks/rust/data/src/lib.rs +2 -2
- package/sdks/rust/data/src/versioned/mod.rs +47 -4
- package/sdks/rust/data/src/versioned/namespace_runner_config.rs +256 -6
- package/sdks/rust/envoy-client/Cargo.toml +25 -0
- package/sdks/rust/envoy-client/src/actor.rs +992 -0
- package/sdks/rust/envoy-client/src/commands.rs +88 -0
- package/sdks/rust/envoy-client/src/config.rs +159 -0
- package/sdks/rust/envoy-client/src/connection.rs +288 -0
- package/sdks/rust/envoy-client/src/context.rs +24 -0
- package/sdks/rust/envoy-client/src/envoy.rs +432 -0
- package/sdks/rust/envoy-client/src/events.rs +62 -0
- package/sdks/rust/envoy-client/src/handle.rs +355 -0
- package/sdks/rust/envoy-client/src/kv.rs +132 -0
- package/sdks/rust/envoy-client/src/latency_channel.rs +27 -0
- package/sdks/rust/envoy-client/src/lib.rs +15 -0
- package/sdks/rust/envoy-client/src/stringify.rs +322 -0
- package/sdks/rust/envoy-client/src/tunnel.rs +265 -0
- package/sdks/rust/envoy-client/src/utils.rs +172 -0
- package/sdks/rust/envoy-protocol/Cargo.toml +22 -0
- package/sdks/rust/envoy-protocol/build.rs +200 -0
- package/sdks/rust/envoy-protocol/src/generated.rs +1 -0
- package/sdks/rust/envoy-protocol/src/lib.rs +8 -0
- package/sdks/rust/envoy-protocol/src/versioned.rs +208 -0
- package/sdks/rust/epoxy-protocol/Cargo.toml +0 -2
- package/sdks/rust/epoxy-protocol/build.rs +7 -0
- package/sdks/rust/epoxy-protocol/src/lib.rs +2 -5
- package/sdks/rust/epoxy-protocol/src/protocol.rs +128 -0
- package/sdks/rust/test-envoy/Cargo.toml +23 -0
- package/sdks/rust/test-envoy/Dockerfile +22 -0
- package/sdks/rust/test-envoy/src/behaviors.rs +141 -0
- package/sdks/rust/test-envoy/src/lib.rs +11 -0
- package/sdks/rust/test-envoy/src/main.rs +4 -0
- package/sdks/rust/test-envoy/src/server.rs +269 -0
- package/sdks/schemas/README.md +1 -2
- package/sdks/schemas/data/namespace.runner_config.v3.bare +24 -0
- package/sdks/schemas/data/namespace.runner_config.v4.bare +25 -0
- package/sdks/schemas/data/namespace.runner_config.v5.bare +26 -0
- package/sdks/schemas/data/pegboard.namespace.runner_alloc_idx.v2.bare +8 -0
- package/sdks/schemas/envoy-protocol/v1.bare +459 -0
- package/sdks/schemas/epoxy-protocol/v2.bare +220 -0
- package/sdks/schemas/runner-protocol/v4.bare +438 -0
- package/sdks/schemas/runner-protocol/v5.bare +430 -0
- package/sdks/schemas/runner-protocol/v6.bare +432 -0
- package/sdks/schemas/runner-protocol/v7.bare +438 -0
- package/sdks/typescript/api-full/.turbo/turbo-build.log +28 -27
- package/sdks/typescript/api-full/build.js +7 -1
- package/sdks/typescript/api-full/package.json +66 -57
- package/sdks/typescript/api-full/rivetkit-engine-api-full-25.5.3.tgz +0 -0
- package/sdks/typescript/api-full/src/Client.ts +41 -10
- package/sdks/typescript/api-full/src/api/client/requests/ActorsDeleteRequest.ts +4 -2
- package/sdks/typescript/api-full/src/api/client/requests/ActorsKvGetRequest.ts +13 -0
- package/sdks/typescript/api-full/src/api/client/requests/ActorsListRequest.ts +6 -0
- package/sdks/typescript/api-full/src/api/client/requests/RunnerConfigsListRequest.ts +4 -0
- package/sdks/typescript/api-full/src/api/client/requests/index.ts +1 -0
- package/sdks/typescript/api-full/src/api/resources/index.ts +1 -0
- package/sdks/typescript/api-full/src/api/resources/metadata/client/Client.ts +97 -0
- package/sdks/typescript/api-full/src/api/resources/metadata/client/index.ts +1 -0
- package/sdks/typescript/api-full/src/api/resources/metadata/index.ts +1 -0
- package/sdks/typescript/api-full/src/api/resources/namespaces/client/Client.ts +12 -2
- package/sdks/typescript/api-full/src/api/resources/namespaces/client/requests/NamespacesListRequest.ts +6 -0
- package/sdks/typescript/api-full/src/api/resources/runners/client/Client.ts +12 -2
- package/sdks/typescript/api-full/src/api/resources/runners/client/requests/RunnersListRequest.ts +6 -0
- package/sdks/typescript/api-full/src/api/types/Actor.ts +2 -0
- package/sdks/typescript/api-full/src/api/types/MetadataGetResponse.ts +14 -0
- package/sdks/typescript/api-full/src/api/types/RunnerConfig.ts +1 -0
- package/sdks/typescript/api-full/src/api/types/RunnerConfigKindServerlessServerless.ts +2 -0
- package/sdks/typescript/api-full/src/api/types/RunnerConfigResponse.ts +9 -0
- package/sdks/typescript/api-full/src/api/types/RunnerConfigServerless.ts +2 -0
- package/sdks/typescript/api-full/src/api/types/RunnerConfigsListResponseRunnerConfigsValue.ts +1 -1
- package/sdks/typescript/api-full/src/api/types/index.ts +2 -0
- package/sdks/typescript/api-full/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts +6 -4
- package/sdks/typescript/api-full/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts +4 -3
- package/sdks/typescript/api-full/src/serialization/types/Actor.ts +2 -0
- package/sdks/typescript/api-full/src/serialization/types/MetadataGetResponse.ts +34 -0
- package/sdks/typescript/api-full/src/serialization/types/RunnerConfig.ts +5 -0
- package/sdks/typescript/api-full/src/serialization/types/RunnerConfigKindServerlessServerless.ts +2 -0
- package/sdks/typescript/api-full/src/serialization/types/RunnerConfigResponse.ts +26 -0
- package/sdks/typescript/api-full/src/serialization/types/RunnerConfigServerless.ts +2 -0
- package/sdks/typescript/api-full/src/serialization/types/RunnerConfigsListResponseRunnerConfigsValue.ts +3 -3
- package/sdks/typescript/api-full/src/serialization/types/index.ts +2 -0
- package/sdks/typescript/api-full/turbo.json +9 -0
- package/sdks/typescript/envoy-protocol/.turbo/turbo-build.log +23 -0
- package/sdks/typescript/{runner-protocol → envoy-protocol}/dist/index.cjs +466 -281
- package/sdks/typescript/envoy-protocol/dist/index.cjs.map +1 -0
- package/sdks/typescript/envoy-protocol/dist/index.d.cts +699 -0
- package/sdks/typescript/envoy-protocol/dist/index.d.ts +699 -0
- package/sdks/typescript/{runner-protocol → envoy-protocol}/dist/index.js +530 -345
- package/sdks/typescript/envoy-protocol/dist/index.js.map +1 -0
- package/sdks/typescript/{runner-protocol → envoy-protocol}/node_modules/.bin/tsc +4 -4
- package/{tests/load → sdks/typescript/envoy-protocol}/node_modules/.bin/tsserver +4 -4
- package/sdks/typescript/envoy-protocol/node_modules/.bin/tsup +21 -0
- package/sdks/typescript/envoy-protocol/node_modules/.bin/tsup-node +21 -0
- package/sdks/typescript/envoy-protocol/package.json +36 -0
- package/sdks/typescript/envoy-protocol/src/index.ts +2331 -0
- package/sdks/typescript/envoy-protocol/tsconfig.json +9 -0
- package/sdks/typescript/envoy-protocol/tsup.config.ts +4 -0
- package/sdks/typescript/runner/package.json +8 -2
- package/sdks/typescript/runner/src/actor.ts +38 -0
- package/sdks/typescript/runner/src/mod.ts +435 -229
- package/sdks/typescript/runner/src/stringify.ts +36 -33
- package/sdks/typescript/runner/src/tunnel.ts +52 -56
- package/sdks/typescript/runner/src/utils.ts +19 -0
- package/sdks/typescript/runner/src/websocket-tunnel-adapter.ts +98 -435
- package/sdks/typescript/runner-protocol/package.json +11 -9
- package/sdks/typescript/runner-protocol/src/index.ts +224 -156
- package/sdks/typescript/runner-protocol/tsconfig.json +1 -9
- package/sdks/typescript/test-runner/.turbo/turbo-build.log +5 -5
- package/sdks/typescript/test-runner/dist/index.js +53 -44
- package/sdks/typescript/test-runner/dist/index.js.map +1 -1
- package/sdks/typescript/test-runner/node_modules/.bin/pino +2 -2
- package/sdks/typescript/test-runner/node_modules/.bin/tsc +4 -4
- package/sdks/typescript/test-runner/node_modules/.bin/tsserver +4 -4
- package/sdks/typescript/test-runner/node_modules/.bin/tsup +4 -4
- package/sdks/typescript/test-runner/node_modules/.bin/tsup-node +4 -4
- package/sdks/typescript/test-runner/node_modules/.bin/tsx +4 -4
- package/sdks/typescript/test-runner/node_modules/.bin/vitest +4 -4
- package/sdks/typescript/test-runner/package.json +3 -3
- package/sdks/typescript/test-runner/src/index.ts +65 -42
- package/sdks/typescript/test-runner/src/log.ts +4 -18
- package/artifacts/errors/actor.no_runners_available.json +0 -5
- package/artifacts/errors/guard.actor_destroyed.json +0 -5
- package/artifacts/errors/guard.actor_not_found.json +0 -5
- package/contrib-docs/ACTOR_KEY_RESERVATION.md +0 -101
- package/contrib-docs/API.md +0 -11
- package/contrib-docs/DOCKER.md +0 -5
- package/contrib-docs/ERRORS.md +0 -13
- package/contrib-docs/GUARD.md +0 -76
- package/contrib-docs/PEGBOARD_TUNNEL_RETRIES.md +0 -83
- package/contrib-docs/RUNNER_LIFECYCLE.md +0 -172
- package/contrib-docs/SDKS.md +0 -9
- package/contrib-docs/TEST_DEPENDENCIES.md +0 -43
- package/contrib-docs/design-choicse/EMBEDDED_KV.md +0 -80
- package/contrib-docs/operate/TRACING_RECONFIGURE.md +0 -78
- package/docker/dev/otel-collector-client/config.yaml +0 -39
- package/docker/dev-host/otel-collector-client/config.yaml +0 -39
- package/docker/dev-multidc/datacenters/dc-a/otel-collector-client/config.yaml +0 -39
- package/docker/dev-multidc/datacenters/dc-b/otel-collector-client/config.yaml +0 -39
- package/docker/dev-multidc/datacenters/dc-c/otel-collector-client/config.yaml +0 -39
- package/docker/dev-multidc-multinode/datacenters/dc-a/otel-collector-client/config.yaml +0 -39
- package/docker/dev-multidc-multinode/datacenters/dc-b/otel-collector-client/config.yaml +0 -39
- package/docker/dev-multidc-multinode/datacenters/dc-c/otel-collector-client/config.yaml +0 -39
- package/docker/dev-multinode/otel-collector-client/config.yaml +0 -39
- package/docker/template/src/services/edge/otel-collector-client.ts +0 -64
- package/packages/actor-kv/Cargo.toml +0 -31
- package/packages/actor-kv/src/key.rs +0 -81
- package/packages/actor-kv/src/lib.rs +0 -357
- package/packages/cache/src/rate_limit.rs +0 -109
- package/packages/cache/tests/integration.rs +0 -582
- package/packages/clickhouse-inserter/Cargo.toml +0 -17
- package/packages/clickhouse-inserter/src/error.rs +0 -16
- package/packages/clickhouse-inserter/src/lib.rs +0 -179
- package/packages/clickhouse-user-query/Cargo.toml +0 -16
- package/packages/clickhouse-user-query/examples/case_sensitivity_demo.rs +0 -100
- package/packages/clickhouse-user-query/examples/group_by_example.rs +0 -53
- package/packages/clickhouse-user-query/examples/string_contains_demo.rs +0 -96
- package/packages/clickhouse-user-query/src/builder.rs +0 -445
- package/packages/clickhouse-user-query/src/error.rs +0 -37
- package/packages/clickhouse-user-query/src/lib.rs +0 -61
- package/packages/clickhouse-user-query/src/query.rs +0 -143
- package/packages/clickhouse-user-query/src/schema.rs +0 -78
- package/packages/clickhouse-user-query/tests/builder_tests.rs +0 -619
- package/packages/clickhouse-user-query/tests/case_sensitivity_tests.rs +0 -307
- package/packages/clickhouse-user-query/tests/integration_tests.rs +0 -540
- package/packages/clickhouse-user-query/tests/query_tests.rs +0 -263
- package/packages/clickhouse-user-query/tests/schema_tests.rs +0 -44
- package/packages/config/src/config/vector.rs +0 -18
- package/packages/engine/src/commands/udb_keys.rs +0 -200
- package/packages/engine/tests/actors_create.rs +0 -524
- package/packages/engine/tests/actors_delete.rs +0 -243
- package/packages/engine/tests/actors_general.rs +0 -191
- package/packages/engine/tests/actors_get.rs +0 -230
- package/packages/engine/tests/actors_get_by_id.rs +0 -170
- package/packages/engine/tests/actors_get_or_create.rs +0 -294
- package/packages/engine/tests/actors_get_or_create_by_id.rs +0 -147
- package/packages/engine/tests/actors_lifecycle.rs +0 -165
- package/packages/engine/tests/actors_list.rs +0 -798
- package/packages/engine/tests/actors_list_names.rs +0 -353
- package/packages/engine/tests/common/ns.rs +0 -36
- package/packages/engine/tests/common/runner.rs +0 -134
- package/packages/engine/tests/runners_dupe_key.rs +0 -27
- package/packages/engine/tests/runners_version.rs +0 -50
- package/packages/env/build.rs +0 -8
- package/packages/epoxy/spec/KEYS.md +0 -33
- package/packages/epoxy/spec/PROPOSAL.md +0 -125
- package/packages/epoxy/spec/RECONFIGURE.md +0 -40
- package/packages/epoxy/src/ops/explicit_prepare.rs +0 -342
- package/packages/epoxy/src/replica/decide_path.rs +0 -51
- package/packages/epoxy/src/replica/lead_consensus.rs +0 -65
- package/packages/epoxy/src/replica/log.rs +0 -84
- package/packages/epoxy/src/replica/messages/accepted.rs +0 -35
- package/packages/epoxy/src/replica/messages/committed.rs +0 -41
- package/packages/epoxy/src/replica/messages/download_instances.rs +0 -69
- package/packages/epoxy/src/replica/messages/pre_accept.rs +0 -69
- package/packages/epoxy/src/replica/utils.rs +0 -111
- package/packages/epoxy/src/workflows/purger.rs +0 -81
- package/packages/guard/src/cache/actor.rs +0 -43
- package/packages/guard/src/middleware.rs +0 -42
- package/packages/guard/src/routing/pegboard_gateway.rs +0 -260
- package/packages/guard-core/src/analytics.rs +0 -46
- package/packages/internal/README.md +0 -1
- package/packages/internal/src/lib.rs +0 -1
- package/packages/internal/src/ops/bump_serverless_autoscaler_global.rs +0 -64
- package/packages/internal/src/ops/cache/mod.rs +0 -1
- package/packages/internal/src/ops/cache/purge_global.rs +0 -81
- package/packages/internal/src/ops/mod.rs +0 -2
- package/packages/namespace/src/ops/runner_config/mod.rs +0 -4
- package/packages/namespace/src/ops/runner_config/upsert.rs +0 -148
- package/packages/pegboard/src/ops/actor/get_runner.rs +0 -64
- package/packages/pegboard/src/ops/runner/find_dc_with_runner.rs +0 -222
- package/packages/pegboard-serverless/src/lib.rs +0 -523
- package/packages/types/src/msgs/mod.rs +0 -1
- package/packages/types/src/msgs/pegboard.rs +0 -5
- package/sdks/rust/epoxy-protocol/src/versioned.rs +0 -206
- package/sdks/rust/runner-protocol/build.rs +0 -115
- package/sdks/rust/runner-protocol/src/lib.rs +0 -10
- package/sdks/rust/runner-protocol/src/versioned.rs +0 -1734
- package/sdks/schemas/epoxy-protocol/v1.bare +0 -260
- package/sdks/typescript/runner/.turbo/turbo-build.log +0 -22
- package/sdks/typescript/runner/dist/mod.cjs +0 -2951
- package/sdks/typescript/runner/dist/mod.cjs.map +0 -1
- package/sdks/typescript/runner/dist/mod.d.cts +0 -326
- package/sdks/typescript/runner/dist/mod.d.ts +0 -326
- package/sdks/typescript/runner/dist/mod.js +0 -2951
- package/sdks/typescript/runner/dist/mod.js.map +0 -1
- package/sdks/typescript/runner/node_modules/.bin/pino +0 -21
- package/sdks/typescript/runner/node_modules/.bin/tsc +0 -21
- package/sdks/typescript/runner/node_modules/.bin/tsserver +0 -21
- package/sdks/typescript/runner/node_modules/.bin/tsup +0 -21
- package/sdks/typescript/runner/node_modules/.bin/tsup-node +0 -21
- package/sdks/typescript/runner/node_modules/.bin/tsx +0 -21
- package/sdks/typescript/runner/node_modules/.bin/uuid +0 -21
- package/sdks/typescript/runner/node_modules/.bin/vitest +0 -21
- package/sdks/typescript/runner-protocol/.turbo/turbo-build.log +0 -22
- package/sdks/typescript/runner-protocol/dist/index.cjs.map +0 -1
- package/sdks/typescript/runner-protocol/dist/index.d.cts +0 -666
- package/sdks/typescript/runner-protocol/dist/index.d.ts +0 -666
- package/sdks/typescript/runner-protocol/dist/index.js.map +0 -1
- package/sdks/typescript/runner-protocol/node_modules/.bin/tsserver +0 -21
- package/sdks/typescript/runner-protocol/node_modules/.bin/tsup +0 -21
- package/sdks/typescript/runner-protocol/node_modules/.bin/tsup-node +0 -21
- package/sdks/typescript/test-runner/Dockerfile +0 -26
- package/tests/load/README.md +0 -28
- package/tests/load/actor-lifecycle/README.md +0 -26
- package/tests/load/actor-lifecycle/actor.ts +0 -41
- package/tests/load/actor-lifecycle/config.ts +0 -14
- package/tests/load/actor-lifecycle/index.ts +0 -62
- package/tests/load/actor-lifecycle/rivet_api.ts +0 -140
- package/tests/load/actor-lifecycle/types.ts +0 -17
- package/tests/load/node_modules/.bin/biome +0 -21
- package/tests/load/node_modules/.bin/tsc +0 -21
- package/tests/load/package.json +0 -15
- package/tests/load/tsconfig.json +0 -20
- package/tests/smoke/README.md +0 -32
- package/tests/smoke/package.json +0 -19
- package/tests/smoke/scripts/connect.ts +0 -41
- package/tests/smoke/src/server/registry.ts +0 -32
- package/tests/smoke/src/server/server.ts +0 -7
- package/tests/smoke/src/smoke-test/index.ts +0 -161
- package/tests/smoke/src/smoke-test/spawn-actor.ts +0 -109
- package/tests/smoke/tsconfig.json +0 -43
- /package/packages/{dump-openapi → api-public-openapi-gen}/build.rs +0 -0
- /package/packages/{dump-openapi → api-public-openapi-gen}/src/lib.rs +0 -0
- /package/{sdks/rust → packages}/runner-protocol/Cargo.toml +0 -0
- /package/{sdks/rust → packages}/runner-protocol/src/compat.rs +0 -0
- /package/{sdks/rust → packages}/runner-protocol/src/generated.rs +0 -0
- /package/{sdks/rust → packages}/runner-protocol/src/uuid_compat.rs +0 -0
- /package/sdks/rust/{runner-protocol → envoy-protocol}/src/util.rs +0 -0
- /package/{tests/smoke → sdks/typescript/envoy-protocol}/turbo.json +0 -0
|
@@ -1,358 +1,66 @@
|
|
|
1
1
|
use anyhow::{Context, Result, bail, ensure};
|
|
2
2
|
use bytes::Bytes;
|
|
3
3
|
use futures_util::{SinkExt, StreamExt};
|
|
4
|
-
use http_body_util::{BodyExt, Full};
|
|
5
|
-
use hyper::{
|
|
4
|
+
use http_body_util::{BodyExt, Full, Limited};
|
|
5
|
+
use hyper::{
|
|
6
|
+
Request, Response, StatusCode,
|
|
7
|
+
body::Incoming as BodyIncoming,
|
|
8
|
+
header::{HeaderName, HeaderValue},
|
|
9
|
+
};
|
|
6
10
|
use hyper_tungstenite;
|
|
7
11
|
use hyper_util::{client::legacy::Client, rt::TokioExecutor};
|
|
8
12
|
use moka::future::Cache;
|
|
9
|
-
use rand;
|
|
10
|
-
use rivet_api_builder::{ErrorResponse, RawErrorResponse};
|
|
13
|
+
use rand::seq::SliceRandom;
|
|
11
14
|
use rivet_api_builder::{RequestIds, X_RIVET_RAY_ID};
|
|
12
|
-
use rivet_error::{INTERNAL_ERROR, RivetError};
|
|
13
|
-
use rivet_metrics::KeyValue;
|
|
14
15
|
use rivet_util::Id;
|
|
15
|
-
use
|
|
16
|
+
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
|
16
17
|
|
|
17
18
|
use rivet_runner_protocol as protocol;
|
|
18
19
|
use std::{
|
|
19
|
-
|
|
20
|
-
collections::{HashMap as StdHashMap, HashSet},
|
|
21
|
-
net::SocketAddr,
|
|
20
|
+
net::{IpAddr, SocketAddr},
|
|
22
21
|
sync::Arc,
|
|
23
22
|
time::{Duration, Instant},
|
|
24
23
|
};
|
|
25
24
|
use tokio::sync::Mutex;
|
|
26
25
|
use tokio::time::timeout;
|
|
27
|
-
use tokio_tungstenite::tungstenite::
|
|
28
|
-
client::IntoClientRequest,
|
|
29
|
-
protocol::{CloseFrame, frame::coding::CloseCode},
|
|
30
|
-
};
|
|
26
|
+
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
|
|
31
27
|
use tracing::Instrument;
|
|
32
28
|
use url::Url;
|
|
33
29
|
|
|
30
|
+
use crate::RouteTarget;
|
|
31
|
+
use crate::request_context::RequestContext;
|
|
32
|
+
use crate::response_body::ResponseBody;
|
|
33
|
+
use crate::route::{
|
|
34
|
+
CacheKeyFn, DEFAULT_ROUTE_TIMEOUT, ResolveRouteOutput, RouteCache, RoutingFn, RoutingOutput,
|
|
35
|
+
};
|
|
36
|
+
use crate::utils::{InFlightCounter, RateLimiter};
|
|
34
37
|
use crate::{
|
|
35
|
-
WebSocketHandle,
|
|
36
|
-
custom_serve::{CustomServeTrait, HibernationResult},
|
|
37
|
-
errors, metrics,
|
|
38
|
-
request_context::RequestContext,
|
|
39
|
-
task_group::TaskGroup,
|
|
38
|
+
WebSocketHandle, custom_serve::HibernationResult, errors, metrics, task_group::TaskGroup, utils,
|
|
40
39
|
};
|
|
41
40
|
|
|
42
|
-
const X_RIVET_TARGET: HeaderName = HeaderName::from_static("x-rivet-target");
|
|
43
|
-
const X_RIVET_ACTOR: HeaderName = HeaderName::from_static("x-rivet-actor");
|
|
44
|
-
const X_RIVET_TOKEN: HeaderName = HeaderName::from_static("x-rivet-token");
|
|
45
41
|
pub const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
|
|
46
42
|
pub const X_RIVET_ERROR: HeaderName = HeaderName::from_static("x-rivet-error");
|
|
47
43
|
|
|
48
|
-
const ROUTE_CACHE_TTL: Duration = Duration::from_secs(60 * 10); // 10 minutes
|
|
49
44
|
const PROXY_STATE_CACHE_TTL: Duration = Duration::from_secs(60 * 60); // 1 hour
|
|
50
45
|
const WEBSOCKET_CLOSE_LINGER: Duration = Duration::from_millis(100); // Keep TCP connection open briefly after WebSocket close
|
|
51
46
|
|
|
52
|
-
/// Response body type that can handle both streaming and buffered responses
|
|
53
|
-
#[derive(Debug)]
|
|
54
|
-
pub enum ResponseBody {
|
|
55
|
-
/// Buffered response body
|
|
56
|
-
Full(Full<Bytes>),
|
|
57
|
-
/// Streaming response body
|
|
58
|
-
Incoming(BodyIncoming),
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
impl http_body::Body for ResponseBody {
|
|
62
|
-
type Data = Bytes;
|
|
63
|
-
type Error = Box<dyn std::error::Error + Send + Sync>;
|
|
64
|
-
|
|
65
|
-
fn poll_frame(
|
|
66
|
-
self: std::pin::Pin<&mut Self>,
|
|
67
|
-
cx: &mut std::task::Context<'_>,
|
|
68
|
-
) -> std::task::Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
|
|
69
|
-
match self.get_mut() {
|
|
70
|
-
ResponseBody::Full(body) => {
|
|
71
|
-
let pin = std::pin::Pin::new(body);
|
|
72
|
-
match pin.poll_frame(cx) {
|
|
73
|
-
std::task::Poll::Ready(Some(Ok(frame))) => {
|
|
74
|
-
std::task::Poll::Ready(Some(Ok(frame)))
|
|
75
|
-
}
|
|
76
|
-
std::task::Poll::Ready(Some(Err(e))) => {
|
|
77
|
-
std::task::Poll::Ready(Some(Err(Box::new(e))))
|
|
78
|
-
}
|
|
79
|
-
std::task::Poll::Ready(None) => std::task::Poll::Ready(None),
|
|
80
|
-
std::task::Poll::Pending => std::task::Poll::Pending,
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
ResponseBody::Incoming(body) => {
|
|
84
|
-
let pin = std::pin::Pin::new(body);
|
|
85
|
-
match pin.poll_frame(cx) {
|
|
86
|
-
std::task::Poll::Ready(Some(Ok(frame))) => {
|
|
87
|
-
std::task::Poll::Ready(Some(Ok(frame)))
|
|
88
|
-
}
|
|
89
|
-
std::task::Poll::Ready(Some(Err(e))) => {
|
|
90
|
-
std::task::Poll::Ready(Some(Err(Box::new(e))))
|
|
91
|
-
}
|
|
92
|
-
std::task::Poll::Ready(None) => std::task::Poll::Ready(None),
|
|
93
|
-
std::task::Poll::Pending => std::task::Poll::Pending,
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
fn is_end_stream(&self) -> bool {
|
|
100
|
-
match self {
|
|
101
|
-
ResponseBody::Full(body) => body.is_end_stream(),
|
|
102
|
-
ResponseBody::Incoming(body) => body.is_end_stream(),
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
fn size_hint(&self) -> http_body::SizeHint {
|
|
107
|
-
match self {
|
|
108
|
-
ResponseBody::Full(body) => body.size_hint(),
|
|
109
|
-
ResponseBody::Incoming(body) => body.size_hint(),
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Routing types
|
|
115
|
-
#[derive(Clone, Debug)]
|
|
116
|
-
pub struct RouteTarget {
|
|
117
|
-
pub actor_id: Option<Id>,
|
|
118
|
-
pub host: String,
|
|
119
|
-
pub port: u16,
|
|
120
|
-
pub path: String,
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
#[derive(Clone, Debug)]
|
|
124
|
-
pub struct RoutingTimeout {
|
|
125
|
-
pub routing_timeout: u64, // in seconds
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
#[derive(Clone, Debug)]
|
|
129
|
-
pub struct RouteConfig {
|
|
130
|
-
pub targets: Vec<RouteTarget>,
|
|
131
|
-
pub timeout: RoutingTimeout,
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
#[derive(Clone)]
|
|
135
|
-
pub enum RoutingOutput {
|
|
136
|
-
/// Return the data to route to.
|
|
137
|
-
Route(RouteConfig),
|
|
138
|
-
/// Return a custom response.
|
|
139
|
-
Response(StructuredResponse),
|
|
140
|
-
/// Return a custom serve handler.
|
|
141
|
-
CustomServe(Arc<dyn CustomServeTrait>),
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
#[derive(Clone, Debug)]
|
|
145
|
-
pub struct StructuredResponse {
|
|
146
|
-
pub status: StatusCode,
|
|
147
|
-
pub message: Cow<'static, str>,
|
|
148
|
-
pub docs: Option<Cow<'static, str>>,
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
impl StructuredResponse {
|
|
152
|
-
pub fn build_response(&self) -> Result<Response<ResponseBody>> {
|
|
153
|
-
let mut body = StdHashMap::new();
|
|
154
|
-
body.insert("message", self.message.clone().into_owned());
|
|
155
|
-
|
|
156
|
-
if let Some(docs) = &self.docs {
|
|
157
|
-
body.insert("docs", docs.clone().into_owned());
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
let body_json = serde_json::to_string(&body)?;
|
|
161
|
-
let bytes = Bytes::from(body_json);
|
|
162
|
-
|
|
163
|
-
let response = Response::builder()
|
|
164
|
-
.status(self.status)
|
|
165
|
-
.header(hyper::header::CONTENT_TYPE, "application/json")
|
|
166
|
-
.body(ResponseBody::Full(Full::new(bytes)))?;
|
|
167
|
-
|
|
168
|
-
Ok(response)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
#[derive(Clone)]
|
|
173
|
-
enum ResolveRouteOutput {
|
|
174
|
-
Target(RouteTarget),
|
|
175
|
-
Response(StructuredResponse),
|
|
176
|
-
CustomServe(Arc<dyn CustomServeTrait>),
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/// Enum defining the type of port the request came in on
|
|
180
|
-
#[derive(Clone, Debug, PartialEq)]
|
|
181
|
-
pub enum PortType {
|
|
182
|
-
Http,
|
|
183
|
-
Https,
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
pub type RoutingFn = Arc<
|
|
187
|
-
dyn for<'a> Fn(
|
|
188
|
-
&'a str,
|
|
189
|
-
&'a str,
|
|
190
|
-
PortType,
|
|
191
|
-
&'a hyper::HeaderMap,
|
|
192
|
-
) -> futures::future::BoxFuture<'a, Result<RoutingOutput>>
|
|
193
|
-
+ Send
|
|
194
|
-
+ Sync,
|
|
195
|
-
>;
|
|
196
|
-
|
|
197
|
-
pub type CacheKeyFn = Arc<
|
|
198
|
-
dyn for<'a> Fn(
|
|
199
|
-
&'a str,
|
|
200
|
-
&'a str,
|
|
201
|
-
&'a hyper::Method,
|
|
202
|
-
PortType,
|
|
203
|
-
&'a hyper::HeaderMap,
|
|
204
|
-
) -> Result<u64>
|
|
205
|
-
+ Send
|
|
206
|
-
+ Sync,
|
|
207
|
-
>;
|
|
208
|
-
|
|
209
|
-
#[derive(Clone, Debug)]
|
|
210
|
-
pub struct MiddlewareConfig {
|
|
211
|
-
pub rate_limit: RateLimitConfig,
|
|
212
|
-
pub max_in_flight: MaxInFlightConfig,
|
|
213
|
-
pub retry: RetryConfig,
|
|
214
|
-
pub timeout: TimeoutConfig,
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
#[derive(Clone, Debug)]
|
|
218
|
-
pub struct RateLimitConfig {
|
|
219
|
-
pub requests: u64,
|
|
220
|
-
pub period: u64, // in seconds
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
#[derive(Clone, Debug)]
|
|
224
|
-
pub struct MaxInFlightConfig {
|
|
225
|
-
pub amount: usize,
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
#[derive(Clone, Debug)]
|
|
229
|
-
pub struct RetryConfig {
|
|
230
|
-
pub max_attempts: u32,
|
|
231
|
-
pub initial_interval: u64, // in milliseconds
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
#[derive(Clone, Debug)]
|
|
235
|
-
pub struct TimeoutConfig {
|
|
236
|
-
pub request_timeout: u64, // in seconds
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
#[derive(Clone, Debug)]
|
|
240
|
-
pub enum MiddlewareResponse {
|
|
241
|
-
Ok(MiddlewareConfig),
|
|
242
|
-
NotFound,
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
pub type MiddlewareFn = Arc<
|
|
246
|
-
dyn for<'a> Fn(
|
|
247
|
-
&'a Id,
|
|
248
|
-
&'a hyper::HeaderMap,
|
|
249
|
-
) -> futures::future::BoxFuture<'a, Result<MiddlewareResponse>>
|
|
250
|
-
+ Send
|
|
251
|
-
+ Sync,
|
|
252
|
-
>;
|
|
253
|
-
|
|
254
|
-
// Cache for routing results
|
|
255
|
-
struct RouteCache {
|
|
256
|
-
cache: Cache<u64, RouteConfig>,
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
impl RouteCache {
|
|
260
|
-
fn new() -> Self {
|
|
261
|
-
Self {
|
|
262
|
-
cache: Cache::builder()
|
|
263
|
-
.max_capacity(10_000)
|
|
264
|
-
.time_to_live(ROUTE_CACHE_TTL)
|
|
265
|
-
.build(),
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
#[tracing::instrument(skip_all)]
|
|
270
|
-
async fn get(&self, key: &u64) -> Option<RouteConfig> {
|
|
271
|
-
self.cache.get(key).await
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
#[tracing::instrument(skip_all)]
|
|
275
|
-
async fn insert(&self, key: u64, result: RouteConfig) {
|
|
276
|
-
self.cache.insert(key, result).await;
|
|
277
|
-
|
|
278
|
-
metrics::ROUTE_CACHE_COUNT.record(self.cache.entry_count(), &[]);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Rate limiter
|
|
283
|
-
struct RateLimiter {
|
|
284
|
-
requests_remaining: u64,
|
|
285
|
-
reset_time: Instant,
|
|
286
|
-
requests_limit: u64,
|
|
287
|
-
period: Duration,
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
impl RateLimiter {
|
|
291
|
-
fn new(requests: u64, period_seconds: u64) -> Self {
|
|
292
|
-
Self {
|
|
293
|
-
requests_remaining: requests,
|
|
294
|
-
reset_time: Instant::now() + Duration::from_secs(period_seconds),
|
|
295
|
-
requests_limit: requests,
|
|
296
|
-
period: Duration::from_secs(period_seconds),
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
fn try_acquire(&mut self) -> bool {
|
|
301
|
-
let now = Instant::now();
|
|
302
|
-
|
|
303
|
-
// Check if we need to reset the counter
|
|
304
|
-
if now >= self.reset_time {
|
|
305
|
-
self.requests_remaining = self.requests_limit;
|
|
306
|
-
self.reset_time = now + self.period;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Try to consume a request
|
|
310
|
-
if self.requests_remaining > 0 {
|
|
311
|
-
self.requests_remaining -= 1;
|
|
312
|
-
true
|
|
313
|
-
} else {
|
|
314
|
-
false
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// In-flight requests counter
|
|
320
|
-
struct InFlightCounter {
|
|
321
|
-
count: usize,
|
|
322
|
-
max: usize,
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
impl InFlightCounter {
|
|
326
|
-
fn new(max: usize) -> Self {
|
|
327
|
-
Self { count: 0, max }
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
fn try_acquire(&mut self) -> bool {
|
|
331
|
-
if self.count < self.max {
|
|
332
|
-
self.count += 1;
|
|
333
|
-
true
|
|
334
|
-
} else {
|
|
335
|
-
false
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
fn release(&mut self) {
|
|
340
|
-
self.count = self.count.saturating_sub(1);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
47
|
// State shared across all request handlers
|
|
345
48
|
pub struct ProxyState {
|
|
346
49
|
config: rivet_config::Config,
|
|
347
50
|
routing_fn: RoutingFn,
|
|
348
51
|
cache_key_fn: CacheKeyFn,
|
|
349
|
-
|
|
52
|
+
// NOTE: Using the hyper legacy client is the only option currently.
|
|
53
|
+
// This is what reqwest uses under the hood. Eventually we'll migrate to h3 once it's ready.
|
|
54
|
+
client: Client<
|
|
55
|
+
hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>,
|
|
56
|
+
Full<Bytes>,
|
|
57
|
+
>,
|
|
350
58
|
route_cache: RouteCache,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
59
|
+
// We use moka::Cache instead of scc::HashMap because it automatically handles TTL and capacity
|
|
60
|
+
rate_limiters: Cache<std::net::IpAddr, Arc<Mutex<RateLimiter>>>,
|
|
61
|
+
in_flight_counters: Cache<std::net::IpAddr, Arc<Mutex<InFlightCounter>>>,
|
|
62
|
+
in_flight_requests: Cache<protocol::RequestId, ()>,
|
|
63
|
+
|
|
356
64
|
tasks: Arc<TaskGroup>,
|
|
357
65
|
}
|
|
358
66
|
|
|
@@ -361,15 +69,17 @@ impl ProxyState {
|
|
|
361
69
|
config: rivet_config::Config,
|
|
362
70
|
routing_fn: RoutingFn,
|
|
363
71
|
cache_key_fn: CacheKeyFn,
|
|
364
|
-
middleware_fn: MiddlewareFn,
|
|
365
|
-
port_type: PortType,
|
|
366
|
-
clickhouse_inserter: Option<clickhouse_inserter::ClickHouseInserterHandle>,
|
|
367
72
|
) -> Self {
|
|
73
|
+
let https_connector = hyper_tls::HttpsConnector::new();
|
|
74
|
+
let client = Client::builder(TokioExecutor::new())
|
|
75
|
+
.pool_idle_timeout(Duration::from_secs(30))
|
|
76
|
+
.build(https_connector);
|
|
77
|
+
|
|
368
78
|
Self {
|
|
369
79
|
config,
|
|
370
80
|
routing_fn,
|
|
371
81
|
cache_key_fn,
|
|
372
|
-
|
|
82
|
+
client,
|
|
373
83
|
route_cache: RouteCache::new(),
|
|
374
84
|
rate_limiters: Cache::builder()
|
|
375
85
|
.max_capacity(10_000)
|
|
@@ -379,119 +89,98 @@ impl ProxyState {
|
|
|
379
89
|
.max_capacity(10_000)
|
|
380
90
|
.time_to_live(PROXY_STATE_CACHE_TTL)
|
|
381
91
|
.build(),
|
|
382
|
-
in_flight_requests:
|
|
383
|
-
port_type,
|
|
384
|
-
clickhouse_inserter,
|
|
92
|
+
in_flight_requests: Cache::builder().max_capacity(10_000_000).build(),
|
|
385
93
|
tasks: TaskGroup::new(),
|
|
386
94
|
}
|
|
387
95
|
}
|
|
388
96
|
|
|
389
|
-
#[tracing::instrument(
|
|
97
|
+
#[tracing::instrument(skip_all)]
|
|
390
98
|
async fn resolve_route(
|
|
391
99
|
&self,
|
|
392
|
-
|
|
393
|
-
path: &str,
|
|
394
|
-
method: &hyper::Method,
|
|
395
|
-
port_type: PortType,
|
|
396
|
-
headers: &hyper::HeaderMap,
|
|
100
|
+
req_ctx: &mut RequestContext,
|
|
397
101
|
ignore_cache: bool,
|
|
398
102
|
) -> Result<ResolveRouteOutput> {
|
|
399
|
-
// Extract just the hostname, stripping the port if present
|
|
400
|
-
let hostname_only = hostname.split(':').next().unwrap_or(hostname);
|
|
401
|
-
|
|
402
103
|
tracing::debug!(
|
|
403
|
-
hostname = %
|
|
404
|
-
path = %path,
|
|
405
|
-
method = %method,
|
|
406
|
-
port_type = ?port_type,
|
|
104
|
+
hostname = %req_ctx.hostname,
|
|
105
|
+
path = %req_ctx.path,
|
|
106
|
+
method = %req_ctx.method,
|
|
407
107
|
"Resolving route for request"
|
|
408
108
|
);
|
|
409
109
|
|
|
410
|
-
let cache_key =
|
|
411
|
-
(self.cache_key_fn)(hostname_only, &path, method, port_type.clone(), headers)?;
|
|
110
|
+
let cache_key = (self.cache_key_fn)(req_ctx)?;
|
|
412
111
|
|
|
413
112
|
// Check cache first
|
|
414
|
-
if !ignore_cache {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
113
|
+
let cache_res = if !ignore_cache {
|
|
114
|
+
self.route_cache.get(&cache_key).await
|
|
115
|
+
} else {
|
|
116
|
+
None
|
|
117
|
+
};
|
|
422
118
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
119
|
+
let res = if let Some(res) = cache_res {
|
|
120
|
+
res
|
|
121
|
+
} else {
|
|
122
|
+
// Not in cache, call routing function with a default timeout
|
|
123
|
+
// Default 15 seconds, routing functions should have their own internal timeouts that are shorter
|
|
124
|
+
tracing::debug!(
|
|
125
|
+
hostname = %req_ctx.hostname,
|
|
126
|
+
path = %req_ctx.path,
|
|
127
|
+
cache_hit = false,
|
|
128
|
+
timeout_seconds = DEFAULT_ROUTE_TIMEOUT.as_secs(),
|
|
129
|
+
"Cache miss, calling routing function"
|
|
130
|
+
);
|
|
426
131
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
132
|
+
let routing_res = timeout(DEFAULT_ROUTE_TIMEOUT, (self.routing_fn)(req_ctx))
|
|
133
|
+
.await
|
|
134
|
+
.map_err(|_| {
|
|
135
|
+
errors::RequestTimeout {
|
|
136
|
+
timeout_seconds: DEFAULT_ROUTE_TIMEOUT.as_secs(),
|
|
137
|
+
}
|
|
138
|
+
.build()
|
|
139
|
+
})??;
|
|
434
140
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
errors::RequestTimeout {
|
|
442
|
-
timeout_seconds: default_timeout.as_secs(),
|
|
443
|
-
}
|
|
444
|
-
.build()
|
|
445
|
-
})?;
|
|
141
|
+
// TODO: Disable route caching for now, determine edge cases with gateway
|
|
142
|
+
// // Cache the result
|
|
143
|
+
// self.route_cache
|
|
144
|
+
// .insert(cache_key, routing_res.clone())
|
|
145
|
+
// .await;
|
|
146
|
+
// tracing::debug!("Added route to cache");
|
|
446
147
|
|
|
447
|
-
|
|
148
|
+
routing_res
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
match res {
|
|
448
152
|
RoutingOutput::Route(result) => {
|
|
449
153
|
tracing::debug!(
|
|
450
|
-
hostname = %
|
|
451
|
-
path = %path,
|
|
154
|
+
hostname = %req_ctx.hostname,
|
|
155
|
+
path = %req_ctx.path,
|
|
452
156
|
targets_count = result.targets.len(),
|
|
453
|
-
timeout_secs = result.timeout.routing_timeout,
|
|
454
157
|
"Received routing result"
|
|
455
158
|
);
|
|
456
159
|
|
|
457
|
-
// Cache the result
|
|
458
|
-
self.route_cache.insert(cache_key, result.clone()).await;
|
|
459
|
-
tracing::debug!("Added route to cache");
|
|
460
|
-
|
|
461
160
|
// Choose a random target
|
|
462
161
|
if let Some(target) = choose_random_target(&result.targets) {
|
|
463
162
|
tracing::debug!(
|
|
464
|
-
hostname = %
|
|
465
|
-
path = %path,
|
|
163
|
+
hostname = %req_ctx.hostname,
|
|
164
|
+
path = %req_ctx.path,
|
|
466
165
|
target_host = %target.host,
|
|
467
166
|
target_port = target.port,
|
|
468
167
|
target_path = %target.path,
|
|
469
|
-
actor_id = ?target.actor_id,
|
|
470
168
|
"Selected target for request"
|
|
471
169
|
);
|
|
472
170
|
Ok(ResolveRouteOutput::Target(target.clone()))
|
|
473
171
|
} else {
|
|
474
172
|
tracing::warn!(
|
|
475
|
-
hostname = %
|
|
476
|
-
path = %path,
|
|
173
|
+
hostname = %req_ctx.hostname,
|
|
174
|
+
path = %req_ctx.path,
|
|
477
175
|
"No route targets available from result"
|
|
478
176
|
);
|
|
479
177
|
Err(errors::NoRouteTargets.build())
|
|
480
178
|
}
|
|
481
179
|
}
|
|
482
|
-
RoutingOutput::Response(response) => {
|
|
483
|
-
tracing::debug!(
|
|
484
|
-
hostname = %hostname_only,
|
|
485
|
-
path = %path,
|
|
486
|
-
status = ?response.status,
|
|
487
|
-
"Routing returned custom response"
|
|
488
|
-
);
|
|
489
|
-
Ok(ResolveRouteOutput::Response(response))
|
|
490
|
-
}
|
|
491
180
|
RoutingOutput::CustomServe(handler) => {
|
|
492
181
|
tracing::debug!(
|
|
493
|
-
hostname = %
|
|
494
|
-
path = %path,
|
|
182
|
+
hostname = %req_ctx.hostname,
|
|
183
|
+
path = %req_ctx.path,
|
|
495
184
|
"Routing returned custom serve handler"
|
|
496
185
|
);
|
|
497
186
|
Ok(ResolveRouteOutput::CustomServe(handler))
|
|
@@ -499,183 +188,109 @@ impl ProxyState {
|
|
|
499
188
|
}
|
|
500
189
|
}
|
|
501
190
|
|
|
191
|
+
/// Returns true if the rate limit was hit.
|
|
502
192
|
#[tracing::instrument(skip_all)]
|
|
503
|
-
async fn
|
|
504
|
-
&self,
|
|
505
|
-
actor_id: &Id,
|
|
506
|
-
headers: &hyper::HeaderMap,
|
|
507
|
-
) -> Result<MiddlewareConfig> {
|
|
508
|
-
// Call the middleware function with a timeout
|
|
509
|
-
let default_timeout = Duration::from_secs(5); // Default 5 seconds
|
|
510
|
-
|
|
511
|
-
let middleware_result =
|
|
512
|
-
timeout(default_timeout, (self.middleware_fn)(actor_id, headers)).await;
|
|
513
|
-
|
|
514
|
-
match middleware_result {
|
|
515
|
-
Ok(result) => match result? {
|
|
516
|
-
MiddlewareResponse::Ok(config) => Ok(config),
|
|
517
|
-
MiddlewareResponse::NotFound => {
|
|
518
|
-
// Default values if middleware not found for this actor
|
|
519
|
-
Ok(MiddlewareConfig {
|
|
520
|
-
rate_limit: RateLimitConfig {
|
|
521
|
-
requests: 100, // 100 requests
|
|
522
|
-
period: 60, // per 60 seconds
|
|
523
|
-
},
|
|
524
|
-
max_in_flight: MaxInFlightConfig {
|
|
525
|
-
amount: 20, // 20 concurrent requests
|
|
526
|
-
},
|
|
527
|
-
retry: RetryConfig {
|
|
528
|
-
max_attempts: 3, // 3 retry attempts
|
|
529
|
-
initial_interval: 100, // 100ms initial interval
|
|
530
|
-
},
|
|
531
|
-
timeout: TimeoutConfig {
|
|
532
|
-
request_timeout: 30, // 30 seconds for requests
|
|
533
|
-
},
|
|
534
|
-
})
|
|
535
|
-
}
|
|
536
|
-
},
|
|
537
|
-
Err(_) => {
|
|
538
|
-
// Default values if middleware times out
|
|
539
|
-
Ok(MiddlewareConfig {
|
|
540
|
-
rate_limit: RateLimitConfig {
|
|
541
|
-
requests: 100, // 100 requests
|
|
542
|
-
period: 60, // per 60 seconds
|
|
543
|
-
},
|
|
544
|
-
max_in_flight: MaxInFlightConfig {
|
|
545
|
-
amount: 20, // 20 concurrent requests
|
|
546
|
-
},
|
|
547
|
-
retry: RetryConfig {
|
|
548
|
-
max_attempts: 3, // 3 retry attempts
|
|
549
|
-
initial_interval: 100, // 100ms initial interval
|
|
550
|
-
},
|
|
551
|
-
timeout: TimeoutConfig {
|
|
552
|
-
request_timeout: 30, // 30 seconds for requests
|
|
553
|
-
},
|
|
554
|
-
})
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
#[tracing::instrument(skip_all)]
|
|
560
|
-
async fn check_rate_limit(
|
|
561
|
-
&self,
|
|
562
|
-
ip_addr: std::net::IpAddr,
|
|
563
|
-
actor_id: &Option<Id>,
|
|
564
|
-
headers: &hyper::HeaderMap,
|
|
565
|
-
) -> Result<bool> {
|
|
566
|
-
let Some(actor_id) = *actor_id else {
|
|
567
|
-
// No rate limiting when actor_id is None
|
|
568
|
-
return Ok(true);
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
// Get actor-specific middleware config
|
|
572
|
-
let middleware_config = self.get_middleware_config(&actor_id, headers).await?;
|
|
573
|
-
|
|
574
|
-
let cache_key = (actor_id, ip_addr);
|
|
575
|
-
|
|
193
|
+
async fn check_rate_limit(&self, req_ctx: &RequestContext) -> Result<bool> {
|
|
576
194
|
// Get existing limiter or create a new one
|
|
577
|
-
let limiter_arc =
|
|
578
|
-
existing_limiter
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
195
|
+
let limiter_arc =
|
|
196
|
+
if let Some(existing_limiter) = self.rate_limiters.get(&req_ctx.client_ip).await {
|
|
197
|
+
existing_limiter
|
|
198
|
+
} else {
|
|
199
|
+
let new_limiter = Arc::new(Mutex::new(RateLimiter::new(
|
|
200
|
+
req_ctx.rate_limit.requests,
|
|
201
|
+
req_ctx.rate_limit.period,
|
|
202
|
+
)));
|
|
203
|
+
self.rate_limiters
|
|
204
|
+
.insert(req_ctx.client_ip, new_limiter.clone())
|
|
205
|
+
.await;
|
|
206
|
+
metrics::RATE_LIMITER_COUNT.set(self.rate_limiters.entry_count() as i64);
|
|
207
|
+
new_limiter
|
|
208
|
+
};
|
|
590
209
|
|
|
591
210
|
// Try to acquire from the limiter
|
|
592
|
-
let
|
|
211
|
+
let acquired = {
|
|
593
212
|
let mut limiter = limiter_arc.lock().await;
|
|
594
213
|
limiter.try_acquire()
|
|
595
214
|
};
|
|
596
215
|
|
|
597
|
-
Ok(
|
|
216
|
+
Ok(!acquired)
|
|
598
217
|
}
|
|
599
218
|
|
|
219
|
+
/// Returns true if the counter could not be acquired.
|
|
600
220
|
#[tracing::instrument(skip_all)]
|
|
601
|
-
async fn acquire_in_flight(
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
// Check in-flight limit if actor_id is present
|
|
608
|
-
if let Some(actor_id) = *actor_id {
|
|
609
|
-
// Get actor-specific middleware config
|
|
610
|
-
let middleware_config = self.get_middleware_config(&actor_id, headers).await?;
|
|
611
|
-
|
|
612
|
-
let cache_key = (actor_id, ip_addr);
|
|
613
|
-
|
|
614
|
-
// Get existing counter or create a new one
|
|
615
|
-
let counter_arc = if let Some(existing_counter) =
|
|
616
|
-
self.in_flight_counters.get(&cache_key).await
|
|
617
|
-
{
|
|
221
|
+
async fn acquire_in_flight(&self, req_ctx: &mut RequestContext) -> Result<bool> {
|
|
222
|
+
let cache_key = req_ctx.client_ip;
|
|
223
|
+
|
|
224
|
+
// Get existing counter or create a new one
|
|
225
|
+
let counter_arc =
|
|
226
|
+
if let Some(existing_counter) = self.in_flight_counters.get(&cache_key).await {
|
|
618
227
|
existing_counter
|
|
619
228
|
} else {
|
|
620
229
|
let new_counter = Arc::new(Mutex::new(InFlightCounter::new(
|
|
621
|
-
|
|
230
|
+
req_ctx.max_in_flight.amount,
|
|
622
231
|
)));
|
|
623
232
|
self.in_flight_counters
|
|
624
233
|
.insert(cache_key, new_counter.clone())
|
|
625
234
|
.await;
|
|
626
|
-
metrics::IN_FLIGHT_COUNTER_COUNT.
|
|
235
|
+
metrics::IN_FLIGHT_COUNTER_COUNT.set(self.in_flight_counters.entry_count() as i64);
|
|
627
236
|
new_counter
|
|
628
237
|
};
|
|
629
238
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
239
|
+
// Try to acquire from the counter
|
|
240
|
+
let acquired = {
|
|
241
|
+
let mut counter = counter_arc.lock().await;
|
|
242
|
+
counter.try_acquire()
|
|
243
|
+
};
|
|
635
244
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
245
|
+
if !acquired {
|
|
246
|
+
return Ok(true); // Rate limited
|
|
639
247
|
}
|
|
640
248
|
|
|
641
249
|
// Generate unique request ID
|
|
642
|
-
|
|
643
|
-
|
|
250
|
+
req_ctx.in_flight_request_id = Some(self.generate_unique_in_flight_request_id().await?);
|
|
251
|
+
|
|
252
|
+
Ok(false)
|
|
644
253
|
}
|
|
645
254
|
|
|
646
255
|
#[tracing::instrument(skip_all)]
|
|
647
256
|
async fn release_in_flight(
|
|
648
257
|
&self,
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
request_id: protocol::RequestId,
|
|
258
|
+
client_ip: IpAddr,
|
|
259
|
+
in_flight_request_id: Option<protocol::RequestId>,
|
|
652
260
|
) {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if let Some(counter_arc) = self.in_flight_counters.get(&cache_key).await {
|
|
657
|
-
let mut counter = counter_arc.lock().await;
|
|
658
|
-
counter.release();
|
|
659
|
-
}
|
|
261
|
+
if let Some(counter_arc) = self.in_flight_counters.get(&client_ip).await {
|
|
262
|
+
let mut counter = counter_arc.lock().await;
|
|
263
|
+
counter.release();
|
|
660
264
|
}
|
|
661
265
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
266
|
+
if let Some(in_flight_request_id) = in_flight_request_id {
|
|
267
|
+
// Release request ID
|
|
268
|
+
self.in_flight_requests
|
|
269
|
+
.invalidate(&in_flight_request_id)
|
|
270
|
+
.await;
|
|
271
|
+
metrics::IN_FLIGHT_REQUEST_COUNT.set(self.in_flight_requests.entry_count() as i64);
|
|
272
|
+
}
|
|
665
273
|
}
|
|
666
274
|
|
|
667
275
|
/// Generate a unique request ID that is not currently in flight
|
|
668
|
-
async fn
|
|
276
|
+
async fn generate_unique_in_flight_request_id(&self) -> Result<protocol::RequestId> {
|
|
669
277
|
const MAX_TRIES: u32 = 100;
|
|
670
|
-
let mut requests = self.in_flight_requests.lock().await;
|
|
671
278
|
|
|
672
279
|
for attempt in 0..MAX_TRIES {
|
|
673
280
|
let request_id = protocol::util::generate_request_id();
|
|
281
|
+
let mut inserted = false;
|
|
674
282
|
|
|
675
283
|
// Check if this ID is already in use
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
284
|
+
self.in_flight_requests
|
|
285
|
+
.entry(request_id)
|
|
286
|
+
.or_insert_with(async {
|
|
287
|
+
inserted = true;
|
|
288
|
+
})
|
|
289
|
+
.await;
|
|
290
|
+
|
|
291
|
+
if inserted {
|
|
292
|
+
metrics::IN_FLIGHT_REQUEST_COUNT.set(self.in_flight_requests.entry_count() as i64);
|
|
293
|
+
|
|
679
294
|
return Ok(request_id);
|
|
680
295
|
}
|
|
681
296
|
|
|
@@ -697,91 +312,66 @@ impl ProxyState {
|
|
|
697
312
|
|
|
698
313
|
// Helper function to choose a random target from a list of targets
|
|
699
314
|
fn choose_random_target(targets: &[RouteTarget]) -> Option<&RouteTarget> {
|
|
700
|
-
|
|
701
|
-
return None;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// Use a simple random index selection
|
|
705
|
-
let random_index = rand::random::<usize>() % targets.len();
|
|
706
|
-
targets.get(random_index)
|
|
315
|
+
targets.choose(&mut rand::thread_rng())
|
|
707
316
|
}
|
|
708
317
|
|
|
709
318
|
// Proxy service
|
|
710
319
|
pub struct ProxyService {
|
|
711
320
|
state: Arc<ProxyState>,
|
|
712
321
|
remote_addr: SocketAddr,
|
|
713
|
-
|
|
714
|
-
// This is what reqwest uses under the hood. Eventually we'll migrate to h3 once it's ready.
|
|
715
|
-
client: Client<hyper_util::client::legacy::connect::HttpConnector, Full<Bytes>>,
|
|
322
|
+
connection_start: Instant,
|
|
716
323
|
}
|
|
717
324
|
|
|
718
325
|
impl ProxyService {
|
|
719
326
|
pub fn new(state: Arc<ProxyState>, remote_addr: SocketAddr) -> Self {
|
|
720
|
-
// Create a client with the hyper-util legacy client
|
|
721
|
-
let client = Client::builder(TokioExecutor::new())
|
|
722
|
-
.pool_idle_timeout(Duration::from_secs(30))
|
|
723
|
-
.build_http();
|
|
724
|
-
|
|
725
327
|
Self {
|
|
726
328
|
state,
|
|
727
329
|
remote_addr,
|
|
728
|
-
|
|
330
|
+
connection_start: Instant::now(),
|
|
729
331
|
}
|
|
730
332
|
}
|
|
731
333
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
334
|
+
/// Process an individual request.
|
|
335
|
+
#[tracing::instrument(name = "guard_request", skip_all, fields(ray_id, req_id, uri=%req.uri()))]
|
|
336
|
+
pub async fn process(&self, mut req: Request<BodyIncoming>) -> Result<Response<ResponseBody>> {
|
|
337
|
+
let start_time = Instant::now();
|
|
736
338
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
339
|
+
let request_ids = RequestIds::new(self.state.config.dc_label());
|
|
340
|
+
req.extensions_mut().insert(request_ids);
|
|
341
|
+
|
|
342
|
+
let current_span = tracing::Span::current();
|
|
343
|
+
|
|
344
|
+
current_span.record("req_id", request_ids.req_id.to_string());
|
|
345
|
+
current_span.record("ray_id", request_ids.ray_id.to_string());
|
|
346
|
+
|
|
347
|
+
// Extract request information for logging and analytics before consuming the request
|
|
348
|
+
let incoming_ray_id = req
|
|
349
|
+
.headers()
|
|
350
|
+
.get(X_RIVET_RAY_ID)
|
|
351
|
+
.and_then(|h| h.to_str().ok())
|
|
352
|
+
.and_then(|id| Id::parse(id).ok());
|
|
744
353
|
let host = req
|
|
745
354
|
.headers()
|
|
746
355
|
.get(hyper::header::HOST)
|
|
747
356
|
.and_then(|h| h.to_str().ok())
|
|
748
|
-
.unwrap_or("unknown")
|
|
749
|
-
|
|
357
|
+
.unwrap_or("unknown")
|
|
358
|
+
.to_string();
|
|
359
|
+
let uri_string = req.uri().to_string();
|
|
750
360
|
let path = req
|
|
751
361
|
.uri()
|
|
752
362
|
.path_and_query()
|
|
753
363
|
.map(|x| x.to_string())
|
|
754
364
|
.unwrap_or_else(|| req.uri().path().to_string());
|
|
365
|
+
let method = req.method().clone();
|
|
755
366
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
let target_res = self
|
|
759
|
-
.state
|
|
760
|
-
.resolve_route(
|
|
761
|
-
host,
|
|
762
|
-
&path,
|
|
763
|
-
req.method(),
|
|
764
|
-
self.state.port_type.clone(),
|
|
765
|
-
req.headers(),
|
|
766
|
-
false,
|
|
767
|
-
)
|
|
768
|
-
.await;
|
|
769
|
-
|
|
770
|
-
let duration_secs = start_time.elapsed().as_secs_f64();
|
|
771
|
-
metrics::RESOLVE_ROUTE_DURATION.record(duration_secs, &[]);
|
|
772
|
-
|
|
773
|
-
// Resolve target
|
|
774
|
-
let target = target_res?;
|
|
775
|
-
if let ResolveRouteOutput::Response(response) = &target {
|
|
776
|
-
// Return the custom response
|
|
777
|
-
return response.build_response();
|
|
778
|
-
}
|
|
367
|
+
current_span.set_attribute("http.request.method", method.to_string());
|
|
368
|
+
current_span.set_attribute("http.path", uri_string.clone());
|
|
779
369
|
|
|
780
|
-
let
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
370
|
+
let user_agent = req
|
|
371
|
+
.headers()
|
|
372
|
+
.get(hyper::header::USER_AGENT)
|
|
373
|
+
.and_then(|h| h.to_str().ok())
|
|
374
|
+
.map(|s| s.to_string());
|
|
785
375
|
|
|
786
376
|
// Extract IP address from X-Forwarded-For header or fall back to remote_addr
|
|
787
377
|
let client_ip = req
|
|
@@ -795,74 +385,327 @@ impl ProxyService {
|
|
|
795
385
|
.and_then(|ip_str| ip_str.parse::<std::net::IpAddr>().ok())
|
|
796
386
|
.unwrap_or_else(|| self.remote_addr.ip());
|
|
797
387
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
.
|
|
801
|
-
.
|
|
802
|
-
.
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
388
|
+
let is_websocket = hyper_tungstenite::is_upgrade_request(&req);
|
|
389
|
+
let mut req_ctx = RequestContext::new(
|
|
390
|
+
self.remote_addr,
|
|
391
|
+
request_ids.ray_id,
|
|
392
|
+
request_ids.req_id,
|
|
393
|
+
host,
|
|
394
|
+
path,
|
|
395
|
+
req.method().clone(),
|
|
396
|
+
req.headers().clone(),
|
|
397
|
+
is_websocket,
|
|
398
|
+
client_ip,
|
|
399
|
+
start_time,
|
|
400
|
+
);
|
|
812
401
|
|
|
813
|
-
//
|
|
814
|
-
|
|
815
|
-
.state
|
|
816
|
-
.acquire_in_flight(client_ip, &actor_id, req.headers())
|
|
817
|
-
.await?
|
|
818
|
-
{
|
|
819
|
-
Some(id) => id,
|
|
820
|
-
None => {
|
|
821
|
-
return Err(errors::RateLimit {
|
|
822
|
-
actor_id,
|
|
823
|
-
method: req.method().to_string(),
|
|
824
|
-
path: path.clone(),
|
|
825
|
-
ip: client_ip.to_string(),
|
|
826
|
-
}
|
|
827
|
-
.build());
|
|
828
|
-
}
|
|
829
|
-
};
|
|
402
|
+
// TLS information would be set here if available (for HTTPS connections)
|
|
403
|
+
// This requires TLS connection introspection and is marked for future enhancement
|
|
830
404
|
|
|
831
|
-
//
|
|
832
|
-
|
|
833
|
-
|
|
405
|
+
// Debug log request information with structured fields (Apache-like access log)
|
|
406
|
+
tracing::debug!(
|
|
407
|
+
?incoming_ray_id,
|
|
408
|
+
ray_id=?req_ctx.ray_id,
|
|
409
|
+
req_id=?req_ctx.req_id,
|
|
410
|
+
method=%req_ctx.method,
|
|
411
|
+
path=%req_ctx.path,
|
|
412
|
+
host=%req_ctx.host,
|
|
413
|
+
remote_addr=%req_ctx.remote_addr,
|
|
414
|
+
uri=%uri_string,
|
|
415
|
+
user_agent=?user_agent,
|
|
416
|
+
"Request received"
|
|
417
|
+
);
|
|
834
418
|
|
|
835
|
-
//
|
|
836
|
-
|
|
837
|
-
|
|
419
|
+
// Used for ws error proxying later
|
|
420
|
+
let mut mock_req_builder = Request::builder()
|
|
421
|
+
.method(req.method().clone())
|
|
422
|
+
.uri(req.uri().clone())
|
|
423
|
+
.version(req.version().clone());
|
|
424
|
+
if let Some(headers) = mock_req_builder.headers_mut() {
|
|
425
|
+
*headers = req.headers().clone();
|
|
838
426
|
}
|
|
427
|
+
if let Some(extensions) = mock_req_builder.extensions_mut() {
|
|
428
|
+
*extensions = req.extensions().clone();
|
|
429
|
+
}
|
|
430
|
+
let mock_req = mock_req_builder.body(())?;
|
|
839
431
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
};
|
|
847
|
-
|
|
848
|
-
let status = match &res {
|
|
849
|
-
Ok(resp) => resp.status().as_u16().to_string(),
|
|
850
|
-
Err(_) => "error".to_string(),
|
|
851
|
-
};
|
|
852
|
-
|
|
853
|
-
// Record metrics
|
|
854
|
-
let duration_secs = start_time.elapsed().as_secs_f64();
|
|
855
|
-
metrics::PROXY_REQUEST_DURATION
|
|
856
|
-
.record(duration_secs, &[KeyValue::new("status", status.clone())]);
|
|
432
|
+
// Process the request
|
|
433
|
+
let mut res = match self.handle_request(req, &mut req_ctx).await {
|
|
434
|
+
Ok(res) => res,
|
|
435
|
+
Err(err) => {
|
|
436
|
+
// Log the error
|
|
437
|
+
tracing::error!(?err, "Request failed");
|
|
857
438
|
|
|
858
|
-
|
|
439
|
+
metrics::PROXY_REQUEST_ERROR_TOTAL
|
|
440
|
+
.with_label_values(&[&err.to_string()])
|
|
441
|
+
.inc();
|
|
859
442
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
443
|
+
// If we receive an error during a websocket request, we attempt to open the websocket anyway
|
|
444
|
+
// so we can send the error via websocket instead of http. Most websocket clients don't handle
|
|
445
|
+
// HTTP errors in a meaningful way resulting in unhelpful errors for the user
|
|
446
|
+
if is_websocket {
|
|
447
|
+
tracing::debug!("Upgrading client connection to WebSocket for error proxy");
|
|
448
|
+
match hyper_tungstenite::upgrade(mock_req, None) {
|
|
449
|
+
Ok((client_response, client_ws)) => {
|
|
450
|
+
tracing::debug!("Client WebSocket upgrade for error proxy successful");
|
|
451
|
+
|
|
452
|
+
self.state.tasks.spawn(
|
|
453
|
+
async move {
|
|
454
|
+
let ws_handle = match WebSocketHandle::new(client_ws).await {
|
|
455
|
+
Ok(ws_handle) => ws_handle,
|
|
456
|
+
Err(err) => {
|
|
457
|
+
tracing::debug!(
|
|
458
|
+
?err,
|
|
459
|
+
"failed initiating websocket handle for error proxy"
|
|
460
|
+
);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
let frame = utils::err_to_close_frame(err, request_ids.ray_id);
|
|
465
|
+
|
|
466
|
+
// Manual conversion to handle different tungstenite versions
|
|
467
|
+
let code_num: u16 = frame.code.into();
|
|
468
|
+
let reason = frame.reason.clone();
|
|
469
|
+
|
|
470
|
+
if let Err(err) = ws_handle
|
|
471
|
+
.send(tokio_tungstenite::tungstenite::Message::Close(Some(
|
|
472
|
+
tokio_tungstenite::tungstenite::protocol::CloseFrame {
|
|
473
|
+
code: code_num.into(),
|
|
474
|
+
reason,
|
|
475
|
+
},
|
|
476
|
+
)))
|
|
477
|
+
.await
|
|
478
|
+
{
|
|
479
|
+
tracing::debug!(
|
|
480
|
+
?err,
|
|
481
|
+
"failed sending websocket error proxy"
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Flush to ensure close frame is sent
|
|
486
|
+
if let Err(err) = ws_handle.flush().await {
|
|
487
|
+
tracing::debug!(
|
|
488
|
+
?err,
|
|
489
|
+
"failed flushing websocket in error proxy"
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Keep TCP connection open briefly to allow client to process close
|
|
494
|
+
tokio::time::sleep(WEBSOCKET_CLOSE_LINGER).await;
|
|
495
|
+
}
|
|
496
|
+
.instrument(tracing::info_span!("ws_error_proxy_task")),
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
// Return the response that will upgrade the client connection
|
|
500
|
+
// For proper WebSocket handshaking, we need to preserve the original response
|
|
501
|
+
// structure but convert it to our expected return type without modifying its content
|
|
502
|
+
tracing::debug!(
|
|
503
|
+
"Returning WebSocket upgrade response for error proxy to client"
|
|
504
|
+
);
|
|
505
|
+
// Extract the parts from the response but preserve all headers and status
|
|
506
|
+
let (mut parts, _) = client_response.into_parts();
|
|
507
|
+
|
|
508
|
+
// Add Sec-WebSocket-Protocol header to the response
|
|
509
|
+
// Many WebSocket clients (e.g. node-ws & Cloudflare) require a protocol in the response
|
|
510
|
+
parts.headers.insert(
|
|
511
|
+
"sec-websocket-protocol",
|
|
512
|
+
hyper::header::HeaderValue::from_static("rivet"),
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
// Create a new response with an empty body - WebSocket upgrades don't need a body
|
|
516
|
+
Response::from_parts(
|
|
517
|
+
parts,
|
|
518
|
+
ResponseBody::Full(Full::<Bytes>::new(Bytes::new())),
|
|
519
|
+
)
|
|
520
|
+
}
|
|
521
|
+
Err(err) => {
|
|
522
|
+
tracing::error!(
|
|
523
|
+
?err,
|
|
524
|
+
"Failed to upgrade client WebSocket for error proxy"
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
utils::err_into_response(
|
|
528
|
+
errors::ConnectionError {
|
|
529
|
+
error_message: format!(
|
|
530
|
+
"Failed to upgrade client WebSocket for error proxy: {}",
|
|
531
|
+
err
|
|
532
|
+
),
|
|
533
|
+
remote_addr: req_ctx.remote_addr.to_string(),
|
|
534
|
+
}
|
|
535
|
+
.build(),
|
|
536
|
+
)?
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
} else {
|
|
540
|
+
utils::err_into_response(err)?
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
if is_websocket && res.status() != StatusCode::SWITCHING_PROTOCOLS {
|
|
546
|
+
tracing::debug!("returned non-101 response to websocket");
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Add ray_id to response headers
|
|
550
|
+
if let Ok(ray_id_value) = request_ids.ray_id.to_string().parse() {
|
|
551
|
+
if let Some(existing_ray_id_value) = res
|
|
552
|
+
.headers()
|
|
553
|
+
.get(X_RIVET_RAY_ID)
|
|
554
|
+
.and_then(|h| h.to_str().ok())
|
|
555
|
+
{
|
|
556
|
+
if ray_id_value != existing_ray_id_value {
|
|
557
|
+
tracing::warn!(
|
|
558
|
+
expected_ray_id=%request_ids.ray_id,
|
|
559
|
+
received_ray_id=%existing_ray_id_value,
|
|
560
|
+
"downstream service set ray id header to a different value",
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
res.headers_mut().insert(X_RIVET_RAY_ID, ray_id_value);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Add cors headers to response
|
|
569
|
+
if let Some(cors) = &req_ctx.cors {
|
|
570
|
+
let headers = res.headers_mut();
|
|
571
|
+
|
|
572
|
+
headers.insert(
|
|
573
|
+
"access-control-allow-origin",
|
|
574
|
+
HeaderValue::from_str(&cors.allow_origin)?,
|
|
575
|
+
);
|
|
576
|
+
headers.insert(
|
|
577
|
+
"access-control-allow-credentials",
|
|
578
|
+
HeaderValue::from_static(if cors.allow_credentials {
|
|
579
|
+
"true"
|
|
580
|
+
} else {
|
|
581
|
+
"false"
|
|
582
|
+
}),
|
|
583
|
+
);
|
|
584
|
+
headers.insert(
|
|
585
|
+
"access-control-expose-headers",
|
|
586
|
+
HeaderValue::from_str(&cors.expose_headers)?,
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
if let Some(allow_methods) = &cors.allow_methods {
|
|
590
|
+
headers.insert(
|
|
591
|
+
"access-control-allow-methods",
|
|
592
|
+
HeaderValue::from_str(allow_methods)?,
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if let Some(allow_headers) = &cors.allow_headers {
|
|
597
|
+
headers.insert(
|
|
598
|
+
"access-control-allow-headers",
|
|
599
|
+
HeaderValue::from_str(allow_headers)?,
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if let Some(max_age) = &cors.max_age {
|
|
604
|
+
headers.insert(
|
|
605
|
+
"access-control-max-age",
|
|
606
|
+
HeaderValue::from_str(&max_age.to_string())?,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Add Vary header to prevent cache poisoning when echoing origin
|
|
611
|
+
if cors.allow_origin != "*" {
|
|
612
|
+
headers.insert("vary", HeaderValue::from_static("Origin"));
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Set span status code
|
|
617
|
+
let status = res.status().as_u16();
|
|
618
|
+
current_span.set_attribute("http.response.status_code", status as i64);
|
|
619
|
+
|
|
620
|
+
let content_length = res
|
|
621
|
+
.headers()
|
|
622
|
+
.get(hyper::header::CONTENT_LENGTH)
|
|
623
|
+
.and_then(|h| h.to_str().ok())
|
|
624
|
+
.and_then(|s| s.parse::<usize>().ok())
|
|
625
|
+
.unwrap_or(0);
|
|
626
|
+
|
|
627
|
+
// Log information about the completed request
|
|
628
|
+
tracing::debug!(
|
|
629
|
+
?incoming_ray_id,
|
|
630
|
+
ray_id=?req_ctx.ray_id,
|
|
631
|
+
req_id=?req_ctx.req_id,
|
|
632
|
+
method = %req_ctx.method,
|
|
633
|
+
path = %req_ctx.path,
|
|
634
|
+
host = %req_ctx.host,
|
|
635
|
+
remote_addr = %req_ctx.remote_addr,
|
|
636
|
+
status = %status,
|
|
637
|
+
content_length = %content_length,
|
|
638
|
+
"Request completed"
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
Ok(res)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
#[tracing::instrument(skip_all)]
|
|
645
|
+
async fn handle_request(
|
|
646
|
+
&self,
|
|
647
|
+
req: Request<BodyIncoming>,
|
|
648
|
+
req_ctx: &mut RequestContext,
|
|
649
|
+
) -> Result<Response<ResponseBody>> {
|
|
650
|
+
// Resolve target
|
|
651
|
+
let target_res = self.state.resolve_route(req_ctx, false).await;
|
|
652
|
+
|
|
653
|
+
let duration_secs = req_ctx.start_time.elapsed().as_secs_f64();
|
|
654
|
+
metrics::RESOLVE_ROUTE_DURATION.observe(duration_secs);
|
|
655
|
+
|
|
656
|
+
let target = target_res?;
|
|
657
|
+
|
|
658
|
+
// Apply rate limiting
|
|
659
|
+
if self.state.check_rate_limit(req_ctx).await? {
|
|
660
|
+
return Err(errors::RateLimit {
|
|
661
|
+
method: req_ctx.method.to_string(),
|
|
662
|
+
path: req_ctx.path.clone(),
|
|
663
|
+
ip: req_ctx.client_ip.to_string(),
|
|
664
|
+
}
|
|
665
|
+
.build());
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Acquire in-flight limit and generate protocol request ID
|
|
669
|
+
if self.state.acquire_in_flight(req_ctx).await? {
|
|
670
|
+
return Err(errors::RateLimit {
|
|
671
|
+
method: req_ctx.method.to_string(),
|
|
672
|
+
path: req_ctx.path.clone(),
|
|
673
|
+
ip: req_ctx.client_ip.to_string(),
|
|
674
|
+
}
|
|
675
|
+
.build());
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Increment metrics
|
|
679
|
+
metrics::PROXY_REQUEST_PENDING.inc();
|
|
680
|
+
metrics::PROXY_REQUEST_TOTAL.inc();
|
|
681
|
+
|
|
682
|
+
let res = if hyper_tungstenite::is_upgrade_request(&req) {
|
|
683
|
+
self.handle_websocket_upgrade(req, req_ctx, target).await
|
|
684
|
+
} else {
|
|
685
|
+
self.handle_http_request(req, req_ctx, target).await
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
let status = match &res {
|
|
689
|
+
Ok(resp) => resp.status().as_u16().to_string(),
|
|
690
|
+
Err(_) => "error".to_string(),
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// Record metrics
|
|
694
|
+
let duration_secs = req_ctx.start_time.elapsed().as_secs_f64();
|
|
695
|
+
metrics::PROXY_REQUEST_DURATION
|
|
696
|
+
.with_label_values(&[status])
|
|
697
|
+
.observe(duration_secs);
|
|
698
|
+
|
|
699
|
+
metrics::PROXY_REQUEST_PENDING.dec();
|
|
700
|
+
|
|
701
|
+
// Release in-flight counter and request ID when done
|
|
702
|
+
let state_clone = self.state.clone();
|
|
703
|
+
let client_ip = req_ctx.client_ip;
|
|
704
|
+
let in_flight_request_id = req_ctx.in_flight_request_id;
|
|
705
|
+
tokio::spawn(
|
|
863
706
|
async move {
|
|
864
707
|
state_clone
|
|
865
|
-
.release_in_flight(client_ip,
|
|
708
|
+
.release_in_flight(client_ip, in_flight_request_id)
|
|
866
709
|
.await;
|
|
867
710
|
}
|
|
868
711
|
.instrument(tracing::info_span!("release_in_flight_task")),
|
|
@@ -875,95 +718,40 @@ impl ProxyService {
|
|
|
875
718
|
async fn handle_http_request(
|
|
876
719
|
&self,
|
|
877
720
|
req: Request<BodyIncoming>,
|
|
721
|
+
req_ctx: &mut RequestContext,
|
|
878
722
|
resolved_route: ResolveRouteOutput,
|
|
879
|
-
request_context: &mut RequestContext,
|
|
880
|
-
client_ip: std::net::IpAddr,
|
|
881
|
-
actor_id: Option<Id>,
|
|
882
723
|
) -> Result<Response<ResponseBody>> {
|
|
883
|
-
//
|
|
884
|
-
let
|
|
885
|
-
&& let Some(actor_id) = &target.actor_id
|
|
886
|
-
{
|
|
887
|
-
self.state
|
|
888
|
-
.get_middleware_config(actor_id, req.headers())
|
|
889
|
-
.await?
|
|
890
|
-
} else {
|
|
891
|
-
// Default middleware config for targets without actor_id
|
|
892
|
-
MiddlewareConfig {
|
|
893
|
-
rate_limit: RateLimitConfig {
|
|
894
|
-
requests: 100, // 100 requests
|
|
895
|
-
period: 60, // per 60 seconds
|
|
896
|
-
},
|
|
897
|
-
max_in_flight: MaxInFlightConfig {
|
|
898
|
-
amount: 20, // 20 concurrent requests
|
|
899
|
-
},
|
|
900
|
-
retry: RetryConfig {
|
|
901
|
-
max_attempts: 3, // 3 retry attempts
|
|
902
|
-
initial_interval: 100, // 100ms initial interval
|
|
903
|
-
},
|
|
904
|
-
timeout: TimeoutConfig {
|
|
905
|
-
request_timeout: 30, // 30 seconds for requests
|
|
906
|
-
},
|
|
907
|
-
}
|
|
908
|
-
};
|
|
909
|
-
|
|
910
|
-
let host = req
|
|
911
|
-
.headers()
|
|
912
|
-
.get(hyper::header::HOST)
|
|
913
|
-
.and_then(|h| h.to_str().ok())
|
|
914
|
-
.unwrap_or("unknown")
|
|
915
|
-
.to_string();
|
|
916
|
-
|
|
917
|
-
let path = req
|
|
918
|
-
.uri()
|
|
919
|
-
.path_and_query()
|
|
920
|
-
.map(|x| x.to_string())
|
|
921
|
-
.unwrap_or_else(|| req.uri().path().to_string());
|
|
922
|
-
|
|
923
|
-
// Set up retry with backoff from middleware config
|
|
924
|
-
let max_attempts = middleware_config.retry.max_attempts;
|
|
925
|
-
let initial_interval = middleware_config.retry.initial_interval;
|
|
926
|
-
let timeout_duration = Duration::from_secs(middleware_config.timeout.request_timeout);
|
|
724
|
+
// Set up retry with backoff
|
|
725
|
+
let timeout_duration = Duration::from_secs(req_ctx.timeout.request_timeout);
|
|
927
726
|
|
|
928
727
|
match resolved_route {
|
|
929
728
|
ResolveRouteOutput::Target(mut target) => {
|
|
930
|
-
// Set service IP from target
|
|
931
|
-
if let Ok(target_ip) =
|
|
932
|
-
format!("{}:{}", target.host, target.port).parse::<std::net::SocketAddr>()
|
|
933
|
-
{
|
|
934
|
-
request_context.service_ip = Some(target_ip.ip());
|
|
935
|
-
}
|
|
936
|
-
|
|
937
729
|
// Read the request body before proceeding with retries
|
|
938
730
|
let (req_parts, body) = req.into_parts();
|
|
939
|
-
let req_body =
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
};
|
|
946
|
-
|
|
947
|
-
// Set actual request body size in analytics
|
|
948
|
-
request_context.client_request_body_bytes = Some(req_body.len() as u64);
|
|
731
|
+
let req_body =
|
|
732
|
+
Limited::new(body, self.state.config.guard().http_max_request_body_size())
|
|
733
|
+
.collect()
|
|
734
|
+
.await
|
|
735
|
+
.map_err(|err| errors::InvalidRequestBody(err.to_string()).build())?
|
|
736
|
+
.to_bytes();
|
|
949
737
|
|
|
950
738
|
// Use a value-returning loop to handle both errors and successful responses
|
|
951
739
|
let mut attempts = 0;
|
|
952
|
-
while attempts < max_attempts {
|
|
740
|
+
while attempts < req_ctx.retry.max_attempts {
|
|
953
741
|
attempts += 1;
|
|
954
742
|
|
|
955
743
|
// Use the common function to build request parts
|
|
956
|
-
let builder =
|
|
957
|
-
.proxied_request_builder(&req_parts, &target)
|
|
744
|
+
let builder = utils::proxied_request_builder(&req_parts, req_ctx, &target)
|
|
958
745
|
.map_err(|err| errors::HttpRequestBuildFailed(err.to_string()).build())?;
|
|
959
746
|
|
|
960
747
|
// Create the final request with body
|
|
961
748
|
let proxied_req = builder
|
|
962
|
-
|
|
749
|
+
// NOTE: the `Bytes` type is cheaply cloneable, this is not resource intensive
|
|
750
|
+
.body(Full::new(req_body.clone()))
|
|
963
751
|
.map_err(|err| errors::RequestBuildError(err.to_string()).build())?;
|
|
964
752
|
|
|
965
753
|
// Send the request with timeout
|
|
966
|
-
let res = timeout(timeout_duration, self.client.request(proxied_req))
|
|
754
|
+
let res = timeout(timeout_duration, self.state.client.request(proxied_req))
|
|
967
755
|
.await
|
|
968
756
|
.map_err(|_| {
|
|
969
757
|
errors::RequestTimeout {
|
|
@@ -975,29 +763,23 @@ impl ProxyService {
|
|
|
975
763
|
match res {
|
|
976
764
|
Ok(resp) => {
|
|
977
765
|
// Check if this is a retryable response
|
|
978
|
-
if should_retry_request_inner(resp.status(), resp.headers()) {
|
|
766
|
+
if utils::should_retry_request_inner(resp.status(), resp.headers()) {
|
|
979
767
|
// Request connect error, might retry
|
|
980
768
|
tracing::debug!(
|
|
981
769
|
"Request attempt {attempts} failed (service unavailable)"
|
|
982
770
|
);
|
|
983
771
|
|
|
984
772
|
// Use backoff and continue
|
|
985
|
-
let backoff =
|
|
773
|
+
let backoff = utils::calculate_backoff(
|
|
774
|
+
attempts,
|
|
775
|
+
req_ctx.retry.initial_interval,
|
|
776
|
+
);
|
|
986
777
|
tokio::time::sleep(backoff).await;
|
|
987
778
|
|
|
988
779
|
// Resolve target again, this time ignoring cache. This makes sure
|
|
989
780
|
// we always re-fetch the route on error
|
|
990
|
-
let ResolveRouteOutput::Target(new_target) =
|
|
991
|
-
.state
|
|
992
|
-
.resolve_route(
|
|
993
|
-
&host,
|
|
994
|
-
&path,
|
|
995
|
-
&req_parts.method,
|
|
996
|
-
self.state.port_type.clone(),
|
|
997
|
-
&req_parts.headers,
|
|
998
|
-
true,
|
|
999
|
-
)
|
|
1000
|
-
.await?
|
|
781
|
+
let ResolveRouteOutput::Target(new_target) =
|
|
782
|
+
self.state.resolve_route(req_ctx, true).await?
|
|
1001
783
|
else {
|
|
1002
784
|
bail!("resolved route does not match Target");
|
|
1003
785
|
};
|
|
@@ -1019,28 +801,27 @@ impl ProxyService {
|
|
|
1019
801
|
// For streaming responses, pass through the body without buffering
|
|
1020
802
|
tracing::debug!("Detected streaming response, preserving stream");
|
|
1021
803
|
|
|
1022
|
-
// We can't easily calculate response size for streaming, so set it to None
|
|
1023
|
-
request_context.guard_response_body_bytes = None;
|
|
1024
|
-
|
|
1025
804
|
let streaming_body = ResponseBody::Incoming(body);
|
|
1026
805
|
return Ok(Response::from_parts(parts, streaming_body));
|
|
1027
806
|
} else {
|
|
1028
807
|
// For non-streaming responses, buffer as before
|
|
1029
|
-
let body_bytes =
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
808
|
+
let body_bytes = Limited::new(
|
|
809
|
+
body,
|
|
810
|
+
self.state.config.guard().http_max_request_body_size(),
|
|
811
|
+
)
|
|
812
|
+
.collect()
|
|
813
|
+
.await
|
|
814
|
+
.map_err(|err| {
|
|
815
|
+
errors::InvalidResponseBody(err.to_string()).build()
|
|
816
|
+
})?
|
|
817
|
+
.to_bytes();
|
|
1037
818
|
|
|
1038
819
|
let full_body = ResponseBody::Full(Full::new(body_bytes));
|
|
1039
820
|
return Ok(Response::from_parts(parts, full_body));
|
|
1040
821
|
}
|
|
1041
822
|
}
|
|
1042
823
|
Err(err) => {
|
|
1043
|
-
if !err.is_connect() || attempts >= max_attempts {
|
|
824
|
+
if !err.is_connect() || attempts >= req_ctx.retry.max_attempts {
|
|
1044
825
|
tracing::error!(
|
|
1045
826
|
?err,
|
|
1046
827
|
?target,
|
|
@@ -1057,22 +838,16 @@ impl ProxyService {
|
|
|
1057
838
|
tracing::debug!(?err, "Request attempt {attempts} failed");
|
|
1058
839
|
|
|
1059
840
|
// Use backoff and continue
|
|
1060
|
-
let backoff =
|
|
841
|
+
let backoff = utils::calculate_backoff(
|
|
842
|
+
attempts,
|
|
843
|
+
req_ctx.retry.initial_interval,
|
|
844
|
+
);
|
|
1061
845
|
tokio::time::sleep(backoff).await;
|
|
1062
846
|
|
|
1063
847
|
// Resolve target again, this time ignoring cache. This makes sure
|
|
1064
848
|
// we always re-fetch the route on error
|
|
1065
|
-
let ResolveRouteOutput::Target(new_target) =
|
|
1066
|
-
.state
|
|
1067
|
-
.resolve_route(
|
|
1068
|
-
&host,
|
|
1069
|
-
&path,
|
|
1070
|
-
&req_parts.method,
|
|
1071
|
-
self.state.port_type.clone(),
|
|
1072
|
-
&req_parts.headers,
|
|
1073
|
-
true,
|
|
1074
|
-
)
|
|
1075
|
-
.await?
|
|
849
|
+
let ResolveRouteOutput::Target(new_target) =
|
|
850
|
+
self.state.resolve_route(req_ctx, true).await?
|
|
1076
851
|
else {
|
|
1077
852
|
bail!("resolved route does not match Target");
|
|
1078
853
|
};
|
|
@@ -1086,77 +861,40 @@ impl ProxyService {
|
|
|
1086
861
|
|
|
1087
862
|
// If we get here, all attempts failed
|
|
1088
863
|
return Err(errors::RetryAttemptsExceeded {
|
|
1089
|
-
attempts: max_attempts,
|
|
864
|
+
attempts: req_ctx.retry.max_attempts,
|
|
1090
865
|
}
|
|
1091
866
|
.build());
|
|
1092
867
|
}
|
|
1093
|
-
ResolveRouteOutput::Response(_) => {
|
|
1094
|
-
unreachable!()
|
|
1095
|
-
}
|
|
1096
868
|
ResolveRouteOutput::CustomServe(mut handler) => {
|
|
1097
|
-
let req_headers = req.headers().clone();
|
|
1098
|
-
let req_method = req.method().clone();
|
|
1099
|
-
|
|
1100
|
-
// Acquire in-flight limit and generate request ID
|
|
1101
|
-
let request_id = match self
|
|
1102
|
-
.state
|
|
1103
|
-
.acquire_in_flight(client_ip, &actor_id, &req_headers)
|
|
1104
|
-
.await?
|
|
1105
|
-
{
|
|
1106
|
-
Some(id) => id,
|
|
1107
|
-
None => {
|
|
1108
|
-
return Err(errors::RateLimit {
|
|
1109
|
-
actor_id,
|
|
1110
|
-
method: req_method.to_string(),
|
|
1111
|
-
path: path.clone(),
|
|
1112
|
-
ip: client_ip.to_string(),
|
|
1113
|
-
}
|
|
1114
|
-
.build());
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
869
|
// Collect request body
|
|
1119
870
|
let (req_parts, body) = req.into_parts();
|
|
1120
|
-
let
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
req_parts,
|
|
1129
|
-
Full::<Bytes>::new(collected_body.clone()),
|
|
1130
|
-
);
|
|
871
|
+
let req_body =
|
|
872
|
+
Limited::new(body, self.state.config.guard().http_max_request_body_size())
|
|
873
|
+
.collect()
|
|
874
|
+
.await
|
|
875
|
+
.map_err(|err| errors::InvalidRequestBody(err.to_string()).build())?
|
|
876
|
+
.to_bytes();
|
|
877
|
+
let req_collected =
|
|
878
|
+
hyper::Request::from_parts(req_parts, Full::<Bytes>::new(req_body));
|
|
1131
879
|
|
|
1132
880
|
// Attempt request
|
|
1133
881
|
let mut attempts = 0;
|
|
1134
|
-
while attempts < max_attempts {
|
|
882
|
+
while attempts < req_ctx.retry.max_attempts {
|
|
1135
883
|
attempts += 1;
|
|
1136
884
|
|
|
1137
|
-
let res = handler
|
|
1138
|
-
|
|
1139
|
-
.await;
|
|
1140
|
-
if should_retry_request(&res) {
|
|
885
|
+
let res = handler.handle_request(req_collected.clone(), req_ctx).await;
|
|
886
|
+
if utils::should_retry_request(&res) {
|
|
1141
887
|
// Request connect error, might retry
|
|
1142
888
|
tracing::debug!("Request attempt {attempts} failed (service unavailable)");
|
|
1143
889
|
|
|
1144
890
|
// Use backoff and continue
|
|
1145
|
-
let backoff =
|
|
891
|
+
let backoff =
|
|
892
|
+
utils::calculate_backoff(attempts, req_ctx.retry.initial_interval);
|
|
1146
893
|
tokio::time::sleep(backoff).await;
|
|
1147
894
|
|
|
1148
895
|
// Refresh route (ignore cache) so subsequent requests can hit new target
|
|
1149
|
-
let ResolveRouteOutput::CustomServe(new_handler) =
|
|
1150
|
-
.state
|
|
1151
|
-
.resolve_route(
|
|
1152
|
-
&host,
|
|
1153
|
-
&path,
|
|
1154
|
-
req_collected.method(),
|
|
1155
|
-
self.state.port_type.clone(),
|
|
1156
|
-
&req_headers,
|
|
1157
|
-
true,
|
|
1158
|
-
)
|
|
1159
|
-
.await?
|
|
896
|
+
let ResolveRouteOutput::CustomServe(new_handler) =
|
|
897
|
+
self.state.resolve_route(req_ctx, true).await?
|
|
1160
898
|
else {
|
|
1161
899
|
bail!("resolved route does not match CustomServe");
|
|
1162
900
|
};
|
|
@@ -1167,7 +905,7 @@ impl ProxyService {
|
|
|
1167
905
|
|
|
1168
906
|
// Release in-flight counter and request ID before returning
|
|
1169
907
|
self.state
|
|
1170
|
-
.release_in_flight(client_ip,
|
|
908
|
+
.release_in_flight(req_ctx.client_ip, req_ctx.in_flight_request_id)
|
|
1171
909
|
.await;
|
|
1172
910
|
return res;
|
|
1173
911
|
}
|
|
@@ -1175,130 +913,33 @@ impl ProxyService {
|
|
|
1175
913
|
// If we get here, all attempts failed
|
|
1176
914
|
// Release in-flight counter and request ID before returning error
|
|
1177
915
|
self.state
|
|
1178
|
-
.release_in_flight(client_ip,
|
|
916
|
+
.release_in_flight(req_ctx.client_ip, req_ctx.in_flight_request_id)
|
|
1179
917
|
.await;
|
|
1180
918
|
return Err(errors::RetryAttemptsExceeded {
|
|
1181
|
-
attempts: max_attempts,
|
|
919
|
+
attempts: req_ctx.retry.max_attempts,
|
|
1182
920
|
}
|
|
1183
921
|
.build());
|
|
1184
922
|
}
|
|
1185
923
|
}
|
|
1186
924
|
}
|
|
1187
925
|
|
|
1188
|
-
/// Modifies the incoming request before it is proxied.
|
|
1189
|
-
fn proxied_request_builder(
|
|
1190
|
-
&self,
|
|
1191
|
-
req_parts: &hyper::http::request::Parts,
|
|
1192
|
-
target: &RouteTarget,
|
|
1193
|
-
) -> Result<hyper::http::request::Builder> {
|
|
1194
|
-
let scheme = if target.port == 443 { "https" } else { "http" };
|
|
1195
|
-
|
|
1196
|
-
// Bracket raw IPv6 hosts
|
|
1197
|
-
let host = if target.host.contains(':') && !target.host.starts_with('[') {
|
|
1198
|
-
format!("[{}]", target.host)
|
|
1199
|
-
} else {
|
|
1200
|
-
target.host.clone()
|
|
1201
|
-
};
|
|
1202
|
-
|
|
1203
|
-
// Ensure path starts with a leading slash
|
|
1204
|
-
let path = if target.path.starts_with('/') {
|
|
1205
|
-
target.path.clone()
|
|
1206
|
-
} else {
|
|
1207
|
-
format!("/{}", target.path)
|
|
1208
|
-
};
|
|
1209
|
-
|
|
1210
|
-
let url = Url::parse(&format!("{scheme}://{host}:{}{}", target.port, path))
|
|
1211
|
-
.context("invalid scheme/host/port when building URL")?;
|
|
1212
|
-
|
|
1213
|
-
// Build the proxied request
|
|
1214
|
-
let mut builder = hyper::Request::builder()
|
|
1215
|
-
.method(req_parts.method.clone())
|
|
1216
|
-
.uri(url.to_string());
|
|
1217
|
-
|
|
1218
|
-
// Modify proxy headers
|
|
1219
|
-
let headers = builder
|
|
1220
|
-
.headers_mut()
|
|
1221
|
-
.expect("request builder unexpectedly in error state");
|
|
1222
|
-
|
|
1223
|
-
headers.remove(X_RIVET_TARGET);
|
|
1224
|
-
headers.remove(X_RIVET_ACTOR);
|
|
1225
|
-
headers.remove(X_RIVET_TOKEN);
|
|
1226
|
-
|
|
1227
|
-
add_proxy_headers_with_addr(headers, &req_parts.headers, self.remote_addr)?;
|
|
1228
|
-
|
|
1229
|
-
Ok(builder)
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
926
|
#[tracing::instrument(skip_all)]
|
|
1233
927
|
async fn handle_websocket_upgrade(
|
|
1234
928
|
&self,
|
|
1235
929
|
req: Request<BodyIncoming>,
|
|
930
|
+
req_ctx: &mut RequestContext,
|
|
1236
931
|
target: ResolveRouteOutput,
|
|
1237
|
-
request_context: &mut RequestContext,
|
|
1238
|
-
client_ip: std::net::IpAddr,
|
|
1239
|
-
actor_id: Option<Id>,
|
|
1240
932
|
) -> Result<Response<ResponseBody>> {
|
|
1241
|
-
// Parsed for retries later
|
|
1242
|
-
let req_host = req
|
|
1243
|
-
.headers()
|
|
1244
|
-
.get(hyper::header::HOST)
|
|
1245
|
-
.and_then(|h| h.to_str().ok())
|
|
1246
|
-
.unwrap_or("unknown")
|
|
1247
|
-
.to_string();
|
|
1248
|
-
let req_path = req
|
|
1249
|
-
.uri()
|
|
1250
|
-
.path_and_query()
|
|
1251
|
-
.map(|x| x.to_string())
|
|
1252
|
-
.unwrap_or_else(|| req.uri().path().to_string());
|
|
1253
|
-
|
|
1254
|
-
// Capture headers and method before request is consumed
|
|
1255
|
-
let req_headers = req.headers().clone();
|
|
1256
|
-
let req_method = req.method().clone();
|
|
1257
|
-
let ray_id = req.extensions().get::<RequestIds>().map(|x| x.ray_id);
|
|
1258
|
-
|
|
1259
|
-
// Get middleware config for this actor if it exists
|
|
1260
|
-
let middleware_config = match &actor_id {
|
|
1261
|
-
Some(actor_id) => {
|
|
1262
|
-
self.state
|
|
1263
|
-
.get_middleware_config(actor_id, &req_headers)
|
|
1264
|
-
.await?
|
|
1265
|
-
}
|
|
1266
|
-
None => {
|
|
1267
|
-
// Default middleware config for targets without actor_id
|
|
1268
|
-
tracing::debug!("Using default middleware config (no actor_id)");
|
|
1269
|
-
MiddlewareConfig {
|
|
1270
|
-
rate_limit: RateLimitConfig {
|
|
1271
|
-
requests: 100, // 100 requests
|
|
1272
|
-
period: 60, // per 60 seconds
|
|
1273
|
-
},
|
|
1274
|
-
max_in_flight: MaxInFlightConfig {
|
|
1275
|
-
amount: 20, // 20 concurrent requests
|
|
1276
|
-
},
|
|
1277
|
-
retry: RetryConfig {
|
|
1278
|
-
max_attempts: 3, // 3 retry attempts
|
|
1279
|
-
initial_interval: 100, // 100ms initial interval
|
|
1280
|
-
},
|
|
1281
|
-
timeout: TimeoutConfig {
|
|
1282
|
-
request_timeout: 30, // 30 seconds for requests
|
|
1283
|
-
},
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
};
|
|
1287
|
-
|
|
1288
|
-
// Set up retry with backoff from middleware config
|
|
1289
|
-
let max_attempts = middleware_config.retry.max_attempts;
|
|
1290
|
-
let initial_interval = middleware_config.retry.initial_interval;
|
|
1291
|
-
|
|
1292
933
|
// Log the headers for debugging
|
|
1293
934
|
tracing::debug!("WebSocket upgrade request headers:");
|
|
1294
|
-
for (name, value) in
|
|
935
|
+
for (name, value) in &req_ctx.headers {
|
|
1295
936
|
if let Ok(val) = value.to_str() {
|
|
1296
937
|
tracing::debug!(" {}: {}", name, val);
|
|
1297
938
|
}
|
|
1298
939
|
}
|
|
1299
940
|
|
|
1300
941
|
// Handle WebSocket upgrade properly with hyper_tungstenite
|
|
1301
|
-
tracing::debug!(
|
|
942
|
+
tracing::debug!(path=%req_ctx.path, "Upgrading client connection to WebSocket");
|
|
1302
943
|
let (client_response, client_ws) = match hyper_tungstenite::upgrade(req, None) {
|
|
1303
944
|
Ok(x) => {
|
|
1304
945
|
tracing::debug!("Client WebSocket upgrade successful");
|
|
@@ -1308,7 +949,7 @@ impl ProxyService {
|
|
|
1308
949
|
tracing::error!(?err, "Failed to upgrade client WebSocket");
|
|
1309
950
|
return Err(errors::ConnectionError {
|
|
1310
951
|
error_message: format!("Failed to upgrade client WebSocket: {}", err),
|
|
1311
|
-
remote_addr:
|
|
952
|
+
remote_addr: req_ctx.remote_addr.to_string(),
|
|
1312
953
|
}
|
|
1313
954
|
.build());
|
|
1314
955
|
}
|
|
@@ -1327,14 +968,17 @@ impl ProxyService {
|
|
|
1327
968
|
|
|
1328
969
|
// Clone needed values for the spawned task
|
|
1329
970
|
let state = self.state.clone();
|
|
1330
|
-
let remote_addr = self.remote_addr;
|
|
1331
971
|
|
|
1332
972
|
// Spawn a new task to handle the WebSocket bidirectional communication
|
|
1333
973
|
match target {
|
|
1334
974
|
ResolveRouteOutput::Target(mut target) => {
|
|
1335
975
|
tracing::debug!("Spawning task to handle WebSocket communication");
|
|
976
|
+
let mut req_ctx = req_ctx.clone();
|
|
977
|
+
|
|
1336
978
|
self.state.tasks.spawn(
|
|
1337
979
|
async move {
|
|
980
|
+
let req_ctx = &mut req_ctx;
|
|
981
|
+
|
|
1338
982
|
// Set up a timeout for the entire operation
|
|
1339
983
|
let timeout_duration = Duration::from_secs(30); // 30 seconds timeout
|
|
1340
984
|
tracing::debug!(
|
|
@@ -1369,7 +1013,7 @@ impl ProxyService {
|
|
|
1369
1013
|
|
|
1370
1014
|
// Now attempt to connect to the upstream server
|
|
1371
1015
|
tracing::debug!("Attempting connect to upstream WebSocket");
|
|
1372
|
-
while attempts < max_attempts {
|
|
1016
|
+
while attempts < req_ctx.retry.max_attempts {
|
|
1373
1017
|
attempts += 1;
|
|
1374
1018
|
|
|
1375
1019
|
// Build the WebSocket URL using the url crate to properly handle IPv6 addresses
|
|
@@ -1414,7 +1058,7 @@ impl ProxyService {
|
|
|
1414
1058
|
tracing::debug!(
|
|
1415
1059
|
"WebSocket request attempt {}/{} to {}",
|
|
1416
1060
|
attempts,
|
|
1417
|
-
max_attempts,
|
|
1061
|
+
req_ctx.retry.max_attempts,
|
|
1418
1062
|
target_url
|
|
1419
1063
|
);
|
|
1420
1064
|
|
|
@@ -1428,10 +1072,9 @@ impl ProxyService {
|
|
|
1428
1072
|
};
|
|
1429
1073
|
|
|
1430
1074
|
// Add proxy headers to the websocket request
|
|
1431
|
-
if let Err(err) = add_proxy_headers_with_addr(
|
|
1075
|
+
if let Err(err) = utils::add_proxy_headers_with_addr(
|
|
1432
1076
|
ws_request.headers_mut(),
|
|
1433
|
-
|
|
1434
|
-
remote_addr,
|
|
1077
|
+
req_ctx,
|
|
1435
1078
|
) {
|
|
1436
1079
|
tracing::error!(
|
|
1437
1080
|
?err,
|
|
@@ -1485,21 +1128,23 @@ impl ProxyService {
|
|
|
1485
1128
|
}
|
|
1486
1129
|
|
|
1487
1130
|
// Check if we've reached max attempts
|
|
1488
|
-
if attempts >= max_attempts {
|
|
1131
|
+
if attempts >= req_ctx.retry.max_attempts {
|
|
1489
1132
|
tracing::debug!(
|
|
1490
1133
|
"All {} WebSocket connection attempts failed",
|
|
1491
|
-
max_attempts
|
|
1134
|
+
req_ctx.retry.max_attempts
|
|
1492
1135
|
);
|
|
1493
1136
|
|
|
1494
1137
|
// Send a close message to the client since we can't connect to upstream
|
|
1495
|
-
|
|
1496
|
-
|
|
1138
|
+
let err = errors::RetryAttemptsExceeded { attempts }.build();
|
|
1139
|
+
tracing::warn!(
|
|
1140
|
+
?err,
|
|
1141
|
+
"sending close message to client due to upstream connection failure"
|
|
1497
1142
|
);
|
|
1498
1143
|
let (mut client_sink, _) = client_ws.split();
|
|
1499
1144
|
match client_sink
|
|
1500
|
-
.send(to_hyper_close(Some(err_to_close_frame(
|
|
1501
|
-
|
|
1502
|
-
ray_id,
|
|
1145
|
+
.send(utils::to_hyper_close(Some(utils::err_to_close_frame(
|
|
1146
|
+
err,
|
|
1147
|
+
req_ctx.ray_id,
|
|
1503
1148
|
))))
|
|
1504
1149
|
.await
|
|
1505
1150
|
{
|
|
@@ -1532,7 +1177,8 @@ impl ProxyService {
|
|
|
1532
1177
|
}
|
|
1533
1178
|
|
|
1534
1179
|
// Use backoff for the next attempt
|
|
1535
|
-
let backoff =
|
|
1180
|
+
let backoff =
|
|
1181
|
+
utils::calculate_backoff(attempts, req_ctx.retry.initial_interval);
|
|
1536
1182
|
tracing::debug!(
|
|
1537
1183
|
"Waiting for {:?} before next connection attempt",
|
|
1538
1184
|
backoff
|
|
@@ -1542,41 +1188,20 @@ impl ProxyService {
|
|
|
1542
1188
|
|
|
1543
1189
|
// Resolve target again, this time ignoring cache. This makes sure
|
|
1544
1190
|
// we always re-fetch the route on error
|
|
1545
|
-
let new_target = state
|
|
1546
|
-
.resolve_route(
|
|
1547
|
-
&req_host,
|
|
1548
|
-
&req_path,
|
|
1549
|
-
&req_method,
|
|
1550
|
-
state.port_type.clone(),
|
|
1551
|
-
&req_headers,
|
|
1552
|
-
true,
|
|
1553
|
-
)
|
|
1554
|
-
.await;
|
|
1191
|
+
let new_target = state.resolve_route(req_ctx, true).await;
|
|
1555
1192
|
|
|
1556
1193
|
match new_target {
|
|
1557
1194
|
Ok(ResolveRouteOutput::Target(new_target)) => {
|
|
1558
1195
|
target = new_target;
|
|
1559
1196
|
}
|
|
1560
|
-
Ok(ResolveRouteOutput::Response(response)) => {
|
|
1561
|
-
tracing::debug!(
|
|
1562
|
-
status=?response.status,
|
|
1563
|
-
message=?response.message,
|
|
1564
|
-
docs=?response.docs,
|
|
1565
|
-
"got response instead of websocket target",
|
|
1566
|
-
);
|
|
1567
|
-
|
|
1568
|
-
// Close the WebSocket connection with the response message
|
|
1569
|
-
let _ = client_ws
|
|
1570
|
-
.close(Some(str_to_close_frame(response.message.as_ref())))
|
|
1571
|
-
.await;
|
|
1572
|
-
return;
|
|
1573
|
-
}
|
|
1574
1197
|
Ok(ResolveRouteOutput::CustomServe(_)) => {
|
|
1198
|
+
let err = errors::WebSocketTargetChanged.build();
|
|
1199
|
+
tracing::warn!(
|
|
1200
|
+
?err,
|
|
1201
|
+
"websocket target changed to custom serve"
|
|
1202
|
+
);
|
|
1575
1203
|
let _ = client_ws
|
|
1576
|
-
.close(Some(err_to_close_frame(
|
|
1577
|
-
errors::WebSocketTargetChanged.build(),
|
|
1578
|
-
ray_id,
|
|
1579
|
-
)))
|
|
1204
|
+
.close(Some(utils::err_to_close_frame(err, req_ctx.ray_id)))
|
|
1580
1205
|
.await;
|
|
1581
1206
|
return;
|
|
1582
1207
|
}
|
|
@@ -1658,7 +1283,7 @@ impl ProxyService {
|
|
|
1658
1283
|
// Signal shutdown to other direction
|
|
1659
1284
|
let _ = shutdown_tx.send(true);
|
|
1660
1285
|
|
|
1661
|
-
to_hyper_close(frame)
|
|
1286
|
+
utils::to_hyper_close(frame)
|
|
1662
1287
|
},
|
|
1663
1288
|
hyper_tungstenite::tungstenite::Message::Frame(_) => {
|
|
1664
1289
|
// Skip frames - they're an implementation detail
|
|
@@ -1809,7 +1434,7 @@ impl ProxyService {
|
|
|
1809
1434
|
// Signal shutdown to other direction
|
|
1810
1435
|
let _ = shutdown_tx.send(true);
|
|
1811
1436
|
|
|
1812
|
-
to_hyper_close(frame)
|
|
1437
|
+
utils::to_hyper_close(frame)
|
|
1813
1438
|
},
|
|
1814
1439
|
tokio_tungstenite::tungstenite::Message::Frame(_) => {
|
|
1815
1440
|
// Skip frames - they're an implementation detail
|
|
@@ -1879,7 +1504,7 @@ impl ProxyService {
|
|
|
1879
1504
|
|
|
1880
1505
|
// Try to send a close frame - ignore errors as the connection might already be closed
|
|
1881
1506
|
tracing::trace!("Attempting to send close message to client");
|
|
1882
|
-
match sink.send(to_hyper_close(None)).await {
|
|
1507
|
+
match sink.send(utils::to_hyper_close(None)).await {
|
|
1883
1508
|
Ok(_) => {
|
|
1884
1509
|
tracing::trace!("Close message sent to client successfully")
|
|
1885
1510
|
}
|
|
@@ -1908,34 +1533,14 @@ impl ProxyService {
|
|
|
1908
1533
|
.instrument(tracing::info_span!("handle_ws_task_target")),
|
|
1909
1534
|
);
|
|
1910
1535
|
}
|
|
1911
|
-
ResolveRouteOutput::Response(_) => unreachable!(),
|
|
1912
1536
|
ResolveRouteOutput::CustomServe(mut handler) => {
|
|
1913
|
-
tracing::debug!(
|
|
1914
|
-
let mut request_context = request_context.clone();
|
|
1915
|
-
let req_headers = req_headers.clone();
|
|
1537
|
+
tracing::debug!(path=%req_ctx.path, "Spawning task to handle WebSocket communication");
|
|
1916
1538
|
let state = self.state.clone();
|
|
1917
|
-
let
|
|
1918
|
-
let req_host = req_host.clone();
|
|
1919
|
-
let req_method = req_method.clone();
|
|
1539
|
+
let mut req_ctx = req_ctx.clone();
|
|
1920
1540
|
|
|
1921
1541
|
self.state.tasks.spawn(
|
|
1922
1542
|
async move {
|
|
1923
|
-
let
|
|
1924
|
-
.acquire_in_flight(client_ip, &actor_id, &req_headers)
|
|
1925
|
-
.await?
|
|
1926
|
-
{
|
|
1927
|
-
Some(id) => id,
|
|
1928
|
-
None => {
|
|
1929
|
-
return Err(errors::RateLimit {
|
|
1930
|
-
actor_id,
|
|
1931
|
-
method: req_method.to_string(),
|
|
1932
|
-
path: req_path.clone(),
|
|
1933
|
-
ip: client_ip.to_string(),
|
|
1934
|
-
}
|
|
1935
|
-
.build()
|
|
1936
|
-
.into());
|
|
1937
|
-
}
|
|
1938
|
-
};
|
|
1543
|
+
let req_ctx = &mut req_ctx;
|
|
1939
1544
|
let mut ws_hibernation_close = false;
|
|
1940
1545
|
let mut after_hibernation = false;
|
|
1941
1546
|
let mut attempts = 0u32;
|
|
@@ -1946,14 +1551,7 @@ impl ProxyService {
|
|
|
1946
1551
|
|
|
1947
1552
|
loop {
|
|
1948
1553
|
match handler
|
|
1949
|
-
.handle_websocket(
|
|
1950
|
-
ws_handle.clone(),
|
|
1951
|
-
&req_headers,
|
|
1952
|
-
&req_path,
|
|
1953
|
-
&mut request_context,
|
|
1954
|
-
request_id,
|
|
1955
|
-
after_hibernation,
|
|
1956
|
-
)
|
|
1554
|
+
.handle_websocket(req_ctx, ws_handle.clone(), after_hibernation)
|
|
1957
1555
|
.await
|
|
1958
1556
|
{
|
|
1959
1557
|
Ok(close_frame) => {
|
|
@@ -1962,7 +1560,7 @@ impl ProxyService {
|
|
|
1962
1560
|
// Send graceful close. This may fail if client already sent
|
|
1963
1561
|
// close frame, which is normal.
|
|
1964
1562
|
tracing::debug!(?close_frame, "sending close frame to client");
|
|
1965
|
-
match ws_handle.send(to_hyper_close(close_frame)).await {
|
|
1563
|
+
match ws_handle.send(utils::to_hyper_close(close_frame)).await {
|
|
1966
1564
|
Ok(_) => {
|
|
1967
1565
|
tracing::debug!("close frame sent successfully");
|
|
1968
1566
|
}
|
|
@@ -1997,7 +1595,7 @@ impl ProxyService {
|
|
|
1997
1595
|
tracing::debug!(?err, "websocket handler error");
|
|
1998
1596
|
|
|
1999
1597
|
// Denotes that the connection did not fail, but the downstream has closed
|
|
2000
|
-
let ws_hibernate = is_ws_hibernate(&err);
|
|
1598
|
+
let ws_hibernate = utils::is_ws_hibernate(&err);
|
|
2001
1599
|
|
|
2002
1600
|
if ws_hibernate {
|
|
2003
1601
|
attempts = 0;
|
|
@@ -2021,8 +1619,8 @@ impl ProxyService {
|
|
|
2021
1619
|
// (starting with the message that caused the hibernation to end)
|
|
2022
1620
|
let res = handler
|
|
2023
1621
|
.handle_websocket_hibernation(
|
|
1622
|
+
req_ctx,
|
|
2024
1623
|
ws_handle.clone(),
|
|
2025
|
-
request_id,
|
|
2026
1624
|
)
|
|
2027
1625
|
.await?;
|
|
2028
1626
|
|
|
@@ -2036,20 +1634,21 @@ impl ProxyService {
|
|
|
2036
1634
|
|
|
2037
1635
|
ws_hibernation_close = true;
|
|
2038
1636
|
}
|
|
2039
|
-
} else if attempts > max_attempts
|
|
2040
|
-
|| !is_retryable_ws_error(&err)
|
|
1637
|
+
} else if attempts > req_ctx.retry.max_attempts
|
|
1638
|
+
|| !utils::is_retryable_ws_error(&err)
|
|
2041
1639
|
{
|
|
2042
1640
|
tracing::debug!(
|
|
1641
|
+
?err,
|
|
2043
1642
|
?attempts,
|
|
2044
|
-
|
|
2045
|
-
"
|
|
1643
|
+
max_attempts=?req_ctx.retry.max_attempts,
|
|
1644
|
+
"websocket failed"
|
|
2046
1645
|
);
|
|
2047
1646
|
|
|
2048
1647
|
// Close WebSocket with error
|
|
2049
1648
|
ws_handle
|
|
2050
|
-
.send(to_hyper_close(Some(
|
|
2051
|
-
err, ray_id,
|
|
2052
|
-
)))
|
|
1649
|
+
.send(utils::to_hyper_close(Some(
|
|
1650
|
+
utils::err_to_close_frame(err, req_ctx.ray_id),
|
|
1651
|
+
)))
|
|
2053
1652
|
.await?;
|
|
2054
1653
|
|
|
2055
1654
|
// Flush to ensure close frame is sent
|
|
@@ -2060,9 +1659,9 @@ impl ProxyService {
|
|
|
2060
1659
|
|
|
2061
1660
|
break;
|
|
2062
1661
|
} else {
|
|
2063
|
-
let backoff =
|
|
1662
|
+
let backoff = utils::calculate_backoff(
|
|
2064
1663
|
attempts,
|
|
2065
|
-
initial_interval,
|
|
1664
|
+
req_ctx.retry.initial_interval,
|
|
2066
1665
|
);
|
|
2067
1666
|
|
|
2068
1667
|
tracing::debug!(
|
|
@@ -2075,40 +1674,21 @@ impl ProxyService {
|
|
|
2075
1674
|
}
|
|
2076
1675
|
|
|
2077
1676
|
// Retry route resolution
|
|
2078
|
-
match state
|
|
2079
|
-
.resolve_route(
|
|
2080
|
-
&req_host,
|
|
2081
|
-
&req_path,
|
|
2082
|
-
&req_method,
|
|
2083
|
-
state.port_type.clone(),
|
|
2084
|
-
&req_headers,
|
|
2085
|
-
true,
|
|
2086
|
-
)
|
|
2087
|
-
.await
|
|
2088
|
-
{
|
|
1677
|
+
match state.resolve_route(req_ctx, true).await {
|
|
2089
1678
|
Ok(ResolveRouteOutput::CustomServe(new_handler)) => {
|
|
2090
1679
|
handler = new_handler;
|
|
2091
1680
|
continue;
|
|
2092
1681
|
}
|
|
2093
|
-
Ok(ResolveRouteOutput::Response(response)) => {
|
|
2094
|
-
ws_handle
|
|
2095
|
-
.send(to_hyper_close(Some(str_to_close_frame(
|
|
2096
|
-
response.message.as_ref(),
|
|
2097
|
-
))))
|
|
2098
|
-
.await?;
|
|
2099
|
-
|
|
2100
|
-
// Flush to ensure close frame is sent
|
|
2101
|
-
ws_handle.flush().await?;
|
|
2102
|
-
|
|
2103
|
-
// Keep TCP connection open briefly to allow client to process close
|
|
2104
|
-
tokio::time::sleep(WEBSOCKET_CLOSE_LINGER).await;
|
|
2105
|
-
}
|
|
2106
1682
|
Ok(ResolveRouteOutput::Target(_)) => {
|
|
1683
|
+
let err = errors::WebSocketTargetChanged.build();
|
|
1684
|
+
tracing::warn!(
|
|
1685
|
+
?err,
|
|
1686
|
+
"websocket target changed to target"
|
|
1687
|
+
);
|
|
2107
1688
|
ws_handle
|
|
2108
|
-
.send(to_hyper_close(Some(
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
))))
|
|
1689
|
+
.send(utils::to_hyper_close(Some(
|
|
1690
|
+
utils::err_to_close_frame(err, req_ctx.ray_id),
|
|
1691
|
+
)))
|
|
2112
1692
|
.await?;
|
|
2113
1693
|
|
|
2114
1694
|
// Flush to ensure close frame is sent
|
|
@@ -2120,10 +1700,14 @@ impl ProxyService {
|
|
|
2120
1700
|
break;
|
|
2121
1701
|
}
|
|
2122
1702
|
Err(err) => {
|
|
1703
|
+
tracing::warn!(
|
|
1704
|
+
?err,
|
|
1705
|
+
"closing websocket due to route resolution error"
|
|
1706
|
+
);
|
|
2123
1707
|
ws_handle
|
|
2124
|
-
.send(to_hyper_close(Some(
|
|
2125
|
-
err, ray_id,
|
|
2126
|
-
)))
|
|
1708
|
+
.send(utils::to_hyper_close(Some(
|
|
1709
|
+
utils::err_to_close_frame(err, req_ctx.ray_id),
|
|
1710
|
+
)))
|
|
2127
1711
|
.await?;
|
|
2128
1712
|
|
|
2129
1713
|
// Flush to ensure close frame is sent
|
|
@@ -2141,7 +1725,7 @@ impl ProxyService {
|
|
|
2141
1725
|
|
|
2142
1726
|
// Release in-flight counter and request ID when task completes
|
|
2143
1727
|
state
|
|
2144
|
-
.release_in_flight(client_ip,
|
|
1728
|
+
.release_in_flight(req_ctx.client_ip, req_ctx.in_flight_request_id)
|
|
2145
1729
|
.await;
|
|
2146
1730
|
|
|
2147
1731
|
Ok(())
|
|
@@ -2173,318 +1757,12 @@ impl ProxyService {
|
|
|
2173
1757
|
}
|
|
2174
1758
|
}
|
|
2175
1759
|
|
|
2176
|
-
impl ProxyService {
|
|
2177
|
-
// Process an individual request
|
|
2178
|
-
#[tracing::instrument(name = "guard_request", skip_all, fields(ray_id, req_id))]
|
|
2179
|
-
pub async fn process(&self, mut req: Request<BodyIncoming>) -> Result<Response<ResponseBody>> {
|
|
2180
|
-
let start_time = Instant::now();
|
|
2181
|
-
|
|
2182
|
-
let request_ids = RequestIds::new(self.state.config.dc_label());
|
|
2183
|
-
req.extensions_mut().insert(request_ids);
|
|
2184
|
-
|
|
2185
|
-
tracing::Span::current()
|
|
2186
|
-
.record("req_id", request_ids.req_id.to_string())
|
|
2187
|
-
.record("ray_id", request_ids.ray_id.to_string());
|
|
2188
|
-
|
|
2189
|
-
// Create request context for analytics tracking
|
|
2190
|
-
let mut request_context =
|
|
2191
|
-
RequestContext::new(self.state.clickhouse_inserter.clone(), request_ids);
|
|
2192
|
-
|
|
2193
|
-
// Extract request information for logging and analytics before consuming the request
|
|
2194
|
-
let incoming_ray_id = req
|
|
2195
|
-
.headers()
|
|
2196
|
-
.get(X_RIVET_RAY_ID)
|
|
2197
|
-
.and_then(|h| h.to_str().ok())
|
|
2198
|
-
.and_then(|id| Id::parse(id).ok());
|
|
2199
|
-
let host = req
|
|
2200
|
-
.headers()
|
|
2201
|
-
.get(hyper::header::HOST)
|
|
2202
|
-
.and_then(|h| h.to_str().ok())
|
|
2203
|
-
.unwrap_or("unknown")
|
|
2204
|
-
.to_string();
|
|
2205
|
-
let uri_string = req.uri().to_string();
|
|
2206
|
-
let path = req
|
|
2207
|
-
.uri()
|
|
2208
|
-
.path_and_query()
|
|
2209
|
-
.map(|x| x.to_string())
|
|
2210
|
-
.unwrap_or_else(|| req.uri().path().to_string());
|
|
2211
|
-
let method = req.method().clone();
|
|
2212
|
-
|
|
2213
|
-
let user_agent = req
|
|
2214
|
-
.headers()
|
|
2215
|
-
.get(hyper::header::USER_AGENT)
|
|
2216
|
-
.and_then(|h| h.to_str().ok())
|
|
2217
|
-
.map(|s| s.to_string());
|
|
2218
|
-
|
|
2219
|
-
// Populate request context with available data
|
|
2220
|
-
request_context.client_ip = Some(self.remote_addr.ip());
|
|
2221
|
-
request_context.client_request_host = Some(host.clone());
|
|
2222
|
-
request_context.client_request_method = Some(method.to_string());
|
|
2223
|
-
request_context.client_request_path = Some(req.uri().path().to_string());
|
|
2224
|
-
request_context.client_request_protocol = Some(format!("{:?}", req.version()));
|
|
2225
|
-
request_context.client_request_scheme =
|
|
2226
|
-
Some(req.uri().scheme_str().unwrap_or("http").to_string());
|
|
2227
|
-
request_context.client_request_uri = Some(path.clone());
|
|
2228
|
-
request_context.client_src_port = Some(self.remote_addr.port());
|
|
2229
|
-
|
|
2230
|
-
if let Some(referer) = req
|
|
2231
|
-
.headers()
|
|
2232
|
-
.get(hyper::header::REFERER)
|
|
2233
|
-
.and_then(|h| h.to_str().ok())
|
|
2234
|
-
{
|
|
2235
|
-
request_context.client_request_referer = Some(referer.to_string());
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
if let Some(ua) = &user_agent {
|
|
2239
|
-
request_context.client_request_user_agent = Some(ua.clone());
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
if let Some(requested_with) = req
|
|
2243
|
-
.headers()
|
|
2244
|
-
.get("x-requested-with")
|
|
2245
|
-
.and_then(|h| h.to_str().ok())
|
|
2246
|
-
{
|
|
2247
|
-
request_context.client_x_requested_with = Some(requested_with.to_string());
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
|
-
// TLS information would be set here if available (for HTTPS connections)
|
|
2251
|
-
// This requires TLS connection introspection and is marked for future enhancement
|
|
2252
|
-
|
|
2253
|
-
// Debug log request information with structured fields (Apache-like access log)
|
|
2254
|
-
tracing::debug!(
|
|
2255
|
-
?incoming_ray_id,
|
|
2256
|
-
ray_id=?request_ids.ray_id,
|
|
2257
|
-
req_id=?request_ids.req_id,
|
|
2258
|
-
method = %method,
|
|
2259
|
-
path = %path,
|
|
2260
|
-
host = %host,
|
|
2261
|
-
remote_addr = %self.remote_addr,
|
|
2262
|
-
uri = %uri_string,
|
|
2263
|
-
protocol = ?self.state.port_type,
|
|
2264
|
-
user_agent = ?user_agent,
|
|
2265
|
-
"Request received"
|
|
2266
|
-
);
|
|
2267
|
-
|
|
2268
|
-
let is_websocket = hyper_tungstenite::is_upgrade_request(&req);
|
|
2269
|
-
|
|
2270
|
-
// Used for ws error proxying later
|
|
2271
|
-
let mut mock_req_builder = Request::builder()
|
|
2272
|
-
.method(req.method().clone())
|
|
2273
|
-
.uri(req.uri().clone())
|
|
2274
|
-
.version(req.version().clone());
|
|
2275
|
-
if let Some(headers) = mock_req_builder.headers_mut() {
|
|
2276
|
-
*headers = req.headers().clone();
|
|
2277
|
-
}
|
|
2278
|
-
if let Some(extensions) = mock_req_builder.extensions_mut() {
|
|
2279
|
-
*extensions = req.extensions().clone();
|
|
2280
|
-
}
|
|
2281
|
-
let mock_req = mock_req_builder.body(())?;
|
|
2282
|
-
|
|
2283
|
-
// Process the request
|
|
2284
|
-
let mut res = match self
|
|
2285
|
-
.handle_request(req, start_time, &mut request_context)
|
|
2286
|
-
.await
|
|
2287
|
-
{
|
|
2288
|
-
Ok(res) => res,
|
|
2289
|
-
Err(err) => {
|
|
2290
|
-
// Log the error
|
|
2291
|
-
tracing::error!(?err, "Request failed");
|
|
2292
|
-
|
|
2293
|
-
metrics::PROXY_REQUEST_ERROR
|
|
2294
|
-
.add(1, &[KeyValue::new("error_type", err.to_string())]);
|
|
2295
|
-
|
|
2296
|
-
// If we receive an error during a websocket request, we attempt to open the websocket anyway
|
|
2297
|
-
// so we can send the error via websocket instead of http. Most websocket clients don't handle
|
|
2298
|
-
// HTTP errors in a meaningful way resulting in unhelpful errors for the user
|
|
2299
|
-
if is_websocket {
|
|
2300
|
-
tracing::debug!("Upgrading client connection to WebSocket for error proxy");
|
|
2301
|
-
match hyper_tungstenite::upgrade(mock_req, None) {
|
|
2302
|
-
Ok((client_response, client_ws)) => {
|
|
2303
|
-
tracing::debug!("Client WebSocket upgrade for error proxy successful");
|
|
2304
|
-
|
|
2305
|
-
self.state.tasks.spawn(
|
|
2306
|
-
async move {
|
|
2307
|
-
let ws_handle = match WebSocketHandle::new(client_ws).await {
|
|
2308
|
-
Ok(ws_handle) => ws_handle,
|
|
2309
|
-
Err(err) => {
|
|
2310
|
-
tracing::debug!(
|
|
2311
|
-
?err,
|
|
2312
|
-
"failed initiating websocket handle for error proxy"
|
|
2313
|
-
);
|
|
2314
|
-
return;
|
|
2315
|
-
}
|
|
2316
|
-
};
|
|
2317
|
-
let frame = err_to_close_frame(err, Some(request_ids.ray_id));
|
|
2318
|
-
|
|
2319
|
-
// Manual conversion to handle different tungstenite versions
|
|
2320
|
-
let code_num: u16 = frame.code.into();
|
|
2321
|
-
let reason = frame.reason.clone();
|
|
2322
|
-
|
|
2323
|
-
if let Err(err) = ws_handle
|
|
2324
|
-
.send(tokio_tungstenite::tungstenite::Message::Close(Some(
|
|
2325
|
-
tokio_tungstenite::tungstenite::protocol::CloseFrame {
|
|
2326
|
-
code: code_num.into(),
|
|
2327
|
-
reason,
|
|
2328
|
-
},
|
|
2329
|
-
)))
|
|
2330
|
-
.await
|
|
2331
|
-
{
|
|
2332
|
-
tracing::debug!(
|
|
2333
|
-
?err,
|
|
2334
|
-
"failed sending websocket error proxy"
|
|
2335
|
-
);
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
.instrument(
|
|
2339
|
-
tracing::info_span!("ws_error_proxy_task", ?request_ids.ray_id),
|
|
2340
|
-
),
|
|
2341
|
-
);
|
|
2342
|
-
|
|
2343
|
-
// Return the response that will upgrade the client connection
|
|
2344
|
-
// For proper WebSocket handshaking, we need to preserve the original response
|
|
2345
|
-
// structure but convert it to our expected return type without modifying its content
|
|
2346
|
-
tracing::debug!(
|
|
2347
|
-
"Returning WebSocket upgrade response for error proxy to client"
|
|
2348
|
-
);
|
|
2349
|
-
// Extract the parts from the response but preserve all headers and status
|
|
2350
|
-
let (mut parts, _) = client_response.into_parts();
|
|
2351
|
-
|
|
2352
|
-
// Add Sec-WebSocket-Protocol header to the response
|
|
2353
|
-
// Many WebSocket clients (e.g. node-ws & Cloudflare) require a protocol in the response
|
|
2354
|
-
parts.headers.insert(
|
|
2355
|
-
"sec-websocket-protocol",
|
|
2356
|
-
hyper::header::HeaderValue::from_static("rivet"),
|
|
2357
|
-
);
|
|
2358
|
-
|
|
2359
|
-
// Create a new response with an empty body - WebSocket upgrades don't need a body
|
|
2360
|
-
Response::from_parts(
|
|
2361
|
-
parts,
|
|
2362
|
-
ResponseBody::Full(Full::<Bytes>::new(Bytes::new())),
|
|
2363
|
-
)
|
|
2364
|
-
}
|
|
2365
|
-
Err(err) => {
|
|
2366
|
-
tracing::error!(
|
|
2367
|
-
?err,
|
|
2368
|
-
"Failed to upgrade client WebSocket for error proxy"
|
|
2369
|
-
);
|
|
2370
|
-
|
|
2371
|
-
err_into_response(
|
|
2372
|
-
errors::ConnectionError {
|
|
2373
|
-
error_message: format!(
|
|
2374
|
-
"Failed to upgrade client WebSocket for error proxy: {}",
|
|
2375
|
-
err
|
|
2376
|
-
),
|
|
2377
|
-
remote_addr: self.remote_addr.to_string(),
|
|
2378
|
-
}
|
|
2379
|
-
.build(),
|
|
2380
|
-
)?
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
} else {
|
|
2384
|
-
err_into_response(err)?
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
};
|
|
2388
|
-
|
|
2389
|
-
if is_websocket && res.status() != StatusCode::SWITCHING_PROTOCOLS {
|
|
2390
|
-
tracing::debug!("returned non-101 response to websocket");
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
// Add ray_id to response headers
|
|
2394
|
-
if let Ok(ray_id_value) = request_ids.ray_id.to_string().parse() {
|
|
2395
|
-
if let Some(existing_ray_id_value) = res
|
|
2396
|
-
.headers()
|
|
2397
|
-
.get(X_RIVET_RAY_ID)
|
|
2398
|
-
.and_then(|h| h.to_str().ok())
|
|
2399
|
-
{
|
|
2400
|
-
if ray_id_value != existing_ray_id_value {
|
|
2401
|
-
tracing::warn!(
|
|
2402
|
-
expected_ray_id=%request_ids.ray_id,
|
|
2403
|
-
received_ray_id=%existing_ray_id_value,
|
|
2404
|
-
"downstream service set ray id header to a different value",
|
|
2405
|
-
);
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
res.headers_mut().insert(X_RIVET_RAY_ID, ray_id_value);
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
let status = res.status().as_u16();
|
|
2413
|
-
|
|
2414
|
-
// Update request context with response details
|
|
2415
|
-
request_context.guard_response_status = Some(status);
|
|
2416
|
-
request_context.service_response_status = Some(status);
|
|
2417
|
-
|
|
2418
|
-
if let Some(content_type) = res
|
|
2419
|
-
.headers()
|
|
2420
|
-
.get(hyper::header::CONTENT_TYPE)
|
|
2421
|
-
.and_then(|h| h.to_str().ok())
|
|
2422
|
-
{
|
|
2423
|
-
request_context.guard_response_content_type = Some(content_type.to_string());
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
if let Some(expires) = res
|
|
2427
|
-
.headers()
|
|
2428
|
-
.get(hyper::header::EXPIRES)
|
|
2429
|
-
.and_then(|h| h.to_str().ok())
|
|
2430
|
-
{
|
|
2431
|
-
request_context.service_response_http_expires = Some(expires.to_string());
|
|
2432
|
-
}
|
|
2433
|
-
|
|
2434
|
-
if let Some(last_modified) = res
|
|
2435
|
-
.headers()
|
|
2436
|
-
.get(hyper::header::LAST_MODIFIED)
|
|
2437
|
-
.and_then(|h| h.to_str().ok())
|
|
2438
|
-
{
|
|
2439
|
-
request_context.service_response_http_last_modified = Some(last_modified.to_string());
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
// Set timing information
|
|
2443
|
-
request_context.service_response_duration_ms =
|
|
2444
|
-
Some(start_time.elapsed().as_millis() as u32);
|
|
2445
|
-
|
|
2446
|
-
// Insert analytics event asynchronously
|
|
2447
|
-
let mut context_clone = request_context.clone();
|
|
2448
|
-
tokio::spawn(
|
|
2449
|
-
async move {
|
|
2450
|
-
if let Err(error) = context_clone.insert_event().await {
|
|
2451
|
-
tracing::warn!(?error, "failed to insert guard analytics event");
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
.instrument(tracing::info_span!("insert_event_task")),
|
|
2455
|
-
);
|
|
2456
|
-
|
|
2457
|
-
let content_length = res
|
|
2458
|
-
.headers()
|
|
2459
|
-
.get(hyper::header::CONTENT_LENGTH)
|
|
2460
|
-
.and_then(|h| h.to_str().ok())
|
|
2461
|
-
.and_then(|s| s.parse::<usize>().ok())
|
|
2462
|
-
.unwrap_or(0);
|
|
2463
|
-
|
|
2464
|
-
// Log information about the completed request
|
|
2465
|
-
tracing::debug!(
|
|
2466
|
-
?incoming_ray_id,
|
|
2467
|
-
ray_id=?request_ids.ray_id,
|
|
2468
|
-
req_id=?request_ids.req_id,
|
|
2469
|
-
method = %method,
|
|
2470
|
-
path = %path,
|
|
2471
|
-
host = %host,
|
|
2472
|
-
remote_addr = %self.remote_addr,
|
|
2473
|
-
status = %status,
|
|
2474
|
-
content_length = %content_length,
|
|
2475
|
-
"Request completed"
|
|
2476
|
-
);
|
|
2477
|
-
|
|
2478
|
-
Ok(res)
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
1760
|
impl Clone for ProxyService {
|
|
2483
1761
|
fn clone(&self) -> Self {
|
|
2484
1762
|
Self {
|
|
2485
1763
|
state: self.state.clone(),
|
|
2486
1764
|
remote_addr: self.remote_addr,
|
|
2487
|
-
|
|
1765
|
+
connection_start: self.connection_start,
|
|
2488
1766
|
}
|
|
2489
1767
|
}
|
|
2490
1768
|
}
|
|
@@ -2499,18 +1777,8 @@ impl ProxyServiceFactory {
|
|
|
2499
1777
|
config: rivet_config::Config,
|
|
2500
1778
|
routing_fn: RoutingFn,
|
|
2501
1779
|
cache_key_fn: CacheKeyFn,
|
|
2502
|
-
middleware_fn: MiddlewareFn,
|
|
2503
|
-
port_type: PortType,
|
|
2504
|
-
clickhouse_inserter: Option<clickhouse_inserter::ClickHouseInserterHandle>,
|
|
2505
1780
|
) -> Self {
|
|
2506
|
-
let state = Arc::new(ProxyState::new(
|
|
2507
|
-
config,
|
|
2508
|
-
routing_fn,
|
|
2509
|
-
cache_key_fn,
|
|
2510
|
-
middleware_fn,
|
|
2511
|
-
port_type,
|
|
2512
|
-
clickhouse_inserter,
|
|
2513
|
-
));
|
|
1781
|
+
let state = Arc::new(ProxyState::new(config, routing_fn, cache_key_fn));
|
|
2514
1782
|
Self { state }
|
|
2515
1783
|
}
|
|
2516
1784
|
|
|
@@ -2522,178 +1790,8 @@ impl ProxyServiceFactory {
|
|
|
2522
1790
|
pub async fn wait_idle(&self) {
|
|
2523
1791
|
self.state.tasks.wait_idle().await
|
|
2524
1792
|
}
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
fn add_proxy_headers_with_addr(
|
|
2528
|
-
headers: &mut hyper::HeaderMap,
|
|
2529
|
-
original_headers: &hyper::HeaderMap,
|
|
2530
|
-
remote_addr: SocketAddr,
|
|
2531
|
-
) -> Result<()> {
|
|
2532
|
-
// Copy headers except Host
|
|
2533
|
-
for (key, value) in original_headers.iter() {
|
|
2534
|
-
if key != hyper::header::HOST {
|
|
2535
|
-
headers.insert(key.clone(), value.clone());
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
|
|
2539
|
-
// Add X-Forwarded-For header
|
|
2540
|
-
if let Some(existing) = original_headers.get(X_FORWARDED_FOR) {
|
|
2541
|
-
if let Ok(forwarded) = existing.to_str() {
|
|
2542
|
-
if !forwarded.contains(&remote_addr.ip().to_string()) {
|
|
2543
|
-
headers.insert(
|
|
2544
|
-
X_FORWARDED_FOR,
|
|
2545
|
-
hyper::header::HeaderValue::from_str(&format!(
|
|
2546
|
-
"{}, {}",
|
|
2547
|
-
forwarded,
|
|
2548
|
-
remote_addr.ip()
|
|
2549
|
-
))?,
|
|
2550
|
-
);
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
} else {
|
|
2554
|
-
headers.insert(
|
|
2555
|
-
X_FORWARDED_FOR,
|
|
2556
|
-
hyper::header::HeaderValue::from_str(&remote_addr.ip().to_string())?,
|
|
2557
|
-
);
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
Ok(())
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
fn err_into_response(err: anyhow::Error) -> Result<Response<ResponseBody>> {
|
|
2564
|
-
let (status, error_response) =
|
|
2565
|
-
if let Some(rivet_err) = err.chain().find_map(|x| x.downcast_ref::<RivetError>()) {
|
|
2566
|
-
let status = match (rivet_err.group(), rivet_err.code()) {
|
|
2567
|
-
("api", "not_found") => StatusCode::NOT_FOUND,
|
|
2568
|
-
("api", "unauthorized") => StatusCode::UNAUTHORIZED,
|
|
2569
|
-
("api", "forbidden") => StatusCode::FORBIDDEN,
|
|
2570
|
-
("guard", "rate_limit") => StatusCode::TOO_MANY_REQUESTS,
|
|
2571
|
-
("guard", "upstream_error") => StatusCode::BAD_GATEWAY,
|
|
2572
|
-
("guard", "routing_error") => StatusCode::BAD_GATEWAY,
|
|
2573
|
-
("guard", "request_timeout") => StatusCode::GATEWAY_TIMEOUT,
|
|
2574
|
-
("guard", "retry_attempts_exceeded") => StatusCode::BAD_GATEWAY,
|
|
2575
|
-
("guard", "actor_not_found") => StatusCode::NOT_FOUND,
|
|
2576
|
-
("guard", "actor_destroyed") => StatusCode::NOT_FOUND,
|
|
2577
|
-
("guard", "service_unavailable") => StatusCode::SERVICE_UNAVAILABLE,
|
|
2578
|
-
("guard", "actor_ready_timeout") => StatusCode::SERVICE_UNAVAILABLE,
|
|
2579
|
-
("guard", "no_route") => StatusCode::NOT_FOUND,
|
|
2580
|
-
_ => StatusCode::BAD_REQUEST,
|
|
2581
|
-
};
|
|
2582
|
-
|
|
2583
|
-
(status, ErrorResponse::from(rivet_err))
|
|
2584
|
-
} else if let Some(raw_err) = err
|
|
2585
|
-
.chain()
|
|
2586
|
-
.find_map(|x| x.downcast_ref::<RawErrorResponse>())
|
|
2587
|
-
{
|
|
2588
|
-
(raw_err.0, raw_err.1.clone())
|
|
2589
|
-
} else {
|
|
2590
|
-
(
|
|
2591
|
-
StatusCode::INTERNAL_SERVER_ERROR,
|
|
2592
|
-
ErrorResponse::from(&RivetError {
|
|
2593
|
-
schema: &rivet_error::INTERNAL_ERROR,
|
|
2594
|
-
meta: None,
|
|
2595
|
-
message: None,
|
|
2596
|
-
}),
|
|
2597
|
-
)
|
|
2598
|
-
};
|
|
2599
|
-
|
|
2600
|
-
let body_json = serde_json::to_vec(&error_response)?;
|
|
2601
|
-
let bytes = Bytes::from(body_json);
|
|
2602
|
-
|
|
2603
|
-
Response::builder()
|
|
2604
|
-
.status(status)
|
|
2605
|
-
.header(hyper::header::CONTENT_TYPE, "application/json")
|
|
2606
|
-
.body(ResponseBody::Full(Full::new(bytes)))
|
|
2607
|
-
.map_err(Into::into)
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
fn should_retry_request(res: &Result<Response<ResponseBody>>) -> bool {
|
|
2611
|
-
match res {
|
|
2612
|
-
Ok(resp) => should_retry_request_inner(resp.status(), resp.headers()),
|
|
2613
|
-
Err(err) => {
|
|
2614
|
-
if let Some(rivet_err) = err.chain().find_map(|x| x.downcast_ref::<RivetError>()) {
|
|
2615
|
-
rivet_err.group() == "guard" && rivet_err.code() == "service_unavailable"
|
|
2616
|
-
} else {
|
|
2617
|
-
false
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
1793
|
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
status == StatusCode::SERVICE_UNAVAILABLE && headers.contains_key(X_RIVET_ERROR)
|
|
2626
|
-
}
|
|
2627
|
-
|
|
2628
|
-
// Determine if a websocket error is retryable (e.g., transient UPS/tunnel issues)
|
|
2629
|
-
fn is_retryable_ws_error(err: &anyhow::Error) -> bool {
|
|
2630
|
-
if let Some(rivet_err) = err.chain().find_map(|x| x.downcast_ref::<RivetError>()) {
|
|
2631
|
-
rivet_err.group() == "guard" && rivet_err.code() == "websocket_service_unavailable"
|
|
2632
|
-
} else {
|
|
2633
|
-
false
|
|
2634
|
-
}
|
|
2635
|
-
}
|
|
2636
|
-
|
|
2637
|
-
pub fn is_ws_hibernate(err: &anyhow::Error) -> bool {
|
|
2638
|
-
if let Some(rivet_err) = err.chain().find_map(|x| x.downcast_ref::<RivetError>()) {
|
|
2639
|
-
rivet_err.group() == "guard" && rivet_err.code() == "websocket_service_hibernate"
|
|
2640
|
-
} else {
|
|
2641
|
-
false
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
fn str_to_close_frame(err: &str) -> CloseFrame {
|
|
2646
|
-
// NOTE: reason cannot be more than 123 bytes as per the WS protocol spec
|
|
2647
|
-
let reason = rivet_util::safe_slice(err, 0, 123).into();
|
|
2648
|
-
|
|
2649
|
-
CloseFrame {
|
|
2650
|
-
code: CloseCode::Error,
|
|
2651
|
-
reason,
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
fn err_to_close_frame(err: anyhow::Error, ray_id: Option<Id>) -> CloseFrame {
|
|
2656
|
-
let rivet_err = err
|
|
2657
|
-
.chain()
|
|
2658
|
-
.find_map(|x| x.downcast_ref::<RivetError>())
|
|
2659
|
-
.cloned()
|
|
2660
|
-
.unwrap_or_else(|| RivetError::from(&INTERNAL_ERROR));
|
|
2661
|
-
|
|
2662
|
-
let code = match (rivet_err.group(), rivet_err.code()) {
|
|
2663
|
-
("ws", "connection_closed") | ("ws", "eviction") => CloseCode::Normal,
|
|
2664
|
-
_ => CloseCode::Error,
|
|
2665
|
-
};
|
|
2666
|
-
|
|
2667
|
-
let reason = if let Some(ray_id) = ray_id {
|
|
2668
|
-
format!("{}.{}#{}", rivet_err.group(), rivet_err.code(), ray_id)
|
|
2669
|
-
} else {
|
|
2670
|
-
format!("{}.{}", rivet_err.group(), rivet_err.code())
|
|
2671
|
-
};
|
|
2672
|
-
|
|
2673
|
-
// NOTE: reason cannot be more than 123 bytes as per the WS protocol
|
|
2674
|
-
let reason = rivet_util::safe_slice(&reason, 0, 123).into();
|
|
2675
|
-
|
|
2676
|
-
CloseFrame { code, reason }
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
fn to_hyper_close(frame: Option<CloseFrame>) -> hyper_tungstenite::tungstenite::Message {
|
|
2680
|
-
if let Some(frame) = frame {
|
|
2681
|
-
// Manual conversion to handle different tungstenite versions
|
|
2682
|
-
let code_num: u16 = frame.code.into();
|
|
2683
|
-
let reason = frame.reason.clone();
|
|
2684
|
-
|
|
2685
|
-
tokio_tungstenite::tungstenite::Message::Close(Some(
|
|
2686
|
-
tokio_tungstenite::tungstenite::protocol::CloseFrame {
|
|
2687
|
-
code: code_num.into(),
|
|
2688
|
-
reason,
|
|
2689
|
-
},
|
|
2690
|
-
))
|
|
2691
|
-
} else {
|
|
2692
|
-
tokio_tungstenite::tungstenite::Message::Close(Some(
|
|
2693
|
-
tokio_tungstenite::tungstenite::protocol::CloseFrame {
|
|
2694
|
-
code: CloseCode::Normal,
|
|
2695
|
-
reason: "ws.closed".into(),
|
|
2696
|
-
},
|
|
2697
|
-
))
|
|
1794
|
+
pub fn remaining_tasks(&self) -> usize {
|
|
1795
|
+
self.state.tasks.remaining_tasks()
|
|
2698
1796
|
}
|
|
2699
1797
|
}
|