{"id":20834942,"url":"https://github.com/dvgica/arrivals","last_synced_at":"2025-05-08T02:22:50.252Z","repository":{"id":56062615,"uuid":"142707563","full_name":"dvgica/arrivals","owner":"dvgica","description":"API gateway with Akka HTTP","archived":false,"fork":false,"pushed_at":"2020-11-27T17:38:00.000Z","size":1502,"stargazers_count":52,"open_issues_count":1,"forks_count":8,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-03-31T16:31:43.755Z","etag":null,"topics":["akka-http","api-gateway","bff","pagerduty","scala"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dvgica.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-28T20:20:42.000Z","updated_at":"2023-07-01T03:58:21.000Z","dependencies_parsed_at":"2022-08-15T12:30:41.335Z","dependency_job_id":null,"html_url":"https://github.com/dvgica/arrivals","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dvgica%2Farrivals","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dvgica%2Farrivals/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dvgica%2Farrivals/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dvgica%2Farrivals/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dvgica","download_url":"https://codeload.github.com/dvgica/arrivals/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252984933,"owners_count":21835884,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["akka-http","api-gateway","bff","pagerduty","scala"],"created_at":"2024-11-18T00:21:56.779Z","updated_at":"2025-05-08T02:22:50.152Z","avatar_url":"https://github.com/dvgica.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Arrivals\n\nThis open-source project provides building blocks for constructing an API Gateway using Akka HTTP. [PagerDuty](https://www.pagerduty.com) has [used it](https://www.youtube.com/watch?v=DRxLFWmvJ8A) in production to build a few [BFFs](https://samnewman.io/patterns/architectural/bff/). Currently, the project is focused on the following areas:\n\n - Proxying HTTP requests to upstream services\n - Authenticating those requests\n - Adding a header to prove authentication to upstream services\n - Filtering and transforming requests and responses\n - Turning a single request into multiple upstream requests, and aggregating the responses into a single response\n\nAs much as possible, Arrivals follows idioms found in Akka HTTP. This means that it exposes `Route`s and `Directive`s to the library user which can be combined with other Akka HTTP-based code.\n\n- [Example Application](#example-application)\n- [Usage](#usage)\n  - [Installation](#installation)\n  - [Introduction and Setup](#introduction-and-setup)\n  - [Routes](#routes)\n  - [Filters](#filters)\n- [API Docs](#api-docs)\n- [License](#license)\n- [Contributing](#contributing)\n- [Contact](#contact)\n- [TODO](#todo)\n\n## Example Application\n\nFor the impatient, an example API Gateway using Arrivals is available in [arrivals-example](https://github.com/PagerDuty/arrivals/blob/master/arrivals-example/src/main/scala/com/pagerduty/arrivals/example/ExampleApp.scala). The example can be run by cloning this repository and running `sbt arrivalsExample/run`. Try the following URLs:\n\n- http://localhost:8080/cats\n- http://localhost:8080/dogs\n- http://localhost:8080/dogs?username=rex\n- http://localhost:8080/all?username=rex\n\nFor more details on what's happening, keep reading.\n\n## Usage\n\n### Installation\n\nAll artifacts are available at the PagerDuty Bintray OSS repository.\n\nAdd the PD Bintray to your resolvers with the following:\n\n```\nresolvers += \"bintray-pagerduty-oss-maven\" at \"https://dl.bintray.com/pagerduty/oss-maven\"\n```\n\n### Configuration\n\nConfig is done via standard Akka HTTP config. An [example `application.conf`](https://github.com/PagerDuty/arrivals/blob/master/arrivals-example/src/main/resources/application.conf) can be found in the example app, with some explanation of what config values are important for an API Gateway.\n\n#### arrivals\n\nThis is the implementation artifact on which applications should depend.\n\n```\n\"com.pagerduty\" %% \"arrivals\" % arrivalsVersion\n```\n\n#### arrivals-api\n\nAuthors of custom implementations (e.g. `Filter`s, `Upstream`s, and `Aggregator`s) which live in a library\n should depend on this artifact, which will hopefully change less frequently.\n\n```\n\"com.pagerduty\" %% \"arrivals-api\" % arrivalsVersion\n```\n\n### Introduction and Setup\n\nArrivals functionality is provided via Akka HTTP `Route`s available in various `object`s or `class`es. These `Route`s\nfunction like any other Akka HTTP route, meaning they can be composed with other `Route`s from Akka and served with the usual\ncall to `Http().bindAndHandle`.\n\nRead more about the Akka Routing DSL [here](https://doc.akka.io/docs/akka-http/current/routing-dsl/index.html).\n\n#### Initialize `ArrivalsContext`\n\nAll Arrivals routes have an `implicit` dependency on an `ArrivalsContext`.\n\n``` scala\nimplicit val system = ActorSystem()\nimplicit val arrivalsCtx = ArrivalsContext(\"localhost\") // \"localhost\" is the hostname for all upstreams in this example\n```\n\nYou must provide an `AddressingConfig`, which is a piece of data used by the proxy to address requests to an\n`Upstream`. For example, it might be the hostname of a load balancer obtained dynamically at runtime from a container scheduler. In the example above, the `AddressingConfig` is just the string `\"localhost\"`.\n\nIn the event that you do not require this data, you can pass `Unit`.\n\nBecause everything in Arrivals is Akka-based, you must implicitly provide the usual Akka `ActorSystem`.\nA `Metrics` provider is optional and defaults to a no-op metrics implementation.\n\n#### Declare an `Upstream`\n\nArrivals requests need somewhere to be proxied. This is called an `Upstream`. Here's an example of a simple upstream\nthat lives on the host provided by `AddressingConfig` at a specific port (1234 in this case):\n\n``` scala\ncase object FooService extends Upstream[String] {\n  val metricsTag = \"foo\"\n  def addressRequest(request: HttpRequest, addressingConfig: String): HttpRequest = {\n    val newUri =\n        request.uri\n          .withAuthority(Authority(Uri.Host(addressingConfig), 1234))\n          .withScheme(\"http\")\n    request.withUri(newUri)\n  }\n}\n```\n\n#### Declare a `Route`\n\nThen, declare a `Route`. Here we use `prefixProxyRoute` (discussed further below):\n\n``` scala\nimport com.pagerduty.arrivals.impl.proxy.ProxyRoutes._\n\nval route = prefixProxyRoute(\"foos\", FooService)\n```\n\n#### Start the Akka HTTP Server\n\nFinally, start the Akka HTTP server as you normally would:\n\n``` scala\nval binding = Http().bindAndHandle(route, \"0.0.0.0\", 8080)\n```\n\nYour proxy server is now running. Keep reading to see what else Arrivals can do for you.\n\n### Routes\n\n#### Proxy Routes\n\nThe `ProxyRoutes` object provides routes to proxy requests to an `Upstream`. No authentication is done. These routes are:\n\n - `prefixProxyRoute` - proxy any request matching the given path prefix\n - `proxyRoute` - proxy all requests (this is usually nested inside other Akka HTTP directives to narrow the scope, or used as a deliberate catch-all at the end of a series of routes)\n\nThese methods are overloaded with various combinations of parameters related to [`Filter`](#filters)s.\n\n#### Auth Proxy Routes\n\nThe `AuthProxyRoutes` class provides routes to proxy requests to an `Upstream`, optionally adding a custom header to any request\nthat is authenticated. **Requests are proxied regardless of whether authentication or authorization succeeded!** Upstream services\nshould always verify the authentication header (e.g. via cryptographic signing) for routes that require authentication.\n\n`AuthProxyRoutes` have an additional dependency on a `HeaderAuthConfig` which describes how to authenticate requests, check permissions,\nand add a custom header if the request passes authentication and authorization. This `HeaderAuthConfig` is provided as an argument to the\n`AuthProxyRoutes` constructor. After construction, the routes can be imported in the typical Akka HTTP style:\n\n``` scala\nval headerAuthConfig = new HeaderAuthConfig { /* ... */ }\nval authProxyRoutes = new AuthProxyRoutes(headerAuthConfig)\n\nimport authProxyRoutes._\n\nval route = prefixAuthProxyRoute(\"bar\", FooService)\n```\n\nSimilar to `ProxyRoutes`, both `prefixAuthProxyRoute` and `authProxyRoute` methods are provided in various permutations to allow for [`Filter`](#filters)s.\n\n#### Aggregator Routes\n\nThe `AggregatorRoutes` class provides routes fulfilled by `Aggregator`s. An `Aggregator` is an entity that, based on an incoming request,\nexecutes multiple waves of user-defined upstream requests, and then allows the user to build a single response from the upstream responses.\n\n`AggregatorRoutes`, like `AuthProxyRoutes`, has a dependency on `HeaderAuthConfig`:\n\n``` scala\nval headerAuthConfig = new HeaderAuthConfig { /* ... */ }\nval aggregatorRoutes = new AggregatorRoutes(headerAuthConfig)\n\ncase object BazAggregator extends Aggregator { /* ... */ }\n\nimport aggregatorRoutes._\n\nval route = prefixAggregatorRoute(\"baz\", BazAggregator)\n```\n\nSimilar to `ProxyRoutes` and `AuthProxyRoutes`, both `prefixAggregatorRoute` and `aggregatorRoute` methods are provided in various permutations to allow for [`Filter`](#filters)s.\n\nFor more details on how to construct an `Aggregator`, please see the [API docs](https://pagerduty.github.io/arrivals/api/com/pagerduty/arrivals/api/aggregator/Aggregator.html) or see the [example app aggregator](https://github.com/PagerDuty/arrivals/blob/master/arrivals-example/src/main/scala/com/pagerduty/arrivals/example/ExampleAggregator.scala).\n\n### Filters\n\nFilters allow for user-defined changes to requests before they are proxied, or responses before they are returned to the client.\n\nAll filters are provided with `RequestData`, a user-defined type, but when used with the `Routes` defined\nby Arrivals this type is set to something specific:\n\n- `ProxyRoutes`: `Unit`\n- `AuthProxyRoutes`: `Option[AuthData]`\n- `AggregatorRoutes`: `AuthData`\n\n`AuthData` is a user-defined type in `AuthenticationConfig`. Users wishing to pass arbitrary data to a `Filter` should use the lower-level `FilterDirectives`.\n\n#### Request Filters\n\nRequest filters can either transform a request into a new one, or short-circuit the rest of the filter/proxy/aggregation steps and\nimmediately return a response.\n\n``` scala\nobject RateLimitRequestFilter extends RequestFilter[Option[UserId]] {\n  def apply(request: HttpRequest, optUserId: Option[UserId]): Future[Either[HttpResponse, HttpRequest]] = {\n    optUserId match {\n      case Some(uId) =\u003e\n        hasUserReachedRateLimit(uId).map { reachedLimit =\u003e\n          if (reachedLimit) {\n            Left(HttpResponse(StatusCodes.EnhanceYourCalm))\n          } else {\n            Right(request.addHeader(RawHeader(\"X-Rate-Limit-Checked\", \"true\")))\n          }\n        }\n      case None =\u003e\n        Future.successful(Left(HttpResponse(StatusCodes.Forbidden, \"This rate-limited endpoint requires auth!\")))\n    }\n  }\n\n  private def hasUserReachedRateLimit(userId: UserId): Future[Boolean] = { /* ... */ }\n}\n```\n\n#### Response Filters\n\nResponse filters simply transform the outgoing response. Unlike `RequestFilter`s, they are not able to complete the request or short-circuit following filters.\n\nAny `Filter` that doesn't use its `RequestData` should specify `Any` for the type parameter.\n\n``` scala\nobject AddCacheControl extends ResponseFilter[Any] {\n  def apply(request: HttpRequest, response: HttpResponse, data: Any): Future[HttpResponse] = {\n    Future.successful(response.addHeader(Cache-Control(no-store))\n  }\n}\n```\n\n#### Specialized/Simplified Filters\n\nSometimes, as above, a simpler filter signature will suffice. Various specializations of `RequestFilter` and `ResponseFilter`\nexist in `com.pagerduty.arrivals.api.filter`, for example:\n\n``` scala\nobject AddCacheControl extends SyncResponseFilter[Any] {\n  def applySync(request: HttpRequest, response: HttpResponse, data: Any): HttpResponse = {\n    response.addHeader(Cache-Control(no-store)\n  }\n}\n```\n\n#### Filter Composition/Chaining\n\nFilters can be composed such that the output of one is fed to the input of the next. This is accomplished by mixing in the\n`ComposableRequestFilter` or `ComposableResponseFitler` traits.\n\n``` scala\nobject FilterOne extends SyncRequestFilter[Any] with ComposableRequestFilter[Any] { /* ... */ }\n\nobject FilterTwo extends RequestFilter[String] { /* ... */ }\n\nimport ExecutionContext.Implicits.global // don't just copy-paste this ExecutionContext please!\nval newFilter: ComposableRequestFilter[String] = FilterOne ~\u003e FilterTwo\n```\n\nAn arbitrary number of filters may be composed.\n\n## API Docs\n\nSee [pagerduty.github.io/arrivals/](https://pagerduty.github.io/arrivals/).\n\n## License\n\nCopyright 2019, PagerDuty, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this work except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n## Contributing\n\nContributions are welcome in the form of pull-requests based on the master branch.\n\nWe ask that your changes are consistently formatted as the rest of the code in this repository, and also that any changes are covered by unit tests.\n\n## Contact\n\nThis library is maintained by the Core team at PagerDuty. Opening a GitHub issue is the best way to get in touch with us.\n\n## TODO\n\n- Metadata logging is inconsistently used because it's a PITA - would be nice to do something less ugly and not include `akka-http-support` in `arrivals-api`\n- De-couple authentication and authorization\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdvgica%2Farrivals","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdvgica%2Farrivals","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdvgica%2Farrivals/lists"}