Tested on rails 3.2.12 and 3.2.11. In another rails project 3.2.11 I do not have this problem with f.file_field , but in the current I do and can not find the reason for this strange behavior, so here is my question.
I have a strange problem with the update action. Here are the relevant parts of the 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
in haml (simplified but has the same behavior):
= form_for @user do |f| .field = f.label :first_name %br = f.text_field :first_name, :size => 40 .actions = f.submit
So, after clicking the Refresh button, everything works as expected, and user attributes are updated. However, when I add the file field as follows:
= 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 click Refresh, then I get a routing error:
No route matches [PUT] "/1"
I donโt understand why he is trying to reach path /1 using the PUT method. On the page displaying this routing error, I see /users/1 in the address bar of the browser.
html for the form is generated here:
<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 is the fun part. 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 the generated html will be the same as in the previous case (the only difference I see is that single quotes instead of double quotes are used for file field attributes):
<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
In fact, it does not work as it should. I just looked at the params hash and saw that the :avatar key was present, but I missed this in the latter case, there is no enctype="multipart/form-data" attribute in the open open tag in html, so the file will not be uploaded. Adding the attribute enctype=multipart/form-data results in a repeated routing error.
I found that when adding the route put ":id" => "users#update" when trying redirect_to user_path(@user) after submitting a multi-page form (there is definitely no routing error for PUT with this route), then there is also a No route matches [GET] "/users/users/1" routing error No route matches [GET] "/users/users/1" .
Here is full of 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
Does anyone have an idea?
UPDATE2
Identifying the problem using multipart forms helped to find this post about the same problem - Routing error with Post / Put messages (passenger headers) , but, unfortunately, there is no solution ...
Update3
I 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 asymmetric and multidisciplinary queries and found this:
not frequent:
"REQUEST_URI"=>"/users/1", "SCRIPT_NAME"=>"", "PATH_INFO"=>"/users/1"
multi-part:
"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 see in the method definition:
match_data = r.path.match(req.path_info)
PATH_INFO used to find the route for processing the request, but in the latter case it is completely erroneous due to something divides REQUEST_URI into two parts. Unfortunately, I donโt have time to finish my investigation today, I hope that I can do it tomorrow.
If someone has enough curiosity to find the origin of the problem faster than me, please:
UPDATE4 (edited)
So, here is the continuation of the study.
method: parse_native_request in the file: /path_to_gemset_here/gems/passenger-3.0.17/lib/phusion_passenger/abstract_request_handler.rb
the headers_data variable 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= a6e5daff1334c083e54b2bcafba43b32e546af9c \ x00 "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= a6e5daff1334c083e54b2bcafba43b32e546af9c \ x00 "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=
This is followed by 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", "_"=>"_"} = BAh7CEkiDHVzZXJfaWQGOgZFRmkGSSIPc2Vzc2lvbl9pZAY7AEZJIiVhMjU2ZjU5N2VmMTE0YTJiOGEwNGJiYzUyYjM2NDg0OQY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFjaGU4VkxmRHhEQW9lbm1hK1RYd3NBKzBJUTcrL2piQ0lLK1EyeHdyOHVjPQY7AEY% 3D - a6e5daff1334c083e54b2bcafba43b32e546af9c", {"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, obviously, is that the headers are packed in a hash - for PATH_INFO (and for other headers) there are two values, and the last (incorrect) overwrites the first (indeed, the problem is the reason that these headers are sent, but I donโt know how to handle this). Hash packing occurs in the split_by_null_into_hash(headers_data) method. Now let's go there.
file: /path_to_gemset_here/gems/passenger-3.0.17/lib/phusion_passenger/utils.rb
The Utils module contains this code:
if defined?(PhusionPassenger::NativeSupport)
In my case, the if condition is satisfied, so now the problem goes to
PhusionPassenger::NativeSupport.split_by_null_into_hash(data)
and this seems to lead us to the file: /path_to_gemset_here/gems/passenger-3.0.17/ext/ruby/passenger_native_support.c
to continue...
UPDATE5
In fact, I decided not to deal with this C -file debugging addek, because I believe that this file is compiled during the installation of the passenger and debug it. I will need to reinstall and reinstall the passenger again and again. So I decided to switch to using the else -part condition, since it seems to achieve exactly the same goal, but obviously a little slower than the precompiled C code. But in my case it does not really matter. So I tried the method definition by including the file in the /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 the behavior of Hash[*args] (more precisely, I can override the ::[] method, but I donโt want it exactly), 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 am not sure that I did not violate any other functions by doing this, so I cannot advise anyone to use this approach. I will use it until I encounter any problem associated with this modification. If so, I will try to find another way to solve the problem.
And the main question still is why those wrong headers are being sent.