Beware of redirects with Ruby’s net/http

Photo by Maik Jonietz on Unsplash

Earlier last week, we learned that Ruby’s built-in net/http library contains a fundamental limitation — net/http does not automatically handle redirect status codes (3xx).

As part of a feature sprint for a client, we had been tackling some functionality around PDFs issued by their system using wicked_pdf, which can generate PDFs from an HTML view in Rails.

Unknowingly, a difference in the configuration in production meant that wicked_pdf was generating HTTP asset URLs rather than HTTPS asset URLs.

Not a major issue, we will get to work on a fix, and in the meantime, the CDN will redirect any HTTP requests to HTTPS anyway, so there shouldn’t be any disruption to end-users — or so we thought.

The CDN was redirecting HTTP requests to HTTPS, but for some reason, the styling of the PDFs was entirely broken. But why? Looking more closely at the source code behind the page revealed something quite interesting:

Source code showing a 301 Moved Permanently embedded within the page

Why is there what looks like a 301 from CloudFront embedded in the page? Behind the scenes, wicked_pdf loads in stylesheets as inline styles.

A bit of digging revealed that wicked_pdf uses the built-in net/http library to load in assets:

def read_from_uri(uri)
  asset = Net::HTTP.get(URI(uri))
  asset.force_encoding('UTF-8') if asset
  asset = gzip(asset) if WickedPdf.config[:expect_gzipped_remote_assets]
  asset
end

It turns out that net/http doesn’t automatically handle redirect status codes such as 301s for you.

The long term fix should be to replace the use of net/http in wicked_pdf with something more resilient however, as a short term fix it’s possible to monkey patch this behaviour within an initializer of your Rails application:

require 'net/http'
class WickedPdf
  module WickedPdfHelper
    module Assets
        private
        def read_from_uri(source)
            # This is a short term work around. This solution makes some
            # crude assumptions such as assuming that there will only ever be
            # a single redirect.
            asset = Net::HTTP.get_response(URI(source))
            if asset.code == "301"
                asset = Net::HTTP.get_response(URI.parse(asset.header['location']))
            end
            asset = asset.body.force_encoding('UTF-8') if asset.body
            asset = gzip(asset) if WickedPdf.config[:expect_gzipped_remote_assets]
            asset
        end
    end
  end
end

Beware of net/http and the lack of built in support for redirects!