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.
- data/README.md +114 -0
- data/lib/http/ext/net_http.rb +26 -0
- data/lib/http/wiretap.rb +158 -0
- 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
|
data/lib/http/wiretap.rb
ADDED
@@ -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
|
+
|