The Crystal Programming Language Forum

How to convert IO to Slice(UInt8)?

I’m trying to use Kemal’s send_file to send a public file from S3. I’m loading the S3 file and it comes back as IO, though send_file requires a Slice(UInt8).

get "/my.pdf" do |env|
  HTTP::Client.get("https://xxxxxxxxx.s3.amazonaws.com/downloads/my.pdf") do |response|
    send_file env, response.body_io, "application/pdf"
  end
end

410 | send_file env, response.body_io, “application/pdf”
^--------
Error: no overload matches ‘send_file’ with types HTTP::Server::Context, IO+, String

Overloads are:

  • send_file(env : HTTP::Server::Context, path : String, mime_type : String | ::Nil = nil, *, filename : String | ::Nil = nil, disposition : String | ::Nil = nil)
  • send_file(env : HTTP::Server::Context, data : Slice(UInt8), mime_type : String | ::Nil = nil, *, filename : String | ::Nil = nil, disposition : String | ::Nil = nil)

Is there a way to convert the IO to a Slice(UInt8)? Or, is there a better way to do this? My goal is to stream the PDF to the browser client and not force an automatic download.

Incidentally, I had this working locally by just sending the file from src/public/downloads/my.pdf. However, that doesn’t work when deployed to Heroku, so I had to switch to hosting the file on S3.

I.e. you want the response body to consist of the contents of the pdf correct? If so you could prob just do like:

get "/my.pdf" do |env|
  HTTP::Client.get("https://xxxxxxxxx.s3.amazonaws.com/downloads/my.pdf") do |response|
    IO.copy response.body_io, env.response
  end
end

send_file is for file downloads, e.g. via Content-Disposition - HTTP | MDN.

1 Like

That works! I had to manually set the content-type too, since the default was text/html.

get "/my.pdf" do |env|
  env.response.headers["Content-Type"] = "application/pdf"
  HTTP::Client.get("https://xxxxxxxxx.s3.amazonaws.com/downloads/my.pdf") do |response|
    IO.copy response.body_io, env.response
  end
end

Thanks!

1 Like