shellac-repl 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []