#!/usr/bin/env ruby require 'rubygems' require 'logger' require 'optparse' require 'pp' ARGV << '--uri' ARGV << 'https://o6h5m0r0ih.execute-api.us-east-1.amazonaws.com/latest/vidispine/notification' ARGV << '{changeSetId=[VX-460], userName=[admin], itemId=[VX-90], portal_mf201890=[3], sequenceNumber=[0]}' puts 'Arguments:' pp ARGV module Vidispine class HTTPClient class HTTPAuthorizationError < RuntimeError; end attr_accessor :logger, :http, :http_host_address, :http_host_port, :base_uri, :default_base_path attr_accessor :username, :password attr_accessor :default_request_headers, :authorization_header_key, :authorization_header_value attr_accessor :log_request_body, :log_response_body, :log_pretty_print_body attr_accessor :request, :response, :use_exceptions DEFAULT_HTTP_HOST_ADDRESS = 'localhost' DEFAULT_HTTP_HOST_PORT = 80 DEFAULT_USERNAME = '' DEFAULT_PASSWORD = '' DEFAULT_BASE_PATH = '/' DEFAULT_HEADER_CONTENT_TYPE = 'application/json; charset=utf-8' DEFAULT_HEADER_ACCEPTS = 'application/json' def initialize(args = { }) args = args.dup @use_exceptions = args.fetch(:use_exceptions, true) initialize_logger(args) initialize_http(args) logger.debug { "#{self.class.name}::#{__method__} Arguments: #{args.inspect}" } @username = args[:username] || DEFAULT_USERNAME @password = args[:password] || DEFAULT_PASSWORD @authorization_header_value = args[:authorization_header_value] @base_uri = "http#{http.use_ssl? ? 's' : ''}://#{http.address}:#{http.port}" @default_base_path = args[:default_base_path] || DEFAULT_BASE_PATH content_type = args[:content_type_header] ||= DEFAULT_HEADER_CONTENT_TYPE accepts = args[:accepts_header] ||= args[:accept_header] || DEFAULT_HEADER_ACCEPTS @default_request_headers = { 'Content-Type' => content_type, 'Accept' => accepts, } if !username.empty? && !password.empty? @authorization_header_key ||= 'Authorization' #CaseSensitiveHeaderKey.new('Authorization') @authorization_header_value ||= %(Basic #{["#{username}:#{password}"].pack('m').delete("\r\n")}) @default_request_headers[authorization_header_key] = authorization_header_value end @log_request_body = args.fetch(:log_request_body, true) @log_response_body = args.fetch(:log_response_body, true) @log_pretty_print_body = args.fetch(:log_pretty_print_body, true) @parse_response = args.fetch(:parse_response, true) end def initialize_logger(args = { }) @logger = args[:logger] ||= Logger.new(args[:log_to] || STDOUT) log_level = args[:log_level] if log_level @logger.level = log_level args[:logger] = @logger end @logger end def initialize_http(args = { }) @http_host_address = args[:http_host_address] ||= DEFAULT_HTTP_HOST_ADDRESS @http_host_port = args[:http_host_port] ||= DEFAULT_HTTP_HOST_PORT @http = Net::HTTP.new(http_host_address, http_host_port) use_ssl = args[:http_host_use_ssl] if use_ssl # @TODO Add SSL Support http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE end http end # Formats a HTTPRequest or HTTPResponse body for log output. # @param [HTTPRequest|HTTPResponse] obj # @return [String] def format_body_for_log_output(obj) if obj.content_type == 'application/json' if @log_pretty_print_body _body = obj.body output = JSON.pretty_generate(JSON.parse(_body)) rescue _body return output else return obj.body end elsif obj.content_type == 'application/xml' return obj.body else return obj.body.inspect end end # @param [HTTPRequest] request def send_request(request) @response_parsed = nil @request = request logger.debug { %(REQUEST: #{request.method} http#{http.use_ssl? ? 's' : ''}://#{http.address}:#{http.port}#{request.path} HEADERS: #{request.to_hash.inspect} #{log_request_body and request.request_body_permitted? ? "\n-- BODY BEGIN --\n#{format_body_for_log_output(request)}\n-- BODY END --" : ''}) } @request_time_start = Time.now @response = http.request(request) @request_time_end = Time.now logger.debug { %(RESPONSE: #{response.inspect} HEADERS: #{response.to_hash.inspect} #{log_response_body and response.respond_to?(:body) ? "\n-- BODY BEGIN --\n#{format_body_for_log_output(response)}\n-- BODY END--" : ''}\nTook: #{@request_time_end - @request_time_start} seconds) } #logger.debug { "Parse Response? #{@parse_response}" } raise HTTPAuthorizationError if @use_exceptions && @response.code == '401' @parse_response ? response_parsed : response.body end def response_parsed @response_parsed ||= begin response_body = response.respond_to?(:body) ? response.body : '' logger.debug { "Parsing Response. #{response_body.inspect}" } case response.content_type when 'application/json' response_body.empty? ? response_body : JSON.parse(response_body) # rescue response else response_body end end end # @param [String] path # @param [Hash|String|Nil] query # @return [URI] def build_uri(path = '', query = nil) _query = query.is_a?(Hash) ? query.map { |k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.respond_to?(:to_s) ? v.to_s : v)}" }.join('&') : query _path = "#{path}#{_query and _query.respond_to?(:empty?) and !_query.empty? ? "?#{_query}" : ''}" URI.parse(File.join(base_uri, _path)) end if RUBY_VERSION.start_with? '1.8.' def request_method_name_to_class_name(method_name) method_name.to_s.capitalize end else def request_method_name_to_class_name(method_name) method_name.to_s.capitalize.to_sym end end # @param [Symbol] method_name (:get) # @param [Hash] args # @option args [Hash] :headers ({}) # @option args [String] :path ('') # @option args [Hash] :query ({}) # @option args [Any] :body (nil) # @param [Hash] options # @option options [Hash] :default_request_headers (@default_request_headers) def call_method(method_name = :get, args = { }, options = { }) headers = args[:headers] || options[:headers] || { } path = args[:path] || '' query = args[:query] || { } body = args[:body] # Allow the default request headers to be overridden _default_request_headers = options.fetch(:default_request_headers, default_request_headers) _default_request_headers ||= { } _headers = _default_request_headers.merge(headers) @uri = build_uri(path, query) klass_name = request_method_name_to_class_name(method_name) klass = Net::HTTP.const_get(klass_name) request = klass.new(@uri.request_uri, _headers) if request.request_body_permitted? _body = (body and !body.is_a?(String)) ? JSON.generate(body) : body logger.debug { "Processing Body: '#{_body}'" } request.body = _body if _body end send_request(request) end def delete(path, options = { }) call_method(:delete, { :path => path }, options) end def get(path, options = { }) call_method(:get, { :path => path }, options) end def head(path, options = { }) call_method(:head, { :path => path }, options) end def options(path, options = { }) call_method(:options, { :path => path }, options) end def put(path, body, options = { }) call_method(:put, { :path => path, :body => body }, options) end def post(path, body, options = { }) call_method(:post, { :path => path, :body => body }, options) end # HTTPClient end class NotificationHandler attr_accessor :logger def initialize(args = { }) initialize_logger(args) initialize_http(args) end def initialize_logger(args = { }) @logger = args[:logger] ||= begin _logger = Logger.new(args[:log_to] || STDERR) _logger.level = args[:log_level] || Logger::DEBUG _logger end end def initialize_http(args = { }) uri_as_str = args[:uri] uri = URI(uri_as_str) args[:http_host_address] = uri.host args[:http_host_port] = uri.port args[:http_host_use_ssl] = uri.scheme == 'https' args[:username] = uri.user args[:password] = uri.password args[:default_base_path] = uri.request_uri @http_client = HTTPClient.new(args) end def forward_notification(notification) @http_client.call_method(:post, :body => notification) end end end LOGGING_LEVELS = { :debug => Logger::DEBUG, :info => Logger::INFO, :warn => Logger::WARN, :error => Logger::ERROR, :fatal => Logger::FATAL } DEFAULT_HEADER_CONTENT_TYPE = 'application/json; charset=utf-8' def log_to_as_string _log_to = arguments[:log_to] case _log_to when STDERR; 'STDERR' when STDOUT; 'STDOUT' else _log_to end end default_arguments = { :log_to => "/tmp/#{File.basename($0)}.log", :log_level => Logger::DEBUG, :options_file_path => File.expand_path(File.basename($0, '.*'), '~/.options'), :http_method => :post } @arguments = default_arguments.dup def arguments; @arguments end argument_parser = OptionParser.new argument_parser.on('--uri URI', 'The address of the server to communicate with.') { |v| arguments[:uri] = v } argument_parser.on('--content-type VALUE', 'The value for the Content-Type header sent in each request.', "\tdefault: #{DEFAULT_HEADER_CONTENT_TYPE}") { |v| arguments[:content_type] = v } argument_parser.on('--method METHOD', 'The HTTP method to use when submitting the notification.', "\tdefault: #{arguments[:http_method]}") { |v| arguments[:http_method] = v.to_sym } argument_parser.on('--pretty-print', 'Will format the output to be more human readable.') { |v| arguments[:pretty_print] = v } argument_parser.on('--log-to FILENAME', 'Log file location.', "\tdefault: #{log_to_as_string}") { |v| arguments[:log_to] = v } argument_parser.on('--log-level LEVEL', LOGGING_LEVELS.keys, "Logging level. Available Options: #{LOGGING_LEVELS.keys.join(', ')}", "\tdefault: #{LOGGING_LEVELS.invert[arguments[:log_level]]}") { |v| arguments[:log_level] = LOGGING_LEVELS[v] } argument_parser.on('--[no-]options-file [FILENAME]', 'Path to a file which contains default command line arguments.', "\tdefault: #{arguments[:options_file_path]}" ) { |v| arguments[:options_file_path] = v} argument_parser.on_tail('-h', '--help', 'Display this message.') { puts help; exit } arguments.clear original_argv_args = ARGV.dup argument_parser.parse!(ARGV) remaining_argv_args = ARGV.dup arguments_from_command_line = arguments.dup options_file_path = arguments_from_command_line[:options_file_path] || default_arguments[:options_file_path] arguments.clear argument_parser.load(options_file_path) arguments_from_options_file = arguments.dup arguments = default_arguments.merge(arguments_from_options_file).merge(arguments_from_command_line) pp arguments # @handler = Vidispine::NotificationHandler.new(arguments) # def logger; @handler.logger end # logger.debug { "ARGUMENTS: #{remaining_argv_args.inspect}" } # @handler.forward_notification(remaining_argv_args)