lib/marlowe/middleware.rb in marlowe-1.0.3 vs lib/marlowe/middleware.rb in marlowe-2.0
- old
+ new
@@ -1,36 +1,97 @@
+# frozen_string_literal: true
+
require 'rack'
require 'request_store'
require 'securerandom'
module Marlowe
- # Marlowe correlation id middleware. Including this into your
- # middleware stack will add a correlation id header as an incoming
- # request, and save that id in a request session variable.
-
- # Name of the default header to look for and put the correlation id in.
- CORRELATION_HEADER = 'Correlation-Id'.freeze
-
+ # Marlowe correlation id middleware. Including this into your middleware
+ # stack will add a correlation id header as an incoming request, and save
+ # that id in a request session variable.
class Middleware
- # Sets the the rack application to +app+
- def initialize(app, opts={})
+ # The name of the default header to look for and put the correlation id in.
+ CORRELATION_HEADER = 'X-Request-Id' #:nodoc:
+
+ # Configure the Marlowe middleware to call +app+ with options +opts+.
+ #
+ # === Options
+ #
+ # <tt>:header</tt>:: The name of the header to inspect. Defaults to
+ # 'X-Request-Id'. Also available as
+ # <tt>:correlation_header</tt>.
+ # <tt>:handler</tt>:: The handler for request correlation IDs. Defaults to
+ # sanitizing provided request IDs or generating a UUID.
+ # If <tt>:simple</tt> is provided, provided request IDs
+ # will not be sanitized. A callable (expecting a single
+ # input of any possible existing request ID) may be
+ # provided to introduce more complex request ID
+ # handling.
+ # <tt>:return</tt>:: If +true+ (the default), the request correlation ID
+ # will be returned as part of the response headers.
+ # <tt>:action_dispatch</tt>:: If +true+, Marlowe will add code to behave
+ # like <tt>ActionDispatch::RequestId</tt>.
+ # Depends on <tt>ActionDispatch::Request</tt>.
+ def initialize(app, opts = {})
@app = app
- @correlation_header = format_http_header(opts[:correlation_header] || Marlowe::CORRELATION_HEADER)
+ @header, @http_header = format_header_name(
+ opts[:header] || opts[:correlation_header] || CORRELATION_HEADER
+ )
+ @handler = opts.fetch(:handler, :clean)
+ @return = opts.fetch(:return, true)
+ @action_dispatch = opts.fetch(:action_dispatch, false)
end
# Stores the incoming correlation id from the +env+ hash. If the correlation
# id has not been sent, a new UUID is generated and the +env+ is modified.
def call(env)
- env[@correlation_header] ||= SecureRandom.uuid
- RequestStore.store[:correlation_id] = env[@correlation_header]
+ req_id = make_request_id(env[@http_header])
+ RequestStore.store[:correlation_id] = env[@http_header] = req_id
- @status, @headers, @response = @app.call(env)
- [@status, @headers, @response]
+ if @action_dispatch
+ req = ActionDispatch::Request.new(env)
+ req.request_id = req_id
+ end
+
+ @app.call(env).tap { |_status, headers, _body|
+ if @return
+ headers[@header] = if @action_dispatch
+ req.request_id
+ else
+ RequestStore.store[:correlation_id]
+ end
+ end
+ }
end
private
- def format_http_header(header)
- ("HTTP_" + header.gsub(/-/, '_').upcase).freeze
+ def format_header_name(header)
+ [
+ header.to_s.tr('_', '-').freeze,
+ "HTTP_#{header.to_s.tr('-', '_').upcase}"
+ ]
+ end
+
+ def make_request_id(request_id)
+ if @handler == :simple
+ simple(request_id)
+ elsif @handler.kind_of?(Proc)
+ simple(@handler.call(request_id))
+ else
+ clean(request_id)
+ end
+ end
+
+ def clean(request_id)
+ simple(request_id).gsub(/[^\w\-]/, '')[0, 255]
+ end
+
+ def simple(request_id)
+ if request_id && !request_id.empty? && request_id !~ /\A[[:space]]*\z/
+ request_id
+ else
+ SecureRandom.uuid
+ end
end
end
end