Rails 3.2 f.file_field导致路由错误 [英] Rails 3.2 f.file_field causes routing error
问题描述
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
属性会导致路由错误再次发生。
我发现 以下是完整的 和耙路径 任何人有任何想法? UPDATE2 揭示问题是多部分形式帮助f ind这个帖子关于相同的问题 - 路由错误与邮政/放置请求(乘客头)但很遗憾没有解决方案... UPDATE3 发现有趣的事情在 我为非多部分和多部分请求检查了 non-multipart: 多部分: 所以问题在这里。正如我在该方法的定义中看到的那样:放入:id=> 用户#更新
在尝试 redirect_to user_path(@user)
时添加的路由在提交多部分表单后路由错误 PUT
),那么也有路由错误没有路由匹配[GET]/ users / users / 1$ c $
$ 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 /
/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在一个非多部分请求中
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 $ c $
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="✓" /><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="✓" /><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屋!