Upload image from unity client to rails server: problem and solution
Bài đăng này đã không được cập nhật trong 8 năm
I. Mở đầu
- Mình cần upload ảnh từ ứng dụng viết bằng Unity lên server rails để làm avatar cho user
II. Server rails
- Để upload ảnh trên server rails mình sử dụng:
- AWS s3 để lưu trữ ảnh
- paperclip gem dùng để upload ảnh
Các bạn có thể xem chi tiết về vấn đề này ở post: https://viblo.asia/nguyenhoa/posts/N0bDM6lov2X4
- Tiếp theo mình viết 1 API để upload avatar:
desc "Upload Avatar"
params do
requires :avatar, type: Rack::Multipart::UploadedFile
end
post "/upload_avatar" do
current_user.avatar = ActionDispatch::Http::UploadedFile.new(permitted_params[:avatar])
extension = permitted_params[:avatar][:type].split('/').last
current_user.avatar_file_name = "#{current_user.user_name}.#{extension}"
if current_user.save
status :ok
success_response! "upload_avatar_success"
else
Rails.logger.error current_user.errors.full_messages.join(", ")
error_response! "upload_avatar_error", :unprocessable_entity
end
end
- Sau khi test thử thành công API với postman => gửi pull request => được merge => ngủ ngon lành.
III.Problem
Và sáng hôm sau khi client Unity gọi API thì báo lỗi (!) what?
ArgumentError (unknown encoding name - "utf-8"):
rack (1.6.4) lib/rack/multipart/parser.rb:211:in `find'
rack (1.6.4) lib/rack/multipart/parser.rb:211:in `block in tag_multipart_encoding'
rack (1.6.4) lib/rack/multipart/parser.rb:207:in `each'
rack (1.6.4) lib/rack/multipart/parser.rb:207:in `tag_multipart_encoding'
rack (1.6.4) lib/rack/multipart/parser.rb:75:in `block (2 levels) in parse'
rack (1.6.4) lib/rack/multipart/parser.rb:250:in `get_data'
rack (1.6.4) lib/rack/multipart/parser.rb:74:in `block in parse'
rack (1.6.4) lib/rack/multipart/parser.rb:56:in `loop'
rack (1.6.4) lib/rack/multipart/parser.rb:56:in `parse'
rack (1.6.4) lib/rack/multipart.rb:25:in `parse_multipart'
rack (1.6.4) lib/rack/request.rb:375:in `parse_multipart'
rack (1.6.4) lib/rack/request.rb:207:in `POST'
Sau khi tìm hiểu thì Unity3d code generate ra multipart http request như sau:
POST path/to/upload HTTP/1.1
X-Unity-Version: 4.6.7f1
Content-Type: multipart/form-data; boundary="DCUxGP7YJupYAgWvtEzroP1c0RShKSqE3o5mpsiG"
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.1.1; A210 Build/JRO03H)
Host: *****
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 1673131
--DCUxGP7YJupYAgWvtEzroP1c0RShKSqE3o5mpsiG
Content-Type: text/plain; charset="utf-8"
Content-disposition: form-data; name="user_sid"
bbf14f82-d2aa-4c07-9fb8-ca6714a7ea97
--DCUxGP7YJupYAgWvtEzroP1c0RShKSqE3o5mpsiG
Content-Type: image/png; charset=UTF-8
Content-disposition: form-data; name="file"; filename="b67879ed-bfed-4491-a8cc-f99cca769f94.png"
trong khi đó brower hay các nền tảng khác sẽ generate ra như sau:
The Content-Type entity-header field indicates the media type of the
entity-body sent to the recipient or, in the case of the HEAD method,
the media type that would have been sent had the request been a GET.
Content-Type = "Content-Type" ":" media-type
Media types are defined in section 3.7. An example of the field is
Content-Type: text/html; charset=ISO-8859-4
Further discussion of methods for identifying the media type of an
entity is provided in section 7.2.1.
Và có thể thấy là Unity3d đã generate ra invalid field "Content-Type" mà gem "Rack" không thể parse được.
IV. Solution
- Với phiên bản 1.6.4 hiện tại thì gem rack vẫn không thể parse được http request trên của unity3d. Vì vậy để xử lí vấn đề này ta cần override lại method "tag_multipart_encoding" trong class Parse của gem này.
- bạn tạo file config/initializers/rack.rb như sau:
require 'rack/utils'
module Rack
module Multipart
class MultipartPartLimitError < Errno::EMFILE; end
class Parser
def tag_multipart_encoding(filename, content_type, name, body)
name = name.to_s
encoding = Encoding::UTF_8
name.force_encoding(encoding)
return if filename
if content_type
list = content_type.split(';')
type_subtype = list.first
type_subtype.strip!
if TEXT_PLAIN == type_subtype
rest = list.drop 1
rest.each do |param|
k,v = param.split('=', 2)
k.strip!
v.strip!
v = v[1..-2] if v[0] == '"' && v[-1] == '"'
encoding = Encoding.find v if k == CHARSET
end
end
end
name.force_encoding(encoding)
body.force_encoding(encoding)
end
end
end
end
Và bùm. vấn đề đã được giải quyết . Dòng duy nhất cần cần thêm là:
v = v[1..-2] if v[0] == '"' && v[-1] == '"'
- Bạn có thể xem trên github: https://github.com/rack/rack/pull/1003/files
Issue này vẫn tồn tại ở Rack version 1.6.4 nhưng khả năng sẽ được fix trong version tiếp theo. Hi vong Rack sớm release version mới!
All rights reserved