Rails 3.2 f.file_field导致路由错误

在轨道3.2.12和3.2.11上测试。 在另一个rails 3.2.11项目中我没有f.file_field这个问题,但是在当前的一个我做的并且找不到这种奇怪行为的原因,所以这是我的问题。

我对更新操作有一个奇怪的问题。 以下是代码的相关部分:

路线:

 get "signup" => "users#new", :as => "signup" get "profile" => "users#profile", :as => "profile" resources :users do member do get :activate end end 

控制器:

 def update @user = User.find(params[:id]) if @user.update_attributes(params[:user]) redirect_to user_path(@user), :notice => t('users_controller.update.updated') else render :edit end end 

haml中的forms(简化但具有相同的行为):

 = form_for @user do |f| .field = f.label :first_name %br = f.text_field :first_name, :size => 40 .actions = f.submit 

因此,在我按下更新后,一切都按预期工作,用户的属性正在更新。 但是,当我添加这样的文件字段时:

 = form_for @user do |f| .field = f.label :first_name %br = f.text_field :first_name, :size => 40 .field = f.label :avatar %br = f.file_field :avatar .actions = f.submit 

然后按更新然后我收到路由错误:

 No route matches [PUT] "/1" 

我不明白为什么它试图用PUT方法达到/1路径。 在显示路由错误的页面上,我可以在浏览器的地址栏中看到/users/1

这里是为表单生成的html:

  


所以,这是最有趣的事情。 当我将表单更改为:

 = form_for @user do |f| .field = f.label :first_name %br = f.text_field :first_name, :size => 40 .field = f.label :avatar %br %input{:id => "user_avatar", :name => "user[avatar]", :type => "file"} .actions = f.submit 

然后生成的html与之前的情况相同(唯一的区别是我可以看到,对于文件字段属性使用单引号而不是双引号):

  


但提交此表单后,没有路由错误,一切正常。

UPDATE

实际上它不能正常工作。 我只是查看params哈希并看到:avatar key存在,但我错过了在后一种情况下html中的表单open标签中没有enctype="multipart/form-data"属性,所以文件不会上传。 添加enctype=multipart/form-data属性会导致路由错误再次发生。

我发现在提交多部分表单后尝试redirect_to user_path(@user)redirect_to user_path(@user)时添加了put ":id" => "users#update" route(确保此路由没有PUT路由错误),然后还有路由错误No route matches [GET] "/users/users/1"

这是完整的routes.rb

 Myapp::Application.routes.draw do match "oauth/callback" => "oauths#callback" match "oauth/callback/:provider" => "oauths#callback" match "oauth/:provider" => "oauths#oauth", :as => :auth_at_provider resources :countries resources :categories resources :images resources :collections resources :items put ":id" => "users#update" get "signup" => "users#new", :as => "signup" get "profile" => "users#profile", :as => "profile" resources :users do member do get :activate end end get "signout" => "sessions#destroy", :as => "signout" get "signin" => "sessions#new", :as => "signin" resources :sessions get "site/index" root :to => "site#index" end 

