shellac-repl 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ require 'rake'
2
+ require 'fileutils'
3
+ $LOAD_PATH << File.expand_path("lib/", __FILE__)
4
+ require File.dirname(__FILE__) + '/lib/shellac/version'
5
+
6
+ desc 'Run tests'
7
+ task :test do
8
+ sh 'rspec spec/'
9
+ end
10
+
11
+
12
+ desc 'Build gem'
13
+ task :build do
14
+ sh 'gem build shellac.gemspec'
15
+ gem_file = "shellac-repl-#{Shellac::VERSION}.gem"
16
+ FileUtils.mkdir_p 'gems'
17
+ FileUtils.mv gem_file, 'gems', :force => true
18
+ end
19
+
20
+ task :default => :test
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "shellac"
4
+
5
+ shellac = Shellac::Runner.new
6
+
7
+ shellac.run
8
+
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # Headers - A simple class defining basic attributes and methods for HTTP headers.
5
+ #
6
+
7
+ class Headers
8
+ # Not much here. Instantiate a hash to store HTTP headers.
9
+ def initialize
10
+ @headers = {}
11
+ end
12
+
13
+ # Add a header to the @headers hash.
14
+ def add_header(key, value)
15
+ if @headers.has_key?(key)
16
+ # If the value is a string, turn it into an array...
17
+ if @headers[key].is_a?(String)
18
+ previous_value = @headers[key]
19
+ @headers[key] = [previous_value, value]
20
+ # ... else append to the array.
21
+ else
22
+ @headers[key] << value
23
+ end
24
+ else
25
+ @headers[key] = value
26
+ end
27
+ end
28
+
29
+ # Return the @headers hash.
30
+ def headers
31
+ @headers
32
+ end
33
+
34
+ # If we reach this method, assume we want to find and return a value from
35
+ # the @headers hash using :method as the key name to look up.
36
+ # It's a hack, but this is an MVP.
37
+ def method_missing(method)
38
+ header_name = method.id2name
39
+ header_words = header_name.split('_')
40
+ header_words.each do |word|
41
+ word.capitalize!
42
+ end
43
+ header_name = header_words.join('-')
44
+ if @headers.has_key?(header_name)
45
+ # TODO: Add some color to help visually separate output. It'd be slick
46
+ # if we used a different color based on request/response object.
47
+ #puts "\e[1;33m#{@headers[header_name]}\e[0;0m"
48
+ @headers[header_name]
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'varnishclient/varnishclient'
4
+ require 'varnishlog/varnishlog'
5
+ require 'headers'
6
+ require 'ripl'
7
+
8
+ # Define some colors we'll use in some output.
9
+ RED = "\e[1;31m"
10
+ GREEN = "\e[1;32m"
11
+ YELLOW = "\e[1;33m"
12
+ NORMAL = "\e[0;0m"
13
+
14
+ module Shellac
15
+
16
+ class Runner
17
+ attr_accessor :varnishclient, :varnishlog
18
+
19
+ def initialize
20
+ @varnishclient = VarnishClient.new
21
+ @varnishlog = VarnishLog.new
22
+ end
23
+
24
+ def run
25
+ # Inform the user of the default request.
26
+ puts "\nDefault request: #{RED}#{@varnishclient.request.uri.to_s}#{NORMAL}"
27
+ puts "HTTP Port (varnishclient.request.port): #{RED}#{@varnishclient.request.port}#{NORMAL}"
28
+ puts "HTTP Request Path (varnishclient.request.path): #{RED}#{@varnishclient.request.path}#{NORMAL}"
29
+ puts "HTTP Host Header (varnishclient.request.host): #{RED}#{@varnishclient.request.host}#{NORMAL}"
30
+ puts "\n#{RED}You may want to to modify this before calling #{NORMAL}@varnishclient.make_request#{RED}!#{NORMAL}"
31
+
32
+ # Start our REPL.
33
+ Ripl.config[:prompt] = "\nshellac> "
34
+ # Bind Ripl to the context of this instance so that all of the objects
35
+ # accessible to it (and their methods) are accessible to the user in the REPL.
36
+ # 'binding' is a private method that returns a Binding object; we can only
37
+ # access it here via the #instance_eval method.
38
+ Ripl.start :binding => self.instance_eval{binding}
39
+ end
40
+
41
+ # 1. Starts a thread to spawn a `varnishlog` subprocess that will catch our HTTP request.
42
+ # 2. Executes the HTTP request to Varnish.
43
+ # 3. Reaps the `varnishlog` thread so that its output can be parsed into the @varnishlog.response object.
44
+ # Returns nothing.
45
+ def make_request
46
+ @varnishlog.start_varnishlog_thread(@varnishclient.request.user_agent)
47
+ # Sleeping is a hack to give the varnishlog thread time to get in place.
48
+ # Without it, there's a roughly 50/50 chance (sometimes worse) that the thread
49
+ # will block on I/O as if it missed the HTTP request (because it did).
50
+ sleep(0.5)
51
+
52
+ @varnishclient.response = @varnishclient.request.make_request
53
+
54
+ case @varnishclient.response.code
55
+ when /2\d{2}/
56
+ color = GREEN
57
+ when /3\d{2}/
58
+ color = YELLOW
59
+ when /4\d{2}/
60
+ color = RED
61
+ else
62
+ color = NORMAL
63
+ end
64
+ puts "\n#{@varnishclient.request.uri.to_s}: #{color}#{@varnishclient.response.code} #{@varnishclient.response.message}#{NORMAL}\n\n"
65
+
66
+ @varnishlog.reap_thread
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Shellac
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "net/http"
4
+ require "uri"
5
+
6
+ #
7
+ # VarnishClient::Request - A class that wraps Net::HTTP::Request objects.
8
+ #
9
+
10
+ class VarnishClient
11
+ class Request
12
+
13
+ attr_accessor :uri, :request
14
+ # Initialize a VarnishClient::Request object.
15
+ def initialize
16
+ # Set some default values.
17
+ # We're running `varnishlog` on localhost.
18
+ default_http_hostname = 'http://localhost'
19
+ default_http_port = '80'
20
+ default_request_uri = '/'
21
+
22
+ @uri = URI.parse("#{default_http_hostname}:#{default_http_port}#{default_request_uri}")
23
+ @http = Net::HTTP.new(@uri.host, @uri.port)
24
+ @request_headers = {
25
+ 'Host' => 'www.example.com',
26
+ 'User-Agent' => 'shellac'
27
+ }
28
+ @request = Net::HTTP::Get.new(@uri.request_uri, @request_headers)
29
+ end
30
+
31
+ # Get the request headers.
32
+ def headers
33
+ @request_headers
34
+ end
35
+
36
+ # Get the Host header for the request.
37
+ def host
38
+ @request['Host']
39
+ end
40
+
41
+ # Set the Host header for the request.
42
+ def host=(host_header)
43
+ @request_headers['Host'] = host_header
44
+ @request = Net::HTTP::Get.new(@uri.request_uri, @request_headers)
45
+ end
46
+
47
+ # Get the path of the request.
48
+ def path
49
+ @uri.request_uri
50
+ end
51
+
52
+ # Set the path of the request.
53
+ def path=(path)
54
+ @uri.path = path
55
+ @request = Net::HTTP::Get.new(@uri.request_uri, @request_headers)
56
+ end
57
+
58
+ # Get the port of the request.
59
+ def port
60
+ @uri.port
61
+ end
62
+
63
+ # Set the port of the request.
64
+ def port=(port)
65
+ @uri.port = port
66
+ @http = Net::HTTP.new(@uri.host, @uri.port)
67
+ @request = Net::HTTP::Get.new(@uri.request_uri, @request_headers)
68
+ end
69
+
70
+ # Get the User-Agent header.
71
+ def user_agent
72
+ @request['User-Agent']
73
+ end
74
+
75
+ # Set the User-Agent header.
76
+ def user_agent=(user_agent)
77
+ @request_headers['User-Agent'] = user_agent
78
+ @request = Net::HTTP::Get.new(@uri.request_uri, @request_headers)
79
+ end
80
+
81
+ # Make the HTTP request.
82
+ def make_request
83
+ response = @http.request(@request)
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "net/http"
4
+
5
+ #
6
+ # VarnishClient::Response - A class that wraps Net::HTTP::Response objects.
7
+ #
8
+
9
+ # To be honest, we aren't doing anything here. This class exists in case
10
+ # some sort of extra work needs to be done beyond what a Net::HTTPResponse
11
+ # object provides.
12
+ # (We're assigning the result of a Net::HTTPRequest to this object.)
13
+
14
+ class VarnishClient
15
+ class Response
16
+
17
+ # Initialize a VarnishClient::Response object.
18
+ def initialize
19
+ @response = Net::HTTPResponse
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'varnishclient/request'
4
+ require 'varnishclient/response'
5
+
6
+ #
7
+ # VarnishClient - A class that wraps Net::HTTP objects as Varnish clients.
8
+ #
9
+
10
+ class VarnishClient
11
+
12
+ attr_accessor :request, :response
13
+ def initialize
14
+ @request = VarnishClient::Request.new
15
+ @response = VarnishClient::Response.new
16
+ end
17
+
18
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'headers'
4
+
5
+ #
6
+ # VarnishLog::Request - This class is used to create objects that represent HTTP
7
+ # requests from Varnish's perspective.
8
+ #
9
+
10
+ class VarnishLog
11
+ class Request < Headers
12
+ @methods = [:method, :url, :protocol]
13
+ @methods.each do |method|
14
+ attr_accessor method
15
+ end
16
+
17
+ # Assume the powers of Headers.
18
+ def initialize
19
+ super
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'headers'
4
+
5
+ #
6
+ # VarnishLog::Response - This class is used to create objects that represent HTTP
7
+ # responses from Varnish's perspective.
8
+ #
9
+
10
+ class VarnishLog
11
+ class Response < Headers
12
+
13
+ # Assume the powers of Headers.
14
+ def initialize
15
+ super
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'varnishlog/request'
4
+ require 'varnishlog/response'
5
+
6
+ #
7
+ # VarnishLog - A class to define objects that contain a transaction captured
8
+ # by `varnishlog`.
9
+ #
10
+
11
+ class VarnishLog
12
+ attr_reader :request, :response
13
+
14
+ # Create a new VarnishLog object.
15
+ #
16
+ # Returns a VarnishLog object with two attributes that are request and response objects.
17
+ def initialize
18
+ @request = VarnishLog::Request.new
19
+ @response = VarnishLog::Response.new
20
+ end
21
+
22
+ def start_varnishlog_thread(user_agent="shellac")
23
+ @varnishlog_thread = Thread.new {
24
+ output = `varnishlog -q 'ReqHeader:User-Agent eq "#{user_agent}"' -k 1`
25
+ }
26
+ @varnishlog_thread
27
+ end
28
+
29
+ def reap_thread
30
+ transaction = @varnishlog_thread.value
31
+ parse(transaction)
32
+ end
33
+
34
+ # Parses the output of a transaction captured by `varnishlog` into attributes
35
+ # that are assigned to a VarnishLog object.
36
+ def parse(transaction)
37
+ items = transaction.split("\n")
38
+
39
+ # Requests
40
+ ## Request headers.
41
+ request_headers = items.grep(/ReqHeader/)
42
+ request_headers.each do |header|
43
+ if match = /-\s+ReqHeader\s+(?<header_name>.*): (?<header_value>.*)/.match(header)
44
+ @request.add_header(match['header_name'], match['header_value'])
45
+ end
46
+ end
47
+
48
+ ## Match ReqMethod.
49
+ if method_match = /-\s+ReqMethod\s+(?<method>.*)/.match(items.grep(/ReqMethod/)[0])
50
+ @request.method = method_match['method']
51
+ end
52
+ ## Match ReqURL.
53
+ if url_match = /-\s+ReqURL\s+(?<url>\/.*)/.match(items.grep(/ReqURL/)[0])
54
+ @request.url = url_match['url']
55
+ end
56
+ ## Match ReqProtocol.
57
+ if protocol_match = /-\s+ReqProtocol\s+(?<protocol>.*)/.match(items.grep(/ReqProtocol/)[0])
58
+ @request.protocol = protocol_match['protocol']
59
+ end
60
+
61
+ # Response.
62
+ ## Response headers.
63
+ response_headers = items.grep(/RespHeader/)
64
+ response_headers.each do |header|
65
+ if match = /-\s+RespHeader\s+(?<header_name>.*): (?<header_value>.*)/.match(header)
66
+ @response.add_header(match['header_name'], match['header_value'])
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Shellac::Runner do
6
+
7
+ before :each do
8
+ @shellac = Shellac::Runner.new
9
+ end
10
+
11
+ describe "#new" do
12
+ it "Creates a new Shellac::Runner object" do
13
+ expect(described_class).to equal(Shellac::Runner)
14
+ end
15
+ it "Should have a .varnishlog attribute that's a VarnishLog object" do
16
+ expect(@shellac.varnishlog).to be_an_instance_of(VarnishLog)
17
+ end
18
+ it "Should have a .varnishclient attribute that's a VarnishClient object" do
19
+ expect(@shellac.varnishclient).to be_an_instance_of(VarnishClient)
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/shellac'
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'spec_helper'
4
+
5
+ describe VarnishClient do
6
+
7
+ before :each do
8
+ @varnishclient = VarnishClient.new
9
+ end
10
+
11
+ describe "#new" do
12
+ it "Creates a new VarnishClient object" do
13
+ expect(described_class).to equal(VarnishClient)
14
+ end
15
+ it "Should have a .request attribute that's a VarnishClient::Request object" do
16
+ expect(@varnishclient.request).to be_an_instance_of(VarnishClient::Request)
17
+ end
18
+ it "Should have a .response attribute that's a VarnishClient::Response object" do
19
+ expect(@varnishclient.response).to be_an_instance_of(VarnishClient::Response)
20
+ end
21
+ end
22
+
23
+ # Request-related tests.
24
+ describe "@varnishclient.request.host" do
25
+ it "defaults to 'www.example.com'" do
26
+ expect(@varnishclient.request.host).to eql('www.example.com')
27
+ end
28
+ it "can be updated to a custom host, like 'www.shellac.love'" do
29
+ @varnishclient.request.host = 'www.shellac.love'
30
+ expect(@varnishclient.request.host).to eql('www.shellac.love')
31
+ end
32
+ end
33
+
34
+ describe "@varnishclient.request.path" do
35
+ it "defaults to '/'" do
36
+ expect(@varnishclient.request.path).to eql('/')
37
+ end
38
+ it "can be updated to a custom path, like '/what-else-can-shellac-do.htm'" do
39
+ @varnishclient.request.path = '/what-else-can-shellac-do.htm'
40
+ expect(@varnishclient.request.path).to eql('/what-else-can-shellac-do.htm')
41
+ end
42
+ end
43
+
44
+ describe "@varnishclient.request.port" do
45
+ it "defaults to port 80" do
46
+ expect(@varnishclient.request.port).to eql(80)
47
+ end
48
+ it "can be updated to a custom port, like 1234" do
49
+ @varnishclient.request.port = 1234
50
+ expect(@varnishclient.request.port).to eql(1234)
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'spec_helper'
4
+
5
+ describe VarnishLog do
6
+
7
+ before :each do
8
+ @varnishlog = VarnishLog.new
9
+ varnishlog_output = "* << Request >> 606123948 \n- Begin req 606123947 rxreq\n- Timestamp Start: 1454350985.766917 0.000000 0.000000\n- Timestamp Req: 1454350985.766917 0.000000 0.000000\n- ReqStart 127.0.0.1 35937\n- ReqMethod GET\n- ReqURL /api/v7/find_user\n- ReqProtocol HTTP/1.1\n- ReqHeader Accept: */*\n- ReqHeader User-Agent: shellac\n- ReqHeader Host: www.example.com\n- ReqHeader Connection: close\n- ReqHeader X-Forwarded-For: 127.0.0.1\n- VCL_call RECV\n- ReqUnset X-Forwarded-For: 127.0.0.1\n- ReqHeader X-Forwarded-For: 127.0.0.1, 127.0.0.1\n- VCL_return hash\n- VCL_call HASH\n- VCL_return lookup\n- Debug \"XXXX MISS\"\n- VCL_call MISS\n- VCL_return fetch\n- Link bereq 606123949 fetch\n- Timestamp Fetch: 1454350985.832164 0.065247 0.065247\n- RespProtocol HTTP/1.1\n- RespStatus 200\n- RespReason OK\n- RespHeader Date: Mon, 01 Feb 2016 18:23:05 GMT\n- RespHeader Server: Apache\n- RespHeader Cache-Control: max-age=60, public\n- RespHeader Vary: Accept-Encoding\n- RespHeader Content-Encoding: gzip\n- RespHeader Content-Length: 726\n- RespHeader X-Cnection: close\n- RespHeader Content-Type: application/json\n- RespHeader X-Varnish: 606123948\n- RespHeader Age: 0\n- RespHeader Via: 1.1 varnish-v4\n- VCL_call DELIVER\n- RespHeader X-Cache-Status: MISS\n- VCL_return deliver\n- Timestamp Process: 1454350985.832222 0.065305 0.000057\n- RespUnset Content-Encoding: gzip\n- RespUnset Content-Length: 726\n- RespHeader Transfer-Encoding: chunked\n- Debug \"RES_MODE 48\"\n- RespHeader Connection: close\n- RespHeader Accept-Ranges: bytes\n- Gzip U D - 726 5243 80 80 5739\n- Timestamp Resp: 1454350985.832356 0.065439 0.000134\n- Debug \"XXX REF 2\"\n- ReqAcct 148 0 148 522 5258 5780\n- End \n\n"
10
+ @varnishlog.parse(varnishlog_output)
11
+ end
12
+
13
+ describe "#new" do
14
+ it "creates a new VarnishLog object" do
15
+ expect(described_class).to equal(VarnishLog)
16
+ end
17
+ it "should have a .request attribute that's a VarnishLog::Request object" do
18
+ expect(@varnishlog.request).to be_an_instance_of(VarnishLog::Request)
19
+ end
20
+ it "should have a .response attribute that's a VarnishLog::Response object" do
21
+ expect(@varnishlog.response).to be_an_instance_of(VarnishLog::Response)
22
+ end
23
+ end
24
+
25
+ # Test an explicitly defined method.
26
+ describe "@varnishlog.request.url" do
27
+ it "returns the URL path requested from Varnish" do
28
+ expect(@varnishlog.request.url).to eql("/api/v7/find_user")
29
+ end
30
+ end
31
+
32
+ # Test an implicitly defined method.
33
+ describe "@varnishlog.response.date" do
34
+ it "returns the Date response header sent by Varnish" do
35
+ expect(@varnishlog.response.date).to eql("Mon, 01 Feb 2016 18:23:05 GMT")
36
+ end
37
+ end
38
+
39
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shellac-repl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Frantz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-02-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ripl
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.7.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.7.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 3.4.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 3.4.0
46
+ description: ! " shellac is a REPL for Varnish's varnishlog command.\n It's a simple
47
+ tool for interacting with an HTTP request/response \n and the request/response
48
+ seen/sent by varnishlog.\n The goal is to help operators dissect activity on very
49
+ busy Varnish servers.\n"
50
+ email:
51
+ - ryanleefrantz@gmail.com
52
+ executables:
53
+ - shellac
54
+ extensions: []
55
+ extra_rdoc_files: []
56
+ files:
57
+ - lib/headers.rb
58
+ - lib/shellac.rb
59
+ - lib/varnishclient/request.rb
60
+ - lib/varnishclient/varnishclient.rb
61
+ - lib/varnishclient/response.rb
62
+ - lib/varnishlog/request.rb
63
+ - lib/varnishlog/varnishlog.rb
64
+ - lib/varnishlog/response.rb
65
+ - lib/shellac/version.rb
66
+ - bin/shellac
67
+ - spec/varnishlog_spec.rb
68
+ - spec/shellac_spec.rb
69
+ - spec/varnishclient_spec.rb
70
+ - spec/spec_helper.rb
71
+ - Rakefile
72
+ homepage: https://github.com/RyanFrantz/shellac
73
+ licenses:
74
+ - MIT
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: 1.9.2
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.23.2
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: A REPL for Varnish's varnishlog command
97
+ test_files: []