Skip to content

Commit e691fc5

Browse files
committed
Add RackEnvBuilder class to construct Rack environment for lifecycle hooks and handlers
1 parent d644114 commit e691fc5

File tree

4 files changed

+424
-23
lines changed

4 files changed

+424
-23
lines changed

docs/handler_plugins.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,34 @@ It should be noted that the `headers` parameter is a Hash with **string keys** (
100100

101101
You can disable header normalization by either setting the environment variable `HOOKS_NORMALIZE_HEADERS` to `false` or by setting the `normalize_headers` option to `false` in the global configuration file.
102102

103+
### `env` Parameter
104+
105+
The `env` parameter is a Hash that contains a modified Rack environment. It provides a lot of context about the request, including information about the request method, path, query parameters, and more. This can be useful for debugging or for accessing additional request information. It is considered *everything plus the kitchen sink* that you might need to know about the request.
106+
107+
Here is a partial example of what the `env` parameter might look like:
108+
109+
```ruby
110+
{
111+
"REQUEST_METHOD" => "POST",
112+
"PATH_INFO" => "/webhooks/example",
113+
"QUERY_STRING" => "foo=bar&baz=123",
114+
"HTTP_VERSION" => "HTTP/1.1",
115+
"REQUEST_URI" => "https://hooks.example.com/webhooks/example?foo=bar&baz=qux",
116+
"SERVER_NAME" => "hooks.example.com",
117+
"SERVER_PORT" => 443,
118+
"CONTENT_TYPE" => "application/json",
119+
"CONTENT_LENGTH" => 123,
120+
"REMOTE_ADDR" => "<IP_ADDRESS>",
121+
"hooks.request_id" => "<REQUEST_ID>",
122+
"hooks.handler" => "ExampleHandler"
123+
"hooks.endpoint_config" => {}
124+
"hooks.start_time" => "2023-10-01T12:34:56Z",
125+
# etc...
126+
}
127+
```
128+
129+
For the complete list of available keys in the `env` parameter, you can refer to the source code at [`lib/hooks/app/rack_env_builder.rb`](../lib/hooks/app/rack_env_builder.rb).
130+
103131
### `config` Parameter
104132

105133
The `config` parameter is a Hash (symbolized) that contains the endpoint configuration. This can include any additional settings or parameters that you want to use in your handler. Most of the time, this won't be used, but sometimes endpoint configs add `opts` that can be useful for the handler.

lib/hooks/app/api.rb

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require "securerandom"
66
require_relative "helpers"
77
require_relative "auth/auth"
8+
require_relative "rack_env_builder"
89
require_relative "../plugins/handlers/base"
910
require_relative "../plugins/handlers/default"
1011
require_relative "../core/logger_factory"
@@ -65,29 +66,15 @@ def self.create(config:, endpoints:, log:)
6566
Core::LogContext.with(request_context) do
6667
begin
6768
# Build Rack environment for lifecycle hooks
68-
rack_env = {
69-
"REQUEST_METHOD" => request.request_method,
70-
"PATH_INFO" => request.path_info,
71-
"QUERY_STRING" => request.query_string,
72-
"HTTP_VERSION" => request.env["HTTP_VERSION"],
73-
"REQUEST_URI" => request.url,
74-
"SERVER_NAME" => request.env["SERVER_NAME"],
75-
"SERVER_PORT" => request.env["SERVER_PORT"],
76-
"CONTENT_TYPE" => request.content_type,
77-
"CONTENT_LENGTH" => request.content_length,
78-
"REMOTE_ADDR" => request.env["REMOTE_ADDR"],
79-
"hooks.request_id" => request_id,
80-
"hooks.handler" => handler_class_name,
81-
"hooks.endpoint_config" => endpoint_config,
82-
"hooks.start_time" => start_time.iso8601,
83-
"hooks.full_path" => full_path
84-
}
85-
86-
# Add HTTP headers to environment
87-
headers.each do |key, value|
88-
env_key = "HTTP_#{key.upcase.tr('-', '_')}"
89-
rack_env[env_key] = value
90-
end
69+
rack_env_builder = RackEnvBuilder.new(
70+
request,
71+
headers,
72+
request_context,
73+
endpoint_config,
74+
start_time,
75+
full_path
76+
)
77+
rack_env = rack_env_builder.build
9178

9279
# Call lifecycle hooks: on_request
9380
Core::PluginLoader.lifecycle_plugins.each do |plugin|

lib/hooks/app/rack_env_builder.rb

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# frozen_string_literal: true
2+
3+
module Hooks
4+
module App
5+
# Builds Rack environment hash for lifecycle hooks and handler processing
6+
#
7+
# This class centralizes the construction of the Rack environment that gets
8+
# passed to lifecycle hooks and handlers, ensuring consistency and making
9+
# it easy to reference the environment structure.
10+
#
11+
# @example Building a Rack environment
12+
# builder = RackEnvBuilder.new(request, headers, request_context)
13+
# rack_env = builder.build
14+
#
15+
class RackEnvBuilder
16+
# Initialize the builder with required components
17+
#
18+
# @param request [Grape::Request] The Grape request object
19+
# @param headers [Hash] Request headers hash
20+
# @param request_context [Hash] Request context containing metadata
21+
# @option request_context [String] :request_id Unique request identifier
22+
# @option request_context [String] :handler Handler class name
23+
# @param endpoint_config [Hash] Endpoint configuration
24+
# @param start_time [Time] Request start time
25+
# @param full_path [String] Full request path including root path
26+
def initialize(request, headers, request_context, endpoint_config, start_time, full_path)
27+
@request = request
28+
@headers = headers
29+
@request_context = request_context
30+
@endpoint_config = endpoint_config
31+
@start_time = start_time
32+
@full_path = full_path
33+
end
34+
35+
# Build the Rack environment hash
36+
#
37+
# Constructs a hash containing standard Rack environment variables
38+
# plus Hooks-specific extensions for lifecycle hooks and handlers.
39+
#
40+
# @return [Hash] Complete Rack environment hash
41+
def build
42+
rack_env = build_base_environment
43+
add_http_headers(rack_env)
44+
rack_env
45+
end
46+
47+
private
48+
49+
# Build the base Rack environment with standard and Hooks-specific variables
50+
# This pretty much creates everything plus the kitchen sink. It will be very rich in information
51+
# and will be used by lifecycle hooks and handlers to access request metadata.
52+
#
53+
# @return [Hash] Base environment hash
54+
def build_base_environment
55+
{
56+
"REQUEST_METHOD" => @request.request_method,
57+
"PATH_INFO" => @request.path_info,
58+
"QUERY_STRING" => @request.query_string,
59+
"HTTP_VERSION" => @request.env["HTTP_VERSION"],
60+
"REQUEST_URI" => @request.url,
61+
"SERVER_NAME" => @request.env["SERVER_NAME"],
62+
"SERVER_PORT" => @request.env["SERVER_PORT"],
63+
"CONTENT_TYPE" => @request.content_type,
64+
"CONTENT_LENGTH" => @request.content_length,
65+
"REMOTE_ADDR" => @request.env["REMOTE_ADDR"],
66+
"hooks.request_id" => @request_context[:request_id],
67+
"hooks.handler" => @request_context[:handler],
68+
"hooks.endpoint_config" => @endpoint_config,
69+
"hooks.start_time" => @start_time.iso8601,
70+
"hooks.full_path" => @full_path
71+
}
72+
end
73+
74+
# Add HTTP headers to the environment with proper Rack naming convention
75+
#
76+
# @param rack_env [Hash] Environment hash to modify
77+
def add_http_headers(rack_env)
78+
@headers.each do |key, value|
79+
env_key = "HTTP_#{key.upcase.tr('-', '_')}"
80+
rack_env[env_key] = value
81+
end
82+
end
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)