Skip to content

Instantly share code, notes, and snippets.

@EmmanuelOga
Created October 28, 2009 03:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EmmanuelOga/220210 to your computer and use it in GitHub Desktop.
Save EmmanuelOga/220210 to your computer and use it in GitHub Desktop.
# rack-httperflog
# httperf wsesslog generator middleware.
# http://github.com/EmmanuelOga/rack-httperflog/
require "net/http"
require "nokogiri"
require "rack"
module Rack
class Httperflog < Struct.new(:app, :ping_urls, :log_path, :flag_path)
def call(env)
status, headers, response = app.call(env)
Httperflog::Logger.new(request_to_log(env), response_to_log(status, headers, response), log_file, ping_urls).perform_logging if logging_enabled?
[status, headers, response]
end
def logging_enabled?
::File.file?(flag_path)
end
def log_file
@lof_file ||= ::File.open(log_path, "a")
end
def request_to_log(env)
env["rack.request"].class <= Rack::Request ? env["rack.request"] : Rack::Request.new(env)
end
def response_to_log(status, headers, response)
r = response.class <= Rack::Response ? response : Rack::Response.new(response, status, headers)
r.status = r.status.to_i # avoid problems with Rack::Response methods that expect the status code to be an integer.
r
end
class Logger < Struct.new(:request, :response, :file, :ping_urls)
def perform_logging
each { |line| file << line }
file.flush
end
def each
return if response.server_error? || response.client_error?
form_data = " contents=\"#{ Rack::Utils.build_query(request.params) }\"" if request.form_data? && !request.params.empty?
yield "#{ request.path_info } method=#{ request.request_method.to_s.upcase }#{ form_data }\n"
paths_to_log.each do |path|
yield " #{ path }\n" if path != request.path_info && (ping_urls != :ping_urls || UrlHelper.url_status_is?("200", request.host, request.port, path))
end
end
def paths_to_log
response.content_type =~ /html/ ? HtmlPathsExtractor.new(response.body).paths : Array.new
end
end
class HtmlPathsExtractor < Struct.new(:source)
def paths
links = attributes_from_nodes("href", "link")
scripts = attributes_from_nodes("src", "script")
imgs = attributes_from_nodes("src", "img")
iframes = attributes_from_nodes("src", "iframe")
links + scripts + imgs + iframes
end
def parsed_body
@parsed_body ||= Nokogiri(source.to_s)
end
def attributes_from_nodes(attribute_name, node_type)
(parsed_body / node_type).map { |node| node[attribute_name].to_s.strip }.select { |path| path =~ /\S/ && path !~ /^http:\/\// }
end
end
module UrlHelper
def url_status_is?(status, host, port, path)
Net::HTTP.start(host, port) {|http| http.get(path) }.code.to_s.strip == status.to_s rescue false
end
extend self
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment