softwaremill / softwaremill/tapir

`SttpClientInterpreter` does not propagate headers in the http response for websockets

Open
#3,959 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Scala
Stars
1.5k
Forks
468
Avg merge
5h 37m
Merged PRs (30d)
34

Description

We are using tapir interceptor to enrich server responses with some contextual information (e.g. correlation id):

private val serverOptions = Http4sServerOptions
  .customiseInterceptors[IO]
  .prependInterceptor(LoggingContextInterceptor)
  .options

/** Tapir interceptor which extracts correlation ID from the request headers and puts correlation ID into Http response header. If
  * correlation ID is not present in the HTTP header, a new one is generated.
  */
object LoggingContextInterceptor extends RequestInterceptor[IO] 

On the client side, we wrap tapir interpreter to extract correlation id from the response header and lift the original

def SttpClientInterpreter#toClientThrowErrors: I => F[O]

to

def SttpClientInterpreterWithRandomCorrelationId#toClientThrowErrors: I => ResponseWithContext[F[O]]

/** Container holding
 *   - http response (e.g. tapir high-level case class),
 *   - logging context of the response (e.g correlation id extracted from http headers)
 *
 * Captured logging context allows to match server-side and client-side log (by correlation id).
 */
case class ResponseWithContext[A](ctx: LoggingContext, response: A)

In case of rest calls the wrapped tapir client properly extracts correlation id from the response.

In case of websockets, correlation id header is missing - the underlying SttpClientInterpreter returns a response without the header. A low-level sttp websocket client does receive the correlation id:

***** sttp websocket client
[2024-07-26 14:49:47,353] INFO  [io-compute-8] TapirWebSocketsCorrelationId:37 - [cid=XTM-QVL-HTR] sttp ws request
[2024-07-26 14:49:47,694] INFO  [io-compute-14] LoggingContextInterceptor:44 - RequestMetadata(GET,/ping,List(x-correlation-id: XTM-QVL-HTR, accept-encoding: gzip, deflate, upgrade: websocket, connection: upgrade, sec-websocket-key: C2/p0NQzhCMWpTQO5jXngQ==, sec-websocket-version: 13, origin: http://localhost:8081, host: localhost:8081, accept: */*, user-agent: AHC/2.1)), List(sttp.tapir.server.ServerEndpoint$$anon$3@249b2b39)
[2024-07-26 14:49:47,719] INFO  [io-compute-14] LoggingContextInterceptor:52 - response: Response(ServerResponse(200,Vector(x-correlation-id: XTM-QVL-HTR)))
[2024-07-26 14:49:47,741] INFO  [io-compute-22] TapirWebSocketsCorrelationId:51 - [cid=XTM-QVL-HTR] Sending 1
[2024-07-26 14:49:47,782] INFO  [io-compute-6] TapirWebSocketsCorrelationId:53 - [cid=XTM-QVL-HTR] Received 1
[2024-07-26 14:49:47,795] INFO  [io-compute-19] TapirWebSocketsCorrelationId:80 - [cid=unset] 

***** tapir websocket client
[2024-07-26 14:49:47,827] INFO  [io-compute-2] SttpClientInterpreterWithRandomCorrelationId:25 - [cid=LZE-JTD-WQP] tapir ws request
[2024-07-26 14:49:47,855] INFO  [io-compute-20] LoggingContextInterceptor:44 - RequestMetadata(GET,/ping,List(Connection: Upgrade, Host: localhost:8081, Upgrade: websocket, User-Agent: Java-http-client/21.0.3, Accept-Encoding: gzip, deflate, Sec-WebSocket-Key: sZ/mmEyrbVLoResXK3uMnA==, Sec-WebSocket-Version: 13, x-correlation-id: LZE-JTD-WQP)), List(sttp.tapir.server.ServerEndpoint$$anon$3@249b2b39)
[2024-07-26 14:49:47,856] INFO  [io-compute-20] LoggingContextInterceptor:52 - response: Response(ServerResponse(200,Vector(x-correlation-id: LZE-JTD-WQP)))
[2024-07-26 14:49:47,870] INFO  [io-compute-0] SttpClientInterpreterWithRandomCorrelationId:28 - [cid=unset] Response(sttp.tapir.client.sttp.ws.fs2.WebSocketToFs2Pipe$$Lambda/0x000071f2346ac678@70e0de77,101,,List(),List(),RequestMetadata(GET,ws://localhost:8081/ping,Vector(Accept-Encoding: gzip, deflate, x-correlation-id: LZE-JTD-WQP)))
[2024-07-26 14:49:47,875] INFO  [io-compute-12] TapirWebSocketsCorrelationId:64 - [cid=unset] Sending 1
[2024-07-26 14:49:47,882] INFO  [io-compute-1] TapirWebSocketsCorrelationId:66 - [cid=unset] Received 1

Repo with the code: https://github.com/kamilkloch/tapir-correlation-id

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by examining SttpClientInterpreter#toClientThrowErrors and the websocket client path, then compare their response handling with regular REST calls. Use the tapir-correlation-id reproduction repository to confirm the response header is available and identify where it is lost; done means websocket responses expose the correlation-id header to the wrapper.

Written by the indexing model from the issue text.

Assessment

Tech stack
scala
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.