Rails 7生成PDF存入Tempfile后POST发送遇编码错误求助
问题
在仅提供API的Rails 7应用中,使用Prawn gem生成PDF文件后,希望存入Tempfile再通过POST请求发送到外部服务,借助Faraday::UploadIO附加文件时,出现编码转换错误:
lib/ruby/3.1.0/delegate.rb:349:in `write': "\xFF" from ASCII-8BIT to UTF-8 (Encoding::UndefinedConversionError)
尝试用file.write(file_io.read.force_encoding('ASCII-8BIT'))强制编码仍无效,相关代码如下:
相关代码文件
lib/file_io.rb
# require 'file_io' and use FileIO to simplify persistence of IO stream as file class FileIO < StringIO attr_reader :original_filename def initialize(stream, filename) super(stream) @original_filename = filename end end
services/mandate/uploader.rb
# class which generates PDF and send it to the API require 'file_io' require 'net/http' module Mandate class Uploader def send_pdf_file(file_io) uri = URI('test_path') req = Net::HTTP::Post.new(uri) req['apiToken'] = Rails.application.credentials.api_token req['Content-Type'] = 'multipart/form-data' file = Tempfile.new('mandate.pdf') file.write(file_io.read) file.close req.set_form([['file', Faraday::UploadIO.new(file.path, 'application/pdf')]], 'multipart/form-data') res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(req) end end def generate_pdf(params) FileIO.new(PDFDocument::Generator.new(params).render, 'document.pdf') end end end
解决方案
错误核心是Tempfile默认以UTF-8编码写入,但PDF是二进制文件,包含ASCII-8BIT字节,直接写入触发编码转换冲突,以下是三种解决方式:
方式1:以二进制模式创建Tempfile
创建Tempfile时指定二进制模式,避免编码转换:
def send_pdf_file(file_io) uri = URI('test_path') req = Net::HTTP::Post.new(uri) req['apiToken'] = Rails.application.credentials.api_token req['Content-Type'] = 'multipart/form-data' # 以二进制模式创建Tempfile,避免编码问题 file = Tempfile.new(['mandate', '.pdf'], binmode: true) file.write(file_io.read) file.rewind # 写完后将指针移到文件开头,确保后续能读取完整内容 req.set_form([['file', Faraday::UploadIO.new(file.path, 'application/pdf')]], 'multipart/form-data') res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(req) end file.close! # 使用完后关闭并删除临时文件 end
方式2:直接用StringIO配合Faraday::UploadIO(推荐)
既然已经有继承自StringIO的FileIO实例,可直接传给Faraday::UploadIO,省去Tempfile写入步骤,减少磁盘IO:
def send_pdf_file(file_io) uri = URI('test_path') req = Net::HTTP::Post.new(uri) req['apiToken'] = Rails.application.credentials.api_token # 直接用FileIO实例创建UploadIO,无需写入临时文件 upload_io = Faraday::UploadIO.new(file_io, 'application/pdf', file_io.original_filename) req.set_form([['file', upload_io]], 'multipart/form-data') res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(req) end end
方式3:手动设置Tempfile编码为ASCII-8BIT
如果必须使用Tempfile,可手动指定文件编码:
file = Tempfile.new('mandate.pdf') file.set_encoding(Encoding::ASCII_8BIT) # 强制设置为二进制编码 file.write(file_io.read) file.rewind
内容的提问来源于stack exchange,提问作者mr_muscle
相关产品推荐
相关产品推荐

