1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
# frozen_string_literal: true
module Puppet::Network::HTTP
end
require_relative '../../../puppet/network/http'
require_relative '../../../puppet/util/profiler'
require_relative '../../../puppet/util/profiler/aggregate'
require 'resolv'
module Puppet::Network::HTTP::Handler
include Puppet::Network::HTTP::Issues
# These shouldn't be allowed to be set by clients
# in the query string, for security reasons.
DISALLOWED_KEYS = %w[node ip]
def register(routes)
# There's got to be a simpler way to do this, right?
dupes = {}
routes.each { |r| dupes[r.path_matcher] = (dupes[r.path_matcher] || 0) + 1 }
dupes = dupes.filter_map { |pm, count| pm if count > 1 }
if dupes.count > 0
raise ArgumentError, _("Given multiple routes with identical path regexes: %{regexes}") % { regexes: dupes.map(&:inspect).join(', ') }
end
@routes = routes
Puppet.debug("Routes Registered:")
@routes.each do |route|
Puppet.debug(route.inspect)
end
end
# Retrieve all headers from the http request, as a hash with the header names
# (lower-cased) as the keys
def headers(request)
raise NotImplementedError
end
# The mime type is always passed to the `set_content_type` method, so
# it is no longer necessary to retrieve the Format's mime type.
#
# @deprecated
def format_to_mime(format)
format.is_a?(Puppet::Network::Format) ? format.mime : format
end
# Create a generic puppet request from the implementation-specific request
# created by the web server
def make_generic_request(request)
request_path = path(request)
Puppet::Network::HTTP::Request.new(
headers(request),
params(request),
http_method(request),
request_path, # path
request_path, # routing_path
client_cert(request),
body(request)
)
end
def with_request_profiling(request)
profiler = configure_profiler(request.headers, request.params)
Puppet::Util::Profiler.profile(
_("Processed request %{request_method} %{request_path}") % { request_method: request.method, request_path: request.path },
[:http, request.method, request.path]
) do
yield
end
ensure
remove_profiler(profiler) if profiler
end
# handle an HTTP request
def process(external_request, response)
# The response_wrapper stores the response and modifies it as a side effect.
# The caller will use the original response
response_wrapper = Puppet::Network::HTTP::Response.new(self, response)
request = make_generic_request(external_request)
set_puppet_version_header(response)
respond_to_errors(response_wrapper) do
with_request_profiling(request) do
find_route_or_raise(request).process(request, response_wrapper)
end
end
end
def respond_to_errors(response)
yield
rescue Puppet::Network::HTTP::Error::HTTPError => e
Puppet.info(e.message)
respond_with_http_error(response, e)
rescue StandardError => e
http_e = Puppet::Network::HTTP::Error::HTTPServerError.new(e)
Puppet.err([http_e.message, *e.backtrace].join("\n"))
respond_with_http_error(response, http_e)
end
def respond_with_http_error(response, exception)
response.respond_with(exception.status, "application/json", exception.to_json)
end
def find_route_or_raise(request)
route = @routes.find { |r| r.matches?(request) }
route || raise(Puppet::Network::HTTP::Error::HTTPNotFoundError.new(
_("No route for %{request} %{path}") % { request: request.method, path: request.path },
HANDLER_NOT_FOUND
))
end
def set_puppet_version_header(response)
response[Puppet::Network::HTTP::HEADER_PUPPET_VERSION] = Puppet.version
end
# Set the response up, with the body and status.
def set_response(response, body, status = 200)
raise NotImplementedError
end
# Set the specified format as the content type of the response.
def set_content_type(response, format)
raise NotImplementedError
end
# resolve node name from peer's ip address
# this is used when the request is unauthenticated
def resolve_node(result)
begin
return Resolv.getname(result[:ip])
rescue => detail
Puppet.err _("Could not resolve %{ip}: %{detail}") % { ip: result[:ip], detail: detail }
end
result[:ip]
end
private
# methods to be overridden by the including web server class
def http_method(request)
raise NotImplementedError
end
def path(request)
raise NotImplementedError
end
def request_key(request)
raise NotImplementedError
end
def body(request)
raise NotImplementedError
end
def params(request)
raise NotImplementedError
end
def client_cert(request)
raise NotImplementedError
end
def decode_params(params)
params.select { |key, _| allowed_parameter?(key) }.each_with_object({}) do |ary, result|
param, value = ary
result[param.to_sym] = parse_parameter_value(param, value)
end
end
def allowed_parameter?(name)
!(name.nil? || name.empty? || DISALLOWED_KEYS.include?(name))
end
def parse_parameter_value(param, value)
if value.is_a?(Array)
value.collect { |v| parse_primitive_parameter_value(v) }
else
parse_primitive_parameter_value(value)
end
end
def parse_primitive_parameter_value(value)
case value
when "true"
true
when "false"
false
when /^\d+$/
Integer(value)
when /^\d+\.\d+$/
value.to_f
else
value
end
end
def configure_profiler(request_headers, request_params)
if request_headers.has_key?(Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase) or Puppet[:profile]
Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::Aggregate.new(Puppet.method(:info), request_params.object_id))
end
end
def remove_profiler(profiler)
profiler.shutdown
Puppet::Util::Profiler.remove_profiler(profiler)
end
end
|