My pseudo-code would look like this:
get "/something/:seed" do |env|
res = HTTP::Client.get(url)
env.response.status_code = res.status_code
env.response.headers.merge! res.headers
env.response.body = res.body_io?
end
I’m basically implementing a reverse proxy for a single API endpoint. I want to pass the body from HTTP::Client with minimal CPU/memory/GC impact. Basically piping it as-is. Currently I am getting:
12 | env.response.body = res.body_io?
^---
Error: undefined method 'body=' for HTTP::Server::Response
HTTP::Server::Response trace:
src/routes/img.cr:12
env.response.body = res.body_io?
and am not really sure the best way to proceed. Using something external to crystal like nginx is not an option for reasons. Using kemal if that matters. The data being passed through is going to be binary and sometimes XML.
Try like
get "/something/:seed" do |env|
HTTP::Client.get(url) do |res|
env.response.status_code = res.status_code
env.response.headers.merge! res.headers
IO.copy res.body_io, env.response
end
end
this didn’t work,
IO.copy res.body_io, env.response.body
resulted in:
Exception: HTTP::Client::Response#body_io cannot be nil (NilAssertionError)
from /usr/share/crystal/src/http/client/response.cr:9:3 in 'body_io'
from src/routes/img.cr:17:11 in '->'
[...]
But I looked further into the IO class and found that these 2 lines seem both seem to work, I’m assuming that at least the second one actually does the efficient piping.
env.response.print res.body
env.response << res.body
Thanks! (I’ll worry about if it’s actually maximally efficient later).
Those two would work, but you’re not streaming at that point. That would load the contents of the request into memory, just to write it back to the response IO.
It seems the body_io
was nil
in this case. Try this:
get "/something/:seed" do |env|
HTTP::Client.get(url) do |res|
env.response.status_code = res.status_code
env.response.headers.merge! res.headers
if b_io = res.body_io?
IO.copy b_io, env.response
end
end
end
That ended up sending empty responses.
body_io
and body
are effectively mutually exclusive in an HTTP::Client::Response
.
You get the body_io
when you pass a block to get
(or whichever HTTP method) and you get body
when you don’t.
require "uri"
require "http"
uri = URI.parse("http://localhost:8080/")
HTTP::Client.get(uri).body
HTTP::Client.get(uri) do |response|
response.body_io.gets_to_end
end
If you call body
from inside the block, it’ll be an empty string. If you call body_io
without the block you get the exception.
I guess we could always introduce two types for this: StreamingResponse
and NonStreamingResponse
, both would inherit from a Response
(just for reuse purposes). Then there would be no confusion… maybe?