Reputation: 1835
I'm trying to write some tooling for Crystal (specifically Kemal) where I can see if the response content type is text/html
and modify the response body thats has already been written to the HTTP::Response
before it is sent to the client by injecting an HTML element into the existing html response body.
I've noticed that HTTP::Server::Response
is write-only, but things like Gzip::Writer
are able to modify the body.
How can I modify the HTTP::Server::Response
html body before it is sent to the client?
Upvotes: 1
Views: 289
Reputation: 4857
It's written in Crystal, so let's just take a look at the source on how others do this.
Taking the CompressHandler
as an example, the basic idea is to replace the response's IO with something that allows the desired control:
context.response.output = Gzip::Writer.new(context.response.output, sync_close: true)
# ...
call_next(context)
So how can we make use of that to modify the response that's being written?
A naive (and slow) example would be to just keep hold of the original output and provide a IO::Memory
instead:
client = context.response.output
io = IO::Memory.new
context.response.output = io
call_next(context)
body = io.to_s
new_body = inject_html(body)
client.print new_body
Of course that would only work when this handler comes before any handler that turns the response into non-plaintext (like the above CompressHandler
).
A smarter solution would provide a custom IO implementation that just wraps the original IO, watching what's written to it and inject whatever it wants to inject at the right point. Examples of such wrapping IOs can be found at IO::Delimited
, IO::Sized
and IO::MultieWriter
among others, the pattern is really common to prevent unnecessary allocations.
Upvotes: 1