Add the sse-websocket module
This commit is contained in:
83
sse-websocket/docs/03-the-payload.md
Normal file
83
sse-websocket/docs/03-the-payload.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# 3. The payload, and a media type that does less than you think
|
||||
|
||||
Previous: [2. The SSE lifecycle](02-sse-lifecycle.md) · Next: [4. STOMP](04-stomp.md)
|
||||
|
||||
---
|
||||
|
||||
The advice you will find is: always pass a media type to `data(..)`, or your object is written
|
||||
with `toString()`. Both halves of that turn out to be wrong on Boot 4.1, and the mechanism
|
||||
underneath explains a real trap that the advice misses.
|
||||
|
||||
## What actually happens
|
||||
|
||||
`SseEmitter.send(Object data, MediaType mediaType)` walks the application's configured
|
||||
`HttpMessageConverter` list and uses **the first converter whose `canWrite(type, mediaType)`
|
||||
returns true**. The media type argument is a *filter*, not a preference. A `null` media type
|
||||
filters nothing, so the first converter that can write the type at all wins.
|
||||
|
||||
`/diag` prints the list in order:
|
||||
|
||||
```
|
||||
ByteArrayHttpMessageConverter
|
||||
StringHttpMessageConverter
|
||||
ResourceHttpMessageConverter
|
||||
ResourceRegionHttpMessageConverter
|
||||
AllEncompassingFormHttpMessageConverter
|
||||
JacksonJsonHttpMessageConverter
|
||||
Jaxb2RootElementHttpMessageConverter
|
||||
```
|
||||
|
||||
Two consequences, both measured by
|
||||
[`SseDataConversionTest`](../src/test/java/com/ankurm/ssews/SseDataConversionTest.java)
|
||||
([`sse-payload-conversion.txt`](output/sse-payload-conversion.txt)):
|
||||
|
||||
**For a record or POJO, the media type changes nothing.** No converter ahead of Jackson claims it,
|
||||
so Jackson writes it either way:
|
||||
|
||||
```
|
||||
=== SseEmitter.event().data(record, APPLICATION_JSON) ===
|
||||
{"seq":7,"host":"node-a","cpu":0.42,"heapMb":512,"at":"2026-09-03T10:00:00Z"}
|
||||
=== SseEmitter.event().data(record) [no media type] ===
|
||||
{"seq":7,"host":"node-a","cpu":0.42,"heapMb":512,"at":"2026-09-03T10:00:00Z"}
|
||||
=== POJO: identical? true ===
|
||||
```
|
||||
|
||||
**For a `String`, the media type *still* changes nothing** — and this is the part that
|
||||
surprised me enough to rewrite the test. `StringHttpMessageConverter` supports `MediaType.ALL` and
|
||||
sits ahead of Jackson, so it claims the write even when you explicitly ask for
|
||||
`application/json`:
|
||||
|
||||
```
|
||||
=== String payload, no media type -> {"already":"json"}
|
||||
=== String payload, APPLICATION_JSON -> {"already":"json"}
|
||||
```
|
||||
|
||||
There is no way to make `send()` JSON-quote a `String`. Good news if you are streaming
|
||||
pre-rendered JSON — it goes out verbatim, not double-encoded. A trap if you assumed a
|
||||
`String` field would be escaped for you: it will not be, and a payload containing a newline
|
||||
becomes two `data:` lines rather than one.
|
||||
|
||||
## Jackson 3, and the `Instant` that proves it
|
||||
|
||||
`"at":"2026-09-03T10:00:00Z"` — an ISO-8601 string, not an epoch number. Boot 4.1.1 manages
|
||||
Jackson **3.1.5** under `tools.jackson`, and `JacksonJsonHttpMessageConverter` is the Jackson 3
|
||||
converter. The Jackson 2 sibling (`MappingJackson2HttpMessageConverter`) is still on the
|
||||
classpath in the messaging stack.
|
||||
|
||||
**Rule for Boot 4: a `2` in a Spring class name means the *previous* Jackson.** The unnumbered
|
||||
name is current, which is the opposite of the convention you would guess, and it is the same fork
|
||||
that catches people in [`../kafka-basics`](../kafka-basics/README.md) and
|
||||
[`../rabbitmq`](../rabbitmq/README.md). For STOMP it is
|
||||
`JacksonJsonMessageConverter` you want, not `MappingJackson2MessageConverter`.
|
||||
|
||||
## Practical guidance
|
||||
|
||||
- Pass `MediaType.APPLICATION_JSON` anyway. It costs nothing, it documents intent, and it makes
|
||||
the code correct against a converter list you did not configure yourself.
|
||||
- Do not stream a `String` and assume it is escaped. Send the object.
|
||||
- Keep events small. Every subscriber pays for one serialisation per event; see
|
||||
[6. Choosing](06-choosing.md).
|
||||
|
||||
---
|
||||
|
||||
Previous: [2. The SSE lifecycle](02-sse-lifecycle.md) · Next: [4. STOMP](04-stomp.md)
|
||||
Reference in New Issue
Block a user