# frozen_string_literal: true require 'appmap/event' require 'appmap/hook/method' require 'appmap/util' require 'rack' module AppMap module Handler class HTTPClientRequest < AppMap::Event::MethodEvent attr_accessor :request_method, :url, :params, :headers def initialize(http, request) super AppMap::Event.next_id_counter, :call, Thread.current.object_id path, query = request.path.split('?') query ||= '' protocol = http.use_ssl? ? 'https' : 'http' port = if http.use_ssl? && http.port == 443 nil elsif !http.use_ssl? && http.port == 80 nil else ":#{http.port}" end url = [ protocol, '://', http.address, port, path ].compact.join self.request_method = request.method self.url = url self.headers = NetHTTPHandler.copy_headers(request) self.params = Rack::Utils.parse_nested_query(query) end def to_h super.tap do |h| h[:http_client_request] = { request_method: request_method, url: url, headers: headers }.compact unless Util.blank?(params) h[:message] = params.keys.map do |key| val = params[key] { name: key, class: val.class.name, value: self.class.display_string(val), object_id: val.__id__, } end end end end end class HTTPClientResponse < AppMap::Event::MethodReturnIgnoreValue attr_accessor :status, :headers def initialize(response, parent_id, elapsed) super AppMap::Event.next_id_counter, :return, Thread.current.object_id if response self.status = response.code.to_i self.headers = NetHTTPHandler.copy_headers(response) else self.headers = {} end self.parent_id = parent_id self.elapsed = elapsed end def to_h super.tap do |h| h[:http_client_response] = { status_code: status, headers: headers }.compact end end end # Handler class for HTTP requests. # Emits HTTP request events instead of method calls. class NetHTTPHandler < Hook::Method def self.copy_headers(obj) {}.tap do |headers| obj.each_header do |key, value| key = key.split('-').map(&:capitalize).join('-') headers[key] = value end end end def handle_call(receiver, args) # request will call itself again in a start block if it's not already started. return unless receiver.started? http = receiver request = args.first HTTPClientRequest.new(http, request) end def handle_return(call_event_id, elapsed, return_value, exception) HTTPClientResponse.new(return_value, call_event_id, elapsed) end end end end