rester 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 891fe3d740381cb54c48e89ad69fca06e1fc06d6
4
+ data.tar.gz: db402da2918dc425d879b3719fc41e976b1de4bf
5
+ SHA512:
6
+ metadata.gz: 1a4992cf642165b17d1e77e854ac9fdaadef82ccc45337548d0a902048bb372dc9574713ff5109f5baba43e3f0c730b1c299c005932314d1ff5bc803ca6d087b
7
+ data.tar.gz: 4b9ef3355867f57f67632b24dffd84b9d39027d94544d8681058b27e01fe204418dc04b3a9b843ad48fdcf9b412460dda07707bdfbc74ed77bac057b38a347e2
data/lib/rester.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'rester/version'
2
+ require 'rack'
3
+
4
+ module Rester
5
+ require 'rester/railtie' if defined?(Rails)
6
+ autoload(:Service, 'rester/service')
7
+ autoload(:Errors, 'rester/errors')
8
+ autoload(:Client, 'rester/client')
9
+ autoload(:Utils, 'rester/utils')
10
+ autoload(:Middleware, 'rester/middleware')
11
+
12
+ class << self
13
+ def load_tasks
14
+ Dir[
15
+ File.expand_path("../../tasks", __FILE__) + '/**.rake'
16
+ ].each { |rake_file| load rake_file }
17
+ end
18
+
19
+ def connect(*args)
20
+ Client.new(*args)
21
+ end
22
+ end # Class Methods
23
+ end # Rester
@@ -0,0 +1,61 @@
1
+ require 'json'
2
+
3
+ module Rester
4
+ class Client
5
+ autoload(:Adapters, 'rester/client/adapters')
6
+
7
+ attr_reader :adapter
8
+
9
+ def initialize(*args)
10
+ case args.first
11
+ when Adapters::Adapter
12
+ self.adapter = args.first
13
+ else
14
+ self.adapter = Adapters::HttpAdapter.new(*args)
15
+ end
16
+ end
17
+
18
+ def connect(*args)
19
+ adapter.connect(*args)
20
+ end
21
+
22
+ def connected?
23
+ adapter.connected? && adapter.get(:test_connection).first == 200
24
+ end
25
+
26
+ protected
27
+
28
+ def adapter=(adapter)
29
+ @adapter = adapter
30
+ end
31
+
32
+ private
33
+
34
+ ##
35
+ # Submits the method to the adapter.
36
+ def method_missing(meth, *args, &block)
37
+ verb, meth = Utils.extract_method_verb(meth)
38
+ _process_response(meth, *adapter.request(verb, meth, *args, &block))
39
+ end
40
+
41
+ def _process_response(meth, status, body)
42
+ if status.between?(200, 299)
43
+ _parse_json(body)
44
+ elsif status == 400
45
+ raise Errors::RequestError, _parse_json(body)[:message]
46
+ elsif status == 404
47
+ raise Errors::InvalidMethodError, meth.to_s
48
+ else
49
+ raise Errors::ServerError, _parse_json(body)[:message]
50
+ end
51
+ end
52
+
53
+ def _parse_json(data)
54
+ if data.is_a?(String) && !data.empty?
55
+ JSON.parse(data, symbolize_names: true)
56
+ else
57
+ {}
58
+ end
59
+ end
60
+ end # Client
61
+ end # Rester
@@ -0,0 +1,8 @@
1
+ module Rester
2
+ class Client
3
+ module Adapters
4
+ autoload(:Adapter, 'rester/client/adapters/adapter')
5
+ autoload(:HttpAdapter, 'rester/client/adapters/http_adapter')
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,114 @@
1
+ module Rester
2
+ module Client::Adapters
3
+ class Adapter
4
+ def initialize(*args)
5
+ connect(*args) unless args.empty?
6
+ end
7
+
8
+ ##
9
+ # Returns the headers defined for this Adapter. Optionally, you may also
10
+ # define additional headers you'd like to add/override.
11
+ def headers(new_headers={})
12
+ (@headers ||= {}).merge!(new_headers)
13
+ end
14
+
15
+ ##
16
+ # Connect to a service. The specific arguments depend on the Adapter
17
+ # subclass.
18
+ def connect(*args)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ ##
23
+ # Returns whether or not the Adapter is connected to a service.
24
+ def connected?
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def request(verb, method, *args, &block)
29
+ _validate_verb(verb)
30
+ params = _validate_params(args.pop) if args.last.is_a?(Hash)
31
+ _validate_args(args)
32
+
33
+ public_send(
34
+ "#{verb}!",
35
+ "/#{method}/#{args.map(&:to_s).join('/')}",
36
+ params
37
+ )
38
+ end
39
+
40
+ def get(method, *args, &block)
41
+ request(:get, method, *args, &block)
42
+ end
43
+
44
+ def post(method, *args, &block)
45
+ request(:post, method, *args, &block)
46
+ end
47
+
48
+ def get!(path, params={})
49
+ raise NotImplementedError
50
+ end
51
+
52
+ def post!(path, params={})
53
+ raise NotImplementedError
54
+ end
55
+
56
+ protected
57
+
58
+ def headers=(h)
59
+ @headers = h
60
+ end
61
+
62
+ private
63
+
64
+ VALID_VERBS = {
65
+ get: true,
66
+ post: true
67
+ }.freeze
68
+
69
+ VALID_ARG_TYPES = {
70
+ String => true,
71
+ Symbol => true,
72
+ Fixnum => true,
73
+ Integer => true,
74
+ Float => true
75
+ }.freeze
76
+
77
+ VALID_PARAM_KEY_TYPES = {
78
+ String => true,
79
+ Symbol => true
80
+ }.freeze
81
+
82
+ VALID_PARAM_VALUE_TYPES = {
83
+ String => true,
84
+ Symbol => true,
85
+ Fixnum => true,
86
+ Integer => true,
87
+ Float => true,
88
+ DateTime => true
89
+ }.freeze
90
+
91
+ def _validate_verb(verb)
92
+ VALID_VERBS[verb] or
93
+ raise ArgumentError, "Invalid verb: #{verb.inspect}"
94
+ end
95
+
96
+ def _validate_args(args)
97
+ args.each { |arg|
98
+ VALID_ARG_TYPES[arg.class] or
99
+ raise ArgumentError, "Invalid argument type: #{arg.inspect}"
100
+ }
101
+ end
102
+
103
+ def _validate_params(params)
104
+ params.each { |key, value|
105
+ VALID_PARAM_KEY_TYPES[key.class] or
106
+ raise ArgumentError, "Invalid param key type: #{key.inspect}"
107
+
108
+ VALID_PARAM_VALUE_TYPES[value.class] or
109
+ raise ArgumentError, "Invalid param value type: #{value.inspect}"
110
+ }
111
+ end
112
+ end # Adapter
113
+ end # Client::Adapters
114
+ end # Rester
@@ -0,0 +1,31 @@
1
+ module Rester
2
+ module Client::Adapters
3
+ class HttpAdapter < Adapter
4
+ autoload(:Connection, 'rester/client/adapters/http_adapter/connection')
5
+
6
+ attr_reader :connection
7
+
8
+ def connect(*args)
9
+ nil.tap { @connection = Connection.new(*args) }
10
+ end
11
+
12
+ def connected?
13
+ !!connection
14
+ end
15
+
16
+ def get!(path, params={})
17
+ _prepare_response(connection.get(path, headers: headers, query: params))
18
+ end
19
+
20
+ def post!(path, params={})
21
+ _prepare_response(connection.post(path, headers: headers, data: params))
22
+ end
23
+
24
+ private
25
+
26
+ def _prepare_response(response)
27
+ [response.code.to_i, response.body]
28
+ end
29
+ end # HttpAdapter
30
+ end # Client::Adapters
31
+ end # Rester
@@ -0,0 +1,58 @@
1
+ require 'net/http'
2
+ require 'openssl'
3
+ require 'uri'
4
+
5
+ module Rester
6
+ module Client::Adapters
7
+ class HttpAdapter::Connection
8
+ DEFAULT_POST_HEADERS = {
9
+ "Content-Type".freeze => "application/x-www-form-urlencoded".freeze
10
+ }.freeze
11
+
12
+ attr_reader :url
13
+
14
+ def initialize(url)
15
+ @url = url.is_a?(String) ? URI(url) : url
16
+ @url.path = @url.path[0..-2] if @url.path[-1] == '/'
17
+ end
18
+
19
+ def get(path, params={})
20
+ _http.get(
21
+ _path(path, params[:query]),
22
+ _prepare_headers(params[:headers])
23
+ )
24
+ end
25
+
26
+ def post(path, params={})
27
+ headers = DEFAULT_POST_HEADERS.merge(_prepare_headers(params[:headers]))
28
+ encoded_data = URI.encode_www_form(params[:data] || {})
29
+ _http.post(_path(path), encoded_data, headers)
30
+ end
31
+
32
+ private
33
+
34
+ def _path(path, query=nil)
35
+ u = url.dup
36
+ u.path += path
37
+ u.query = URI.encode_www_form(query) if query && !query.empty?
38
+ u.request_uri
39
+ end
40
+
41
+ def _http
42
+ Net::HTTP.new(url.hostname, url.port).tap { |http|
43
+ if (http.use_ssl=url.is_a?(URI::HTTPS))
44
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
45
+
46
+ http.cert_store = OpenSSL::X509::Store.new.tap { |s|
47
+ s.set_default_paths
48
+ }
49
+ end
50
+ }
51
+ end
52
+
53
+ def _prepare_headers(headers)
54
+ Hash[(headers || {}).map { |k, v| [k.to_s, v.to_s] }]
55
+ end
56
+ end # HttpAdapter::Connection
57
+ end # Client::Adapters
58
+ end # Rester
@@ -0,0 +1,52 @@
1
+ module Rester
2
+ module Errors
3
+ class << self
4
+ ##
5
+ # Throws an error instead of raising it, which is more performant. Must
6
+ # be caught by an appropriate error handling wrapper.
7
+ def throw_error!(klass, message=nil)
8
+ error = message ? klass.new(message) : klass.new
9
+ throw :error, error
10
+ end
11
+ end # Class Methods
12
+
13
+ class Error < StandardError; end
14
+
15
+ ##
16
+ # Packet errors
17
+ class PacketError < Error; end
18
+ class InvalidEncodingError < PacketError; end
19
+
20
+ #############
21
+ # Http Errors
22
+ class HttpError < Error; end
23
+
24
+ ##
25
+ # Request Errors
26
+
27
+ # 400 Error
28
+ class RequestError < HttpError; end
29
+
30
+ # 401 Error
31
+ class AuthenticationError < RequestError; end
32
+
33
+ # 403 Error
34
+ class ForbiddenError < RequestError; end
35
+
36
+ # 404 Not Found
37
+ class NotFoundError < RequestError; end
38
+ class InvalidMethodError < NotFoundError; end
39
+
40
+ # 500 ServerError
41
+ class ServerError < RequestError; end
42
+
43
+ ##
44
+ # Server Errors
45
+
46
+ # General Errors
47
+ class InvalidValueError < Error; end
48
+
49
+ # Rester Errors
50
+ class ServiceNotDefinedError < Error; end
51
+ end # Errors
52
+ end # Rester
@@ -0,0 +1,6 @@
1
+ module Rester
2
+ module Middleware
3
+ autoload(:Base, 'rester/middleware/base')
4
+ autoload(:ErrorHandling, 'rester/middleware/error_handling')
5
+ end
6
+ end
@@ -0,0 +1,44 @@
1
+ module Rester
2
+ module Middleware
3
+ class Base
4
+ attr_reader :app
5
+ attr_reader :options
6
+
7
+ def initialize(app, options = {})
8
+ @app = app
9
+ @options = options
10
+ end
11
+
12
+ def call(env)
13
+ app.call(env)
14
+ end
15
+
16
+ def service
17
+ @__service ||= _find_service
18
+ end
19
+
20
+ private
21
+
22
+ def _find_service
23
+ service = app
24
+
25
+ loop {
26
+ break if service.is_a?(Service)
27
+
28
+ [:app, :target].each { |meth|
29
+ if service.respond_to?(meth)
30
+ service = service.public_send(meth)
31
+ break
32
+ end
33
+ }
34
+ }
35
+
36
+ service.is_a?(Service) && service
37
+ end
38
+
39
+ def _error!(klass, message=nil)
40
+ Errors.throw_error!(klass, message)
41
+ end
42
+ end # Base
43
+ end # Middleware
44
+ end # Rester
@@ -0,0 +1,49 @@
1
+ require 'json'
2
+
3
+ module Rester
4
+ module Middleware
5
+ ##
6
+ # Provides error handling for Rester. Should be mounted above all other
7
+ # Rester middleware.
8
+ class ErrorHandling < Base
9
+ def call(env)
10
+ error = catch(:error) {
11
+ begin
12
+ return super
13
+ rescue Exception => error
14
+ throw :error, error
15
+ end
16
+ }
17
+
18
+ _error_to_response(error).finish
19
+ end
20
+
21
+ private
22
+
23
+ def _error_to_response(error)
24
+ Rack::Response.new(
25
+ [JSON.dump(message: error.message)],
26
+ _error_to_http_code(error),
27
+ { "Content-Type" => "application/json"}
28
+ )
29
+ end
30
+
31
+ def _error_to_http_code(error)
32
+ case error
33
+ when Errors::NotFoundError
34
+ 404
35
+ when Errors::ForbiddenError
36
+ 403
37
+ when Errors::AuthenticationError
38
+ 401
39
+ when Errors::RequestError
40
+ 400
41
+ when Errors::ServerError
42
+ 500
43
+ else
44
+ 500
45
+ end
46
+ end
47
+ end # ErrorHandling
48
+ end # Middleware
49
+ end # Rester
@@ -0,0 +1,12 @@
1
+ require 'rester'
2
+ require 'rails'
3
+
4
+ module Rester
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :rester
7
+
8
+ rake_tasks do
9
+ Rester.load_tasks
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,137 @@
1
+ require 'uri'
2
+ require 'rack'
3
+
4
+ module Rester
5
+ class Service
6
+ ##
7
+ # The base set of middleware to use for every service.
8
+ # Middleware will be executed in the order specified.
9
+ BASE_MIDDLEWARE = [
10
+ Rack::Head,
11
+ Middleware::ErrorHandling
12
+ ].freeze
13
+
14
+ # Used to signify an empty body
15
+ class EmptyResponse; end
16
+
17
+ class << self
18
+ def instance
19
+ @instance ||= new
20
+ end
21
+
22
+ # The call method needs to call the rack_call method, which adds additional
23
+ # rack middleware.
24
+ def call(env)
25
+ instance.rack_call(env)
26
+ end
27
+
28
+ def method_missing(meth, *args, &block)
29
+ instance.public_send(meth, *args, &block)
30
+ end
31
+
32
+ ###
33
+ # Middleware DSL
34
+ ###
35
+
36
+ def use(klass, *args)
37
+ _middleware << [klass, *args]
38
+ end
39
+
40
+ def _middleware
41
+ @__middleware ||= BASE_MIDDLEWARE.dup
42
+ end
43
+ end # Class methods
44
+
45
+ attr_reader :request
46
+
47
+ def initialize(opts={})
48
+ @_opts = opts.dup
49
+ end
50
+
51
+ ##
52
+ # To be called by Rack. Wraps the app in middleware.
53
+ def rack_call(env)
54
+ _rack_app.call(env)
55
+ end
56
+
57
+ ##
58
+ # Call the service app directly.
59
+ #
60
+ # Duplicates the instance before processing the request so individual requests
61
+ # can't impact each other.
62
+ def call(env)
63
+ dup.call!(env)
64
+ end
65
+
66
+ ##
67
+ # Actually process the request.
68
+ #
69
+ # Calls methods that may modify instance variables, so the instance should
70
+ # be dup'd beforehand.
71
+ def call!(env)
72
+ @request = Rack::Request.new(env)
73
+ _process_request
74
+ end
75
+
76
+ ##
77
+ # Built in service method called by Client#connected?
78
+ def test_connection(params={})
79
+ end
80
+
81
+ private
82
+
83
+ def _rack_app
84
+ @__rack_app ||= _build_rack_app
85
+ end
86
+
87
+ def _build_rack_app
88
+ Rack::Builder.new.tap { |app|
89
+ self.class._middleware.each { |m| app.use(*m) }
90
+ app.run self
91
+ }.to_app
92
+ end
93
+
94
+ def _process_request
95
+ error!(Errors::NotFoundError) unless request.get? || request.post?
96
+ method, *args = _parse_path
97
+ params = _parse_params
98
+ method = "#{method}!" if request.post?
99
+ retval = public_send(method, *args, params)
100
+ _response(request.post? ? 201 : 200, _prepare_response(retval))
101
+ end
102
+
103
+ def _prepare_response(retval)
104
+ retval ||= {}
105
+
106
+ unless retval.is_a?(Hash)
107
+ error!(Errors::ServerError, "Invalid response: #{retval.inspect}")
108
+ end
109
+
110
+ JSON.dump(retval)
111
+ end
112
+
113
+ def _parse_path
114
+ path = request.path
115
+ uri = URI(path)
116
+ uri.path.split('/')[1..-1]
117
+ end
118
+
119
+ def _parse_params
120
+ if request.get?
121
+ request.GET
122
+ elsif request.post?
123
+ request.POST
124
+ end
125
+ end
126
+
127
+ def _response(status, body=EmptyResponse, headers={})
128
+ body = body == EmptyResponse ? [] : [body]
129
+ headers = headers.merge("Content-Type" => "application/json")
130
+ Rack::Response.new(body, status, headers).finish
131
+ end
132
+
133
+ def _error!(klass, message=nil)
134
+ Errors.throw_error!(klass, message)
135
+ end
136
+ end # Service
137
+ end # Rester
@@ -0,0 +1,49 @@
1
+ require 'date'
2
+
3
+ module Rester
4
+ module Utils
5
+ class << self
6
+ ##
7
+ # Determines the HTTP method/verb based on the method name.
8
+ # Defaults to GET but if the method ends with "!" it uses POST.
9
+ def extract_method_verb(meth)
10
+ meth = meth.to_s
11
+
12
+ if meth[-1] == '!'
13
+ [:post, meth[0..-2]]
14
+ else
15
+ [:get, meth]
16
+ end
17
+ end
18
+
19
+ def walk(object, context=nil, &block)
20
+ case object
21
+ when Hash
22
+ Hash[
23
+ object.map { |key, val|
24
+ [walk(key, :hash_key, &block), walk(val, :hash_value, &block)]
25
+ }
26
+ ]
27
+ when Array
28
+ object.map { |obj| walk(obj, :array_elem, &block) }
29
+ when Range
30
+ Range.new(
31
+ walk(object.begin, :range_begin, &block),
32
+ walk(object.end, :range_end, &block),
33
+ object.exclude_end?
34
+ )
35
+ else
36
+ yield object, context
37
+ end
38
+ end
39
+
40
+ def symbolize_keys(hash)
41
+ hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
42
+ end
43
+
44
+ def classify(str)
45
+ str.to_s.split("_").map(&:capitalize).join
46
+ end
47
+ end # Class methods
48
+ end # Utils
49
+ end # Rester
@@ -0,0 +1,3 @@
1
+ module Rester
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rester
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Robert Honer
8
+ - Kayvon Ghaffari
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-09-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.5'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.5.2
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '1.5'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.5.2
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ type: :development
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ - !ruby/object:Gem::Dependency
49
+ name: rspec-rails
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: sqlite3
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ - !ruby/object:Gem::Dependency
77
+ name: rails
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 4.0.0
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 4.0.0
90
+ description: A framework for creating simple RESTful interfaces between services.
91
+ email:
92
+ - robert@ribbonpayments.com
93
+ - kayvon@ribbon.co
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files: []
97
+ files:
98
+ - lib/rester.rb
99
+ - lib/rester/client.rb
100
+ - lib/rester/client/adapters.rb
101
+ - lib/rester/client/adapters/adapter.rb
102
+ - lib/rester/client/adapters/http_adapter.rb
103
+ - lib/rester/client/adapters/http_adapter/connection.rb
104
+ - lib/rester/errors.rb
105
+ - lib/rester/middleware.rb
106
+ - lib/rester/middleware/base.rb
107
+ - lib/rester/middleware/error_handling.rb
108
+ - lib/rester/railtie.rb
109
+ - lib/rester/service.rb
110
+ - lib/rester/utils.rb
111
+ - lib/rester/version.rb
112
+ homepage: http://github.com/ribbon/rester
113
+ licenses:
114
+ - BSD
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 2.4.8
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: A framework for creating simple RESTful interfaces between services.
136
+ test_files: []