http-wiretap 0.1.0

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.
Files changed (4) hide show
  1. data/README.md +114 -0
  2. data/lib/http/ext/net_http.rb +26 -0
  3. data/lib/http/wiretap.rb +158 -0
  4. metadata +133 -0
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ HTTP Wiretap - An HTTP Recorder
2
+ ===============================
3
+
4
+ ## DESCRIPTION
5
+
6
+ HTTP Wiretap is a library used to log HTTP requests and responses within your
7
+ application. This can be useful when trying to determine what is occurring in
8
+ your application or when trying to debug someone else's application.
9
+
10
+ This library follows the rules of [Semantic Versioning](http://semver.org/).
11
+
12
+
13
+ ## RUNNING
14
+
15
+ To install HTTP Wiretap, simply install the gem:
16
+
17
+ $ [sudo] gem install http-wiretap
18
+
19
+ And enable it within your application:
20
+
21
+ require 'http/wiretap'
22
+ HTTP::Wiretap.start()
23
+
24
+ And disable it when you're done:
25
+
26
+ HTTP::Wiretap.stop()
27
+
28
+ Or clear out your log directory while running:
29
+
30
+ HTTP::Wiretap.clear()
31
+
32
+ By default, all requests will be logged to `http-log` of your present working
33
+ directory. Also, your `http-log` directory will be cleared out each time you
34
+ run `start()`.
35
+
36
+
37
+ ## MODES
38
+
39
+ HTTP Wiretap will log HTTP traffic in two modes:
40
+
41
+ 1. Raw - Logs traffic in the order that it occurred.
42
+ 2. Host - Groups traffic by host and path
43
+
44
+ When logging, data written will be linked with a symbolic link to save space and
45
+ reduce I/O.
46
+
47
+ The `http-log` directory is separated by mode and then each request is written
48
+ into a directory that contains a `request` file and a `response` file. Each of
49
+ these files contain the headers and body.
50
+
51
+
52
+ ## EXAMPLE
53
+
54
+ If your application were to make the following requests:
55
+
56
+ http://abc.com/login
57
+ http://xyz.com/users
58
+ http://xyz.com/users/1/favorites
59
+ http://abc.com/fetch_info.pl?foo=bar
60
+ http://abc.com/fetch_info.pl?foo=baz
61
+
62
+ Then your `http-log` directory will show the following:
63
+
64
+ + http-log/
65
+ + raw/
66
+ + 0/
67
+ + request
68
+ + response
69
+ + 1/
70
+ + request
71
+ + response
72
+ + 2/
73
+ + request
74
+ + response
75
+ + 3/
76
+ + request
77
+ + response
78
+ + host/
79
+ + abc.com/
80
+ + login/
81
+ + 0/
82
+ + request
83
+ + response
84
+ + fetch_info.pl/
85
+ + 0/
86
+ + request
87
+ + response
88
+ + 1/
89
+ + request
90
+ + response
91
+ + xyz.com/
92
+ + users/
93
+ + 0/
94
+ + request
95
+ + response
96
+ + 1/
97
+ + favorites/
98
+ + 0/
99
+ + request
100
+ + response
101
+
102
+ There can be overlap when making calls to a `/users` path and then a `/users/0`
103
+ path since requests are numbered. If anyone has a better idea of how to
104
+ structure this while keeping it simple, please let me know.
105
+
106
+
107
+ ## CONTRIBUTE
108
+
109
+ If you'd like to contribute to HTTP Wiretap, start by forking the repository
110
+ on GitHub:
111
+
112
+ http://github.com/benbjohnson/http-wiretap
113
+
114
+ Please add test coverage to your code when submitting pull requests.
@@ -0,0 +1,26 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+
4
+ module Net
5
+ class HTTP
6
+ def request_with_wiretap(request, body = nil, &block)
7
+ request_id = nil
8
+
9
+ # Log request
10
+ if ::HTTP::Wiretap.enabled
11
+ request_id = ::HTTP::Wiretap.log_request(self, request)
12
+ end
13
+
14
+ # Send request
15
+ response = request_without_wiretap(request, body, &block)
16
+
17
+ # Log response
18
+ if ::HTTP::Wiretap.enabled
19
+ ::HTTP::Wiretap.log_response(self, response, request_id)
20
+ end
21
+ end
22
+
23
+ alias_method :request_without_wiretap, :request
24
+ alias_method :request, :request_with_wiretap
25
+ end
26
+ end
@@ -0,0 +1,158 @@
1
+ dir = File.join(File.dirname(File.expand_path(__FILE__)), '..')
2
+ $:.unshift(dir)
3
+
4
+ require 'http/ext/net_http'
5
+ require 'fileutils'
6
+
7
+ module HTTP
8
+ class Wiretap
9
+ VERSION = '0.1.0'
10
+
11
+ ############################################################################
12
+ # Static Initializers
13
+ ############################################################################
14
+
15
+ @enabled = false
16
+
17
+
18
+ ############################################################################
19
+ # Static Properties
20
+ ############################################################################
21
+
22
+ # A flag stating if wiretap has started
23
+ def self.enabled
24
+ @enabled
25
+ end
26
+
27
+
28
+ # The root directory where logs are stored.
29
+ def self.log_directory
30
+ @log_directory || 'http-log'
31
+ end
32
+
33
+ # Sets the root directory where logs are stored.
34
+ def self.log_directory=(dir)
35
+ @log_directory = dir
36
+ end
37
+
38
+
39
+ ############################################################################
40
+ # Static Methods
41
+ ############################################################################
42
+
43
+ # Begins logging requests and responses.
44
+ def self.start()
45
+ # Reset the sequential request identifier
46
+ @next_id = 0
47
+ @host_request_paths = {}
48
+ @host_request_next_id = {}
49
+
50
+ # Enable logging
51
+ @enabled = true
52
+
53
+ # Clear log directory
54
+ FileUtils.rm_rf(log_directory)
55
+ end
56
+
57
+ # Stops logging requests and responses.
58
+ def self.stop()
59
+ # Disable logging
60
+ @enabled = false
61
+ end
62
+
63
+ # Stops logging and then restarts.
64
+ def self.restart()
65
+ self.stop()
66
+ self.start()
67
+ end
68
+
69
+ # Logs a request's headers and body to a file. The file will be written to:
70
+ #
71
+ # <log_directory>/raw/<request_number>/request
72
+ #
73
+ # A symbolic link will be made from that directory to the `host/` directory.
74
+ #
75
+ # @param [Net::HTTP] http the object sending the request
76
+ # @param [Net::HTTP::Request] request the request being logged
77
+ #
78
+ # @return [Fixnum] the sequential identifier for this request
79
+ def self.log_request(http, request)
80
+ return unless @enabled
81
+
82
+ # Retrieve the request identifier
83
+ request_id = @next_id
84
+ @next_id += 1
85
+
86
+ # Create raw log directory
87
+ raw_dir = File.expand_path("#{log_directory}/raw/#{request_id}")
88
+ ::FileUtils.mkdir_p(raw_dir)
89
+
90
+ # Write request to file
91
+ File.open("#{raw_dir}/request", 'w') do |file|
92
+ # Write method and path
93
+ file.write("#{request.method} #{request.path} HTTP/1.1\r\n")
94
+
95
+ # Write headers
96
+ connection = *request.get_fields('Connection') || 'close'
97
+ request.each_capitalized do |header_name, header_value|
98
+ if header_name != 'Connection'
99
+ file.write("#{header_name}: #{header_value}\r\n")
100
+ end
101
+ end
102
+ file.write("Connection: #{connection}\r\n")
103
+ file.write("Host: #{http.address}:#{http.port}\r\n")
104
+ file.write("\r\n")
105
+
106
+ # Write body
107
+ file.write(request.body) unless request.body.nil?
108
+ end
109
+
110
+ # Link to host-based log
111
+ host_request_name = "#{http.address}#{request.path}"
112
+ host_request_id = @host_request_next_id[host_request_name] ||= 0
113
+ @host_request_next_id[host_request_name] += 1
114
+ host_dir = File.expand_path("#{log_directory}/host/#{host_request_name}/#{host_request_id}")
115
+ @host_request_paths[request_id] = host_dir
116
+ ::FileUtils.mkdir_p(host_dir)
117
+ ::FileUtils.ln_sf("#{raw_dir}/request", "#{host_dir}/request")
118
+
119
+ return request_id
120
+ end
121
+
122
+ # Logs a response's headers and body to a file. The file will be written to:
123
+ #
124
+ # <log_directory>/raw/<request_number>/response
125
+ #
126
+ # A symbolic link will be made from that directory to the `host/` directory.
127
+ #
128
+ # @param [Net::HTTP] http the object sending the request
129
+ # @param [Net::HTTP::Request] request the request being logged
130
+ # @param [Fixnum] request_id the sequential identifier for the request
131
+ def self.log_response(http, response, request_id)
132
+ return unless @enabled
133
+
134
+ # Create log directory
135
+ raw_dir = File.expand_path("#{log_directory}/raw/#{request_id}")
136
+ ::FileUtils.mkdir_p(raw_dir)
137
+
138
+ # Write response to file
139
+ File.open("#{raw_dir}/response", 'w') do |file|
140
+ file.write("HTTP/#{response.http_version} #{response.code}\r\n")
141
+
142
+ # Write headers
143
+ response.each_capitalized do |header_name, header_value|
144
+ file.write("#{header_name}: #{header_value}\r\n")
145
+ end
146
+ file.write("\r\n")
147
+
148
+ # Write body
149
+ file.write(response.body) unless response.body.nil?
150
+ end
151
+
152
+ # Link to host-based log
153
+ host_dir = @host_request_paths[request_id]
154
+ ::FileUtils.mkdir_p(host_dir)
155
+ ::FileUtils.ln_sf("#{raw_dir}/response", "#{host_dir}/response")
156
+ end
157
+ end
158
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: http-wiretap
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Ben Johnson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-23 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 31
30
+ segments:
31
+ - 2
32
+ - 4
33
+ - 0
34
+ version: 2.4.0
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: mocha
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 35
46
+ segments:
47
+ - 0
48
+ - 9
49
+ - 12
50
+ version: 0.9.12
51
+ type: :development
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: unindentable
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 27
62
+ segments:
63
+ - 0
64
+ - 1
65
+ - 0
66
+ version: 0.1.0
67
+ type: :development
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakeweb
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 27
78
+ segments:
79
+ - 1
80
+ - 3
81
+ - 0
82
+ version: 1.3.0
83
+ type: :development
84
+ version_requirements: *id004
85
+ description:
86
+ email:
87
+ - benbjohnson@yahoo.com
88
+ executables: []
89
+
90
+ extensions: []
91
+
92
+ extra_rdoc_files: []
93
+
94
+ files:
95
+ - lib/http/ext/net_http.rb
96
+ - lib/http/wiretap.rb
97
+ - README.md
98
+ has_rdoc: true
99
+ homepage: http://github.com/benbjohnson/http-wiretap
100
+ licenses: []
101
+
102
+ post_install_message:
103
+ rdoc_options: []
104
+
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ hash: 3
122
+ segments:
123
+ - 0
124
+ version: "0"
125
+ requirements: []
126
+
127
+ rubyforge_project:
128
+ rubygems_version: 1.3.7
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: An HTTP Recorder
132
+ test_files: []
133
+