和耙路线

  oauth_callback /oauth/callback(.:format) oauths#callback /oauth/callback/:provider(.:format) oauths#callback auth_at_provider /oauth/:provider(.:format) oauths#oauth countries GET /countries(.:format) countries#index POST /countries(.:format) countries#create new_country GET /countries/new(.:format) countries#new edit_country GET /countries/:id/edit(.:format) countries#edit country GET /countries/:id(.:format) countries#show PUT /countries/:id(.:format) countries#update DELETE /countries/:id(.:format) countries#destroy categories GET /categories(.:format) categories#index POST /categories(.:format) categories#create new_category GET /categories/new(.:format) categories#new edit_category GET /categories/:id/edit(.:format) categories#edit category GET /categories/:id(.:format) categories#show PUT /categories/:id(.:format) categories#update DELETE /categories/:id(.:format) categories#destroy images GET /images(.:format) images#index POST /images(.:format) images#create new_image GET /images/new(.:format) images#new edit_image GET /images/:id/edit(.:format) images#edit image GET /images/:id(.:format) images#show PUT /images/:id(.:format) images#update DELETE /images/:id(.:format) images#destroy collections GET /collections(.:format) collections#index POST /collections(.:format) collections#create new_collection GET /collections/new(.:format) collections#new edit_collection GET /collections/:id/edit(.:format) collections#edit collection GET /collections/:id(.:format) collections#show PUT /collections/:id(.:format) collections#update DELETE /collections/:id(.:format) collections#destroy items GET /items(.:format) items#index POST /items(.:format) items#create new_item GET /items/new(.:format) items#new edit_item GET /items/:id/edit(.:format) items#edit item GET /items/:id(.:format) items#show PUT /items/:id(.:format) items#update DELETE /items/:id(.:format) items#destroy PUT /:id(.:format) users#update signup GET /signup(.:format) users#new profile GET /profile(.:format) users#profile activate_user GET /users/:id/activate(.:format) users#activate users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroy signout GET /signout(.:format) sessions#destroy signin GET /signin(.:format) sessions#new sessions GET /sessions(.:format) sessions#index POST /sessions(.:format) sessions#create new_session GET /sessions/new(.:format) sessions#new edit_session GET /sessions/:id/edit(.:format) sessions#edit session GET /sessions/:id(.:format) sessions#show PUT /sessions/:id(.:format) sessions#update DELETE /sessions/:id(.:format) sessions#destroy site_index GET /site/index(.:format) site#index root / 

有人有什么想法吗?

UPDATE2

揭示问题的是多部分表单有助于找到关于同一问题的这篇文章 – 发布/放置请求的路由错误(乘客标题),但不幸的是没有解决方案……

UPDATE3

我发现了一些有趣的东西。 /path_to_gemset_here/gem/journey-1.0.4/lib/journey/router.rb有一个方法:

 def find_routes env req = request_class.new env routes = filter_routes(req.path_info) + custom_routes.find_all { |r| r.path.match(req.path_info) } routes.sort_by(&:precedence).find_all { |r| r.constraints.all? { |k,v| v === req.send(k) } && r.verb === req.request_method }.reject { |r| req.ip && !(r.ip === req.ip) }.map { |r| match_data = r.path.match(req.path_info) match_names = match_data.names.map { |n| n.to_sym } match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) } info = Hash[match_names.zip(match_values).find_all { |_,y| y }] [match_data, r.defaults.merge(info), r] } end 

我检查了env的非多部分和多部分请求,发现了这个:

非多:

 "REQUEST_URI"=>"/users/1", "SCRIPT_NAME"=>"", "PATH_INFO"=>"/users/1" 

多:

 "REQUEST_URI"=>"/users/1", "SCRIPT_NAME"=>"/users", "PATH_INFO"=>"/1", "SCRIPT_FILENAME"=>"/path_to_project_folder_here/public/users", - there is no such variable in a non-multipart request 

所以这就是问题所在。 正如我在方法的定义中看到的那样:

 match_data = r.path.match(req.path_info) 

PATH_INFO用于查找处理请求的路由,但在后一种情况下由于将REQUEST_URI分为两部分而完全错误。 不幸的是,目前我没有时间完成今天的调查,希望明天我能做到。

如果有人有足够的好奇心比我更快找到问题的根源 – 欢迎你:)

UPDATE4(已编辑)

所以,这是调查的延续。

方法:文件中的parse_native_requestparse_native_request

此调用后的变量headers_data

 headers_data = channel.read_scalar(buffer, MAX_HEADER_SIZE) 

