class Sentry::HTTPTransport

def conn

def conn
  server = URI(@dsn.server)
  # connection respects proxy setting from @transport_configuration, or environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)
  # Net::HTTP will automatically read the env vars.
  # See https://ruby-doc.org/3.2.2/stdlibs/net/Net/HTTP.html#class-Net::HTTP-label-Proxies
  connection =
    if proxy = normalize_proxy(@transport_configuration.proxy)
      ::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
    else
      ::Net::HTTP.new(server.hostname, server.port)
    end
  connection.use_ssl = server.scheme == "https"
  connection.read_timeout = @transport_configuration.timeout
  connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
  connection.open_timeout = @transport_configuration.open_timeout
  ssl_configuration.each do |key, value|
    connection.send("#{key}=", value)
  end
  connection
end

def do_request(endpoint, headers, body)

def do_request(endpoint, headers, body)
  conn.start do |http|
    request = ::Net::HTTP::Post.new(endpoint, headers)
    request.body = body
    http.request(request)
  end
end

def endpoint

def endpoint
  @dsn.envelope_endpoint
end

def generate_auth_header

def generate_auth_header
  @dsn&.generate_auth_header(client: USER_AGENT)
end

def handle_rate_limited_response(headers)

def handle_rate_limited_response(headers)
  rate_limits =
    if rate_limits = headers[RATE_LIMIT_HEADER]
      parse_rate_limit_header(rate_limits)
    elsif retry_after = headers[RETRY_AFTER_HEADER]
      # although Sentry doesn't send a date string back
      # based on HTTP specification, this could be a date string (instead of an integer)
      retry_after = retry_after.to_i
      retry_after = DEFAULT_DELAY if retry_after == 0
      { nil => Time.now + retry_after }
    else
      { nil => Time.now + DEFAULT_DELAY }
    end
  rate_limits.each do |category, limit|
    if current_limit = @rate_limits[category]
      if current_limit < limit
        @rate_limits[category] = limit
      end
    else
      @rate_limits[category] = limit
    end
  end
end

def has_rate_limited_header?(headers)

def has_rate_limited_header?(headers)
  headers[RETRY_AFTER_HEADER] || headers[RATE_LIMIT_HEADER]
end

def initialize(*args)

def initialize(*args)
  super
  log_debug("Sentry HTTP Transport will connect to #{@dsn.server}") if @dsn
end

def normalize_proxy(proxy)

Returns:
  • (Hash) - Normalized proxy config that will be passed into `Net::HTTP`

Parameters:
  • proxy (String, URI, Hash) -- Proxy config value passed into `config.transport`.
def normalize_proxy(proxy)
  return proxy unless proxy
  case proxy
  when String
    uri = URI(proxy)
    { uri: uri, user: uri.user, password: uri.password }
  when URI
    { uri: proxy, user: proxy.user, password: proxy.password }
  when Hash
    proxy
  end
end

def parse_rate_limit_header(rate_limit_header)

def parse_rate_limit_header(rate_limit_header)
  time = Time.now
  result = {}
  limits = rate_limit_header.split(",")
  limits.each do |limit|
    next if limit.nil? || limit.empty?
    begin
      retry_after, categories = limit.strip.split(":").first(2)
      retry_after = time + retry_after.to_i
      categories = categories.split(";")
      if categories.empty?
        result[nil] = retry_after
      else
        categories.each do |category|
          result[category] = retry_after
        end
      end
    rescue StandardError
    end
  end
  result
end

def send_data(data)

def send_data(data)
  encoding = ""
  if should_compress?(data)
    data = Zlib.gzip(data)
    encoding = GZIP_ENCODING
  end
  headers = {
    "Content-Type" => CONTENT_TYPE,
    "Content-Encoding" => encoding,
    "User-Agent" => USER_AGENT
  }
  auth_header = generate_auth_header
  headers["X-Sentry-Auth"] = auth_header if auth_header
  response = do_request(endpoint, headers, data)
  if response.code.match?(/\A2\d{2}/)
    handle_rate_limited_response(response) if has_rate_limited_header?(response)
  elsif response.code == "413"
    error_message = "HTTP 413: Envelope dropped due to exceeded size limit"
    error_message += " (body: #{response.body})" if response.body && !response.body.empty?
    log_warn(error_message)
    raise Sentry::SizeExceededError, error_message
  elsif response.code == "429"
    log_debug("the server responded with status 429")
    handle_rate_limited_response(response)
  else
    error_info = "the server responded with status #{response.code}"
    error_info += "\nbody: #{response.body}"
    error_info += " Error in headers is: #{response['x-sentry-error']}" if response["x-sentry-error"]
    raise Sentry::ExternalError, error_info
  end
rescue SocketError, *HTTP_ERRORS => e
  on_error if respond_to?(:on_error)
  raise Sentry::ExternalError.new(e&.message)
end

def should_compress?(data)

def should_compress?(data)
  @transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
end

def ssl_configuration

def ssl_configuration
  configuration = {
    verify: @transport_configuration.ssl_verification,
    ca_file: @transport_configuration.ssl_ca_file
  }.merge(@transport_configuration.ssl || {})
  configuration[:verify_mode] = configuration.delete(:verify) ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
  configuration
end