Skip to content

Commit

Permalink
add CI::Transport::Adapters::Net to move network layer code to this l…
Browse files Browse the repository at this point in the history
…ibrary
  • Loading branch information
anmarchenko committed Jun 18, 2024
1 parent 93d83ca commit c092d67
Show file tree
Hide file tree
Showing 7 changed files with 520 additions and 129 deletions.
118 changes: 118 additions & 0 deletions lib/datadog/ci/transport/adapters/net.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# frozen_string_literal: true

require_relative "../../ext/transport"

module Datadog
module CI
module Transport
module Adapters
# Adapter for Net::HTTP
class Net
attr_reader \
:hostname,
:port,
:timeout,
:ssl

def initialize(hostname:, port:, ssl:, timeout_seconds:)
@hostname = hostname
@port = port
@timeout = timeout_seconds
@ssl = ssl
end

def open(&block)
req = ::Net::HTTP.new(hostname, port)

req.use_ssl = ssl
req.open_timeout = req.read_timeout = timeout

req.start(&block)
end

def call(path:, payload:, headers:, verb:)
if respond_to?(verb)
send(verb, path: path, payload: payload, headers: headers)
else
raise "Unknown HTTP method [#{verb}]"
end
end

def post(path:, payload:, headers:)
post = ::Net::HTTP::Post.new(path, headers)
post.body = payload

# Connect and send the request
http_response = open do |http|
http.request(post)
end

# Build and return response
Response.new(http_response)
end

class Response
attr_reader :http_response

def initialize(http_response)
@http_response = http_response
end

def payload
return @decompressed_payload if defined?(@decompressed_payload)

if gzipped?(http_response.body)
Datadog.logger.debug("Decompressing gzipped response payload")
@decompressed_payload = Gzip.decompress(http_response.body)
else
http_response.body
end
end

def header(name)
http_response[name]
end

def code
http_response.code.to_i
end

def ok?
code.between?(200, 299)
end

def unsupported?
code == 415
end

def not_found?
code == 404
end

def client_error?
code.between?(400, 499)
end

def server_error?
code.between?(500, 599)
end

def gzipped?(body)
return false if body.nil? || body.empty?

# no-dd-sa
first_bytes = body[0, 2]
return false if first_bytes.nil? || first_bytes.empty?

first_bytes.b == Datadog::CI::Ext::Transport::GZIP_MAGIC_NUMBER
end

def inspect
"#{super}, http_response:#{http_response}"
end
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/datadog/ci/transport/api/agentless.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def build_http_client(url, compress:)

Datadog::CI::Transport::HTTP.new(
host: uri.host,
port: uri.port,
port: uri.port || 80,
ssl: uri.scheme == "https" || uri.port == 443,
compress: compress
)
Expand Down
61 changes: 6 additions & 55 deletions lib/datadog/ci/transport/http.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# frozen_string_literal: true

require "delegate"
require "datadog/core/transport/http/adapters/net"
require "datadog/core/transport/http/env"
require "datadog/core/transport/request"
require "socket"

require_relative "gzip"
require_relative "adapters/net"
require_relative "../ext/transport"

module Datadog
Expand All @@ -24,7 +22,7 @@ class HTTP
MAX_RETRIES = 3
INITIAL_BACKOFF = 1