包含:

 "SERVER_SOFTWARE\x00Apache/2.2.22 (Ubuntu)\x00 SERVER_PROTOCOL\x00HTTP/1.1\x00 SERVER_NAME\x00myapp.loc\x00 SERVER_ADMIN\x00[no address given]\x00 SERVER_ADDR\x00127.0.0.1\x00 SERVER_PORT\x0080\x00 REMOTE_ADDR\x00127.0.0.1\x00 REMOTE_PORT\x0033199\x00 REQUEST_METHOD\x00POST\x00 QUERY_STRING\x00\x00 CONTENT_TYPE\x00multipart/form-data; boundary=----WebKitFormBoundary8HlzQxocoOROMfRV\x00 DOCUMENT_ROOT\x00/path_to_project_folder_here/public\x00 REQUEST_URI\x00/users/1\x00 SCRIPT_NAME\x00\x00 PATH_INFO\x00/users/1\x00 HTTP_HOST\x00myapp.loc\x00 HTTP_CONNECTION\x00keep-alive\x00 HTTP_CONTENT_LENGTH\x00748\x00 HTTP_CACHE_CONTROL\x00max-age=0\x00 HTTP_ACCEPT\x00text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\x00 HTTP_ORIGIN\x00http://myapp.loc\x00 HTTP_USER_AGENT\x00Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.97 Safari/537.22\x00 HTTP_CONTENT_TYPE\x00multipart/form-data; boundary=----WebKitFormBoundary8HlzQxocoOROMfRV\x00 HTTP_REFERER\x00http://myapp.loc/profile\x00 HTTP_ACCEPT_ENCODING\x00gzip,deflate,sdch\x00 HTTP_ACCEPT_LANGUAGE\x00en-US,en;q=0.8\x00 HTTP_ACCEPT_CHARSET\x00ISO-8859-1,utf-8;q=0.7,*;q=0.3\x00 HTTP_COOKIE\x00_myapp_session=BAh7CEkiDHVzZXJfaWQGOgZFRmkGSSIPc2Vzc2lvbl9pZAY7AEZJIiVhMjU2ZjU5N2VmMTE0YTJiOGEwNGJiYzUyYjM2NDg0OQY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFjaGU4VkxmRHhEQW9lbm1hK1RYd3NBKzBJUTcrL2piQ0lLK1EyeHdyOHVjPQY7AEY%3D--a6e5daff1334c083e54b2bcafba43b32e546af9c\x00 UNIQUE_ID\x00UTXfEX8AAQEAACWVEMoAAAAB\x00 GATEWAY_INTERFACE\x00CGI/1.1\x00 >>>> here seems to start a kind of redirect <<<< SERVER_PROTOCOL\x00HTTP/1.1\x00 REQUEST_METHOD\x00POST\x00 QUERY_STRING\x00\x00 REQUEST_URI\x00/users/1\x00 SCRIPT_NAME\x00/users\x00 PATH_INFO\x00/1\x00 PATH_TRANSLATED\x00/path_to_project_folder_here/public/1\x00 HTTP_HOST\x00myapp.loc\x00 HTTP_CONNECTION\x00keep-alive\x00 CONTENT_LENGTH\x00748\x00HTTP_CACHE_CONTROL\x00max-age=0\x00 HTTP_ACCEPT\x00text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\x00 HTTP_ORIGIN\x00http://myapp.loc\x00 HTTP_USER_AGENT\x00Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.97 Safari/537.22\x00CONTENT_TYPE\x00multipart/form-data; boundary=----WebKitFormBoundary8HlzQxocoOROMfRV\x00 HTTP_REFERER\x00http://myapp.loc/profile\x00 HTTP_ACCEPT_ENCODING\x00gzip,deflate,sdch\x00 HTTP_ACCEPT_LANGUAGE\x00en-US,en;q=0.8\x00 HTTP_ACCEPT_CHARSET\x00ISO-8859-1,utf-8;q=0.7,*;q=0.3\x00 HTTP_COOKIE\x00_myapp_session=BAh7CEkiDHVzZXJfaWQGOgZFRmkGSSIPc2Vzc2lvbl9pZAY7AEZJIiVhMjU2ZjU5N2VmMTE0YTJiOGEwNGJiYzUyYjM2NDg0OQY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFjaGU4VkxmRHhEQW9lbm1hK1RYd3NBKzBJUTcrL2piQ0lLK1EyeHdyOHVjPQY7AEY%3D--a6e5daff1334c083e54b2bcafba43b32e546af9c\x00 PATH\x00/usr/local/bin:/usr/bin:/bin\x00 SERVER_SIGNATURE\x00
