content_gateway 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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/COPYING +24 -0
- data/Changelog +42 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +61 -0
- data/README.md +134 -0
- data/Rakefile +10 -0
- data/content-gateway.gemspec +26 -0
- data/lib/content_gateway/cache.rb +59 -0
- data/lib/content_gateway/exceptions.rb +79 -0
- data/lib/content_gateway/gateway.rb +153 -0
- data/lib/content_gateway/request.rb +48 -0
- data/lib/content_gateway/version.rb +3 -0
- data/lib/content_gateway.rb +23 -0
- data/spec/integration/content_gateway/gateway_spec.rb +410 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/unit/content_gateway/cache_spec.rb +134 -0
- data/spec/unit/content_gateway/gateway_spec.rb +121 -0
- data/spec/unit/content_gateway/request_spec.rb +105 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 65fe5be0eff06af34c50732980238a937d3f8249
|
4
|
+
data.tar.gz: edd6943756c0136824ba2fefa40768e6c83fa0bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4183a37c2624be76daa48c6f8238e10db77a260903583e2e4bb92123e238e3ed357d5b0393c2de56d3b723f1dc733419c0747b73aef25615a5ed14dcb03cf64c
|
7
|
+
data.tar.gz: 0405a719b4a119bcc110d6eb077b3f962a1a5d7fd0673110c348c1a713bc2cf575ef4865c4011c7e97fa330e4eb4598d1b649a502ef111d922cb909922d868c1
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
content-gateway
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p481
|
data/COPYING
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2014, Globo.com - Webmedia
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of the Globo.com - Webmedia nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL GLOBO.COM - WEBMEDIA BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Changelog
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
2014-10-21 [0.1.0]
|
2
|
+
|
3
|
+
* Adding delete_json method.
|
4
|
+
* A lot of refactoring.
|
5
|
+
|
6
|
+
2014-10-07 [0.0.14]
|
7
|
+
|
8
|
+
* Ignoring cache after an internal server error when skip_cache = true.
|
9
|
+
* Mapping 5xx errors to ContentGateway::ServerError.
|
10
|
+
|
11
|
+
2014-04-01 [0.0.13]
|
12
|
+
|
13
|
+
* Added support for HTTP 409 Conflict error.
|
14
|
+
|
15
|
+
2014-04-01 [0.0.12]
|
16
|
+
|
17
|
+
* Require debugger on spec_helper.
|
18
|
+
|
19
|
+
2014-01-17 [0.0.11]
|
20
|
+
|
21
|
+
* Adding delete method support
|
22
|
+
|
23
|
+
2014-01-17 [0.0.10]
|
24
|
+
|
25
|
+
* Do not depend on a specific version of activesupport.
|
26
|
+
|
27
|
+
2013-08-15 [0.0.9]
|
28
|
+
|
29
|
+
* Using try to get proxy from config to avoid error when not exists.
|
30
|
+
|
31
|
+
2013-08-15 [0.0.8]
|
32
|
+
|
33
|
+
* Return empty when result is not a RestClient object and do not have "code" method.
|
34
|
+
|
35
|
+
2013-08-15 [0.0.7]
|
36
|
+
|
37
|
+
* Default params now are optional
|
38
|
+
|
39
|
+
2013-08-14 [0.0.6]
|
40
|
+
|
41
|
+
* First release
|
42
|
+
* Replaces Esportes API gateway.rb
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
content_gateway (0.1.0)
|
5
|
+
activesupport
|
6
|
+
json
|
7
|
+
rest-client
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (4.0.2)
|
13
|
+
i18n (~> 0.6, >= 0.6.4)
|
14
|
+
minitest (~> 4.2)
|
15
|
+
multi_json (~> 1.3)
|
16
|
+
thread_safe (~> 0.1)
|
17
|
+
tzinfo (~> 0.3.37)
|
18
|
+
atomic (1.1.14)
|
19
|
+
byebug (3.5.1)
|
20
|
+
columnize (~> 0.8)
|
21
|
+
debugger-linecache (~> 1.2)
|
22
|
+
slop (~> 3.6)
|
23
|
+
columnize (0.8.9)
|
24
|
+
debugger-linecache (1.2.0)
|
25
|
+
diff-lcs (1.2.5)
|
26
|
+
i18n (0.6.9)
|
27
|
+
json (1.8.1)
|
28
|
+
mime-types (2.0)
|
29
|
+
minitest (4.7.5)
|
30
|
+
multi_json (1.7.9)
|
31
|
+
rest-client (1.6.7)
|
32
|
+
mime-types (>= 1.16)
|
33
|
+
rspec (3.1.0)
|
34
|
+
rspec-core (~> 3.1.0)
|
35
|
+
rspec-expectations (~> 3.1.0)
|
36
|
+
rspec-mocks (~> 3.1.0)
|
37
|
+
rspec-core (3.1.7)
|
38
|
+
rspec-support (~> 3.1.0)
|
39
|
+
rspec-expectations (3.1.2)
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
41
|
+
rspec-support (~> 3.1.0)
|
42
|
+
rspec-mocks (3.1.3)
|
43
|
+
rspec-support (~> 3.1.0)
|
44
|
+
rspec-support (3.1.2)
|
45
|
+
simplecov (0.7.1)
|
46
|
+
multi_json (~> 1.0)
|
47
|
+
simplecov-html (~> 0.7.1)
|
48
|
+
simplecov-html (0.7.1)
|
49
|
+
slop (3.6.0)
|
50
|
+
thread_safe (0.1.3)
|
51
|
+
atomic
|
52
|
+
tzinfo (0.3.38)
|
53
|
+
|
54
|
+
PLATFORMS
|
55
|
+
ruby
|
56
|
+
|
57
|
+
DEPENDENCIES
|
58
|
+
byebug
|
59
|
+
content_gateway!
|
60
|
+
rspec (>= 2.3.0)
|
61
|
+
simplecov (>= 0.7.1)
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# Content Gateway
|
2
|
+
|
3
|
+
An easy way to get external content with two cache levels. The first is a performance cache and second is the stale.
|
4
|
+
|
5
|
+
Content Gateway lets you set a timeout for any request.
|
6
|
+
If the configured timeout is reached without response, it searches for cached data.
|
7
|
+
If cache is unavailable or expired, it returns the stale cache data.
|
8
|
+
Only then, if stale cache is also unavailable or expired, it raises an exception
|
9
|
+
|
10
|
+
## Dependencies
|
11
|
+
|
12
|
+
- Ruby >= 1.9
|
13
|
+
- ActiveSupport (for cache store)
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'content_gateway'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install content_gateway
|
28
|
+
|
29
|
+
## Configuration
|
30
|
+
|
31
|
+
`ContentGateway::Gateway` class accepts a configuration object with the following parameters:
|
32
|
+
|
33
|
+
- `timeout`: request timeout in seconds
|
34
|
+
- `cache_expires_in`: cache data expiration time, in seconds
|
35
|
+
- `cache_stale_expires_in`: stale cache data expiration time, in seconds
|
36
|
+
- `cache`: cache store instance. This may be an instance of `ActiveSupport::Cache`
|
37
|
+
- `proxy`: proxy address, if needed
|
38
|
+
|
39
|
+
Configuration object example:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
config = OpenStruct.new(
|
43
|
+
timeout: 2,
|
44
|
+
cache_expires_in: 1800,
|
45
|
+
cache_stale_expires_in: 86400,
|
46
|
+
cache: ActiveSupport::Cache.lookup_store(:memory_store),
|
47
|
+
proxy: "http://proxy.example.com/"
|
48
|
+
)
|
49
|
+
```
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
|
53
|
+
`ContentGateway::Gateway` expects four parameters:
|
54
|
+
|
55
|
+
- a label, which is used in the log messages
|
56
|
+
- a config object, just as described above
|
57
|
+
- an URL Generator object. This may be any object that responds to a `generate` method, like this:
|
58
|
+
- an optional hash with default params. Currently, it only supports default headers
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class UrlGenerator
|
62
|
+
def generate(resource_path, params = {})
|
63
|
+
args = ""
|
64
|
+
args = "?#{params.map {|k, v| "#{k}=#{v}"}.join("&")}" if params.any?
|
65
|
+
"http://example.com/#{resource_path}#{args}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
default_params = { headers: { Accept: "application/json" } }
|
70
|
+
|
71
|
+
gateway = ContentGateway::Gateway.new("My API", config, UrlGenerator.new, default_params)
|
72
|
+
```
|
73
|
+
|
74
|
+
Every param may be overrided on each request.
|
75
|
+
|
76
|
+
This Gateway object supports the following methods:
|
77
|
+
|
78
|
+
### GET
|
79
|
+
|
80
|
+
To do a GET request, you may use the `get` or `get_json` methods. The second one parses the response as JSON.
|
81
|
+
Optional parameters are supported:
|
82
|
+
|
83
|
+
- `timeout`: overwrites the default timeout
|
84
|
+
- `expires_in`: overwrites the default cache expiration time
|
85
|
+
- `stale_expires_in`: overwrites the default stale cache expiration time
|
86
|
+
- `skip_cache`: if set to `true`, ignores cache and stale cache
|
87
|
+
- `headers`: a hash with request headers
|
88
|
+
|
89
|
+
Every other parameter is passed to URLGenerator `generate` method (like query string parameters).
|
90
|
+
|
91
|
+
Examples:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
gateway.get("/path", timeout: 3)
|
95
|
+
|
96
|
+
gateway.get_json("/path.json", skip_cache: true)
|
97
|
+
```
|
98
|
+
|
99
|
+
### POST, PUT and DELETE
|
100
|
+
|
101
|
+
POST, PUT and DELETE verbs are also supported, but ignore cache and stale cache.
|
102
|
+
The gateway object offers the equivalent methods for these verbs (`post`, `post_json`, `put`, `put_json`, `delete` and `delete_json`).
|
103
|
+
The only optional parameter supported by these methods is `payload`.
|
104
|
+
Every other parameter is passed to URLGenerator `generate` method (like query string parameters).
|
105
|
+
|
106
|
+
Examples:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
gateway.post("/api/post_example", payload: { param1: "value" })
|
110
|
+
|
111
|
+
gateway.put_json("/api/put_example.json", query_string_param: "value")
|
112
|
+
|
113
|
+
gateway.delete("/api/delete_example", id: "100")
|
114
|
+
```
|
115
|
+
|
116
|
+
## Authors
|
117
|
+
|
118
|
+
- [Emerson Macedo](https://github.com/emerleite)
|
119
|
+
- [Guilherme Garnier](https://github.com/ggarnier)
|
120
|
+
- [Daniel Martins](https://github.com/danielfm)
|
121
|
+
- [Rafael Biriba](https://github.com/rafaelbiriba)
|
122
|
+
- [Célio Latorraca](https://github.com/celiofonseca)
|
123
|
+
|
124
|
+
## Contributing
|
125
|
+
|
126
|
+
1. Fork it
|
127
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
128
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
129
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
130
|
+
5. Create new Pull Request
|
131
|
+
|
132
|
+
## License
|
133
|
+
|
134
|
+
Copyright (c) 2014 Globo.com - Webmedia. See [COPYING](https://github.com/globocom/content_gateway/blob/master/COPYING) for more details.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'content_gateway/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "content_gateway"
|
7
|
+
gem.version = ContentGateway::VERSION
|
8
|
+
gem.authors = ["Emerson Macedo", "Guilherme Garnier", "Daniel Martins", "Rafael Biriba", "Célio Latorraca"]
|
9
|
+
gem.email = ["emerleite@gmail.com", "guilherme.garnier@gmail.com", "daniel.tritone@gmail.com", "biribarj@gmail.com", "celio.la@gmail.com"]
|
10
|
+
gem.description = %q{An easy way to get external content with two cache levels. The first is a performance cache and second is the stale}
|
11
|
+
gem.summary = %q{Content Gateway}
|
12
|
+
gem.homepage = ""
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_dependency "activesupport"
|
20
|
+
gem.add_dependency "rest-client"
|
21
|
+
gem.add_dependency "json"
|
22
|
+
|
23
|
+
gem.add_development_dependency "rspec", ">= 2.3.0"
|
24
|
+
gem.add_development_dependency "simplecov", ">= 0.7.1"
|
25
|
+
gem.add_development_dependency "byebug"
|
26
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ContentGateway
|
2
|
+
class Cache
|
3
|
+
attr_reader :status
|
4
|
+
|
5
|
+
def initialize(config, url, method, params = {})
|
6
|
+
@config = config
|
7
|
+
@url = url
|
8
|
+
@method = method.to_sym
|
9
|
+
@skip_cache = params[:skip_cache] || false
|
10
|
+
end
|
11
|
+
|
12
|
+
def use?
|
13
|
+
!@skip_cache && [:get, :head].include?(@method)
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch(request, params = {})
|
17
|
+
timeout = params[:timeout] || @config.timeout
|
18
|
+
expires_in = params[:expires_in] || @config.cache_expires_in
|
19
|
+
stale_expires_in = params[:stale_expires_in] || @config.cache_stale_expires_in
|
20
|
+
|
21
|
+
begin
|
22
|
+
Timeout.timeout(timeout) do
|
23
|
+
@config.cache.fetch(@url, expires_in: expires_in) do
|
24
|
+
@status = "MISS"
|
25
|
+
response = request.execute
|
26
|
+
|
27
|
+
@config.cache.write(stale_key, response, expires_in: stale_expires_in)
|
28
|
+
response
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
rescue Timeout::Error => e
|
33
|
+
begin
|
34
|
+
serve_stale
|
35
|
+
rescue ContentGateway::StaleCacheNotAvailableError
|
36
|
+
raise ContentGateway::TimeoutError.new(@url, e, timeout)
|
37
|
+
end
|
38
|
+
|
39
|
+
rescue ContentGateway::ServerError => e
|
40
|
+
begin
|
41
|
+
serve_stale
|
42
|
+
rescue ContentGateway::StaleCacheNotAvailableError
|
43
|
+
raise e
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def serve_stale
|
49
|
+
@config.cache.read(stale_key).tap do |cached|
|
50
|
+
raise ContentGateway::StaleCacheNotAvailableError.new unless cached
|
51
|
+
@status = "STALE"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def stale_key
|
56
|
+
@stale_key ||= "stale:#{@url}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ContentGateway
|
2
|
+
class BaseError < StandardError
|
3
|
+
attr_reader :resource_url, :wrapped_exception, :status_code, :info
|
4
|
+
|
5
|
+
def initialize(resource_url, wrapped_exception = nil, status_code = nil, info = nil)
|
6
|
+
@resource_url = resource_url
|
7
|
+
@wrapped_exception = wrapped_exception
|
8
|
+
@status_code = status_code
|
9
|
+
@info = info
|
10
|
+
|
11
|
+
message = @resource_url.dup
|
12
|
+
if @wrapped_exception
|
13
|
+
message << " - #{@wrapped_exception.message}"
|
14
|
+
message << " - #{@info}" if @info
|
15
|
+
end
|
16
|
+
|
17
|
+
super(message)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class UnauthorizedError < BaseError
|
22
|
+
def initialize(resource_url, wrapped_exception = nil)
|
23
|
+
super(resource_url, wrapped_exception, 401)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Forbidden < BaseError
|
28
|
+
def initialize(resource_url, wrapped_exception = nil)
|
29
|
+
super(resource_url, wrapped_exception, 403)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ResourceNotFound < BaseError
|
34
|
+
def initialize(resource_url, wrapped_exception = nil)
|
35
|
+
super(resource_url, wrapped_exception, 404)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class TimeoutError < BaseError
|
40
|
+
def initialize(resource_url, wrapped_exception = nil, timeout = nil)
|
41
|
+
info = "TIMEOUT (max #{timeout} s)" if timeout
|
42
|
+
|
43
|
+
super(resource_url, wrapped_exception, 408, info)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ConflictError < BaseError
|
48
|
+
def initialize(resource_url, wrapped_exception = nil)
|
49
|
+
super(resource_url, wrapped_exception, 409)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ValidationError < BaseError
|
54
|
+
attr_reader :errors
|
55
|
+
|
56
|
+
def initialize(resource_url, wrapped_exception = nil)
|
57
|
+
super(resource_url, wrapped_exception, 422)
|
58
|
+
|
59
|
+
if wrapped_exception
|
60
|
+
response = wrapped_exception.response
|
61
|
+
@errors = JSON.parse(response) if response.present?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class ServerError < BaseError
|
67
|
+
def initialize(resource_url, wrapped_exception = nil, status_code = nil)
|
68
|
+
super(resource_url, wrapped_exception, status_code, "SERVER ERROR")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class ConnectionFailure < BaseError
|
73
|
+
def initialize(resource_url, wrapped_exception = nil)
|
74
|
+
super(resource_url, wrapped_exception, 500)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class StaleCacheNotAvailableError < StandardError; end
|
79
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module ContentGateway
|
2
|
+
class Gateway
|
3
|
+
def initialize(label, config, url_generator, default_params = {})
|
4
|
+
@label = label
|
5
|
+
@config = config
|
6
|
+
@url_generator = url_generator
|
7
|
+
@default_params = default_params
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(resource_path, params = {})
|
11
|
+
timeout = params.delete :timeout
|
12
|
+
expires_in = params.delete :expires_in
|
13
|
+
stale_expires_in = params.delete :stale_expires_in
|
14
|
+
skip_cache = params.delete :skip_cache
|
15
|
+
headers = (params.delete :headers) || @default_params[:headers]
|
16
|
+
|
17
|
+
url = self.generate_url(resource_path, params)
|
18
|
+
|
19
|
+
measure("GET - #{url}") do
|
20
|
+
data = { method: :get, url: url }.tap do |h|
|
21
|
+
h[:headers] = headers if headers.present?
|
22
|
+
end
|
23
|
+
send_request(data, skip_cache: skip_cache, expires_in: expires_in, stale_expires_in: stale_expires_in, timeout: timeout)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def post(resource_path, params = {})
|
28
|
+
payload = params.delete :payload
|
29
|
+
url = self.generate_url(resource_path, params)
|
30
|
+
|
31
|
+
measure("POST - #{url}") do
|
32
|
+
send_request({ method: :post, url: url, payload: payload }, params)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def put(resource_path, params = {})
|
37
|
+
payload = params.delete :payload
|
38
|
+
url = self.generate_url(resource_path, params)
|
39
|
+
|
40
|
+
measure("PUT - #{url}") do
|
41
|
+
send_request({ method: :put, url: url, payload: payload }, params)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(resource_path, params = {})
|
46
|
+
payload = params.delete :payload
|
47
|
+
url = self.generate_url(resource_path, params)
|
48
|
+
|
49
|
+
measure("DELETE - #{url}") do
|
50
|
+
send_request({ method: :delete, url: url, payload: payload }, params)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_json(resource_path, params = {})
|
55
|
+
JSON.parse get(resource_path, params)
|
56
|
+
end
|
57
|
+
|
58
|
+
def post_json(resource_path, params = {})
|
59
|
+
JSON.parse post(resource_path, params)
|
60
|
+
end
|
61
|
+
|
62
|
+
def put_json(resource_path, params = {})
|
63
|
+
JSON.parse put(resource_path, params)
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete_json(resource_path, params = {})
|
67
|
+
JSON.parse delete(resource_path, params)
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_url(resource_path, params = {})
|
71
|
+
@url_generator.generate(resource_path, params)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def send_request(request_data, params = {})
|
77
|
+
method = request_data[:method] || :get
|
78
|
+
url = request_data[:url]
|
79
|
+
headers = request_data[:headers]
|
80
|
+
payload = request_data[:payload]
|
81
|
+
|
82
|
+
@cache = ContentGateway::Cache.new(@config, url, method, params)
|
83
|
+
@request = ContentGateway::Request.new(method, url, headers, payload, @config.try(:proxy))
|
84
|
+
|
85
|
+
begin
|
86
|
+
do_request(params)
|
87
|
+
|
88
|
+
rescue ContentGateway::BaseError => e
|
89
|
+
message = "#{prefix(e.status_code)} :: #{color_message(e.resource_url)}"
|
90
|
+
message << " - #{e.info}" if e.info
|
91
|
+
logger.info message
|
92
|
+
|
93
|
+
raise e
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def do_request(params = {})
|
98
|
+
if @cache.use?
|
99
|
+
@cache.fetch(@request, timeout: params[:timeout], expires_in: params[:expires_in], stale_expires_in: params[:stale_expires_in])
|
100
|
+
else
|
101
|
+
@request.execute
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def measure(message)
|
106
|
+
result = nil
|
107
|
+
time_elapsed = Benchmark.measure { result = yield }
|
108
|
+
sufix = "finished in #{humanize_elapsed_time(time_elapsed.real)}. "
|
109
|
+
cache_log = (@cache.status || "HIT").to_s.ljust(4, " ")
|
110
|
+
log_message = "#{prefix(code(result))} :: #{cache_log} #{color_message(message)} #{sufix}"
|
111
|
+
|
112
|
+
logger.info log_message
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
def code(result)
|
117
|
+
result.respond_to?(:code) ? result.code : ""
|
118
|
+
end
|
119
|
+
|
120
|
+
def humanize_elapsed_time(time_elapsed)
|
121
|
+
time_elapsed >= 1 ? "%.3f secs" % time_elapsed : "#{(time_elapsed * 1000).to_i} ms"
|
122
|
+
end
|
123
|
+
|
124
|
+
def prefix(code = nil)
|
125
|
+
"[#{@label}] #{color_code(code)}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def color_message(message)
|
129
|
+
"\033[1;33m#{message}\033[0m"
|
130
|
+
end
|
131
|
+
|
132
|
+
def color_code(code)
|
133
|
+
color = code == 200 ? "32" : "31"
|
134
|
+
code_message = code.to_s.ljust(3, " ")
|
135
|
+
"\033[#{color}m#{code_message}\033[0m"
|
136
|
+
end
|
137
|
+
|
138
|
+
def logger
|
139
|
+
@logger || lambda do
|
140
|
+
if defined?(Rails)
|
141
|
+
Rails.logger
|
142
|
+
else
|
143
|
+
log = ::Logger.new STDOUT
|
144
|
+
log.formatter = lambda {|severity, datetime, progname, msg|
|
145
|
+
"#{datetime.strftime("%Y-%m-%d %H:%M:%S")} #{severity.upcase} #{msg}\n"
|
146
|
+
}
|
147
|
+
|
148
|
+
log
|
149
|
+
end
|
150
|
+
end.yield
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ContentGateway
|
2
|
+
class Request
|
3
|
+
def initialize(method, url, headers = {}, payload = {}, proxy = nil)
|
4
|
+
data = { method: method, url: url, proxy: proxy || :none }.tap do |h|
|
5
|
+
h[:payload] = payload if payload.present?
|
6
|
+
h[:headers] = headers if headers.present?
|
7
|
+
end
|
8
|
+
|
9
|
+
@client = RestClient::Request.new(data)
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
@client.execute
|
14
|
+
|
15
|
+
rescue RestClient::ResourceNotFound => e1
|
16
|
+
raise ContentGateway::ResourceNotFound.new url, e1
|
17
|
+
|
18
|
+
rescue RestClient::Unauthorized => e2
|
19
|
+
raise ContentGateway::UnauthorizedError.new url, e2
|
20
|
+
|
21
|
+
rescue RestClient::UnprocessableEntity => e3
|
22
|
+
raise ContentGateway::ValidationError.new url, e3
|
23
|
+
|
24
|
+
rescue RestClient::Forbidden => e4
|
25
|
+
raise ContentGateway::Forbidden.new url, e4
|
26
|
+
|
27
|
+
rescue RestClient::Conflict => e5
|
28
|
+
raise ContentGateway::ConflictError.new url, e5
|
29
|
+
|
30
|
+
rescue RestClient::Exception => e6
|
31
|
+
status_code = e6.http_code
|
32
|
+
if status_code < 500
|
33
|
+
raise e6
|
34
|
+
else
|
35
|
+
raise ContentGateway::ServerError.new url, e6, status_code
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue StandardError => e7
|
39
|
+
raise ContentGateway::ConnectionFailure.new url, e7
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def url
|
45
|
+
@client.url
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|