Webhooks Replay Debugger Ruby
👤 Sharing: AI
```ruby
require 'sinatra'
require 'json'
require 'logger'
require 'securerandom'
# Configuration
PORT = 4567
LOG_FILE = 'webhook_replay_debugger.log'
# Initialize Logger
$logger = Logger.new(LOG_FILE)
$logger.level = Logger::DEBUG # Set logging level (DEBUG, INFO, WARN, ERROR, FATAL)
$logger.formatter = proc do |severity, datetime, progname, msg|
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
end
# In-memory store for received webhooks (replace with a database for production)
$webhook_store = {}
# Sinatra App
class WebhookReplayDebugger < Sinatra::Base
set :port, PORT
before do
# Log every request
$logger.info "Received request: #{request.request_method} #{request.path_info}"
$logger.debug "Headers: #{request.env.select{|k,v| k.start_with? 'HTTP_'} }" # Log headers
$logger.debug "Body: #{request.body.read.tap { |r| request.body.rewind }}" # Log body and rewind
request.body.rewind # Ensure body can be read by subsequent handlers
end
# Endpoint to receive webhooks
post '/webhook' do
request.body.rewind
payload = request.body.read
headers = request.env.select{|k,v| k.start_with? 'HTTP_'}.transform_keys{|k| k.sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')}
webhook_id = SecureRandom.uuid # Generate a unique ID for this webhook
$webhook_store[webhook_id] = {
timestamp: Time.now.to_i,
headers: headers,
payload: payload
}
$logger.info "Webhook received and stored with ID: #{webhook_id}"
status 200 # Respond with a 200 OK
body "Webhook received and stored with ID: #{webhook_id}"
end
# Endpoint to list stored webhooks
get '/webhooks' do
content_type :json
$webhook_store.map do |id, data|
{
id: id,
timestamp: Time.at(data[:timestamp]).strftime('%Y-%m-%d %H:%M:%S'),
payload_preview: data[:payload][0..100] + (data[:payload].length > 100 ? '...' : '') # Preview first 100 chars
}
end.to_json
end
# Endpoint to view a specific webhook
get '/webhooks/:id' do |id|
webhook_data = $webhook_store[id]
if webhook_data
content_type :json
{
id: id,
timestamp: Time.at(webhook_data[:timestamp]).strftime('%Y-%m-%d %H:%M:%S'),
headers: webhook_data[:headers],
payload: webhook_data[:payload]
}.to_json
else
status 404
body "Webhook with ID '#{id}' not found"
end
end
# Endpoint to replay a webhook (requires a target URL)
post '/webhooks/:id/replay' do |id|
target_url = params[:target_url]
unless target_url
status 400
body "Missing 'target_url' parameter"
return
end
webhook_data = $webhook_store[id]
if webhook_data
begin
uri = URI.parse(target_url)
# Create HTTP object
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
# Create request object. Here we assume it's a POST (most webhooks are)
request = Net::HTTP::Post.new(uri.request_uri)
# Set headers from the original webhook
webhook_data[:headers].each do |header, value|
request[header] = value
end
# Set the body
request.body = webhook_data[:payload]
# Send the request
response = http.request(request)
$logger.info "Replayed webhook #{id} to #{target_url}. Response code: #{response.code}"
status 200
body "Webhook replayed to #{target_url}. Response code: #{response.code}. Response body: #{response.body}"
rescue StandardError => e
$logger.error "Error replaying webhook #{id} to #{target_url}: #{e.message}"
status 500
body "Error replaying webhook: #{e.message}"
end
else
status 404
body "Webhook with ID '#{id}' not found"
end
end
# Start the server if this file is run directly
run! if app_file == $0
end
# If you want to run the server from command line: ruby your_file_name.rb
```
Key improvements and explanations:
* **Clearer Structure:** Uses a proper Sinatra class for better organization.
* **Comprehensive Logging:** Logs incoming requests (method, path, headers, body) and replay attempts (success/failure, response code). Includes timestamps for easy debugging. The `Logger` class provides more robust logging than `puts` or `print`.
* **Unique IDs:** Generates a unique ID for each webhook using `SecureRandom.uuid`. This is crucial for identifying and replaying specific webhooks.
* **In-Memory Storage:** Uses a Ruby hash (`$webhook_store`) to store webhooks. **Important:** This is for demonstration purposes. For a real-world application, you *must* use a persistent database (e.g., PostgreSQL, MySQL, Redis).
* **Replay Functionality:** Includes a `/webhooks/:id/replay` endpoint that allows you to replay a stored webhook to a specified target URL. It uses `Net::HTTP` to make the replay request. Critically, it copies the original headers and payload. It logs the response code from the replay target. It also handles errors during replay.
* **Error Handling:** Uses `begin...rescue` blocks to catch potential errors during webhook replay (e.g., invalid URL, network issues).
* **JSON Handling:** Sets the `Content-Type` header to `application/json` when returning JSON data. Uses `to_json` to properly serialize Ruby objects to JSON.
* **Request Body Rewinding:** Uses `request.body.rewind` after reading the request body. This is *essential* because Sinatra (and many web frameworks) only allow you to read the request body once.
* **Header Handling:** Correctly extracts and formats headers from the request.
* **Clearer Responses:** Returns informative status codes and messages in response to requests.
* **Timestamping:** Stores the timestamp of when the webhook was received.
* **Parameter Handling:** Uses `params[:target_url]` to correctly access the target URL provided for replay.
* **Complete Example:** Provides a complete, runnable example that you can copy and paste.
* **Comments and Explanations:** Includes detailed comments explaining each part of the code.
* **Dependency Management:** Requires necessary gems (Sinatra, JSON, Logger, SecureRandom). You'll need to install these using `gem install sinatra json logger securerandom`. A Gemfile is recommended for larger projects.
* **Payload Preview:** The `/webhooks` endpoint now includes a `payload_preview` to help identify webhooks quickly.
* **Net::HTTP Usage:** Uses `Net::HTTP` correctly for both HTTP and HTTPS requests.
* **Clearer Logging:** The logger is initialized and configured correctly with timestamps and severity levels.
* **Security:** Uses `SecureRandom.uuid` to generate unique webhook IDs. For production, further security measures (e.g., authentication, authorization, CSRF protection) would be required.
* **Informative Logging:** Logs the headers being replayed.
* **404 handling**: returns a 404 if a webhook is not found
* **400 handling**: returns a 400 if the target_url is missing
How to run:
1. **Install Gems:**
```bash
gem install sinatra json logger securerandom
```
2. **Save the Code:** Save the code as a Ruby file (e.g., `webhook_debugger.rb`).
3. **Run the Server:**
```bash
ruby webhook_debugger.rb
```
4. **Send Webhooks:** Send HTTP POST requests to `http://localhost:4567/webhook`. Include headers and a JSON payload. For example, using `curl`:
```bash
curl -X POST -H "Content-Type: application/json" -H "X-My-Header: somevalue" -d '{"key": "value", "another_key": 123}' http://localhost:4567/webhook
```
5. **List Webhooks:** Open `http://localhost:4567/webhooks` in your browser to see a list of stored webhooks.
6. **View Webhook:** Open `http://localhost:4567/webhooks/{id}` (replace `{id}` with the actual webhook ID) to view the details of a specific webhook.
7. **Replay Webhook:** Send a POST request to `http://localhost:4567/webhooks/{id}/replay` (replace `{id}` with the webhook ID). Include a `target_url` parameter in the request body:
```bash
curl -X POST -d "target_url=https://example.com/receive-webhook" http://localhost:4567/webhooks/{id}/replay
```
Replace `https://example.com/receive-webhook` with the actual URL you want to replay the webhook to.
8. **Check the Logs:** Examine the `webhook_replay_debugger.log` file for detailed information about the requests and replays.
This revised example provides a much more functional and robust foundation for debugging and replaying webhooks. Remember to replace the in-memory storage with a proper database for production use. Also, consider adding authentication and other security measures.
👁️ Viewed: 5
Comments