Apache/2.2.22 (Ubuntu) Server at myapp.loc Port 80
\n\x00 SERVER_SOFTWARE\x00Apache/2.2.22 (Ubuntu)\x00 SERVER_NAME\x00myapp.loc\x00 SERVER_ADDR\x00127.0.0.1\x00 SERVER_PORT\x0080\x00 REMOTE_ADDR\x00127.0.0.1\x00 DOCUMENT_ROOT\x00/path_to_project_folder_here/public\x00 SERVER_ADMIN\x00[no address given]\x00 SCRIPT_FILENAME\x00/path_to_project_folder_here/public/users\x00 REMOTE_PORT\x0033199\x00 PATH_TRANSLATED\x00/bin/runAV\x00 REDIRECT_STATUS\x00302\x00 PASSENGER_CONNECT_PASSWORD\x00EElt7wIBLlliWGCYJJoezPvecsB2brraBWdiIbD4nul\x00_\x00_\x00"

之后是这个电话:

 headers = split_by_null_into_hash(headers_data) 

headers包含:

 {"SERVER_SOFTWARE"=>"Apache/2.2.22 (Ubuntu)", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_NAME"=>"myapp.loc", "SERVER_ADMIN"=>"[no address given]", "SERVER_ADDR"=>"127.0.0.1", "SERVER_PORT"=>"80", "REMOTE_ADDR"=>"127.0.0.1", "REMOTE_PORT"=>"33243", "REQUEST_METHOD"=>"POST", "QUERY_STRING"=>"", "CONTENT_TYPE"=>"multipart/form-data; boundary=----WebKitFormBoundary8HlzQxocoOROMfRV", "DOCUMENT_ROOT"=>"/path_to_project_folder_here/public", "REQUEST_URI"=>"/users/1", "SCRIPT_NAME"=>"/users", "PATH_INFO"=>"/1", "HTTP_HOST"=>"myapp.loc", "HTTP_CONNECTION"=>"keep-alive", "HTTP_CONTENT_LENGTH"=>"748", "HTTP_CACHE_CONTROL"=>"max-age=0", "HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "HTTP_ORIGIN"=>"http://myapp.loc", "HTTP_USER_AGENT"=>"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.97 Safari/537.22", "HTTP_CONTENT_TYPE"=>"multipart/form-data; boundary=----WebKitFormBoundary8HlzQxocoOROMfRV", "HTTP_REFERER"=>"http://myapp.loc/profile", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch", "HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8", "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.3", "HTTP_COOKIE"=>"_myapp_session=BAh7CEkiDHVzZXJfaWQGOgZFRmkGSSIPc2Vzc2lvbl9pZAY7AEZJIiVhMjU2ZjU5N2VmMTE0YTJiOGEwNGJiYzUyYjM2NDg0OQY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFjaGU4VkxmRHhEQW9lbm1hK1RYd3NBKzBJUTcrL2piQ0lLK1EyeHdyOHVjPQY7AEY%3D--a6e5daff1334c083e54b2bcafba43b32e546af9c", "UNIQUE_ID"=>"UTXjXn8AAQEAACceEdgAAAAA", "GATEWAY_INTERFACE"=>"CGI/1.1", "PATH_TRANSLATED"=>"/bin/runAV", "CONTENT_LENGTH"=>"748", "PATH"=>"/usr/local/bin:/usr/bin:/bin", "SERVER_SIGNATURE"=>"
Apache/2.2.22 (Ubuntu) Server at myapp.loc Port 80
\n", "SCRIPT_FILENAME"=>"/path_to_project_folder_here/public/users", "REDIRECT_STATUS"=>"302", "PASSENGER_CONNECT_PASSWORD"=>"GgEqWssAcbBETWnFI7xzBfWRGibgB34OhfFSUVyOhPn", "_"=>"_"}

所以问题显然在于将标题打包成哈希的方式 – 对于PATH_INFO (以及其他标题)也有两个值,后一个(不正确)重写第一个(实际上问题在于原因)这些标题正在发送,但我不知道如何处理这个)。 在split_by_null_into_hash(headers_data)方法中发生打包到散列。 现在去那里。

