You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 23:05:20