ActionDispatch::Http::UploadedFile
传到controller的params中的文件,会被包装成ActionDispatch::Http::UploadedFile,于是搜下它在哪里生成
$ gems git:(master) ag UploadedFile -G 'action.*5\.1'
actionpack-5.1.2/lib/action_controller/metal/strong_parameters.rb
456: # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
888: ActionDispatch::Http::UploadedFile,
889: Rack::Test::UploadedFile,
actionpack-5.1.2/lib/action_controller/test_case.rb
142: when Rack::Test::UploadedFile
actionpack-5.1.2/lib/action_dispatch.rb
78: autoload :UploadedFile, "action_dispatch/http/upload"
actionpack-5.1.2/lib/action_dispatch/http/upload.rb
11: class UploadedFile
actionpack-5.1.2/lib/action_dispatch/request/utils.rb
50: ActionDispatch::Http::UploadedFile.new(params)
actionpack-5.1.2/lib/action_dispatch/testing/test_process.rb
7: # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type)</tt>:
20: Rack::Test::UploadedFile.new(path, mime_type, binary)
如无意外,就是actionpack-5.1.2/lib/action_dispatch/request/utils.rb了,于是加入断点
From: /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/http/upload.rb @ line 27 ActionDispatch::Http::UploadedFile#initialize:
26: def initialize(hash) # :nodoc:
=> 27: binding.pry
28: @tempfile = hash[:tempfile]
29: raise(ArgumentError, ":tempfile is required") unless @tempfile
30:
31: if hash[:filename]
32: @original_filename = hash[:filename].dup
33:
34: begin
35: @original_filename.encode!(Encoding::UTF_8)
36: rescue EncodingError
37: @original_filename.force_encoding(Encoding::UTF_8)
38: end
39: else
40: @original_filename = nil
41: end
42:
43: @content_type = hash[:type]
44: @headers = hash[:head]
45: end
[2] pry(#<ActionDispatch::Http::UploadedFile>)> _bsi_
=> {0=>#<Binding:69810035258640 ActionDispatch::Http::UploadedFile#initialize /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/http/upload.rb:27>,
1=>#<Binding:69810096889400 ActionDispatch::Request::Utils::NoNilParamEncoder.normalize_encode_params /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/request/utils.rb:50>,
2=>#<Binding:69810096945400 ActionDispatch::Request::Utils::NoNilParamEncoder.block in normalize_encode_params /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/request/utils.rb:53>,
3=>#<Binding:69810096984900 ActionDispatch::Request::Utils::NoNilParamEncoder.normalize_encode_params /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/request/utils.rb:52>,
4=>#<Binding:69810097032940 ActionDispatch::Request::Utils::NoNilParamEncoder.block in normalize_encode_params /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/request/utils.rb:53>,
5=>#<Binding:69810097080980 ActionDispatch::Request::Utils::NoNilParamEncoder.normalize_encode_params /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/request/utils.rb:52>,
6=>#<Binding:69810097145420 ActionDispatch::Request::Utils.normalize_encode_params /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/request/utils.rb:20>,
7=>#<Binding:69810097193520 ActionDispatch::Request#block in POST /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/http/request.rb:361>,
8=>#<Binding:69810032817940 ActionDispatch::Request#fetch_header /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/request.rb:57>,
9=>#<Binding:69810032633460 ActionDispatch::Request#POST /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/http/request.rb:357>,
10=>#<Binding:69810097372300 ActionDispatch::Request#parameters /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/http/parameters.rb:53>,
11=>#<Binding:69810097411820 ActionDispatch::Request#filtered_parameters /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/http/filter_parameters.rb:41>,
12=>#<Binding:69810097459460 StudentsController#process_action /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_controller/metal/instrumentation.rb:21>,
13=>#<Binding:69810097507080 StudentsController#process_action /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_controller/metal/params_wrapper.rb:252>,
14=>#<Binding:69810097587500 StudentsController#process_action /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/activerecord-5.1.2/lib/active_record/railties/controller_runtime.rb:22>,
15=>#<Binding:69810035015520 StudentsController#process /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/abstract_controller/base.rb:124>,
16=>#<Binding:69810034443500 StudentsController#process /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionview-5.1.2/lib/action_view/rendering.rb:30>,
17=>#<Binding:69810097756960 StudentsController#dispatch /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_controller/metal.rb:189>,
18=>#<Binding:69810097804600 StudentsController.dispatch /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_controller/metal.rb:253>,
19=>#<Binding:69810097852180 ActionDispatch::Routing::RouteSet::Dispatcher#dispatch /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/routing/route_set.rb:49>,
20=>#<Binding:69810097891740 ActionDispatch::Routing::RouteSet::Dispatcher#serve /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/routing/route_set.rb:31>,
21=>#<Binding:69810097939440 ActionDispatch::Journey::Router#block in serve /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/journey/router.rb:46>,
22=>#<Binding:69810034636800 ActionDispatch::Journey::Router#serve /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/journey/router.rb:33>,
23=>#<Binding:69810098069320 ActionDispatch::Routing::RouteSet#call /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/routing/route_set.rb:832>,
其中ActionDispatch::Request::Utils.normalize_encode_params发于这里
module ActionDispatch
class Request
def POST
fetch_header("action_dispatch.request.request_parameters") do
pr = parse_formatted_parameters(params_parsers) do |params|
super || {}
end
self.request_parameters = Request::Utils.normalize_encode_params(pr)
end
rescue Http::Parameters::ParseError # one of the parse strategies blew up
self.request_parameters = Request::Utils.normalize_encode_params(super || {})
raise
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
end
alias :request_parameters :POST
def request_parameters=(params)
raise if params.nil?
set_header("action_dispatch.request.request_parameters".freeze, params)
end
它的上几层是ActionDispatch::Request#filtered_parameters,而filtered_parameters发于这里
module ActionController
module Instrumentation
extend ActiveSupport::Concern
def process_action(*args)
raw_payload = {
controller: self.class.name,
action: action_name,
params: request.filtered_parameters,
headers: request.headers,
format: request.format.ref,
method: request.request_method,
path: request.fullpath
}
ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
begin
result = super
payload[:status] = response.status
result
ensure
append_info_to_payload(payload)
end
end
end
再回头观察ActionDispatch::Http::UploadedFile初始化时传入的参数,其中有:tempfile指向上传的文件,命名为RackMultipartXXX,估计是前面rack栈对http请求的读取,而生成的临时文件
[5] pry(#<ActionDispatch::Http::UploadedFile>)> hash
=> {:filename=>"win10-md5.txt",
:type=>"text/plain",
:name=>"student[photo]",
:tempfile=>#<File:/tmp/RackMultipart20170830-3092-7hj5e8.txt>,
:head=>"Content-Disposition: form-data; name=\"student[photo]\"; filename=\"win10-md5.txt\"\r\n" + "Content-Type: text/plain\r\n"}
而rails的参数处理也针对:tempfile来抽取成ActionDispatch::Http::UploadedFile
module ActionDispatch
class Request
class Utils # :nodoc:
class ParamEncoder # :nodoc:
# Convert nested Hash to HashWithIndifferentAccess.
#
def self.normalize_encode_params(params)
case params
when Array
handle_array params
when Hash
if params.has_key?(:tempfile)
ActionDispatch::Http::UploadedFile.new(params)
else
params.each_with_object({}) do |(key, val), new_hash|
new_hash[key] = normalize_encode_params(val)
end.with_indifferent_access
end
else
params
end
end
end
不过,这已经递归到photo这层了,未encode的params是这样的
=> {"utf8"=>"✓",
"_method"=>"patch",
"authenticity_token"=>"wQ8nK2K0kxpo8o7HqukKiquNkvvpLZnEqJ9sPLSnWy43oZ4+TK4kIk4nC6uZ7Kj0aufzPXNpVCWSA3hxvtGFYA==",
"student"=>
{"name"=>"c",
"grade"=>"1",
"photo"=>
{:filename=>"win10-md5.txt",
:type=>"text/plain",
:name=>"student[photo]",
:tempfile=>#<File:/tmp/RackMultipart20170830-3320-uq32df.txt>,
:head=>"Content-Disposition: form-data; name=\"student[photo]\"; filename=\"win10-md5.txt\"\r\n" + "Content-Type: text/plain\r\n"}},
"commit"=>"Update Student"}
而controller中的params(StrongParameters),它其实是包装了request.parameters
module StrongParameters
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
def params
@_params ||= Parameters.new(request.parameters)
end
而request.parameters主要是合成request_parameters(POST)和query_parameters(GET)
module ActionDispatch
module Http
module Parameters
def parameters
params = get_header("action_dispatch.request.parameters")
return params if params
params = begin
request_parameters.merge(query_parameters)
rescue EOFError
query_parameters.dup
end
params.merge!(path_parameters)
params = set_binary_encoding(params)
set_header("action_dispatch.request.parameters", params)
params
end
request_parameters(刚才分析过)和query_parameters(GET)如下
module ActionDispatch
class Request
def GET
fetch_header("action_dispatch.request.query_parameters") do |k|
rack_query_params = super || {}
# Check for non UTF-8 parameter values, which would cause errors later
Request::Utils.check_param_encoding(rack_query_params)
set_header k, Request::Utils.normalize_encode_params(rack_query_params)
end
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}")
end
alias :query_parameters :GET
# Override Rack's POST method to support indifferent access
def POST
fetch_header("action_dispatch.request.request_parameters") do
pr = parse_formatted_parameters(params_parsers) do |params|
super || {}
end
self.request_parameters = Request::Utils.normalize_encode_params(pr)
end
rescue Http::Parameters::ParseError # one of the parse strategies blew up
self.request_parameters = Request::Utils.normalize_encode_params(super || {})
raise
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}")
end
alias :request_parameters :POST
检查fetch_header来源,可知其最终会作用于request.env(也就是贯穿rack栈的那个hash)的fetch,因此,经normalize_encode_params包装成ActionDispatch::Http::UploadedFile的上传文件便得以在controller的params中又再取出来使用
至此可知,就是rack会读取http请求,将文件数据包装成一个File,,然后Rails又包装成ActionDispatch::Http::UploadedFile
那么rack又是怎样包装File的呢?搜索RackMultipart,然后加入断点
From: /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart/parser.rb @ line 11 :
6:
7: class Parser
8: BUFSIZE = 16384
9: TEXT_PLAIN = "text/plain"
10: TEMPFILE_FACTORY = lambda { |filename, content_type|
=> 11: binding.pry
12: Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0".freeze, '%00'.freeze))])
13: }
14:
15: class BoundedIO # :nodoc:
16: def initialize(io, content_length)
[1] pry(Rack::Multipart::Parser)> _bsi_
=> {0=>#<Binding:70276671071600 Rack::Multipart::Parser.block in <class:Parser> /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart/parser.rb:11>,
1=>#<Binding:70276671104460 Rack::Multipart::Parser::Collector#on_mime_head /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart/parser.rb:134>,
2=>#<Binding:70276671129120 Rack::Multipart::Parser#handle_mime_head /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart/parser.rb:259>,
3=>#<Binding:70276671145660 Rack::Multipart::Parser#block in run_parser /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart/parser.rb:212>,
4=>#<Binding:70276671170340 Rack::Multipart::Parser#run_parser /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart/parser.rb:205>,
5=>#<Binding:70276671195000 Rack::Multipart::Parser#on_read /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart/parser.rb:188>,
6=>#<Binding:70276671219600 Rack::Multipart::Parser.parse /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart/parser.rb:69>,
7=>#<Binding:70276671252320 Rack::Multipart.extract_multipart /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/multipart.rb:52>,
8=>#<Binding:70276671268820 Rack::Request#parse_multipart /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/request.rb:472>,
9=>#<Binding:70276671293420 Rack::Request#POST /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/request.rb:335>,
10=>#<Binding:70276671318020 Rack::MethodOverride#method_override_param /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/method_override.rb:39>,
11=>#<Binding:70276671342540 Rack::MethodOverride#method_override /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/method_override.rb:27>,
12=>#<Binding:70276671366880 Rack::MethodOverride#call /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/method_override.rb:15>,
13=>#<Binding:70276671391100 Rack::Runtime#call /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/runtime.rb:22>,
14=>#<Binding:70276671407620 ActionDispatch::Executor#call /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/middleware/executor.rb:12>,
15=>#<Binding:70276671430880 ActionDispatch::Static#call /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.1.2/lib/action_dispatch/middleware/static.rb:125>,
16=>#<Binding:70276671495580 Rack::Sendfile#call /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.3/lib/rack/sendfile.rb:111>,
17=>#<Binding:70276671518980 School::Application#call /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/railties-5.1.2/lib/rails/engine.rb:522>,
18=>#<Binding:70276671534700 Puma::Configuration::ConfigMiddleware#call /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/puma-3.9.1/lib/puma/configuration.rb:224>,
19=>#<Binding:70276671558460 Puma::Server#handle_request /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/puma-3.9.1/lib/puma/server.rb:602>,
20=>#<Binding:70276671582160 Puma::Server#process_client /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/puma-3.9.1/lib/puma/server.rb:435>,
21=>#<Binding:70276671597740 Puma::Server#block in run /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/puma-3.9.1/lib/puma/server.rb:299>,
22=>#<Binding:70276671621740 Puma::ThreadPool#block in spawn_thread /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/puma-3.9.1/lib/puma/thread_pool.rb:120>}
[2] pry(Rack::Multipart::Parser)>