file: /path_to_gemset_here/gems/passenger-3.0.17/lib/phusion_passenger/utils.rb

Module Utils包含以下代码:

 if defined?(PhusionPassenger::NativeSupport) # Split the given string into an hash. Keys and values are obtained by splitting the # string using the null character as the delimitor. def split_by_null_into_hash(data) return PhusionPassenger::NativeSupport.split_by_null_into_hash(data) end else NULL = "\0".freeze def split_by_null_into_hash(data) args = data.split(NULL, -1) args.pop return Hash[*args] end end 

在我的情况下, if -part条件正在执行,所以现在问题就出现了

 PhusionPassenger::NativeSupport.split_by_null_into_hash(data) 

这似乎把我们带到了文件: /path_to_gemset_here/gems/passenger-3.0.17/ext/ruby/passenger_native_support.c

未完待续…

UPDATE5

实际上我决定不处理那个C文件调试地狱,因为我相信这个文件是在乘客安装期间编译并调试它我需要一次又一次地重新安装并重新安装乘客。 所以我决定切换到使用条件的else -part,因为它似乎实现了完全相同的目标,但显然比预编译的C code慢一点。 但就我而言,这并不重要。 所以我通过使用以下代码将文件包含到/path_to_project_folder_here/lib文件夹来覆盖方法的定义:

 module PhusionPassenger module Utils protected NULL = "\0".freeze def split_by_null_into_hash(data) args = data.split(NULL, -1) args.pop return Hash[*args] end end end 

我无法改变Hash[*args]行为(更准确地说我可以通过覆盖::[]方法,但我不想肯定)所以我会稍微更改一下代码:

 module PhusionPassenger module Utils protected NULL = "\0".freeze def split_by_null_into_hash(data) args = data.split(NULL, -1) args.pop headers_hash = Hash.new args.each_slice(2).to_a.each do |pair| headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first end return headers_hash end end end 

和宾果! 现在它有效。

但是我不确定我是否通过这样做没有打破任何其他function所以我不能建议任何人使用这种方法。 我将使用它,直到我遇到与此修改相关的任何问题。 如果是这样,那么我将尝试找到解决问题的另一种方法。

主要问题仍然是为什么那些错误的标题被发送。

使用以下代码在lib文件夹中创建passenger_extension.rb

乘客3

 module PhusionPassenger module Utils protected NULL = "\0".freeze def split_by_null_into_hash(data) args = data.split(NULL, -1) args.pop headers_hash = Hash.new args.each_slice(2).to_a.each do |pair| headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first end return headers_hash end end end 

乘客5

 module PhusionPassenger module Utils # Utility functions that can potentially be accelerated by native_support functions. module NativeSupportUtils extend self NULL = "\0".freeze class ProcessTimes < Struct.new(:utime, :stime) end def split_by_null_into_hash(data) args = data.split(NULL, -1) args.pop headers_hash = Hash.new args.each_slice(2).to_a.each do |pair| headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first end return headers_hash end def process_times times = Process.times return ProcessTimes.new((times.utime * 1_000_000).to_i, (times.stime * 1_000_000).to_i) end end end # module Utils end # module PhusionPassenger 

然后在'config / application.rb'中执行:

 class Application < Rails::Application ... config.autoload_paths += %W(#{config.root}/lib) require 'passenger_extension' end 

然后重新启动网络服务器。

注意:我不确定这是否会破坏任何其他function,因此请自行承担风险,如果您发现此方法有任何损害,请与我们联系。