I need to get a request uuid or time when server get a request. It's easy in Rails, but I'm working on a gem and I would like it to be more generic. So I would like it to work also with Sinatra and every other Ruby application which works in a http server.
This is another problem, it's a gem. I can't put Time.now at the beggining of my application controller. I need it to be generic, so it should work with different frameworks.
What would you propose?
You can implement a Rack middleware which you can use independently from your actual application framework (as long as it used rack, which is true for at least Rails, Sinatra, Padriono and most other Ruby web frameworks).
Rails already includes a middleware for adding a unique ID to a request of required in ActionDispatch::RequestId. Another alternative could be the rack-request-id gem.
A minimal versions of this midleware could look like this:
class RequestIdMiddleware
def initialize(app)
#app = app
end
def call(env)
env['request_id'] = env['HTTP_X_REQUEST_ID'] || SecureRandom.uuid
env['request_started_at'] = Time.now
#app.call(env)
end
end
You can then use this middleware in your config.ru or by adding this to your application.rb in Rails:
config.middleware.use RequestIdMiddleware
Related
I have a custom rack middleware used by my Rails 4 application. The middleware itself is just here to default Accept and Content-Type headers to application/json if the client did not provide a valid information (I'm working on an API). So before each request it changes those headers and after each request it adds a custom X-Something-Media-Type head with a custom media type information.
I would like to switch to Puma, therefore I'm a bit worried about the thread-safety of such a middleware. I did not play with instances variables, except once for the common #app.call that we encounter in every middleware, but even here I reproduced something I've read in RailsCasts' comments :
def initialize(app)
#app = app
end
def call(env)
dup._call(env)
end
def _call(env)
...
status, headers, response = #app.call(env)
...
Is the dup._call really useful in order to handle thread-safety problems ?
Except that #app instance variable I only play with the current request built with the current env variable :
request = Rack::Request.new(env)
And I call env.update to update headers and forms informations.
Is it dangerous enough to expect some issues with that middleware when I'll switch from Webrick to a concurrent web server such as Puma ?
If yes, do you know a handful way to make some tests en isolate portions of my middleware which are non-thread-safe ?
Thanks.
Yes, it's necessary to dup the middleware to be thread-safe. That way, anything instance variables you set from _call will be set on the duped instance, not the original. You'll notice that web frameworks that are built around Rack work this way:
Pakyow
Sinatra
One way to unit test this is to assert that _call is called on a duped instance rather than the original.
Rails itself is based around multiple independent processes that are stateless inbetween requests. I need to add a stateful centralized service (a game automatcher) to my Rails app.
From what little I know I should make that stateful service a rack application. Is there some tutorial out there on how to make a rack application and also importantly how to communicate with it from Rails. What is the idiomatic way to deploy it with Rails and the idiomatic place to put it in my git Rails codebase?
I got my question answered in another question:
How to read POST data in rack request
require 'json'
class Greeter
def call(env)
req = Rack::Request.new(env)
if req.post?
puts req.POST()
end
[200, {"Content-Type" => "application/json"}, [{x:"Hello World!"}.to_json]]
end
end
run Greeter.new
and use JSON.parse( req.body.read ) to parse POST data.
Another option other than rack is to daemonize your app.
I have been struggling with a problem for the past days in a Ruby on Rails App I'm currently working on. I have different countries and for each country we use different Amazon S3 buckets. Amazon S3 key credentials are stored as constants in config/environments/environment_name.rb(ex:demo.rb) There is no way for me to determine which country we are operating from the config file. I can determine which country we are operating from the controllers,models,views,etc but not from the config file. Is there a Ruby meta programming or some other kind of magic that I'm not aware of so that I want to say if we are working on UK as a country in the app, use UK's bucket credentials or Germany as a country, use Germany's bucket credentials? I can't think of a way to pass parameters to environment files from the app itself. Thank you very much in advance for all your helps.
Rather than actually pass the configuration details to whichever S3 client you're using at launch, you should probably select the relevant credentials for each request. Your config file can define them all in a hash like so:
# config/s3.rb
S3_BUCKETS => {
:us => 'our-files-us',
:gb => 'our-files-gb',
:tz => 'special-case'
}
Then you can select the credentials on request like so (in maybe your AppController):
bucket_name = S3_BUCKETS[I18n.locale]
# pass this info to your S3 client
Make sense?
Write a little middleware if you want to keep the knowledge of the per-country configuration out of the main application.
A middleware is extremely simple. A do-nothing middleware looks like this:
class DoesNothing
def initialize(app, *args)
#app = app
end
def call(env)
#app.call(env)
end
end
Rack powers applications through chaining a series of middlewares together... each one is given a reference to #app, which is the next link in the chain, and it must invoke #call on that application. The one at the end of the chain runs the app.
So in your case, you can do some additional configuration in here.
class PerCountryConfiguration
def initialize(app)
#app = app
end
def call(env)
case env["COUNTRY"]
when "AU"
Rails.application.config.s3_buckets = { ... }
when "US"
Rails.application.config.s3_buckets = { ... }
... etc
end
#app.call(env)
end
end
There are several ways to use the middleware, but since it depends on access to the Rails environment, you'll want to do it from inside Rails. Put it in your application.rb:
config.middleware.use PerCountryConfiguration
If you want to pass additional arguments to the constructor of your middleware, just list them after the class name:
config.middleware.use PerCountryConfiguration, :some_argument
You can also mount the middleware from inside of ApplicationController, which means all of the initializers and everything will have already been executed, so it may be too far along the chain.
I'm looking for a quick and easy way to generate a unique per-request ID in rails that I can then use for logging across a particular request.
Any solution should ideally not make too much use of the default logging code, as I'm running the application under both jruby and ruby.
Backupify produced a great article about this: http://blog.backupify.com/2012/06/27/contextual-logging-with-log4r-and-graylog/
We wanted the request_id (that is generated by rails and available at request.uuid to be present on all messages throughout the request. In order to get it into the rack logging (the list of parameters and the timing among others), we added it to the MDC in a rack middleware.
application.rb:
config.middleware.insert_after "ActionDispatch::RequestId", "RequestIdContext"
app/controllers/request_id_context.rb: (had trouble finding it in lib for some reason)
class RequestIdContext
def initialize(app)
#app = app
end
def call(env)
Log4r::MDC.get_context.keys.each {|k| Log4r::MDC.remove(k) }
Log4r::MDC.put("pid", Process.pid)
Log4r::MDC.put("request_id", env["action_dispatch.request_id"])
#app.call(env)
end
end
If you push jobs onto delay job/resque, put the request_id into the queue. and in your worker pull it off and set into the MDC. Then you can trace the requests the whole way through
It looks like lograge (gem) automatically puts request.uuid in your logs.
They have this pattern:
bfb1bf03-8e12-456e-80f9-85afaf246c7f
This is now a feature of rails:
class WidgetsController < ApplicationController
def get
puts request.request_id
end
end
Maybe the NDC feature of log4r is usefull to you.
If I am running a Rails 2 or Rails 3 app, is there a way to print out the web server's name on a page (such as /foos/index)... or if Rails doesn't have any knowledge what the server is, can Rack do it?
In the CGI environment, the SERVER_SOFTWARE variable contains the name of the web server (and its version, unless the web server is configured to exclude this).
For Rails, you can use ENV['SERVER_SOFTWARE'] anywhere to obtain the web server name.
For Rack applications, you can use env['SERVER_SOFTWARE'] where env is available.
Why do you want to do this?
One common reason is for debugging - if one server is having a problem, it would be nice to know which server.
A better way to aid debugging is to include a custom header with the server name. You can write a simple Rack middleware that will do that.
# lib/rack/server_name_header.rb
module Rack
class ServerNameHeader
attr_reader :app, :hostname
def initialize(app)
#app = app
#hostname = `hostname`
end
def call(env)
status, headers, body = *app.call(env)
headers['X-Server-Name'] = hostname
[status, headers, body]
end
end
end
# config/environments/production.rb
require File.expand_path('../../../lib/rack/server_name_header', __FILE__)
My::Application.configure do
config.middleware.use Rack::ServerNameHeader
# all the other stuff
end