Add stats reporting actor

parent 146f35ec
......@@ -22,7 +22,8 @@ def request(method, resource, headers = nil, body = nil)
private_key: private_key,
redis: Redis::PooledClient.new(url: ENV["REDIS_URL"]? || "redis://localhost"),
bindhost: "localhost",
port: 0
port: 0,
stats: PubRelay::Stats.new
).call(context)
{response.status_code, response_body.to_s, response.headers}
......
......@@ -48,7 +48,7 @@ describe PubRelay::WebServer::HTTPSignature do
end
expect_signature_fails("", "did not contain '='")
expect_signature_fails("foo=bar, foo2", %q(param "foo2" did not contain '='))
expect_signature_fails("foo=bar, foo2", %q(param did not contain '=': "foo2"))
expect_signature_fails(%q(foo="), %q(malformed quoted-string))
expect_signature_fails(%q(foo="bar), %q(malformed quoted-string))
expect_signature_fails(%q(foo="bar\"), %q(malformed quoted-string))
......@@ -207,7 +207,7 @@ describe PubRelay::WebServer::HTTPSignature do
status_code, body = post_inbox(signed_request.headers, signed_request.body)
status_code.should eq(400)
body.should contain(%q(Header "user-agent" was supposed to be signed but was missing from the request))
body.should contain(%q(Header was supposed to be signed but was missing from the request: "user-agent"))
end
end
end
......
......@@ -8,17 +8,21 @@ class PubRelay < Earl::Supervisor
VERSION = "0.1.0"
def initialize(
@domain : String,
@private_key : OpenSSL::RSA,
@redis : Redis::PooledClient,
@bindhost : String,
@port : Int32
domain : String,
private_key : OpenSSL::RSA,
redis : Redis::PooledClient,
bindhost : String,
port : Int32
)
super()
web_server = PubRelay::WebServer.new(@domain, @private_key, @redis, @bindhost, @port)
stats = Stats.new
monitor(stats)
web_server = WebServer.new(domain, private_key, redis, bindhost, port, stats)
monitor(web_server)
end
end
require "./stats"
require "./web_server"
class PubRelay::Stats
record HTTPResponsePayload,
response_code : String,
domain : String?
include Earl::Artist(HTTPResponsePayload)
@response_codes = Hash(String, Int32).new(default_value: 0)
@response_codes_per_domain = Hash(String, Hash(String, Int32)).new do |hash, key|
hash[key] = Hash(String, Int32).new(default_value: 0)
end
def call(response : HTTPResponsePayload)
@response_codes[response.response_code] += 1
@response_codes_per_domain[response.domain || "NO DOMAIN"][response.response_code] += 1
end
def to_json(io)
{
response_codes: @response_codes,
response_codes_per_domain: @response_codes_per_domain,
}.to_json(io)
end
end
class HTTP::Server::Context
property relay_request_domain : String?
end
class PubRelay::WebServer
include HTTP::Handler
include Earl::Agent
......@@ -5,9 +9,10 @@ class PubRelay::WebServer
class ClientError < Exception
getter status_code : Int32
getter error_code : String
def initialize(@status_code, message)
super(message)
def initialize(@status_code, @error_code, user_portion)
super("#{@error_code} #{user_portion}")
end
end
......@@ -16,7 +21,8 @@ class PubRelay::WebServer
@private_key : OpenSSL::RSA,
@redis : Redis::PooledClient,
@bindhost : String,
@port : Int32
@port : Int32,
@stats : Stats
)
end
......@@ -48,6 +54,8 @@ class PubRelay::WebServer
serve_webfinger(context)
when {"GET", "/actor"}
serve_actor(context)
when {"GET", "/stats"}
serve_stats(context)
when {"POST", "/inbox"}
handle_inbox(context)
else
......@@ -73,11 +81,14 @@ class PubRelay::WebServer
case exception
when ClientError
log.warn "#{log_message} #{exception.message}"
@stats.send Stats::HTTPResponsePayload.new(exception.error_code, context.relay_request_domain)
when Exception
log.error log_message
log.error exception
@stats.send Stats::HTTPResponsePayload.new("500", context.relay_request_domain)
else
log.debug log_message
@stats.send Stats::HTTPResponsePayload.new(context.response.status_code.to_s, context.relay_request_domain)
end
end
......@@ -119,6 +130,11 @@ class PubRelay::WebServer
}.to_json(ctx.response)
end
private def serve_stats(ctx)
ctx.response.content_type = "application/json"
@stats.to_json(ctx.response)
end
private def handle_inbox(context)
InboxHandler.new(context, @domain, @redis).handle
end
......@@ -127,8 +143,8 @@ class PubRelay::WebServer
"https://#{@domain}#{path}"
end
private def error(status_code, message)
raise WebServer::ClientError.new(status_code, message)
private def error(status_code, error_code, user_message = "")
raise WebServer::ClientError.new(status_code, error_code, user_message)
end
end
......
......@@ -11,6 +11,8 @@ struct PubRelay::WebServer::HTTPSignature
key_id = signature_params["keyId"]?
error(400, "Invalid Signature: keyId not present") unless key_id
@context.relay_request_domain = (URI.parse(key_id).hostname rescue nil) || key_id
signature = signature_params["signature"]?
error(400, "Invalid Signature: signature not present") unless signature
......@@ -38,7 +40,7 @@ struct PubRelay::WebServer::HTTPSignature
if public_key.verify(OpenSSL::Digest.new("SHA256"), signature, signed_string)
{body, actor}
else
error(401, "Invalid Signature: cryptographic signature did not verify for #{key_id.inspect}")
error(401, "Invalid Signature: cryptographic signature did not verify", "for #{key_id.inspect}")
end
end
......@@ -50,7 +52,7 @@ struct PubRelay::WebServer::HTTPSignature
signature_header.split(',') do |param|
parts = param.split('=', 2)
unless parts.size == 2
error(400, "Invalid Signature: param #{param.strip.inspect} did not contain '='")
error(400, "Invalid Signature: param did not contain '=':", param.strip.inspect)
end
# This is an 'auth-param' defined in https://tools.ietf.org/html/rfc7235#section-2.1
......@@ -59,12 +61,12 @@ struct PubRelay::WebServer::HTTPSignature
if value.starts_with? '"'
unless value.ends_with?('"') && value.size > 2
error(400, "Invalid Signature: malformed quoted-string in param #{param.strip.inspect}")
error(400, "Invalid Signature: malformed quoted-string in param", param.strip.inspect)
end
value = HTTP.dequote_string(value[1..-2]) rescue nil
unless value
error(400, "Invalid Signature: malformed quoted-string in param #{param.strip.inspect}")
error(400, "Invalid Signature: malformed quoted-string in param", param.strip.inspect)
end
end
......@@ -87,7 +89,7 @@ struct PubRelay::WebServer::HTTPSignature
raise "BUG: cached_fetch_json returned neither Actor nor Key"
end
rescue ex : JSON::Error
error(400, "Invalid JSON from fetching #{key_id.inspect}: #{ex.inspect_with_backtrace}")
error(400, "Invalid JSON from fetching", "#{key_id.inspect}: #{ex.inspect_with_backtrace}")
end
private def cached_fetch_json(url, json_class : JsonType.class) : JsonType forall JsonType
......@@ -96,7 +98,7 @@ struct PubRelay::WebServer::HTTPSignature
# TODO use HTTP::Client.new and set read timeout
response = HTTP::Client.get(url, headers: headers)
unless response.status_code == 200
error(400, "Got non-200 response from fetching #{url.inspect}")
error(400, "Got non-200 response from fetching", url.inspect)
end
JsonType.from_json(response.body)
end
......@@ -115,7 +117,7 @@ struct PubRelay::WebServer::HTTPSignature
else
request_header = request.headers[header_name]?
unless request_header
error(400, "Header #{header_name.inspect} was supposed to be signed but was missing from the request")
error(400, "Header was supposed to be signed but was missing from the request:", header_name.inspect)
end
"#{header_name}: #{request_header}"
end
......@@ -126,8 +128,8 @@ struct PubRelay::WebServer::HTTPSignature
@context.request
end
private def error(status_code, message)
raise WebServer::ClientError.new(status_code, message)
private def error(status_code, error_code, user_message = "")
raise WebServer::ClientError.new(status_code, error_code, user_message)
end
class Actor
......
......@@ -18,7 +18,7 @@ class PubRelay::WebServer::InboxHandler
begin
activity = Activity.from_json(request_body)
rescue ex : JSON::Error
error(400, "Invalid activity JSON\n#{ex.inspect_with_backtrace}")
error(400, "Invalid activity JSON:", "\n#{ex.inspect_with_backtrace}")
end
case activity
......@@ -79,8 +79,8 @@ class PubRelay::WebServer::InboxHandler
# DeliverWorker.async.perform_bulk(bulk_args)
end
private def error(status_code, message)
raise WebServer::ClientError.new(status_code, message)
private def error(status_code, error_code, user_message = "")
raise WebServer::ClientError.new(status_code, error_code, user_message)
end
private def route_url(path)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment