Rails 3.2 f.file_field导致路由错误 [英] Rails 3.2 f.file_field causes routing error

查看:178
本文介绍了Rails 3.2 f.file_field导致路由错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

routes:

  get注册=> users#new,:as => 注册
获取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

form in haml(简化但具有相同的行为):
$ b $ $ p $ = 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

然后按Update更新,我得到路由错误:

没有路线匹配[PUT]/ 1

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

这里是

生成的html格式为:

 < form accept-charset =UTF-8action =/ users / 1 class =edit_userenctype =multipart / form-dataid =edit_user_1method =post>< div style =margin:0; padding:0; display:inline>< input name =utf8type =hiddenvalue =&#x2713; />< input name =_ methodtype =hiddenvalue =put/>< input name =authenticity_tokentype =hiddenvalue =che8VLfDxDAoenma + TXwsA + 0IQ7 + / jbCIK + Q2xwr8uc = />< / div> 
< div class ='field'>
< label for =user_first_name>名字< / label>
< br>
< input id =user_first_namename =user [first_name]size =40type =textvalue =Anton/>
< / div>
< div class ='field'>
< label for =user_avatar>头像< / label>
< br>
< input id =user_avatarname =user [avatar]type =file/>
< / div>
< div class ='actions'>
< input name =committype =submitvalue =更新用户/>
< / div>
< / form>

所以,最有趣的是这里。当我将表单更改为此时:

  = 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 (与我以前的情况相同)(我可以看到的唯一区别是,对于文件字段属性,使用单引号而不是双引号):

 < form accept-charset =UTF-8action =/ users / 1class =edit_userid =edit_user_1method =post>< div style =margin :0; padding:0; display:inline>< input name =utf8type =hiddenvalue =&#x2713; />< input name =_ methodtype =hiddenvalue =put/>< input name =authenticity_tokentype =hiddenvalue =che8VLfDxDAoenma + TXwsA + 0IQ7 + / jbCIK + Q2xwr8uc = />< / div> 
< div class ='field'>
< label for =user_first_name>名字< / label>
< br>
< input id =user_first_namename =user [first_name]size =40type =textvalue =Anton/>
< / div>
< div class ='field'>
< label for =user_avatar>头像< / label>
< br>
< input id ='user_avatar'name ='user [avatar]'type ='file'>
< / div>
< div class ='actions'>
< input name =committype =submitvalue =更新用户/>
< / div>
< / form>

但是在提交此表单后,没有路由错误,并且所有内容都按照原样运行。



更新

实际上,它不会像应该那样工作。我只是看着 params 散列,并看到:avatar 键存在,但我错过了后者的情况在html中的open标签中没有 enctype =multipart / form-data属性,所以文件不会被上传。添加 enctype = multipart / form-data 属性会导致路由错误再次发生。



我发现放入:id=> 用户#更新在尝试 redirect_to user_path(@user)时添加的路由在提交多部分表单后路由错误 PUT ),那么也有路由错误没有路由匹配[GET]/ users / users / 1 $ b

以下是完整的 routes.rb

  Myapp :: Application.routes.draw do 

匹配oauth / callback=> oauths#callback
匹配oauth / callback /:provider=> oauths#callback
匹配oauth /:provider=> oauths#oauth,:as =>> :auth_at_provider

资源:国家
资源:类别
资源:图片
资源:集合
资源:商品

放置:id=> 用户#更新
获得注册=> users#new,:as => 注册
获取profile=> users#profile,:as => profile
资源:用户做
成员做
获得:激活
结束
结束

获得signout=> sessions#destroy,:as => signout
获得signin=> sessions#new,:as =>> 登录
资源:会话

获取网站/索引

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#创建
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(.:格式)类别#销毁
图片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 /项目(。:格式)项目#创建
new_item GET /items/new(.:format)项目#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
注册GET /signup(.:format)users#new
profile GET /profile(.:format)users #profile
activate_user GET /users/:id/activate(.:format)users#激活
用户GET /users(.:format)users#index $ b $ 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(.:格式)会话#destroy
site_index GET /site/index(.:format)site#index
root /

任何人有任何想法?

UPDATE2



揭示问题是多部分形式帮助f ind这个帖子关于相同的问题 - 路由错误与邮政/放置请求(乘客头)但很遗憾没有解决方案...



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 ,并发现这一点:



non-multipart:

 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在一个非多部分请求中

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

pre $匹配数据= r.path.match(req.path_info)

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



如果任何人有足够的好奇心找到问题比我快 - 欢迎您:)
$ b $ p UPDATE4(已编辑)



所以,这里是调查的延续。



方法: parse_native_request
in file: /path_to_gemset_here/gems/passenger-3.0.17/lib/phusion_passenger/abstract_request_handler.rb



