we-call 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +25 -0
- data/Appraisals +31 -0
- data/CHANGELOG +38 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +189 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/we-call.rb +1 -0
- data/lib/we/call.rb +22 -0
- data/lib/we/call/annotations.rb +34 -0
- data/lib/we/call/configuration.rb +16 -0
- data/lib/we/call/connection.rb +115 -0
- data/lib/we/call/deprecated.rb +40 -0
- data/lib/we/call/middleware.rb +8 -0
- data/lib/we/call/middleware/client.rb +9 -0
- data/lib/we/call/middleware/client/detect_deprecations.rb +58 -0
- data/lib/we/call/middleware/server.rb +9 -0
- data/lib/we/call/middleware/server/log_user_agent.rb +39 -0
- data/lib/we/call/version.rb +5 -0
- data/we-call.gemspec +39 -0
- metadata +274 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2b5f8847896e17a2fec34ca12c0f3de5ac7a3462
|
4
|
+
data.tar.gz: 40f62ebf46c1096cdbb427e9b59314c5cc9b108b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5b3df1717cb053439ce90d57ce8087014959a47a65ac7b8ea42edf0dc15cbd672f60d0487076a0d3f5c2f30cbfe17711fc53d9e23946ac7a911f0886ec5edb9c
|
7
|
+
data.tar.gz: 28867ed3ab8fc0c9583ce732b288d81fcc50f9e1c802190da690f3ac9727b7074f18e337f3bb890c9cf82736da1e6183b8c45d3c76650e58703c3b42caf9a312
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
language: ruby
|
2
|
+
before_install:
|
3
|
+
- gem install bundler
|
4
|
+
before_script:
|
5
|
+
- appraisal install
|
6
|
+
script:
|
7
|
+
- bundle exec appraisal rspec
|
8
|
+
gemfiles:
|
9
|
+
- faraday_0.9.gemfile
|
10
|
+
- faraday_0.10.gemfile
|
11
|
+
- faraday_0.11.gemfile
|
12
|
+
- faraday_0.12.gemfile
|
13
|
+
- faraday_0.13.gemfile
|
14
|
+
- rails_4.2.gemfile
|
15
|
+
- rails_5.0.gemfile
|
16
|
+
- rails_5.1.gemfile
|
17
|
+
rvm:
|
18
|
+
- 2.2.7
|
19
|
+
- 2.3.4
|
20
|
+
- 2.4.1
|
21
|
+
- ruby-head
|
22
|
+
matrix:
|
23
|
+
allow_failures:
|
24
|
+
- rvm: ruby-head
|
25
|
+
fast_finish: true
|
data/Appraisals
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
appraise "faraday-0.13" do
|
2
|
+
gem "faraday", "~> 0.13.0"
|
3
|
+
end
|
4
|
+
|
5
|
+
appraise "faraday-0.12" do
|
6
|
+
gem "faraday", "~> 0.12.0"
|
7
|
+
end
|
8
|
+
|
9
|
+
appraise "faraday-0.11" do
|
10
|
+
gem "faraday", "~> 0.11.0"
|
11
|
+
end
|
12
|
+
|
13
|
+
appraise "faraday-0.10" do
|
14
|
+
gem "faraday", "~> 0.10.0"
|
15
|
+
end
|
16
|
+
|
17
|
+
appraise "faraday-0.9" do
|
18
|
+
gem "faraday", "~> 0.9.0"
|
19
|
+
end
|
20
|
+
|
21
|
+
appraise "rails-5.1" do
|
22
|
+
gem "rails", "~> 5.1.0"
|
23
|
+
end
|
24
|
+
|
25
|
+
appraise "rails-5.0" do
|
26
|
+
gem "rails", "~> 5.0.0"
|
27
|
+
end
|
28
|
+
|
29
|
+
appraise "rails-4.2" do
|
30
|
+
gem "rails", "~> 4.2.0"
|
31
|
+
end
|
data/CHANGELOG
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
7
|
+
|
8
|
+
## Unreleased
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Configurable `We::Call.configure` which accepts a config block
|
12
|
+
- Config option `config.app_name` to avoid providing `app:` in every connection initializer
|
13
|
+
- Added the concept of Annotations. Simply `extend We::Call::Annotations` in a base controller to get cool stuff
|
14
|
+
- First annotation: `We::Call::Deprecated` added to mark controller methods as deprecated
|
15
|
+
- Added `We::Call::Middleware::Client::DetectDeprecations` that automatically registers as a faraday response middleware to report deprecated endpoints
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- Defaults to setting `X-App-Name` instead of `X-WeWork-App` (override with config.app_name_header)
|
19
|
+
- Defaults to setting `X-App-Env` instead of `X-WeWork-Env` (override with config.app_env_header)
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
- Switched from manually requiring to using module autoload to reduce memory footprint
|
23
|
+
|
24
|
+
## v0.4.2
|
25
|
+
|
26
|
+
### Fixed
|
27
|
+
- Manually setting `conn.adapter` would result in double adapters (two requests made!)
|
28
|
+
|
29
|
+
## v0.4.1
|
30
|
+
|
31
|
+
### Fixed
|
32
|
+
- Improved support for Faraday 0.8 - 0.9.
|
33
|
+
|
34
|
+
## v0.4.0
|
35
|
+
|
36
|
+
### Added
|
37
|
+
- `We::Call::Connection.new` requires `timeout: 1` where 1 is seconds.
|
38
|
+
- `We::Call::Connection.new` accepts `open_timeout: 1` where 1 is seconds.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 WeWork Projects
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# We::Call
|
2
|
+
|
3
|
+
[![Build Status][travis-image]][travis-url]
|
4
|
+
[![Coverage Status][coveralls-image]][coveralls-url]
|
5
|
+
[![MIT License][license-image]][license-url]
|
6
|
+
|
7
|
+
![Call me Maybe](https://cloud.githubusercontent.com/assets/67381/25590846/0c3145ea-2e80-11e7-9166-76448e0134a8.jpeg)
|
8
|
+
|
9
|
+
Requires metadata and offers client/server middleware for making HTTP requests, tracking calls, raising deprecations, supporting trace IDs, throttling, etc.
|
10
|
+
|
11
|
+
## Goals
|
12
|
+
|
13
|
+
- Work just like Faraday out of the box
|
14
|
+
- Remove some of the guesswork that comes with HTTP service orientated architecures
|
15
|
+
- Provide sane defaults whenever possible, but ask for more information if required
|
16
|
+
- Facilitate [HTTP Evolution](https://www.mnot.net/blog/2012/12/04/api-evolution.html)
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'we-call'
|
23
|
+
```
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# config/initializers/we-call.rb
|
27
|
+
|
28
|
+
We::Call.configure do |config|
|
29
|
+
config.app_name = 'service-a' # default nil (Connection class falls back to APP_NAME or Rails name)
|
30
|
+
config.app_env = 'staging' # default nil (Connection class back to RACK_ENV || RAILS_ENV)
|
31
|
+
config.detect_deprecations = false # default true
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
As this is a Faraday wrapper, the only thing that will change from normal Faraday usage is initialization.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
connection = We::Call::Connection.new(host: 'https://some-service.example.com/', timeout: 5)
|
39
|
+
|
40
|
+
# or with a Faraday connection block
|
41
|
+
connection = We::Call::Connection.new(host: 'https://some-service.example.com/', timeout: 5) do |conn|
|
42
|
+
conn.token_auth('abc123token')
|
43
|
+
conn.headers['Foo'] = 'bar'
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
See more connection block options in the [Faraday documentation](https://github.com/lostisland/faraday).
|
48
|
+
|
49
|
+
### Provide an App
|
50
|
+
|
51
|
+
An application should provide its own name in the user agent when calling other services. This is important in case this app busts a local cache, causing it to stampeding herd other service(s).
|
52
|
+
|
53
|
+
Other services need to know which server is causing the problem, so no connections are allowed through `We::Call` without an app being set.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# Provided at config
|
57
|
+
connection = We::Call.configure do |config|
|
58
|
+
config.app_name = 'Service A'
|
59
|
+
end
|
60
|
+
|
61
|
+
# Provided at initialization
|
62
|
+
connection = We::Call::Connection.new(host: 'https://service-b.example.com/', app: 'Service A', timeout: 5)
|
63
|
+
```
|
64
|
+
|
65
|
+
_Ofc services could lie about this, so do not use App Name for any sort of security. For that you need to use tokens assigned to applications. This is essentially just forcing a user agent._
|
66
|
+
|
67
|
+
### Provide an Env
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# Provided at config
|
71
|
+
connection = We::Call.configure do |config|
|
72
|
+
config.app_env = 'staging'
|
73
|
+
end
|
74
|
+
|
75
|
+
# Provided at initialization
|
76
|
+
connection = We::Call::Connection.new(host: 'https://service-b.example.com/', env: 'staging', timeout: 5)
|
77
|
+
```
|
78
|
+
|
79
|
+
Not only is knowing the app name important, but knowing the env is necessary too. Sometimes people configure stuff wrong, and Service A (staging) will hit Service B (production) 😨.
|
80
|
+
|
81
|
+
If you are using Rack or Rails, you should not need to do this, as it'll use RACK_ENV or RAILS_ENV by default.
|
82
|
+
|
83
|
+
### Timeouts
|
84
|
+
|
85
|
+
By default Faraday will let HTTP calls go on forever. In reality this is often 30 seconds for e.g: a Heroku app. Asking developers to make a choice about how long they're willing to wait on this call gives them a chance to consider an acceptable timeout.
|
86
|
+
|
87
|
+
The lower this number can be the better, as it reduces time web threads spend waiting for calls that are unlikely to respond anyway.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
# Provided at initialization
|
91
|
+
connection = We::Call::Connection.new(host: 'https://service-b.example.com/', timeout: 5)
|
92
|
+
```
|
93
|
+
|
94
|
+
Timeouts can only be provided at initialization of a connection, as they should be different for each service. This is down to the sad reality that some internal services are more performant than others, and various third-parties will have different SLAs.
|
95
|
+
|
96
|
+
As well as `timeout: num_seconds` which can set the entire open/read (essentially the total response time of the server), another optional argument exists for `open_timeout: numseconds`. This is how long We::Call should spend waiting for a vague sign of life from the server, which by default is 2.
|
97
|
+
|
98
|
+
### Deprecations
|
99
|
+
|
100
|
+
We::Call helps with a bunch of things, such as the logic to go on client and server side to handle deprecations, both logging calls made to this servies deprecated endpoints, and alerting services when they make calls to deprecated endpoints.
|
101
|
+
|
102
|
+
Currently this is done using a simple annotation.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
# app/controllers/api_controller.rb
|
106
|
+
|
107
|
+
extend We::Call::Annotations
|
108
|
+
```
|
109
|
+
|
110
|
+
Then a controller can be annotated as such:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# app/controllers/foo_controller.rb
|
114
|
+
|
115
|
+
class FooController < ApiController
|
116
|
+
+We::Call::Deprecated.new(date: '2018-01-07 00:00:00 EDT')
|
117
|
+
def show
|
118
|
+
# ...
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
It's as simple as that. This annotation will inject a [Sunset header](https://tools.ietf.org/html/draft-wilde-sunset-header-03) and everyone will know its being deprecated.
|
124
|
+
|
125
|
+
## Middleware
|
126
|
+
|
127
|
+
### Client
|
128
|
+
|
129
|
+
**DetectDeprecations**
|
130
|
+
|
131
|
+
Automatically enabled, this Faraday middleware will watch for the [Sunset header](https://tools.ietf.org/html/draft-wilde-sunset-header-03) and send warning to `ActiveSupport::Deprecation` if enabled, or to whatever is in `ENV['rake.logger']`.
|
132
|
+
|
133
|
+
### Server
|
134
|
+
|
135
|
+
**LogUserAgent**
|
136
|
+
|
137
|
+
_(Optional)_ Log the User Agent, which might just be browser information (merely kinda handy), or could be an app name, like the one `We::Call::Connection` asks you for.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
config.middleware.insert_after Rails::Rack::Logger, We::Call::Middleware::Server::LogUserAgent
|
141
|
+
```
|
142
|
+
|
143
|
+
Easy! Check your logs for `user_agent=service-name; app_name=service-name;` The `app_name` will only show up if this was called by `We::Call::Connection` (as this is the only thing setting the `X-App-Name` header.)
|
144
|
+
|
145
|
+
## Requirements
|
146
|
+
|
147
|
+
- **Ruby:** v2.2 - v2.4
|
148
|
+
- **Rails:** v4.2 - v5.1
|
149
|
+
- **Faraday:** v0.8 - v0.13
|
150
|
+
|
151
|
+
_For now this gem requires Rails 4.2+ due to some ActiveController functionality we are taking advantage of. Future work will include making this purely rack based._
|
152
|
+
|
153
|
+
## TODO
|
154
|
+
|
155
|
+
- [ ] Support adding href to Deprecate to make a `Link` with rel=sunset as per Sunset RFC draft 03
|
156
|
+
- [ ] Remove Rails as a dependency (soft requirement on `ActiveSupport::Deprecated` is fine)
|
157
|
+
- [ ] Split DetectDeprecations into standalone `faraday-sunset` gem
|
158
|
+
- [ ] Pass Trace IDs along
|
159
|
+
- [ ] Work on sane defaults for retries and error raising
|
160
|
+
|
161
|
+
## Testing
|
162
|
+
|
163
|
+
To run tests and modify locally, you'll want to `bundle install` in this directory.
|
164
|
+
|
165
|
+
```
|
166
|
+
bundle exec rspec
|
167
|
+
```
|
168
|
+
|
169
|
+
## Development
|
170
|
+
|
171
|
+
If you want to test this gem within an application, update your Gemfile to have something like this: `gem 'we-call', github: 'wework/we-call', branch: 'BRANCHNAME'` and set your local config: `bundle config --local local.we-call path/to/we-call`
|
172
|
+
|
173
|
+
Simply revert the Gemfile change (updating the version as necessary!) and remove the config with `bundle config --delete local.we-call`.
|
174
|
+
|
175
|
+
References: [Blog Post](https://rossta.net/blog/how-to-specify-local-ruby-gems-in-your-gemfile.html) and [Bundle Documentation](https://bundler.io/v1.2/git.html#local)
|
176
|
+
|
177
|
+
## Contributing
|
178
|
+
|
179
|
+
Bug reports and pull requests are welcome on GitHub at [wework/we-call](https://github.com/wework/we-call). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
180
|
+
|
181
|
+
|
182
|
+
[coveralls-image]:https://coveralls.io/repos/github/wework/we-call/badge.svg?branch=master
|
183
|
+
[coveralls-url]:https://coveralls.io/github/wework/we-call?branch=master
|
184
|
+
|
185
|
+
[travis-url]:https://travis-ci.org/wework/we-js-logger
|
186
|
+
[travis-image]: https://travis-ci.org/wework/we-js-logger.svg?branch=master
|
187
|
+
|
188
|
+
[license-url]: LICENSE
|
189
|
+
[license-image]: http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "we/call"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/we-call.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'we/call'
|
data/lib/we/call.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module We
|
2
|
+
module Call
|
3
|
+
autoload :Annotations, "we/call/annotations"
|
4
|
+
autoload :Connection, "we/call/connection"
|
5
|
+
autoload :Configuration, "we/call/configuration"
|
6
|
+
autoload :Deprecated, "we/call/deprecated"
|
7
|
+
autoload :Middleware, "we/call/middleware"
|
8
|
+
autoload :VERSION, "we/call/version"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.configure
|
19
|
+
yield(configuration)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module We
|
2
|
+
module Call
|
3
|
+
module Annotations
|
4
|
+
# Enable annotations
|
5
|
+
include RubyDecorators
|
6
|
+
|
7
|
+
def self.extended(base)
|
8
|
+
base.class_eval do
|
9
|
+
# TODO Maybe this after_action could be a really simple call in base controllers
|
10
|
+
# Having it here obviously ties We::Call to Rails, and is probably more than the annotations
|
11
|
+
# class should be doing
|
12
|
+
after_action do |controller|
|
13
|
+
|
14
|
+
# TODO Maybe controller.class and params action could be passed to something in We::Call::Deprecated
|
15
|
+
klass = controller.class
|
16
|
+
method = params['action']
|
17
|
+
deprecation = We::Call::Deprecated.get("#{klass}##{method}")
|
18
|
+
|
19
|
+
if deprecation.present?
|
20
|
+
# Shove a deprecation warning into the console or wherever it goes
|
21
|
+
if defined? ActiveSupport
|
22
|
+
ActiveSupport::Deprecation.warn("#{klass}##{method} is deprecated for removal on #{deprecation[:date].iso8601}")
|
23
|
+
end
|
24
|
+
|
25
|
+
# Shove a Sunset header into HTTP Response for clients to sniff on
|
26
|
+
# https://tools.ietf.org/html/draft-wilde-sunset-header-03
|
27
|
+
response.headers['Sunset'] = deprecation[:date].httpdate
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
|
4
|
+
module We
|
5
|
+
module Call
|
6
|
+
class Configuration
|
7
|
+
attr_accessor :app_env, :app_env_header, :app_name, :app_name_header, :detect_deprecations
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@app_env_header = 'X-App-Env'
|
11
|
+
@app_name_header = 'X-App-Name'
|
12
|
+
@detect_deprecations = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
|
4
|
+
module We
|
5
|
+
module Call
|
6
|
+
module Connection
|
7
|
+
Faraday::Response.register_middleware detect_deprecations: We::Call::Middleware::Client::DetectDeprecations
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
OPEN_TIMEOUT = 2
|
12
|
+
|
13
|
+
class MissingApp < ArgumentError; end
|
14
|
+
class MissingEnv < ArgumentError; end
|
15
|
+
class MissingTimeout < ArgumentError; end
|
16
|
+
class MissingOpenTimeout < ArgumentError; end
|
17
|
+
|
18
|
+
parent_builder_class = defined?(Faraday::RackBuilder) ? Faraday::RackBuilder : Faraday::Builder
|
19
|
+
|
20
|
+
QueryableBuilder = Class.new(parent_builder_class) do
|
21
|
+
def adapter?
|
22
|
+
@has_adapter || false
|
23
|
+
end
|
24
|
+
|
25
|
+
def adapter(key, *args, &block)
|
26
|
+
super
|
27
|
+
@has_adapter = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param [Object] host
|
32
|
+
# @param [Integer] timeout
|
33
|
+
# @param [Integer] open_timeout
|
34
|
+
# @param [String] app
|
35
|
+
# @param [String] env
|
36
|
+
# @yieldparam [Faraday::Connection] Faraday connection object is yielded to a block
|
37
|
+
def new(host:, timeout: nil, open_timeout: OPEN_TIMEOUT, app: guess_app, env: guess_env, &block)
|
38
|
+
@host = host
|
39
|
+
@app = app or raise_missing_app!
|
40
|
+
@env = env or raise_missing_env!
|
41
|
+
@timeout = timeout or raise_missing_timeout!
|
42
|
+
@open_timeout = open_timeout or raise_missing_open_timeout!
|
43
|
+
create(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :app, :env, :host, :timeout, :open_timeout
|
49
|
+
|
50
|
+
# @return [Faraday::Connection] Preconfigured Faraday Connection object, for hitting get, post, etc.
|
51
|
+
def create
|
52
|
+
builder = QueryableBuilder.new(&Proc.new { |_| })
|
53
|
+
|
54
|
+
::Faraday.new(url: host, builder: builder) do |faraday|
|
55
|
+
faraday.headers['User-Agent'] = app
|
56
|
+
faraday.headers[config.app_name_header] = app
|
57
|
+
faraday.headers[config.app_env_header] = env
|
58
|
+
faraday.options[:timeout] = timeout
|
59
|
+
faraday.options[:open_timeout] = open_timeout
|
60
|
+
|
61
|
+
if config.detect_deprecations
|
62
|
+
if defined? ActiveSupport::Deprecation
|
63
|
+
faraday.response :detect_deprecations, active_support: true
|
64
|
+
else
|
65
|
+
faraday.response :detect_deprecations, logger: ENV['rack.logger']
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
yield faraday if block_given?
|
70
|
+
|
71
|
+
faraday.adapter Faraday.default_adapter unless faraday.builder.adapter?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def config
|
76
|
+
We::Call.configuration
|
77
|
+
end
|
78
|
+
|
79
|
+
def raise_missing_app!
|
80
|
+
raise MissingApp, 'app must be set, e.g: pokedex'
|
81
|
+
end
|
82
|
+
|
83
|
+
def raise_missing_env!
|
84
|
+
raise MissingEnv, 'env must be set, e.g: staging'
|
85
|
+
end
|
86
|
+
|
87
|
+
def raise_missing_timeout!
|
88
|
+
raise MissingTimeout, 'timeout must be set, maybe 5 (seconds) would be a good value. This is the open & read timeout, a.k.a max response time.'
|
89
|
+
end
|
90
|
+
|
91
|
+
def raise_missing_open_timeout!
|
92
|
+
raise MissingOpenTimeout, 'open_timeout must be set, and defaults to 1 second. This is the time until a connection is established with another server, and after 1 sec it\'s probably not there.'
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [String] Environment (usually 'development', 'staging', 'production', etc.)
|
96
|
+
def guess_env
|
97
|
+
return config.app_env if config.app_env
|
98
|
+
ENV['RAILS_ENV'] || ENV['RACK_ENV']
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [String] Check for config.app_name, or detect name from Rails application
|
102
|
+
def guess_app
|
103
|
+
return config.app_name if config.app_name
|
104
|
+
return ENV['APP_NAME'] if ENV['APP_NAME']
|
105
|
+
rails_app_name
|
106
|
+
end
|
107
|
+
|
108
|
+
def rails_app_name
|
109
|
+
if (defined? ::Rails) && !::Rails.application.nil?
|
110
|
+
::Rails.application.class.parent_name.underscore.dasherize
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "ruby_decorators"
|
2
|
+
|
3
|
+
module We
|
4
|
+
module Call
|
5
|
+
class Deprecated < RubyDecorator
|
6
|
+
@@methods = {}
|
7
|
+
|
8
|
+
def initialize(date:)
|
9
|
+
@date = date
|
10
|
+
end
|
11
|
+
|
12
|
+
def set(method, value)
|
13
|
+
@@methods[method] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.get(method)
|
17
|
+
@@methods[method]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.methods
|
21
|
+
@@methods
|
22
|
+
end
|
23
|
+
|
24
|
+
# Called when annotation is used
|
25
|
+
def call(this, *args, &blk)
|
26
|
+
set("#{this.owner}##{this.name}", date: normalize_datetime(@date))
|
27
|
+
this.call(*args, &blk)
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def normalize_datetime(datetime)
|
33
|
+
datetime = DateTime.parse(datetime) if datetime.is_a? String
|
34
|
+
datetime = datetime.to_datetime if datetime.respond_to? :to_datetime
|
35
|
+
return datetime if datetime.respond_to? :httpdate
|
36
|
+
raise TypeError, 'The date should be a Date, DateTime, Time or string containing a valid date and time'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# This class is going to be split into its own faraday-sunset soon, but I want to get the functionality nailed first
|
2
|
+
module We
|
3
|
+
module Call
|
4
|
+
module Middleware
|
5
|
+
module Client
|
6
|
+
class DetectDeprecations < Faraday::Middleware
|
7
|
+
class NoOutputForWarning < StandardError; end
|
8
|
+
|
9
|
+
# Initialize the middleware
|
10
|
+
#
|
11
|
+
# @param [Type] app describe app
|
12
|
+
# @param [Hash] options = {}
|
13
|
+
# @return void
|
14
|
+
def initialize(app, options = {})
|
15
|
+
super(app)
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Faraday::Env] no idea what this does
|
20
|
+
# @return [Faraday::Response] response from the middleware
|
21
|
+
def call(env)
|
22
|
+
@app.call(env).on_complete do |response_env|
|
23
|
+
datetime = sunset_header(response_env.response_headers)
|
24
|
+
report_deprecated_usage(env, datetime) unless datetime.nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
# Check to see if there is a Sunset header, which contains deprecation date
|
31
|
+
#
|
32
|
+
# @param [Faraday::Response] response object with headers and whatnot
|
33
|
+
# @return [DateTime|nil] date time object of the expected deprecation date
|
34
|
+
def sunset_header(headers)
|
35
|
+
return if headers[:sunset].nil?
|
36
|
+
DateTime.parse(headers[:sunset])
|
37
|
+
end
|
38
|
+
|
39
|
+
def report_deprecated_usage(env, datetime)
|
40
|
+
if datetime > DateTime.now
|
41
|
+
warning = "Endpoint #{env.url} is deprecated for removal on #{datetime.iso8601}"
|
42
|
+
else
|
43
|
+
warning = "Endpoint #{env.url} was deprecated for removal on #{datetime.iso8601} and could be removed AT ANY TIME"
|
44
|
+
end
|
45
|
+
|
46
|
+
if @options[:active_support]
|
47
|
+
ActiveSupport::Deprecation.warn(warning)
|
48
|
+
elsif @options[:logger] && @options[:logger].respond_to?(:warn)
|
49
|
+
@options[:logger].warn(warning)
|
50
|
+
else
|
51
|
+
raise NoOutputForWarning, "Pass active_support: true or logger: SomeLoggerWithWriteMethod when registering middleware"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module We
|
2
|
+
module Call
|
3
|
+
module Middleware
|
4
|
+
module Server
|
5
|
+
class LogUserAgent
|
6
|
+
def initialize app
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
line = "user_agent=#{env['HTTP_USER_AGENT']};"
|
12
|
+
line += " app_name=#{env[incoming_app_name_header]};" if env[incoming_app_name_header]
|
13
|
+
line += " app_env=#{env[incoming_app_env_header]};" if env[incoming_app_env_header]
|
14
|
+
output(line)
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def output(line)
|
21
|
+
puts line
|
22
|
+
end
|
23
|
+
|
24
|
+
def config
|
25
|
+
We::Call.configuration
|
26
|
+
end
|
27
|
+
|
28
|
+
def incoming_app_env_header
|
29
|
+
@incoming_app_env_header ||= "HTTP_#{config.app_env_header.upcase.gsub!(/-/, '_')}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def incoming_app_name_header
|
33
|
+
@incoming_app_name_header ||= "HTTP_#{config.app_name_header.upcase.gsub!(/-/, '_')}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/we-call.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'we/call/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "we-call"
|
8
|
+
spec.version = We::Call::VERSION
|
9
|
+
spec.authors = ["WeWork Engineering"]
|
10
|
+
spec.email = ["engineering@wework.com"]
|
11
|
+
|
12
|
+
spec.summary = "Making healthy happy HTTP calls"
|
13
|
+
spec.description = "Handles conventions of making calls to other services, with required metadata for tracking calls between services, deprecations of endpoints, trace IDs, throttling, etc."
|
14
|
+
spec.homepage = "https://github.com/wework/we-call-gem"
|
15
|
+
spec.licenses = ['MIT']
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(spec)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "bin"
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
spec.metadata["yard.run"] = "yri"
|
23
|
+
|
24
|
+
spec.add_dependency "faraday", ">= 0.9.0", "< 0.14"
|
25
|
+
spec.add_dependency "faraday_middleware", '~> 0'
|
26
|
+
spec.add_dependency "ruby_decorators", '~> 0.0'
|
27
|
+
spec.add_dependency "rails", ">= 4.2"
|
28
|
+
|
29
|
+
spec.add_development_dependency "appraisal", "~> 2"
|
30
|
+
spec.add_development_dependency "coveralls", '~> 0.7'
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
32
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
33
|
+
spec.add_development_dependency "rspec", "~> 3.5"
|
34
|
+
spec.add_development_dependency "simplecov", '~> 0.15'
|
35
|
+
spec.add_development_dependency "hashie", "~> 3.5"
|
36
|
+
spec.add_development_dependency "pry"
|
37
|
+
spec.add_development_dependency "yard"
|
38
|
+
spec.add_development_dependency "vcr", '~> 3'
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: we-call
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- WeWork Engineering
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0.14'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.9.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0.14'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: faraday_middleware
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: ruby_decorators
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rails
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '4.2'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '4.2'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: appraisal
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '2'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: coveralls
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0.7'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.7'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: bundler
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.14'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '1.14'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rake
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '12.0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '12.0'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: rspec
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '3.5'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '3.5'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: simplecov
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0.15'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0.15'
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: hashie
|
161
|
+
requirement: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '3.5'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '3.5'
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: pry
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
type: :development
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
- !ruby/object:Gem::Dependency
|
188
|
+
name: yard
|
189
|
+
requirement: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
type: :development
|
195
|
+
prerelease: false
|
196
|
+
version_requirements: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
201
|
+
- !ruby/object:Gem::Dependency
|
202
|
+
name: vcr
|
203
|
+
requirement: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - "~>"
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: '3'
|
208
|
+
type: :development
|
209
|
+
prerelease: false
|
210
|
+
version_requirements: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - "~>"
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: '3'
|
215
|
+
description: Handles conventions of making calls to other services, with required
|
216
|
+
metadata for tracking calls between services, deprecations of endpoints, trace IDs,
|
217
|
+
throttling, etc.
|
218
|
+
email:
|
219
|
+
- engineering@wework.com
|
220
|
+
executables: []
|
221
|
+
extensions: []
|
222
|
+
extra_rdoc_files: []
|
223
|
+
files:
|
224
|
+
- ".coveralls.yml"
|
225
|
+
- ".gitignore"
|
226
|
+
- ".rspec"
|
227
|
+
- ".travis.yml"
|
228
|
+
- Appraisals
|
229
|
+
- CHANGELOG
|
230
|
+
- Gemfile
|
231
|
+
- LICENSE
|
232
|
+
- README.md
|
233
|
+
- Rakefile
|
234
|
+
- bin/console
|
235
|
+
- bin/setup
|
236
|
+
- lib/we-call.rb
|
237
|
+
- lib/we/call.rb
|
238
|
+
- lib/we/call/annotations.rb
|
239
|
+
- lib/we/call/configuration.rb
|
240
|
+
- lib/we/call/connection.rb
|
241
|
+
- lib/we/call/deprecated.rb
|
242
|
+
- lib/we/call/middleware.rb
|
243
|
+
- lib/we/call/middleware/client.rb
|
244
|
+
- lib/we/call/middleware/client/detect_deprecations.rb
|
245
|
+
- lib/we/call/middleware/server.rb
|
246
|
+
- lib/we/call/middleware/server/log_user_agent.rb
|
247
|
+
- lib/we/call/version.rb
|
248
|
+
- we-call.gemspec
|
249
|
+
homepage: https://github.com/wework/we-call-gem
|
250
|
+
licenses:
|
251
|
+
- MIT
|
252
|
+
metadata:
|
253
|
+
yard.run: yri
|
254
|
+
post_install_message:
|
255
|
+
rdoc_options: []
|
256
|
+
require_paths:
|
257
|
+
- lib
|
258
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
259
|
+
requirements:
|
260
|
+
- - ">="
|
261
|
+
- !ruby/object:Gem::Version
|
262
|
+
version: '0'
|
263
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
264
|
+
requirements:
|
265
|
+
- - ">="
|
266
|
+
- !ruby/object:Gem::Version
|
267
|
+
version: '0'
|
268
|
+
requirements: []
|
269
|
+
rubyforge_project:
|
270
|
+
rubygems_version: 2.6.8
|
271
|
+
signing_key:
|
272
|
+
specification_version: 4
|
273
|
+
summary: Making healthy happy HTTP calls
|
274
|
+
test_files: []
|