I am writing a custom middleware for my rails app, to decrypt/encrypt parameters in a post request. I have configured the middleware in 'config/environments/staging.rb'
config.middleware.use CustomMiddleware
In order to read the request params i am reading the env obj like this
rack_input = env['rack.input'].read
I am able to access the parameter and manipulate as needed. But when i pass over on the env obj to the app object which is the receiver. I am getting
"ActionDispatch::Http::Parameters::ParseError"
Full error
Error occurred while parsing request parameters.
Contents:
F, [2017-11-15T02:13:50.230088 #24060] FATAL -- : [9aa25c7e-56e7-4894-ba30-3a01f60ae4fc]
F, [2017-11-15T02:13:50.230340 #24060] FATAL -- : [9aa25c7e-56e7-4894-ba30-3a01f60ae4fc] ActionDispatch::Http::Parameters::ParseError (no implicit conversion of nil into String):
F, [2017-11-15T02:13:50.230563 #24060] FATAL -- : [9aa25c7e-56e7-4894-ba30-3a01f60ae4fc]
F, [2017-11-15T02:13:50.230782 #24060] FATAL -- : [9aa25c7e-56e7-4894-ba30-3a01f60ae4fc] app/middleware/custom_middleware.rb:34:in `call'
My code is somewhat like this
class CustomMiddleware
include Encryption
def initialize(app)
#app = app
end
def set_payload_params(env)
rack_input = env['rack.input'].read
#args = JSON.parse(rack_input) rescue {}
end
def call(env)
request = Rack::Request.new(env)
set_payload_params(env)
payload = #args['payload']
decrypt_params
status, headers, response = #app.call(env)
encrypt_params
end
If i don't read the env object and hard code the parameters, there is no such parsing error. And my controller is responding with data w.r.t to hardcode parameters.
My Rails version is 5.1.4
My ruby version is 2.4.2
I have done a similar middleware in another rails app(4.2.8) which is working fine.
Any insight on this will be of much help. Thanks in advance.
This error occurs cause after params are empty after read in middleware (this is stream).
rack_input = env['rack.input'].read
After execute this code rack params are blank. Now you need to make
env['rack.input'].rewind
or
request.body.rewind
Now your request has params again and Rails can simply continue job.
Added a hack to set content-type as "" in custom middleware, which avoided parsing on the input before forwarding the input to next component in the stack.
class CustomMiddleware
include Encryption
def initialize(app)
#app = app end
def set_payload_params(env)
rack_input = env['rack.input'].read
#args = JSON.parse(rack_input) rescue {} end
def call(env)
env['CONTENT_TYPE'] = ''
request = Rack::Request.new(env)
set_payload_params(env)
payload = #args['payload']
decrypt_params
status, headers, response = #app.call(env)
encrypt_params
end
Related
I have a Rails 6 app to which users can upload CSV files. Rails/Rack imposes a limit in the number of params that can be included in a request, and I've set this to a size larger than likely submissions to my app. However, I would like to return a friendly response if a too-large file is uploaded.
It looks like I need to add some custom middleware, to catch and rescue the error, but I can't get the code to work - the basic error is still raised without my rescue block being called.
The error from the server is:
Rack app error handling request { POST /[PATH_TO]/datasets }
#<RangeError: exceeded available parameter key space>
The code in my app/middleware/catch_errors.rb file is basically taken from a previous SO answer, where someone was catching ActionDispatch::ParamsParser::ParseError in JSON, but with my own code in the rescue block (which I realise may not work properly in this context, but that's not the issue right now):
class CatchErrors
def initialize(_app)
#app = _app
end
def call(_env)
begin
#app.call(_env)
rescue RangeError => _error
_error_output = "There were too many fields in the data you submitted: #{_error}"
if env['HTTP_ACCEPT'] =~ /application\/html/
Rails.logger.error("Caught RangeError: #{_error}")
flash[:error_title] = 'Too many fields in your data'
flash[:error_detail1] = _error_output
render 'static_pages/error', status: :bad_request
elsif env['HTTP_ACCEPT'] =~ /application\/json/
return [
:bad_request, { "Content-Type" => "application/json" },
[ { status: :bad_request, error: _error_output }.to_json ]
]
else
raise _error
end
end
end
end
I'm loading it in config.application.rb like this:
require_relative '../app/middleware/catch_errors'
...
config.middleware.use CatchErrors
I'm resetting the size limit for testing in app/initializers/rack.rb like this:
if Rack::Utils.respond_to?("key_space_limit=")
Rack::Utils.key_space_limit = 1
end
Any help gratefully received!
First, execute command to see all middlewares:
bin/rails middleware
config.middleware.use place your middleware at the bottom of the middleware stack. Because of that it can not catch error. Try to place it at the top:
config.middleware.insert_before 0, CatchErrors
Another point to mention, may be you will need to config.middleware.move_after or even config.middleware.delete some middleware. For instance, while tinkering I needed to place:
config.middleware.move_after CatchErrors, Rack::MiniProfiler
I am trying to set the 'X-Frame-Options' header on all responses returned by my Rails application. It seems like this header is not set on 404 or 500 type responses. How can I configure Rails to always include this header?
It appears I somehow need to hook into Rails to ensure these headers are always set.
I am having success using the below middleware as my 'exceptions_app'.
class XSecurityHandler
def initialize(app)
#app = app
end
def call(env)
_status, headers, response = #app.call(env)
headers['X-Frame-Options'] = "SAMEORIGIN"
headers['X-Content-Type-Options'] = "nosniff"
[status(env), headers, response]
end
private
def status(env)
path = env["ORIGINAL_FULLPATH"]
if path == "/404"
404
elsif path == "/422"
422
else
500
end
end
end
Set default headers in config/application.rb:
config.action_dispatch.default_headers['X-Frame-Options'] = 'SAMEORIGIN'
but it wont work, if you have configured reverse proxy (like nginx) to serve static assets which does not exists (404), but i think you know about this :)
I'm trying to find the best place to do this. My middleware looks like this :
class Fixer
def initialize app
#app = app
end
def call env
if env["HTTP_ORIGIN"] == "https://where_i_expect_it_to_come_from.com/"
env['rack.input'] = StringIO.new('{"yo":"momma"}') # <-- But this info is not actually written before the call is passed!
end
env['rack.input'].rewind
status, headers, response = #app.call env
return [status, headers, response]
end
end
Rails.application.config.middleware.insert_before ActionDispatch::ParamsParser, Fixer
It seems that even when I rewrite the call here, the info is not actually rewritten properly. Any ideas how I can have my content written before its bubbled up?
i think this problem is the same as here: https://stackoverflow.com/questions/22712663/getting-a-routing-error-when-my-routes-are-clearly-marked and here I have a third party service that I believe is sending me malformed JSON
from what i understand you want to fix some incoming request-data.
as a simplified example:
curl -X PUT -d "user[name]=something" http://localhost:3000/users/123
i change something to phoet in the fixer
class Fixer
def initialize(app)
#app = app
end
def call(env)
env['rack.input'] = StringIO.new("user[name]=phoet")
#app.call(env)
end
end
and add it to application.rb
config.middleware.insert_before "ActionDispatch::ParamsParser", "Fixer"
when i request my app now, i see the logs
[localhost] [fdff4eb0-8387-41] Started PUT "/users/123" for 127.0.0.1 at 2014-03-30 13:16:02 -0400
[localhost] [fdff4eb0-8387-41] Processing by UsersController#update as */*
[localhost] [fdff4eb0-8387-41] Parameters: {"user"=>{"name"=>"phoet"}, "id"=>"123"}
A site I manage is getting constant requests for a javascript file that no longer exists, from an older version of the site. These requests take up a lot of resources because they get routed through Rails every time to return a 404. I am thinking it would be much better to have Rack handle that specific URL and return 404 itself. Is that correct? If so, how would I set that up?
I have been checking out this blog post which I think is kinda the way to move forward (ie, inheritance from some existing Rack module):
http://icelab.com.au/articles/wrapping-rack-middleware-to-exclude-certain-urls-for-rails-streaming-responses/
So I ended up writing my own little bit of middleware:
module Rack
class NotFoundUrls
def initialize(app, exclude)
#app = app
#exclude = exclude
end
def call(env)
status, headers, response = #app.call(env)
req = Rack::Request.new(env)
return [status, headers, response] if !#exclude.include?(URI.unescape(req.fullpath))
content = 'Not Found'
[404, {'Content-Type' => 'text/html', 'Content-Length' => content.size.to_s}, [content]]
end
end
end
and then adding this to the config.ru file:
use Rack::NotFoundUrls, ['/javascripts/some.old.file.js']
It's the first time I've done this so let me know if there's any glaring mistakes...
The rack-contrib gem includes a Rack::NotFound middleware component (among many other useful elements) which should do the job:
https://github.com/rack/rack-contrib/
I am using Ruby on Rails 3 and I am trying to implement an API. I would like to solve some strange problems that I have on returning data after a web client HTTP GET Request. In few words, problems are in the response body returned values for which I get "too much" "" (see the examples belowe) and, sometime, in returning JSON data.
On the web service side in my Rack middleware I have:
class Testing
def initialize(app)
#app = app
end
def call(env)
accounts = Account.find([1,2,3])
resp_test = accounts.count
[200, {}, resp_test] # No [200, {'Content-Type' => 'application/json'}, resp_test]
end
end
On the client side if I see the response I have
# debug response.body
---
In this case the problem is the accounts.count that returns a value of "" in the response body. It is possible that accounts.count doesn't do what it should do.
I also encountered some problems when I didn't return JSON data. For example, debugging variables on the client side, sometime I got a body response value of "" if I didn't return JSON DATA like this:
# On the service side in the Rack middleware file
[200, {}, resp_test] # No[200, {'Content-Type' => 'application/json'}, resp_test.to_json]
The response are:
# Case don't returning JSON data
# debug response.body
---
# Case returning JSON data
# debug response.body
--- test_value
What is the problem? If it is accounts.count or Account.find([1,2,3]), how can I make that to work in order to return correct value?
first your middleware should be after ActiveRecord::ConnectionAdapters::ConnectionManagement when calling rake middleware
second try to return resp_test.to_s