Skip to content
David An edited this page Jan 15, 2021 · 2 revisions

Passthroughs and Entities

--> You cannot use Orch's passthrough directive if you also extract the request into a custom class via entity(as[...]) in the same route! <--

Now that you've read through the above Akka docs, here's why the entity and passthrough directives are incompatible ...

The two entity types we are concerned with for the majority of our APIs are Strict and Default. The others - Chunked, CloseDelimited, IndefiniteLength - concern very large request streams, such as file uploads. Strict and Default are used for requests with a defined size, i.e. that contain a Content-Length request header. The overwhelming majority of our APIs handle defined-size requests; that is, the caller has fully calculated its payload before making a request to Orch.

When receiving an incoming request payload, Akka will create its RequestEntity as Strict if and only if the full entity is "already available in memory"; else it will create a Default entity backed by an akka stream of bytes. The same user request, if repeated, can and will be interpreted as Strict or Default seemingly unpredictably. This is especially true for "large" requests, where the definition of "large" is "bordering on the size of akka's currently free buffers". In practice, these buffers grow and shrink based on other concurrent requests and akka internals. You should never assume you will be working with either a Strict or a Default entity.

Strict entities, because they reside as a materialized class fully in memory, can be read by code multiple times. Default entities, because they are a stream, can only be read once. If you attempt to read it more than once, you will get the dreaded Substream Source cannot be materialized more than once error.

Both the entity and the passthrough directive require the ability to read the RequestEntity. Thus, you will get an error if you use both directives against a Default entity ... and you have no guarantee that your entity will be Strict or Default.

If your route requires both entity-extraction and passthrough functionality, choose one of these approaches instead:

  1. Recommended: Extract the entity using entity(as[...]), then call the underlying service via userAuthedRequest or a custom DAO, passing only the class you extracted. This delegates the Strict vs. Default logic to akka, which handles it internally to entity(as[...]). Then, we never touch the request object or its entity again; we only use the class to which we extracted the entity. See https://github.com/broadinstitute/firecloud-orchestration/pull/830/files for an example of this.
  2. Force the entity to Strict via [toStrictEntity](https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/basic-directives/toStrictEntity.html). Wrap your route in toStrictEntity, which forces Default entities to Strict and makes them safe to use. This has internal akka safeguards so it has some protection, but is less ideal: it brings the entire entity into memory before extracting to a custom class. Using option 1 instead allows akka to implement streamed casting in the future, even if they are not doing it now. See https://github.com/broadinstitute/firecloud-orchestration/pull/823/files for an example of this.
Clone this wiki locally