diff --git a/README.md b/README.md index 6c53581..0d87a6a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Sniffer supports most common HTTP accessing libraries: * [Ethon](https://github.com/typhoeus/ethon) * [Typhoeus](https://github.com/typhoeus/typhoeus) * [EM-HTTP-Request](https://github.com/igrigorik/em-http-request) +* [Excon](https://github.com/excon/excon) diff --git a/lib/sniffer.rb b/lib/sniffer.rb index 298df03..464e502 100644 --- a/lib/sniffer.rb +++ b/lib/sniffer.rb @@ -64,3 +64,4 @@ def logger require_relative "sniffer/adapters/curb_adapter" require_relative "sniffer/adapters/ethon_adapter" require_relative "sniffer/adapters/eventmachine_adapter" +require_relative "sniffer/adapters/excon_adapter" diff --git a/lib/sniffer/adapters/excon_adapter.rb b/lib/sniffer/adapters/excon_adapter.rb new file mode 100644 index 0000000..79acc59 --- /dev/null +++ b/lib/sniffer/adapters/excon_adapter.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Sniffer + module Adapters + module ExconAdapter + def self.included(base) + base.class_eval do + middlewares = ::Excon.defaults[:middlewares] + + response_parser_index = middlewares.index(::Excon::Middleware::ResponseParser) + middlewares.insert(response_parser_index - 1, Sniffer::Adapters::ExconAdapter::Request) + middlewares.insert(response_parser_index + 1, Sniffer::Adapters::ExconAdapter::Response) + end + end + + class Request < ::Excon::Middleware::Base + def request_call(params) + if Sniffer.enabled? + data_item = Sniffer::DataItem.new + data_item.request = Sniffer::DataItem::Request.new(host: params[:host], + method: params[:method], + query: ::Excon::Utils.query_string(params), + headers: params[:headers], + body: params[:body].to_s, + port: params[:port]) + + Sniffer.store(data_item) + params[:sniffer_data_item] = data_item + end + + super(params) + end + end + + class Response < ::Excon::Middleware::Base + def response_call(params) + if Sniffer.enabled? + data_item = params.delete(:sniffer_data_item) + response = params[:response] + data_item.response = Sniffer::DataItem::Response.new(status: response[:status], + headers: response[:headers], + body: response[:body], + timing: 'fake') # TODO: think about timing + + Sniffer.store(data_item) + data_item.log + end + + super(params) + end + end + end + end +end + +Excon.send(:include, Sniffer::Adapters::ExconAdapter) if defined?(::Excon) diff --git a/sniffer.gemspec b/sniffer.gemspec index 0191995..6a4fbfa 100644 --- a/sniffer.gemspec +++ b/sniffer.gemspec @@ -36,4 +36,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "ethon", ">= 0.11.0" spec.add_development_dependency "typhoeus", ">= 0.9.0" spec.add_development_dependency "em-http-request", ">= 1.1.0" + spec.add_development_dependency "excon", ">= 0.59.0" end diff --git a/spec/sniffer/adapters/excon_adapter_spec.rb b/spec/sniffer/adapters/excon_adapter_spec.rb new file mode 100644 index 0000000..63851e0 --- /dev/null +++ b/spec/sniffer/adapters/excon_adapter_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Excon do + let(:headers) { { "accept-encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "accept" => "*/*", "user-agent" => "Ruby", "host" => "localhost:4567" } } + let(:get_request) do + Excon.get('http://localhost:4567/?lang=ruby&author=matz', headers: headers) + end + + let(:get_request_dynamic_params) do + connection = Excon.new("http://localhost:4567/") + connection.request(method: :get, query: { lang: :ruby, author: :matz }, headers: headers) + end + + let(:post_request) { Excon.post('http://localhost:4567/data?lang=ruby', body: "author=Matz") } + let(:post_json) do + Excon.post('http://localhost:4567/json', body: { 'lang' => 'Ruby', 'author' => 'Matz' }.to_json, headers: { 'content-type' => 'application/json; charset=UTF-8' }) + end + + it 'logs', enabled: true do + logger = double + Sniffer.config.logger = logger + expect(logger).to receive(:log).with(0, "{\"port\":4567,\"host\":\"localhost\",\"query\":\"?lang=ruby&author=matz\",\"rq_accept_encoding\":\"gzip;q=1.0,deflate;q=0.6,identity;q=0.3\",\"rq_accept\":\"*/*\",\"rq_user_agent\":\"Ruby\",\"rq_host\":\"localhost:4567\",\"method\":\"get\",\"request_body\":\"\",\"status\":200,\"rs_content_type\":\"text/html;charset=utf-8\",\"rs_x_xss_protection\":\"1; mode=block\",\"rs_x_content_type_options\":\"nosniff\",\"rs_x_frame_options\":\"SAMEORIGIN\",\"rs_content_length\":\"2\",\"timing\":0.0006,\"response_body\":\"OK\"}") + get_request + end + + it_behaves_like "a sniffered", "excon" +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 16c52f3..95f5557 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,7 @@ require "typhoeus" require "ethon" require "em-http-request" +require "excon" require "sniffer" require "pry-byebug" @@ -36,6 +37,10 @@ c.syntax = :expect end + config.before(:all) do + sleep(1) # HACK: wait a little for fake server start + end + config.before(:each) do Sniffer.reset! end diff --git a/spec/yaml/excon/get_response.yaml b/spec/yaml/excon/get_response.yaml new file mode 100644 index 0000000..3e1b4b0 --- /dev/null +++ b/spec/yaml/excon/get_response.yaml @@ -0,0 +1,22 @@ +--- +:request: + :host: "localhost" + :query: "?lang=ruby&author=matz" + :headers: + accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + accept: "*/*" + user-agent: Ruby + host: "localhost:4567" + :body: "" + :method: :get + :port: 4567 +:response: + :status: 200 + :headers: + content-type: text/html;charset=utf-8 + x-xss-protection: 1; mode=block + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + content-length: '2' + :body: OK + :timing: 0.0006 diff --git a/spec/yaml/excon/get_response_dynamic.yaml b/spec/yaml/excon/get_response_dynamic.yaml new file mode 100644 index 0000000..0ffce44 --- /dev/null +++ b/spec/yaml/excon/get_response_dynamic.yaml @@ -0,0 +1,22 @@ +--- +:request: + :host: "localhost" + :query: "?author=matz&lang=ruby" + :headers: + accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + accept: "*/*" + user-agent: Ruby + host: "localhost:4567" + :body: "" + :method: :get + :port: 4567 +:response: + :status: 200 + :headers: + content-type: text/html;charset=utf-8 + x-xss-protection: 1; mode=block + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + content-length: '2' + :body: OK + :timing: 0.0006 diff --git a/spec/yaml/excon/json_response.yaml b/spec/yaml/excon/json_response.yaml new file mode 100644 index 0000000..734dddb --- /dev/null +++ b/spec/yaml/excon/json_response.yaml @@ -0,0 +1,20 @@ +--- +:request: + :host: "localhost" + :port: 4567 + :query: "" + :headers: + content-type: application/json; charset=UTF-8 + host: localhost:4567 + content-length: 31 + :body: '{"lang":"Ruby","author":"Matz"}' + :method: :post + :port: 4567 +:response: + :status: 200 + :headers: + content-type: text/json + x-content-type-options: nosniff + content-length: '15' + :body: '{"status":"OK"}' + :timing: 0.0006 diff --git a/spec/yaml/excon/post_response.yaml b/spec/yaml/excon/post_response.yaml new file mode 100644 index 0000000..65acda7 --- /dev/null +++ b/spec/yaml/excon/post_response.yaml @@ -0,0 +1,22 @@ +--- +:request: + :host: "localhost" + :port: 4567 + :query: "?lang=ruby" + :headers: + user-agent: "excon/0.59.0" + host: localhost:4567 + content-length: 11 + :body: author=Matz + :method: :post + :port: 4567 +:response: + :status: 201 + :headers: + content-type: text/html;charset=utf-8 + x-xss-protection: 1; mode=block + x-content-type-options: nosniff + x-frame-options: SAMEORIGIN + content-length: '7' + :body: Created + :timing: 0.0006