变量 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 [未给出地址] \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; border = ---- 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,如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

个;>>>这里似乎开始了一种重定向<<<<

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,如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端口80< / address> \ 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 [无给定的地址] \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)

头文件包含:

  {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; border = ---- 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=>保持活跃,
HTTP_CONTENT_LENGTH=>748,
HTTP_CACHE_CONTROL=>max-age = 0,
HTTP_ACCEPT 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,如Gecko)Chrome / 25.0.1364.97 Safari / 537.22,
HTTP_CONTENT_TYPE=>multipart / form-data ; border = ---- 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=>< address> Apache / 2.2.22(Ubuntu)Server at myapp.loc Port 80< / address> \\\

SCRIPT_FILENAME=>/ path_to_project_folder_here / public / users,
REDIRECT_STATUS=>302 ,
PASSENGER_CONNECT_PASSWORD=>GgEqWssAcbBETWnFI7xzBfWRGibgB34OhfFSUVyOhPn,_=>_}

所以问题显然在于头被打包成hash的方式 - 对于 PATH_INFO (以及其他头文件)和后者有两个值一个(不正确)重写第一个(确实问题在于为什么这些头文件正在发送,但我不知道如何处理这个问题)。打包到散列中发生在 split_by_null_into_hash(headers_data)方法中。现在去那里。



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

模块 Utils 包含以下代码:

  if defined?(PhusionPassenger :: NativeSupport)
#将给定的字符串拆分成一个散列。键和值通过使用空字符作为分隔符分割
#字符串来获得。
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条件正在执行,所以现在问题发生在

pre $ Ph cpPesenger :: 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 - 部分,因为它似乎实现了完全相同的目标,但显然比预编译的 ç -code。但在我看来,这并不重要。所以我通过在这个代码中包含一个文件到 / path_to_project_folder_here / lib 文件夹来覆盖方法的定义:

 模块PhusionPassenger 
模块实用程序

保护

NULL =\0.freeze

def split_by_null_into_hash (data)
args = data.split(NULL,-1)
args.pop
return Hash [* args]
end

end
end

我无法更改哈希[* args] 行为(更准确地说我可以通过重写 :: [] 方法但我不确定),所以我会更改代码有点:

 模块PhusionPassenger 
模块实用程序

保护

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,除非headers_hash.keys.include? pair.first
end
return headers_hash
end
$ b $ end
end

和Bingo!现在它可以工作了。

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



主要问题仍然是为什么发送这些错误的标题。 / p>

解决方案

lib中创建 passenger_extension.rb



乘客3

 模块PhusionPassenger 
模块Utils

保护

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 。每个do | pair |
headers_hash [pair.first] = pair.last,除非headers_hash.keys.include? pair.first
end
return headers_hash
end
$ b $ end
end

乘客5

  module PhusionPassenger 
模块Utils

#native_support函数可能会加速的实用程序函数。
模块NativeSupportUtils
扩展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,除非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

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


$ b 注意:我是不确定这不会破坏任何其他功能,因此请自行承担风险,如果您发现此方法有任何伤害,请告诉我们。


Tested on rails 3.2.12 and 3.2.11. In another rails 3.2.11 project I do not have this issue with f.file_field, but in current one I do and can't find a reason for this strange behaviour, so here is my question.

I have a weird problem with update action. Here are relevant parts of code:

routes:

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

controller:

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

form in haml (simplified but it has the same behaviour):

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

So, after I press Update everything works as expected and user's attributes are being updated. However when I add a file field like this:

= 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

and I press Update then I get routing error:

No route matches [PUT] "/1"

I don't understand why it tries to reach /1 path with PUT method. On the page showing that routing error I can see /users/1 in the browser's address bar.

here is generated html for the form:

  <form accept-charset="UTF-8" action="/users/1" class="edit_user" enctype="multipart/form-data" id="edit_user_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /><input name="_method" type="hidden" value="put" /><input name="authenticity_token" type="hidden" value="che8VLfDxDAoenma+TXwsA+0IQ7+/jbCIK+Q2xwr8uc=" /></div>
    <div class='field'>
      <label for="user_first_name">First name</label>
      <br>
      <input id="user_first_name" name="user[first_name]" size="40" type="text" value="Anton" />
    </div>
    <div class='field'>
      <label for="user_avatar">Avatar</label>
      <br>
      <input id="user_avatar" name="user[avatar]" type="file" />
    </div>
    <div class='actions'>
      <input name="commit" type="submit" value="Update User" />
    </div>
  </form>

So, here comes the most interesting thing. When I change my form to this:

= 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

