rubyonjets/jets

Unable to dynamically generate file once deployed

Closed this issue · 2 comments

Software Version
Operating System macOS Mojave
Jets 1.9.30
Ruby 2.5.3

Expected Behaviour

I'm expecting to be able to render binary content dynamically and serve it as a file.

Current Behavior

Locally this works just fine. I get this

image

However, once deployed instead of returning binary data it's returning base64 encoded data.

image

The differences are a little more clear when I run a console command

Local

HTTP/1.1 200 OK
Content-Type: image/png
Transfer-Encoding: chunked
X-Runtime: 0.048170
x-jets-base64: yes



+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+

Amazon

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 200
Content-Type: image/png
Date: Fri, 19 Jul 2019 18:41:53 GMT
Via: 1.1 8918721f9949345e08455e61518a59ed.cloudfront.net (CloudFront)
X-Amz-Cf-Id: MUNmNGVKbNkoPFZnE-N0LDy8MKS-Tp-9qJAmsl6FFglRt8gHkR38Eg==
X-Amz-Cf-Pop: IAD79-C1
X-Amzn-Trace-Id: Root=1-5d320ef0-482dda58bf0d8278e323c530;Sampled=0
X-Cache: Miss from cloudfront
X-Runtime: 0.045916
x-amz-apigw-id: dFdFpEwaoAMFlwg=
x-amzn-RequestId: e060b6a1-aa54-11e9-aa26-4b05e05efc18
x-jets-base64: yes
x-jets-call-count: 8
x-jets-prewarm-count: 1

iVBORw0KGgoAAAANSUhEUgAAAK8AAAB4AQAAAACs0p9PAAAAWElEQVR4nGP6
jw38Y2LACoau8G3v6KlXp4SlqU3zXX3Zx3t22OStMwaVA0eFR4VHhUeFR4VH
hUeFR4WpKKy6dWm2ds6qWbeyNofqbtmauirXO2NQOZAKwgDXJyraIZqw7QAA
AABJRU5ErkJggg==

Step-by-step reproduction instructions

jets new test1 --mode api --no-database --no-webpacker
cd test1
bundle install
jets generate controller generate

Then add this code to the controller

require 'barby'
require 'barby/barcode/code_39'
require 'barby/outputter/png_outputter'

class GenerateController < ApplicationController
  def show
    barcode = Barby::Code39.new("1234567890")
    render plain: barcode.to_png,
           base64: true,
           content_type: 'image/png'
  end
end

and update the routes.rb

Jets.application.routes.draw do
  root "generate#show"
  get 'generate', to: 'generate#show'

  # The jets/public#show controller can serve static utf8 content out of the public folder.
  # Note, as part of the deploy process Jets uploads files in the public folder to s3
  # and serves them out of s3 directly. S3 is well suited to serve static assets.
  # More info here: https://rubyonjets.com/docs/extras/assets-serving/
  any "*catchall", to: "generate#show"
end

Solution Suggestion

I'm not familiar enough with the differences between the deployed environment to know what might be going wrong here.

Thanks for the details in the report.

Yeah, have played around with this a lot before.

It has to do with how API Gateway and binary content work together. API Gateway, will only serve the image correctly if certain Accept headers are sent.

Does not work:

curl URL -H 'Accept: image/webp,image/*, **/** ;q=0.8' https://api-gateway-dns/image.png # responds with base64 text while

Works:

curl URL -H 'Accept: image/webp' https://api-gateway-dns/image.png # responds with image correclty

This is important because with web browsers, we don't control the Accept header when the URL is being hit directly. IE: An HTML img tag. So they don't get served correctly.

This is not great behavior of API Gateway.

Some discussion is also in here with links to this known issue: https://community.rubyonjets.com/t/no-send-data-to-send-files-as-response/144/2

It's not ideal, but the recommendation is to upload assets to s3 and serve from there.

This is also a reason why files in the public folder are automatically uploaded to s3 and jets serves them out of s3 as part of the jets deploy process: Jets Assets Serving

S3 Lifecycle Policy

Looks like this app generates dynamic images and they are not meant to be kept around. If you end up taking the s3 approach, you may want to use an S3 Lifecycle Policy to automatically remove the images in the bucket.

Idea: Warning to User

Note, just had an idea. It'll be nice if there was some logic, maybe middleware, that throws an error or puts out warnings telling the user that it won't work locally before deploying. Though, unsure if it's worth it because API Gateway might provide a solution eventually.

Got it! Thank you for the very detailed response. We're not able to send custom headers for this project unfortunately. But good to know where the actual issue lies.