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