then generated html is right the same as in previous case (the only difference I can see is that for the file field attributes single quotes instead of double quotes are used):

  <form accept-charset="UTF-8" action="/users/1" class="edit_user" id="edit_user_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /><input name="_method" type="hidden" value="put" /><input name="authenticity_token" type="hidden" value="che8VLfDxDAoenma+TXwsA+0IQ7+/jbCIK+Q2xwr8uc=" /></div>
    <div class='field'>
      <label for="user_first_name">First name</label>
      <br>
      <input id="user_first_name" name="user[first_name]" size="40" type="text" value="Anton" />
    </div>
    <div class='field'>
      <label for="user_avatar">Avatar</label>
      <br>
      <input id='user_avatar' name='user[avatar]' type='file'>
    </div>
    <div class='actions'>
      <input name="commit" type="submit" value="Update User" />
    </div>
  </form>

But after submitting this form there is no routing error and everything works as it should.

UPDATE

Actually it doesn't work as it should to. I just looked at the params hash and saw that :avatar key is present, but I missed that in the latter case there is no enctype="multipart/form-data" attribute in the form open tag in html, so file wouldn't be uploaded. Adding enctype=multipart/form-data attribute causes routing error to happen again.

I've found that with put ":id" => "users#update" route added when it tries to redirect_to user_path(@user) after submitting multipart form (for sure with this route there is no routing error for PUT), then there is also routing error No route matches [GET] "/users/users/1".

Here is full 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

and rake routes

  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        /

Anybody have any idea?

UPDATE2

Revealing the problem is with multipart forms helped to find this post about the same problem - Routing Error with Post/Put requests (Passenger Headers) but unfortunately there's no solution...

UPDATE3

I've found something interesting. There is a method in /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

I checked env for both non-multipart and multipart requests and found this:

non-multipart:

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

multipart:

"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

So here is the problem. As I can see in the method's definition:

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

PATH_INFO is used to find a route to handle request, but in the latter case it is completely wrong due to something divides REQUEST_URI into two parts. Unfortunately currently I have no time to finish my investigation today, hope I'll be able to do it tomorrow.

If anyone will have enough curiosity to find an origin of the problem faster than me - you are welcome :)

UPDATE4 (edited)

So, here is a continuation of the investigation.

method: parse_native_request in file: /path_to_gemset_here/gems/passenger-3.0.17/lib/phusion_passenger/abstract_request_handler.rb

variable headers_data after this call:

headers_data = channel.read_scalar(buffer, MAX_HEADER_SIZE)

contains:

"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<address>Apache/2.2.22 (Ubuntu) Server at myapp.loc Port 80</address>\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"

After that follows this call:

headers = split_by_null_into_hash(headers_data)

and headers contains:

{"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"=>"<address>Apache/2.2.22 (Ubuntu) Server at myapp.loc Port 80</address>\n", 
"SCRIPT_FILENAME"=>"/path_to_project_folder_here/public/users", 
"REDIRECT_STATUS"=>"302", 
"PASSENGER_CONNECT_PASSWORD"=>"GgEqWssAcbBETWnFI7xzBfWRGibgB34OhfFSUVyOhPn", "_"=>"_"}

So the problem is obviously in the way in which headers are being packed into hash - there are two values for PATH_INFO (and for other headers too) and the latter one (incorrect) rewrites the first one (indeed the problem is in the reason why these headers are being sent but I don't know how to handle this). Packing into hash is happening in split_by_null_into_hash(headers_data) method. Now going there.

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

Module Utils contains this code:

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

In my case the if-part of condition is being executed, so now the problem goes to

PhusionPassenger::NativeSupport.split_by_null_into_hash(data)

and that seems to take us to file: /path_to_gemset_here/gems/passenger-3.0.17/ext/ruby/passenger_native_support.c

to be continued...

UPDATE5

Actually I decided not to deal with that C-file debugging hell as I believe that this file is being compiled during passenger installation and to debug it I would need to reinstall and reinstall passenger again and again. So I decided to switch to using the else-part of the condition as it seems to achieve exactly the same goal, but obviously a bit slower than precompiled C-code. But in my case it doesn't really matter. So I overrode method's definition by including a file to /path_to_project_folder_here/lib folder with this code:

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

I can't change Hash[*args] behaviour (more precisely saying I can by overriding ::[] method but I don't want to for sure) so I'll change the code a bit:

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

And Bingo! Now it works.

However I'm not sure that I didn't break any other functionality by doing this so I can't advice to anyone to use this approach. I'll be using it until I face any problem related to this modification. If it will be the case then I'll try to find another way to solve the problem.

And main question still remains about why those wrong headers are being sent.

解决方案

Create passenger_extension.rb in the lib folder with this code:

Passenger 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

Passenger 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

And then in 'config/application.rb' do:

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

And then restart a webserver.

NOTICE: I'm not sure that this doesn't break any other functionality so use it on your own risk and please let me know if you find any harm from this approach.

这篇关于Rails 3.2 f.file_field导致路由错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