Ruby on Rails:多Word文件打包Zip单次send_data及空字节错误排查
Why You're Getting the Multiple Renders Error
You can only send one HTTP response per request. Calling send_data inside a loop tries to send multiple attachments, which Rails blocks with the Multiple Renders error. The fix is to bundle all Word files into a single zip archive and send that once.
Option 1: Forward the Original Zip from the API
If the API already returns a zip containing all your Word files, you can send the raw response directly without processing:
def generate_multi_letter(template, letterName, params) @Document_URL = "#{ENV["API_HOST"]}/api/StateDocument/GenerateDocX/#{template}" response = HTTParty.post(@Document_URL, body: params.to_json, headers: { "Content-Type" => "application/json" }) send_data response.body, filename: "#{letterName}.zip", type: "application/zip", disposition: "attachment" end
Option 2: Create a Custom Zip (for File Renaming/Processing)
If you need to modify the files (e.g., rename entries) before sending, use an in-memory zip (via StringIO) to avoid writing to disk:
require 'zip' require 'stringio' def generate_multi_letter(template, letterName, params) @Document_URL = "#{ENV["API_HOST"]}/api/StateDocument/GenerateDocX/#{template}" response = HTTParty.post(@Document_URL, body: params.to_json, headers: { "Content-Type" => "application/json" }) # Initialize an in-memory zip to hold our files output_zip = StringIO.new output_zip.set_encoding("BINARY") Zip::OutputStream.open(output_zip) do |zos| # Read each entry from the API's zip response Zip::InputStream.open(StringIO.new(response.body)) do |zis| while entry = zis.get_next_entry # Optional: Rename the file (customize this as needed) new_filename = "#{letterName}_#{entry.name}" zos.put_next_entry(new_filename) zos.write(zis.read) end end end # Reset the stream pointer to the start before reading output_zip.rewind send_data output_zip.read, filename: "#{letterName}.zip", type: "application/zip", disposition: "attachment" end
Fixing the "Path Name Contains Null Byte" Error
This error occurs in your second code snippet because you're passing raw binary data (the API response body) to Zip::File.open, which expects a file path string. Binary data often includes null bytes (\0), which are invalid in file paths.
To fix this:
- Use
StringIOto wrap binary data when working with in-memory zip files (like in Option 2 above). - Never pass raw binary strings to methods that expect file system paths.
Issues in Your Second Code Snippet
Zip::File.open(response, ...):responseis binary data, not a file path—useStringIOinstead.- Typo:
zipfile.addshould bezip.add(your variable is namedzip). - Incorrect filename: You're using
.docxfor a zip file—change it to.zip. - Unnecessary Tempfile: You can handle everything in memory with
StringIOinstead of writing to temp files.
内容的提问来源于stack exchange,提问作者Sean Gillespie