def initialize(host:, timeout: DEFAULT_TIMEOUT, port: nil, ssl: true, compress: false)
def initialize(host:, port:, timeout: DEFAULT_TIMEOUT, ssl: true, compress: false)
@host = host
@port = port
@timeout = timeout
Expand Down Expand Up @@ -70,7 +68,7 @@ def request(

def perform_http_call(path:, payload:, headers:, verb:, retries: MAX_RETRIES, backoff: INITIAL_BACKOFF)
adapter.call(
build_env(path: path, payload: payload, headers: headers, verb: verb)
path: path, payload: payload, headers: headers, verb: verb
)
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, SocketError, Net::HTTPBadResponse => e
Datadog.logger.debug("Failed to send request with #{e} (#{e.message})")
Expand All @@ -87,65 +85,18 @@ def perform_http_call(path:, payload:, headers:, verb:, retries: MAX_RETRIES, ba
end
end

def build_env(path:, payload:, headers:, verb:)
env = Datadog::Core::Transport::HTTP::Env.new(
Datadog::Core::Transport::Request.new
)
env.body = payload
env.path = path
env.headers = headers
env.verb = verb
env
end

def adapter
settings = AdapterSettings.new(hostname: host, port: port, ssl: ssl, timeout_seconds: timeout)
@adapter ||= Datadog::Core::Transport::HTTP::Adapters::Net.new(settings)
@adapter ||= Datadog::CI::Transport::Adapters::Net.new(
hostname: host, port: port, ssl: ssl, timeout_seconds: timeout
)
end

# adds compatibility with Datadog::Tracing transport and
# provides ungzipping capabilities
class ResponseDecorator < ::SimpleDelegator
def payload
return @decompressed_payload if defined?(@decompressed_payload)

if gzipped?(__getobj__.payload)
Datadog.logger.debug("Decompressing gzipped response payload")
@decompressed_payload = Gzip.decompress(__getobj__.payload)
else
__getobj__.payload
end
end

def trace_count
0
end

def gzipped?(payload)
return false if payload.nil? || payload.empty?

# no-dd-sa
first_bytes = payload[0, 2]
return false if first_bytes.nil? || first_bytes.empty?

first_bytes.b == Datadog::CI::Ext::Transport::GZIP_MAGIC_NUMBER
end
end

class AdapterSettings
attr_reader :hostname, :port, :ssl, :timeout_seconds

def initialize(hostname:, port: nil, ssl: true, timeout_seconds: nil)
@hostname = hostname
@port = port
@ssl = ssl
@timeout_seconds = timeout_seconds
end

def ==(other)
hostname == other.hostname && port == other.port && ssl == other.ssl &&
timeout_seconds == other.timeout_seconds
end
end
end
end
Expand Down
63 changes: 63 additions & 0 deletions sig/datadog/ci/transport/adapters/net.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module Datadog
module CI
module Transport
module Adapters
class Net
@hostname: String

@port: Integer

@timeout: Float

@ssl: bool

attr_reader hostname: String

attr_reader port: Integer

attr_reader timeout: Float

attr_reader ssl: bool

def initialize: (hostname: String, port: Integer, ssl: bool, timeout_seconds: Integer) -> void

def open: () { (Net::HTTP http) -> ::Net::HTTPResponse } -> ::Net::HTTPResponse

def call: (path: String, payload: String, headers: Hash[String, String], verb: String) -> Response

def post: (path: String, payload: String, headers: Hash[String, String]) -> Response

class Response
@http_response: ::Net::HTTPResponse

@decompressed_payload: String

attr_reader http_response: ::Net::HTTPResponse

def initialize: (::Net::HTTPResponse http_response) -> void

def payload: () -> String

def header: (String name) -> String?

def code: () -> Integer

def ok?: () -> bool

def unsupported?: () -> bool

def not_found?: () -> bool

def client_error?: () -> bool

def server_error?: () -> bool

def gzipped?: (String body) -> bool

def inspect: () -> ::String
end
end
end
end
end
end
26 changes: 5 additions & 21 deletions sig/datadog/ci/transport/http.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ module Datadog
module CI
module Transport
class HTTP
@adapter: Datadog::Core::Transport::HTTP::Adapters::Net
@adapter: Datadog::CI::Transport::Adapters::Net

attr_reader host: String
attr_reader port: Integer?
attr_reader port: Integer
attr_reader ssl: bool
attr_reader timeout: Integer
attr_reader compress: bool
Expand All @@ -22,31 +22,15 @@ module Datadog
MAX_RETRIES: 3
INITIAL_BACKOFF: 1

def initialize: (host: String, ?port: Integer?, ?ssl: bool, ?timeout: Integer, ?compress: bool) -> void
def initialize: (host: String, port: Integer, ?ssl: bool, ?timeout: Integer, ?compress: bool) -> void

def request: (?verb: String, payload: String, headers: Hash[String, String], path: String, ?retries: Integer, ?backoff: Integer, ?accept_compressed_response: bool) -> ResponseDecorator

private

def adapter: () -> Datadog::Core::Transport::HTTP::Adapters::Net
def adapter: () -> Datadog::CI::Transport::Adapters::Net

def build_env: (payload: String, headers: Hash[String, String], path: String, verb: String) -> Datadog::Core::Transport::HTTP::Env

def perform_http_call: (payload: String, headers: Hash[String, String], path: String, verb: String, ?retries: Integer, ?backoff: Integer) -> Datadog::Core::Transport::Response

class AdapterSettings
attr_reader hostname: String
attr_reader port: Integer?
attr_reader ssl: bool
attr_reader timeout_seconds: Integer

@hostname: String
@port: Integer?
@ssl: bool
@timeout_seconds: Integer

def initialize: (hostname: String, ?port: Integer?, ?ssl: bool, ?timeout_seconds: Integer) -> void
end
def perform_http_call: (payload: String, headers: Hash[String, String], path: String, verb: String, ?retries: Integer, ?backoff: Integer) -> Datadog::CI::Transport::Adapters::Net::Response

class ResponseDecorator < ::SimpleDelegator
include Datadog::Core::Transport::Response
Expand Down
Loading

0 comments on commit c092d67

Please sign in to comment.