Upload image from unity client to rails server: problem and solution
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: ...
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