Encoding / Decoding

The HTTP spec defines a Content-Encoding header, which signifies whether the entity body of an HTTP message is “encoded” and, if so, by which algorithm. The only commonly used content encodings are compression algorithms.

Currently, Apache Pekko HTTP supports the compression and decompression of HTTP requests and responses with the gzip or deflate encodings. The core logic for this lives in the org.apache.pekko.http.scaladsl.coding package.org.apache.pekko.http.javadsl.coding.Coder enum class.

Server side

The support is not enabled automatically, but must be explicitly requested. For enabling message encoding/decoding with Routing DSL see the CodingDirectives.

Usually, it suffices to surround routes that should support response encoding by the encodeResponse directive, and routes that should support request decoding by the decodeRequest directive. Those directives will automatically and transparently enable support for encodings and negotiating which encoding to use out of the default encodings supported. The set of predefined MediaTypesMediaTypes contains a hint whether content of a given media type would benefit from compression.

Client side

There is currently no high-level or automatic support for decoding responses on the client-side.

The following example shows how to decode responses manually based on the Content-Encoding header:

Scala
sourceimport org.apache.pekko
import pekko.actor.ActorSystem
import pekko.http.scaladsl.Http
import pekko.http.scaladsl.coding.Coders
import pekko.http.scaladsl.model._, headers.HttpEncodings

import scala.concurrent.Future

implicit val system = ActorSystem()
implicit val ec: ExecutionContext = system.dispatcher

val http = Http()

val requests: Seq[HttpRequest] = Seq(
  "https://httpbin.org/gzip", // Content-Encoding: gzip in response
  "https://httpbin.org/deflate", // Content-Encoding: deflate in response
  "https://httpbin.org/get" // no Content-Encoding in response
).map(uri => HttpRequest(uri = uri))

def decodeResponse(response: HttpResponse): HttpResponse = {
  val decoder = response.encoding match {
    case HttpEncodings.gzip =>
      Coders.Gzip
    case HttpEncodings.deflate =>
      Coders.Deflate
    case HttpEncodings.identity =>
      Coders.NoCoding
    case other =>
      log.warning(s"Unknown encoding [$other], not decoding")
      Coders.NoCoding
  }

  decoder.decodeMessage(response)
}

val futureResponses: Future[Seq[HttpResponse]] =
  Future.traverse(requests)(http.singleRequest(_).map(decodeResponse))

futureResponses.futureValue.foreach { resp =>
  system.log.info(s"response is ${resp.toStrict(1.second).futureValue}")
}

system.terminate()
Java
sourceimport org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.http.javadsl.Http;
import org.apache.pekko.http.javadsl.coding.Coder;
import org.apache.pekko.http.javadsl.model.HttpRequest;
import org.apache.pekko.http.javadsl.model.HttpResponse;
import org.apache.pekko.http.scaladsl.model.headers.HttpEncodings;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.stream.Collectors;

public class HttpClientDecodingExampleTest {

  public static void main(String[] args) throws Exception {

    final ActorSystem system = ActorSystem.create();

    final List<HttpRequest> httpRequests =
        Arrays.asList(
            HttpRequest.create("https://httpbin.org/gzip"), // Content-Encoding: gzip in response
            HttpRequest.create(
                "https://httpbin.org/deflate"), // Content-Encoding: deflate in response
            HttpRequest.create("https://httpbin.org/get") // no Content-Encoding in response
            );

    final Http http = Http.get(system);

    final Function<HttpResponse, HttpResponse> decodeResponse =
        response -> {
          // Pick the right coder
          final Coder coder;
          if (HttpEncodings.gzip().equals(response.encoding())) {
            coder = Coder.Gzip;
          } else if (HttpEncodings.deflate().equals(response.encoding())) {
            coder = Coder.Deflate;
          } else {
            coder = Coder.NoCoding;
          }

          // Decode the entity
          return coder.decodeMessage(response);
        };

    List<CompletableFuture<HttpResponse>> futureResponses =
        httpRequests.stream()
            .map(req -> http.singleRequest(req).thenApply(decodeResponse))
            .map(CompletionStage::toCompletableFuture)
            .collect(Collectors.toList());

    for (CompletableFuture<HttpResponse> futureResponse : futureResponses) {
      final HttpResponse httpResponse = futureResponse.get();
      system
          .log()
          .info(
              "response is: "
                  + httpResponse.entity().toStrict(1000, system).toCompletableFuture().get());
    }

    system.terminate();
  }
}