I really love to learn how the things work under de hood, specially when it comes to technology. Currently, I'm studying ruby more deeply and trying to use it only with rack in order to understand how rack based frameworks work.
At this moment, rack middlewares are getting me crazy. Why? Although middlewares are very simple, I'm a little confused about the @app.call(env)
. For the sake of clarity, consider the following code:
class MyCustomMiddleware
def initialize(app)
@app = app
end
def call(env)
@app.call(env) if env['REQUEST_METHOD'] != 'POST'
body = env['rack.input'].clone
body = JSON.parse(body.gets || {}, symbolize_names: true)
body[:some_message] = "Peace, Love and Hope"
env.update('rack.input', StringIO.new(JSON.dump(body)))
@app.call(env)
env
end
All I want to do is change the request body if (and only if) the request method is POST
. If the request method is any other type than "POST", I want to pass the request to the next middleware (it works this way in Rack, right?). The problem, is that all the code is being executed, no matter if the request method is POST
or not.
Maybe it can be a misunderstanding in regard to rack middlewares, as I'm used to work with Express.js
. In Express
, you have a stack of middlewares in which the requests pass through, and, each middleware calls the next()
method in order to "release" the request. I was thinking that @app.call(env)
would be similar to the Express
' next()
method... But looks like not, as the request is not being released when I call it and all the code is being executed.
Can somebody explain me what this method really does and point me where is my error?
@app.call
doesn't terminate the execution of your handler - it just calls the next middleware in the chain. It is expected that each middleware will either call the next in the chain and return its return value, or terminate the chain by returning an array of [status_code, body, headers]
. Each middleware is expected to pass the array of [status_code, body, headers]
back up the chain, by returning that value out of its #call
method. Recall that in Ruby, the return value of the last statement of each method is implicitly returned to its caller.
As written, you're going to invoke the remaining middleware in the stack, then discard its result, and then continue on with your handler, run the code, invoke the remaining middleware stack again, and then finally return that result back upstream.
Just explicitly return
if you want to bail out of the handler:
def call(env)
return @app.call(env) if env['REQUEST_METHOD'] != 'POST'
body = env['rack.input'].clone
body = JSON.parse(body.gets || {}, symbolize_names: true)
body[:some_message] = "Peace, Love and Hope"
env.update('rack.input', StringIO.new(JSON.dump(body)))
@app.call(env)
end
It may be more clear to just run your mutators conditionally, then always @app.call
to terminate the handler:
def call(env)
mutate!(env) if env['REQUEST_METHOD'] == "POST"
@app.call(env)
end
def mutate!(env)
body = env['rack.input'].clone
body = JSON.parse(body.gets || {}, symbolize_names: true)
body[:some_message] = "Peace, Love and Hope"
env.update('rack.input', StringIO.new(JSON.dump(body)))
end
Since @app.call
is the last statement in #call
here, its return value is returned to your middleware's caller.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With