Skip to content

Smithy4s clients should set an appropriate Accept header #1863

@bpholt

Description

@bpholt

We've seen some issues making requests of specific server implementations when using smithy4s and @simpleRestJson, and I think it's because smithy4s isn't setting the Accept header when making requests. I was able to demonstrate this using this Smithy spec:

$version: "2"
namespace echo.headers
use alloy#simpleRestJson

map RequestHeaders {
    key: String
    value: String
}

structure Response {
    @required
    value: RequestHeaders
}

@http(method: "GET", uri: "/", code: 200)
operation GetRoot {
    input: Unit,
    output: Response
}

@simpleRestJson
service EchoHeaders {
    operations: [GetRoot]
}

and demo app:

package echo.headers

import cats._
import cats.data.OptionT
import cats.effect.{Trace => _, _}
import cats.mtl.Local
import cats.syntax.all._
import com.comcast.ip4s._
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.Server
import org.http4s.syntax.all._
import org.http4s.{Headers, HttpRoutes, Uri}
import smithy4s.http4s.SimpleRestJsonBuilder

class EchoHeadersImpl[F[_] : Functor](requestHeaders: F[Headers]) extends EchoHeaders[F] {
  override def getRoot(): F[Response] =
    requestHeaders
      .map {
        _.headers
          .map { h =>
            h.name.toString -> h.value
          }
          .toMap
      }
      .map(Response(_))
}

class RequestHeaderProvidingMiddleware[F[_] : Monad](implicit L: Local[F, Headers]) extends (HttpRoutes[F] => HttpRoutes[F]) {
  override def apply(routes: HttpRoutes[F]): HttpRoutes[F] =
    HttpRoutes[F] { request =>
      Local[OptionT[F, *], Headers].scope {
        routes(request)
      }(request.headers)
    }
}

object EchoHeadersApp extends IOApp.Simple {
  private def server: Resource[IO, Server] =
    IO.local(Headers.empty)
      .toResource
      .flatMap { implicit l: Local[IO, Headers] =>
        SimpleRestJsonBuilder
          .routes(new EchoHeadersImpl(l.ask))
          .resource
          .map(new RequestHeaderProvidingMiddleware[IO])
      }
      .map(_.orNotFound)
      .flatMap {
        EmberServerBuilder
          .default[IO]
          .withHost(host"localhost")
          .withPort(port"0")
          .withHttpApp(_)
          .build
      }

  private def client(uri: Uri): Resource[IO, EchoHeaders[IO]] =
    EmberClientBuilder
      .default[IO]
      .build
      .flatMap {
        SimpleRestJsonBuilder(echo.headers.EchoHeaders)
          .client(_)
          .uri(uri)
          .resource
      }

  override def run: IO[Unit] =
    server
      .map(_.baseUri)
      .flatMap(client)
      .use(_.getRoot().flatMap(IO.println))
}

Running the app emits the following to the console:

15:09:46.674 [io-compute-1] INFO org.http4s.ember.server.EmberServerBuilderCompanionPlatform -- Ember-Server service bound to address: 127.0.0.1:49462
Response(Map(Host -> 127.0.0.1:49462, Date -> Tue, 16 Dec 2025 21:09:46 GMT, Connection -> keep-alive, User-Agent -> http4s-ember/0.23.33))

Note the absence of an Accept header, which I was expecting to be present and set to Accept: application/json.

This doesn't cause issues when using a smithy4s server, but some servers are stricter and refuse to handle requests without content negotiation via the Accept header.

Tested with Smithy4s 0.18.45.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions