ServerSentEvent support for data being a multi-line string and possible other ServerSentEvent enhancements
See original GitHub issueFor server sent events data can be multi-line and in that case multiple data:
lines should be sent as outlined here https://html.spec.whatwg.org/multipage/server-sent-events.html
The current implementation only sends a single data:
line followed by the entire data string. So if that data string
final case class ServerSentEvent(
data: String,
eventType: Option[String] = None,
id: Option[EventId] = None,
retry: Option[Long] = None
) extends Renderable {
def render(writer: Writer): writer.type = {
writer << "data: " << data << "\n"
eventType.foreach(writer << "event: " << _ << "\n")
id match {
case None =>
case Some(EventId.reset) => writer << "id\n"
case Some(EventId(id)) => writer << "id: " << id << "\n"
}
retry.foreach(writer << "retry: " << _ << "\n")
writer << "\n"
}
override def toString: String =
s"ServerSentEvent($data,$eventType,$id,$retry)"
}
Here is the fix we are using in production noting it does a few more things than just fix the multi-line data issue that are worth considering i.e. comments and data is not required which allow for being a more complete sse implementation.
If these look good (or just the data fix only) I am happy to put together an MR. Noting sending comment lines is the idiomatic ping for sse so it is critical for any robust real world implementation of SSE.
data changing from String to Option[String] is likely a breaking change but a worthwhile one to do now to get a complete ServerSentEvent implementation. The change in http4s’s internals appears trivial (only a few places) clients using it though will break so it may be best to time that change with a larger version change.
final case class ServerSentEvent(
data: Option[String] = None,
comment: Option[String] = None,
eventType: Option[String] = None,
id: Option[EventId] = None,
retry: Option[FiniteDuration] = None
) extends Renderable {
def render(writer: Writer): writer.type = {
data.foreach ( _.linesIterator.foreach { dataLine =>
writer << "data: " << dataLine << "\n"
})
comment.foreach(_.linesIterator.foreach { commentLine =>
writer << ": " << commentLine << "\n"
})
eventType.foreach(writer << "event: " << _ << "\n")
id match {
case None =>
case Some(EventId.reset) => writer << "id\n"
case Some(EventId(id)) => writer << "id: " << id << "\n"
}
retry.foreach(writer << "retry: " << _.toMillis << "\n")
writer << "\n"
}
override def toString: String =
s"ServerSentEvent($data,$eventType,$id,$retry)"
}
thoughts !?!?!
Issue Analytics
- State:
- Created 2 years ago
- Comments:8 (4 by maintainers)
Top GitHub Comments
Now that we have added comment to ServerSentEvent we will need to apply the multi-line fix to that as well.
Noting this is mainly waiting on https://github.com/typelevel/fs2/pull/2418 to work it’s way out. As one can’t properly decode a server sent events response / protocol without the actual line endings. The fact that we assume a line is terminated by \n without really knowing causes some corner cases in the unit tests that will get cleaned up if/when we switch to the ultimate fs2.text.linesWithEndings solution
Mostly done in #4888, but leaving open to track the nuance of preserving carriage returns.