http-wiretap 0.1.0

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