tshield 0.10.0.0 → 0.11.0.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.
- checksums.yaml +4 -4
- data/README.md +103 -35
- data/config/tshield.yml +6 -4
- data/lib/tshield.rb +1 -0
- data/lib/tshield/configuration.rb +3 -1
- data/lib/tshield/controllers/helpers/session_helpers.rb +15 -0
- data/lib/tshield/controllers/requests.rb +23 -25
- data/lib/tshield/controllers/sessions.rb +19 -6
- data/lib/tshield/extensions/string_extensions.rb +10 -0
- data/lib/tshield/options.rb +8 -28
- data/lib/tshield/request.rb +29 -121
- data/lib/tshield/request_matching.rb +122 -0
- data/lib/tshield/request_vcr.rb +139 -0
- data/lib/tshield/response.rb +3 -1
- data/lib/tshield/server.rb +2 -2
- data/lib/tshield/version.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/tshield/configuration_spec.rb +6 -0
- data/spec/tshield/fixtures/config/tshield.yml +2 -0
- data/spec/tshield/fixtures/matching/example.json +55 -0
- data/spec/tshield/options_spec.rb +38 -0
- data/spec/tshield/request_matching_spec.rb +137 -0
- data/spec/tshield/request_vcr_spec.rb +101 -0
- data/tshield.gemspec +4 -0
- metadata +94 -4
- data/spec/tshield/request_spec.rb +0 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 618a5c8f2527911612734f7aaddd21fab7ba82066409a451ad4bd39ac42eae33
|
4
|
+
data.tar.gz: 4637697bcef288f4276e664adb653eaa7c6aae7b898cdf8b238ac27a8499b55c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8b7d0731557762fa096a583845865817848b301b7f82c3e6ea467953d587b116ac72dce5930698051cdaf192c6ae3a1b34d501d1bb35bc32a36b265a039017b
|
7
|
+
data.tar.gz: 440c90f16f7ef256078a115b2f725bf94ac747375a40b01b3d33d6dab8da427a598555487e70c2568e7e6b5e0585f4b9ce6e0cf0b5e3772effeacc6ce8969620
|
data/README.md
CHANGED
@@ -25,7 +25,11 @@ To run server execute this command
|
|
25
25
|
tshield
|
26
26
|
|
27
27
|
Default port is **4567**
|
28
|
-
|
28
|
+
|
29
|
+
#### Command Line Options
|
30
|
+
|
31
|
+
* __-port__: Overwrite default port (4567)
|
32
|
+
* __-help__: Show all command line options
|
29
33
|
|
30
34
|
#### Config example
|
31
35
|
|
@@ -49,7 +53,102 @@ domains:
|
|
49
53
|
- /users
|
50
54
|
```
|
51
55
|
|
52
|
-
## Config options
|
56
|
+
## Config options for Pattern Matching
|
57
|
+
|
58
|
+
An example of file to create a stub:
|
59
|
+
|
60
|
+
All files should be in `matching` directory.
|
61
|
+
Each file should be a valid JSON array of objects and each object must contain
|
62
|
+
at least the following attributes:
|
63
|
+
|
64
|
+
* __method__: a http method.
|
65
|
+
* __path__: url path.
|
66
|
+
* __response__: object with response data.
|
67
|
+
|
68
|
+
Response must be contain the following attributes:
|
69
|
+
|
70
|
+
* __headers__: key value object with expected headers to match. In the evaluation process
|
71
|
+
this stub will be returned if all headers are in request.
|
72
|
+
* __status__: integer used http status respose.
|
73
|
+
* __body__: content to be returned.
|
74
|
+
|
75
|
+
Optional request attributes:
|
76
|
+
|
77
|
+
* __headers__: key value object with expected headers to match. In the evaluation process
|
78
|
+
this stub will be returned if all headers are in request.
|
79
|
+
* __query__: works like headers but use query params.
|
80
|
+
|
81
|
+
__Important__: If VCR config conflicts with Matching config Matching will be
|
82
|
+
used. Matching config have priority.
|
83
|
+
|
84
|
+
### Session Configuration
|
85
|
+
|
86
|
+
To register stub into a session create an object with following attributes:
|
87
|
+
|
88
|
+
* __session__: name of session.
|
89
|
+
* __stubs__: an array with objects described above.
|
90
|
+
|
91
|
+
### Example of matching configuration
|
92
|
+
|
93
|
+
```json
|
94
|
+
[{
|
95
|
+
"method": "GET",
|
96
|
+
"path": "/matching/example",
|
97
|
+
"query": {
|
98
|
+
"user": 123
|
99
|
+
},
|
100
|
+
"response": {
|
101
|
+
"body": "matching-example-response-with-query",
|
102
|
+
"headers": {},
|
103
|
+
"status": 200
|
104
|
+
}
|
105
|
+
},
|
106
|
+
{
|
107
|
+
"method": "GET",
|
108
|
+
"path": "/matching/example",
|
109
|
+
"response": {
|
110
|
+
"body": "matching-example-response",
|
111
|
+
"headers": {},
|
112
|
+
"status": 200
|
113
|
+
}
|
114
|
+
},
|
115
|
+
{
|
116
|
+
"method": "POST",
|
117
|
+
"path": "/matching/example",
|
118
|
+
"headers": {
|
119
|
+
"user": "123"
|
120
|
+
},
|
121
|
+
"response": {
|
122
|
+
"body": "matching-example-response-with-headers",
|
123
|
+
"headers": {},
|
124
|
+
"status": 200
|
125
|
+
}
|
126
|
+
},
|
127
|
+
{
|
128
|
+
"method": "POST",
|
129
|
+
"path": "/matching/example",
|
130
|
+
"response": {
|
131
|
+
"body": "matching-example-response-with-post",
|
132
|
+
"headers": {},
|
133
|
+
"status": 200
|
134
|
+
}
|
135
|
+
},
|
136
|
+
{
|
137
|
+
"session": "example-session",
|
138
|
+
"stubs": [{
|
139
|
+
"method": "GET",
|
140
|
+
"path": "/matching/example",
|
141
|
+
"response": {
|
142
|
+
"body": "matching-example-response-in-session",
|
143
|
+
"headers": {},
|
144
|
+
"status": 200
|
145
|
+
}
|
146
|
+
}]
|
147
|
+
}
|
148
|
+
]
|
149
|
+
```
|
150
|
+
|
151
|
+
## Config options for VCR
|
53
152
|
```yaml
|
54
153
|
request:
|
55
154
|
timeout: 8
|
@@ -167,37 +266,6 @@ This features files are used as base for the component tests.
|
|
167
266
|
## Samples
|
168
267
|
#### Basic sample for a client app requesting a server API
|
169
268
|
[examples/client-api-nodejs](examples/client-api-nodejs)
|
170
|
-
#### Basic sample for componente/integration test
|
171
|
-
**[WIP]**
|
172
|
-
|
173
|
-
## Setup for local development
|
174
|
-
|
175
|
-
First install dependencies.
|
176
|
-
_We recommend use of the RVM to manage project dependencies.__
|
177
|
-
|
178
|
-
```
|
179
|
-
bundle install
|
180
|
-
```
|
181
|
-
|
182
|
-
### Run server to development
|
183
|
-
|
184
|
-
To start server execute:
|
185
|
-
|
186
|
-
`rake server`
|
187
|
-
|
188
|
-
### Build
|
189
|
-
|
190
|
-
To generate ruby gem execute:
|
191
|
-
|
192
|
-
`rake build`
|
193
|
-
|
194
|
-
### Test
|
195
|
-
|
196
|
-
To run all unit tests:
|
197
|
-
|
198
|
-
`rake spec`
|
199
|
-
|
200
|
-
To run all component tests:
|
201
|
-
|
202
|
-
`rake component__tests`
|
203
269
|
|
270
|
+
## Contributing
|
271
|
+
[Hacking or Contributing to Tshield](CONTRIBUTING.md)
|
data/config/tshield.yml
CHANGED
data/lib/tshield.rb
CHANGED
@@ -25,6 +25,8 @@ module TShield
|
|
25
25
|
# (NEED IMPROVEMENT github-issue #https://github.com/diegorubin/tshield/issues/17)
|
26
26
|
# not_save_headers: List of headers that should be ignored in generated
|
27
27
|
# file
|
28
|
+
# ignore_query_params: List of params that should be ignored in
|
29
|
+
# generated directory
|
28
30
|
#
|
29
31
|
attr_reader :request
|
30
32
|
attr_reader :domains
|
@@ -63,7 +65,7 @@ module TShield
|
|
63
65
|
end
|
64
66
|
|
65
67
|
def get_headers(domain)
|
66
|
-
domains[domain]['headers'] || {}
|
68
|
+
(domains[domain] || {})['headers'] || {}
|
67
69
|
end
|
68
70
|
|
69
71
|
def get_name(domain)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TShield
|
4
|
+
module Controllers
|
5
|
+
module Helpers
|
6
|
+
# Session Helpers
|
7
|
+
module SessionHelpers
|
8
|
+
def self.current_session_name(request)
|
9
|
+
session = TShield::Sessions.current(request.ip)
|
10
|
+
session ? session[:name] : 'no-session'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'sinatra'
|
4
4
|
|
5
|
-
require '
|
6
|
-
|
5
|
+
require 'tshield/controllers/helpers/session_helpers'
|
7
6
|
require 'tshield/options'
|
8
7
|
require 'tshield/configuration'
|
9
|
-
require 'tshield/
|
8
|
+
require 'tshield/request_matching'
|
9
|
+
require 'tshield/request_vcr'
|
10
10
|
require 'tshield/sessions'
|
11
11
|
|
12
12
|
module TShield
|
13
13
|
module Controllers
|
14
|
+
# Requests Handler
|
14
15
|
module Requests
|
15
16
|
PATHP = %r{([a-zA-Z0-9/\._-]+)}.freeze
|
16
17
|
|
@@ -44,25 +45,26 @@ module TShield
|
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
48
|
+
# Requests Handler Helpers
|
47
49
|
module Helpers
|
50
|
+
def self.build_headers(request)
|
51
|
+
headers = request.env.select { |key, _value| key =~ /HTTP/ }
|
52
|
+
headers['Content-Type'] = request.content_type || 'application/json'
|
53
|
+
headers
|
54
|
+
end
|
55
|
+
|
48
56
|
def treat(params, request, _response)
|
49
57
|
path = params.fetch('captures', [])[0]
|
50
58
|
|
51
|
-
debugger if TShield::Options.instance.break?(path: path, moment: :before)
|
52
|
-
|
53
59
|
method = request.request_method
|
54
60
|
request_content_type = request.content_type
|
55
|
-
|
56
|
-
headers = {
|
57
|
-
'Content-Type' => request.content_type || 'application/json'
|
58
|
-
}
|
59
|
-
|
60
|
-
add_headers(headers, path)
|
61
|
+
session_name = TShield::Controllers::Helpers::SessionHelpers.current_session_name(request)
|
61
62
|
|
62
63
|
options = {
|
63
64
|
method: method,
|
64
|
-
headers:
|
65
|
+
headers: Helpers.build_headers(request),
|
65
66
|
raw_query: request.env['QUERY_STRING'],
|
67
|
+
session: session_name,
|
66
68
|
ip: request.ip
|
67
69
|
}
|
68
70
|
|
@@ -73,13 +75,18 @@ module TShield
|
|
73
75
|
replace: '')
|
74
76
|
options[:body] = result
|
75
77
|
end
|
78
|
+
api_response = TShield::RequestMatching.new(path, options).match_request
|
76
79
|
|
77
|
-
|
80
|
+
unless api_response
|
81
|
+
add_headers(headers, path)
|
78
82
|
|
79
|
-
|
83
|
+
api_response ||= TShield::RequestVCR.new(path, options).response
|
84
|
+
end
|
80
85
|
|
81
86
|
logger.info(
|
82
|
-
"original=#{api_response.original} method=#{method} path=#{path}
|
87
|
+
"original=#{api_response.original} method=#{method} path=#{path}"\
|
88
|
+
"content-type=#{request_content_type}"\
|
89
|
+
"session=#{session_name}"
|
83
90
|
)
|
84
91
|
|
85
92
|
status api_response.status
|
@@ -87,17 +94,8 @@ module TShield
|
|
87
94
|
body api_response.body
|
88
95
|
end
|
89
96
|
|
90
|
-
def set_content_type(_request_content_type)
|
91
|
-
content_type :json
|
92
|
-
end
|
93
|
-
|
94
|
-
def current_session_name(request)
|
95
|
-
session = TShield::Sessions.current(request.ip)
|
96
|
-
session ? session[:name] : 'no-session'
|
97
|
-
end
|
98
|
-
|
99
97
|
def add_headers(headers, path)
|
100
|
-
configuration.get_headers(domain(path)).each do |source, destiny|
|
98
|
+
(configuration.get_headers(domain(path)) || {}).each do |source, destiny|
|
101
99
|
headers[destiny] = request.env[source] unless request.env[source].nil?
|
102
100
|
end
|
103
101
|
end
|
@@ -7,18 +7,31 @@ require 'tshield/sessions'
|
|
7
7
|
|
8
8
|
module TShield
|
9
9
|
module Controllers
|
10
|
+
# Actions to handle sessions
|
10
11
|
module Sessions
|
11
12
|
def self.registered(app)
|
12
|
-
|
13
|
-
|
13
|
+
session_path = TShield::Configuration.singleton.session_path
|
14
|
+
ip = request.ip
|
15
|
+
register_get(app, session_path, ip)
|
16
|
+
register_post(app, session_path, ip, params)
|
17
|
+
register_delete(app, session_path, ip)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.register_get(app, session_path, ip)
|
21
|
+
app.get session_path do
|
22
|
+
TShield::Sessions.current(ip).to_json
|
14
23
|
end
|
24
|
+
end
|
15
25
|
|
16
|
-
|
17
|
-
|
26
|
+
def self.register_post(app, session_path, params, ip)
|
27
|
+
app.post session_path do
|
28
|
+
TShield::Sessions.start(ip, params[:name]).to_json
|
18
29
|
end
|
30
|
+
end
|
19
31
|
|
20
|
-
|
21
|
-
|
32
|
+
def self.register_delete(app, session_path, ip)
|
33
|
+
app.delete session_path do
|
34
|
+
TShield::Sessions.stop(ip).to_json
|
22
35
|
end
|
23
36
|
end
|
24
37
|
end
|
data/lib/tshield/options.rb
CHANGED
@@ -23,35 +23,20 @@ module TShield
|
|
23
23
|
parse unless options[:skip_parse]
|
24
24
|
end
|
25
25
|
|
26
|
-
def break?(args = {})
|
27
|
-
check_breakpoint(args)
|
28
|
-
end
|
29
|
-
|
30
26
|
def configuration_file
|
31
27
|
@options.fetch(:configuration_file, 'config/tshield.yml')
|
32
28
|
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
def check_breakpoint(args)
|
37
|
-
check_breakpoint_moment(args)
|
38
|
-
end
|
39
|
-
|
40
|
-
def check_breakpoint_moment(args)
|
41
|
-
@options["#{args[:moment]}_pattern".to_sym] =~ args[:path]
|
30
|
+
def port
|
31
|
+
@options.fetch(:port, 4567)
|
42
32
|
end
|
43
33
|
|
44
|
-
|
45
|
-
opts.on('-b', '--break-before-request [PATTERN]',
|
46
|
-
'Breakpoint before request') do |pattern|
|
47
|
-
@options[:before_pattern] = Regexp.new(pattern)
|
48
|
-
end
|
49
|
-
end
|
34
|
+
private
|
50
35
|
|
51
|
-
def
|
52
|
-
opts.on('-
|
53
|
-
'
|
54
|
-
@options[:
|
36
|
+
def register_port(opts)
|
37
|
+
opts.on('-p', '--port [PORT]',
|
38
|
+
'Sinatra port') do |port|
|
39
|
+
@options[:port] = port.to_i
|
55
40
|
end
|
56
41
|
end
|
57
42
|
|
@@ -62,11 +47,6 @@ module TShield
|
|
62
47
|
end
|
63
48
|
end
|
64
49
|
|
65
|
-
def register_patterns(opts)
|
66
|
-
register_before_pattern(opts)
|
67
|
-
register_after_pattern(opts)
|
68
|
-
end
|
69
|
-
|
70
50
|
def register_version(opts)
|
71
51
|
opts.on('-v', '--version', 'Show version') do
|
72
52
|
TShield.logger.info(TShield::Version.to_s)
|
@@ -90,8 +70,8 @@ module TShield
|
|
90
70
|
|
91
71
|
def register(opts)
|
92
72
|
register_configuration(opts)
|
93
|
-
register_patterns(opts)
|
94
73
|
register_version(opts)
|
74
|
+
register_port(opts)
|
95
75
|
register_help(opts)
|
96
76
|
end
|
97
77
|
end
|
data/lib/tshield/request.rb
CHANGED
@@ -1,166 +1,74 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'httparty'
|
4
|
-
require 'json'
|
5
|
-
require 'byebug'
|
6
|
-
|
7
|
-
require 'digest/sha1'
|
8
|
-
|
9
|
-
require 'tshield/configuration'
|
10
|
-
require 'tshield/options'
|
11
|
-
require 'tshield/response'
|
12
3
|
require 'tshield/sessions'
|
13
4
|
|
14
5
|
module TShield
|
6
|
+
# Base of request mock methods
|
15
7
|
class Request
|
16
|
-
attr_reader :
|
8
|
+
attr_reader :configuration
|
9
|
+
attr_writer :content_idx
|
17
10
|
|
18
|
-
def initialize
|
19
|
-
@path = path
|
20
|
-
@options = options
|
11
|
+
def initialize
|
21
12
|
@configuration = TShield::Configuration.singleton
|
22
|
-
@options[:timeout] = @configuration.request['timeout']
|
23
|
-
@options[:verify] = @configuration.request['verify_ssl']
|
24
|
-
request
|
25
|
-
end
|
26
|
-
|
27
|
-
def request
|
28
|
-
unless @options[:raw_query].nil? || @options[:raw_query].empty?
|
29
|
-
@path = "#{@path}?#{@options[:raw_query]}"
|
30
|
-
end
|
31
|
-
|
32
|
-
@url = "#{domain}#{@path}"
|
33
|
-
|
34
|
-
if exists
|
35
|
-
@response = get_current_response
|
36
|
-
@response.original = false
|
37
|
-
else
|
38
|
-
@method = method
|
39
|
-
@configuration.get_before_filters(domain).each do |filter|
|
40
|
-
@method, @url, @options = filter.new.filter(@method, @url, @options)
|
41
|
-
end
|
42
|
-
|
43
|
-
raw = HTTParty.send(@method.to_s, @url, @options)
|
44
|
-
|
45
|
-
@configuration.get_after_filters(domain).each do |filter|
|
46
|
-
raw = filter.new.filter(raw)
|
47
|
-
end
|
48
|
-
|
49
|
-
@response = save(raw)
|
50
|
-
|
51
|
-
@response.original = true
|
52
|
-
end
|
53
|
-
current_session[:counter].add(@path, method) if current_session
|
54
|
-
debugger if TShield::Options.instance.break?(path: @path, moment: :after)
|
55
|
-
@response
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
def domain
|
61
|
-
@domain ||= @configuration.get_domain_for(@path)
|
62
|
-
end
|
63
|
-
|
64
|
-
def name
|
65
|
-
@name ||= @configuration.get_name(domain)
|
66
|
-
end
|
67
|
-
|
68
|
-
def method
|
69
|
-
@options[:method].downcase
|
70
13
|
end
|
71
14
|
|
72
|
-
|
73
|
-
headers = {}
|
74
|
-
raw_response.headers.each do |k, v|
|
75
|
-
headers[k] = v unless @configuration.not_save_headers(domain).include? k
|
76
|
-
end
|
77
|
-
|
78
|
-
content = {
|
79
|
-
body: raw_response.body,
|
80
|
-
status: raw_response.code,
|
81
|
-
headers: headers
|
82
|
-
}
|
83
|
-
|
84
|
-
write(content)
|
85
|
-
|
86
|
-
TShield::Response.new(raw_response.body, headers, raw_response.code)
|
87
|
-
end
|
15
|
+
protected
|
88
16
|
|
89
17
|
def current_session
|
90
18
|
TShield::Sessions.current(@options[:ip])
|
91
19
|
end
|
92
20
|
|
93
|
-
def
|
94
|
-
return @content if @content
|
95
|
-
|
96
|
-
@content = JSON.parse(File.open(destiny).read)
|
97
|
-
@content['body'] = File.open(destiny(true)).read unless @content['body']
|
98
|
-
@content
|
99
|
-
end
|
100
|
-
|
101
|
-
def file_exists
|
21
|
+
def session_destiny(request_path)
|
102
22
|
session = current_session
|
103
|
-
|
104
|
-
File.exist?(destiny)
|
105
|
-
end
|
23
|
+
return unless session
|
106
24
|
|
107
|
-
|
108
|
-
|
25
|
+
request_path = File.join(request_path, session[:name])
|
26
|
+
Dir.mkdir(request_path) unless File.exist?(request_path)
|
109
27
|
end
|
110
28
|
|
111
|
-
def
|
112
|
-
|
29
|
+
def content_destiny
|
30
|
+
"#{destiny}.content"
|
113
31
|
end
|
114
32
|
|
115
|
-
def
|
116
|
-
|
33
|
+
def headers_destiny
|
34
|
+
"#{destiny}.json"
|
117
35
|
end
|
118
36
|
|
119
|
-
def destiny
|
37
|
+
def destiny
|
120
38
|
request_path = File.join('requests')
|
121
39
|
Dir.mkdir(request_path) unless File.exist?(request_path)
|
122
40
|
|
123
|
-
|
124
|
-
if session
|
125
|
-
request_path = File.join(request_path, session[:name])
|
126
|
-
Dir.mkdir(request_path) unless File.exist?(request_path)
|
127
|
-
end
|
41
|
+
session_destiny(request_path)
|
128
42
|
|
129
43
|
name_path = File.join(request_path, name)
|
130
44
|
Dir.mkdir(name_path) unless File.exist?(name_path)
|
131
45
|
|
132
|
-
|
46
|
+
cleared_path = clear_path(@path)
|
47
|
+
path_path = File.join(name_path, safe_dir(cleared_path))
|
48
|
+
|
133
49
|
Dir.mkdir(path_path) unless File.exist?(path_path)
|
134
50
|
|
135
51
|
method_path = File.join(path_path, method)
|
136
52
|
Dir.mkdir(method_path) unless File.exist?(method_path)
|
137
53
|
|
138
|
-
|
139
|
-
File.join(method_path, destiny_name)
|
54
|
+
File.join(method_path, @content_idx.to_s)
|
140
55
|
end
|
141
56
|
|
142
|
-
def
|
143
|
-
|
144
|
-
|
145
|
-
f.close
|
57
|
+
def clear_path(path)
|
58
|
+
skip_query_params = configuration.domains[@domain]['skip_query_params']
|
59
|
+
url_path, params = path.split('?')
|
146
60
|
|
147
|
-
|
61
|
+
return path if !skip_query_params || !params
|
148
62
|
|
149
|
-
|
150
|
-
|
151
|
-
|
63
|
+
cleared_params = params.split('&').select do |param|
|
64
|
+
param unless skip_query_params.include?(param.gsub(/=.*?$/, ''))
|
65
|
+
end.join('&')
|
152
66
|
|
153
|
-
|
67
|
+
[url_path, cleared_params].join('?')
|
154
68
|
end
|
155
69
|
|
156
|
-
def
|
157
|
-
|
158
|
-
path = url.gsub(/(\?.*)/, '')
|
159
|
-
params = Digest::SHA1.hexdigest Regexp.last_match(1)
|
160
|
-
"#{path.gsub(%r{/}, '-').gsub(/^-/, '')}?#{params}"
|
161
|
-
else
|
162
|
-
url.gsub(%r{/}, '-').gsub(/^-/, '')
|
163
|
-
end
|
70
|
+
def method
|
71
|
+
@options[:method].downcase
|
164
72
|
end
|
165
73
|
end
|
166
74
|
end
|