Skip to content

Commit 0022804

Browse files
authored
Lazy lookup and lazy object (#2002)
* mime_types middleware cache. * Replace %w by actual Supporter methods CONST * Replace %w by Supported methods CONST * Fixed [] leak. Its now generating empty array when needed. * Introducing lazy generated lookup tables (cache) and lazy_object. * Using Lazy_Object for request headers and Lazy lookup table when generating string. It reduced string allocations and lazy_object add a significant performance * Added CHANGELOG.md entry. * Change - to .freeze * keys.include? replaced by key?(name)
1 parent 53d826f commit 0022804

17 files changed

+176
-65
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#### Features
44

55
* Your contribution here.
6+
* [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx).
67

78
#### Fixes
89

lib/grape/api/instance.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def call!(env)
7474
# (see #cascade?)
7575
def cascade(value = nil)
7676
if value.nil?
77-
inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
77+
inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
7878
else
7979
namespace_inheritable(:cascade, value)
8080
end
@@ -178,7 +178,7 @@ def call(env)
178178
# errors from reaching upstream. This is effectivelly done by unsetting
179179
# X-Cascade. Default :cascade is true.
180180
def cascade?
181-
return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
181+
return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade)
182182
return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
183183
true
184184
end
@@ -209,7 +209,7 @@ def add_head_not_allowed_methods_and_options_methods
209209
route_settings[:endpoint] = route.app
210210

211211
# using the :any shorthand produces [nil] for route methods, substitute all manually
212-
route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
212+
route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS if route_settings[:methods].include?('*')
213213
end
214214
end
215215

lib/grape/dsl/inside_route.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def redirect(url, permanent: false, body: nil, **_options)
177177
def status(status = nil)
178178
case status
179179
when Symbol
180-
raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status)
180+
raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)
181181
@status = Rack::Utils.status_code(status)
182182
when Integer
183183
@status = status

lib/grape/dsl/routing.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,11 @@ def route(methods, paths = ['/'], route_options = {}, &block)
142142
reset_validations!
143143
end
144144

145-
%w[get post put head delete options patch].each do |meth|
146-
define_method meth do |*args, &block|
145+
Grape::Http::Headers::SUPPORTED_METHODS.each do |supported_method|
146+
define_method supported_method.downcase do |*args, &block|
147147
options = args.extract_options!
148148
paths = args.first || ['/']
149-
route(meth.upcase, paths, options, &block)
149+
route(supported_method, paths, options, &block)
150150
end
151151
end
152152

lib/grape/http/headers.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'grape/util/lazy_object'
4+
35
module Grape
46
module Http
57
module Headers
@@ -27,6 +29,29 @@ module Headers
2729

2830
FORMAT = 'format'
2931

32+
HTTP_HEADERS = Grape::Util::LazyObject.new do
33+
common_http_headers = %w[
34+
Version
35+
Host
36+
Connection
37+
Cache-Control
38+
Dnt
39+
Upgrade-Insecure-Requests
40+
User-Agent
41+
Sec-Fetch-Dest
42+
Accept
43+
Sec-Fetch-Site
44+
Sec-Fetch-Mode
45+
Sec-Fetch-User
46+
Accept-Encoding
47+
Accept-Language
48+
Cookie
49+
].freeze
50+
common_http_headers.each_with_object({}) do |header, response|
51+
response["HTTP_#{header.upcase.tr('-', '_')}"] = header
52+
end.freeze
53+
end
54+
3055
def self.find_supported_method(route_method)
3156
Grape::Http::Headers::SUPPORTED_METHODS.detect { |supported_method| supported_method.casecmp(route_method).zero? }
3257
end

lib/grape/middleware/base.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,9 @@ def content_type
7474
end
7575

7676
def mime_types
77-
types_without_params = {}
78-
content_types.each_pair do |k, v|
77+
@mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params|
7978
types_without_params[v.split(';').first] = k
8079
end
81-
types_without_params
8280
end
8381

8482
private

lib/grape/namespace.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'grape/util/cache'
4+
35
module Grape
46
# A container for endpoints or other namespaces, which allows for both
57
# logical grouping of endpoints as well as sharing common configuration.
@@ -25,13 +27,21 @@ def requirements
2527

2628
# (see ::joined_space_path)
2729
def self.joined_space(settings)
28-
(settings || []).map(&:space).join('/')
30+
settings&.map(&:space)
2931
end
3032

3133
# Join the namespaces from a list of settings to create a path prefix.
3234
# @param settings [Array] list of Grape::Util::InheritableSettings.
3335
def self.joined_space_path(settings)
34-
Grape::Router.normalize_path(joined_space(settings))
36+
Grape::Router.normalize_path(JoinedSpaceCache[joined_space(settings)])
37+
end
38+
39+
class JoinedSpaceCache < Grape::Util::Cache
40+
def initialize
41+
@cache ||= Hash.new do |h, joined_space|
42+
h[joined_space] = -joined_space.join('/')
43+
end
44+
end
3545
end
3646
end
3747
end

lib/grape/path.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'grape/util/cache'
4+
35
module Grape
46
# Represents a path to an endpoint.
57
class Path
@@ -58,7 +60,7 @@ def suffix
5860
end
5961

6062
def path
61-
Grape::Router.normalize_path(parts.join('/'))
63+
Grape::Router.normalize_path(PartsCache[parts])
6264
end
6365

6466
def path_with_suffix
@@ -71,6 +73,14 @@ def to_s
7173

7274
private
7375

76+
class PartsCache < Grape::Util::Cache
77+
def initialize
78+
@cache ||= Hash.new do |h, parts|
79+
h[parts] = -parts.join('/')
80+
end
81+
end
82+
end
83+
7484
def parts
7585
parts = [mount_path, root_prefix].compact
7686
parts << ':version' if uses_path_versioning?

lib/grape/request.rb

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'grape/util/lazy_object'
4+
35
module Grape
46
class Request < Rack::Request
57
HTTP_PREFIX = 'HTTP_'
@@ -30,14 +32,17 @@ def grape_routing_args
3032
end
3133

3234
def build_headers
33-
headers = {}
34-
env.each_pair do |k, v|
35-
next unless k.to_s.start_with? HTTP_PREFIX
36-
37-
k = k[5..-1].split('_').each(&:capitalize!).join('-')
38-
headers[k] = v
35+
Grape::Util::LazyObject.new do
36+
env.each_pair.with_object({}) do |(k, v), headers|
37+
next unless k.to_s.start_with? HTTP_PREFIX
38+
transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
39+
headers[transformed_header] = v
40+
end
3941
end
40-
headers
42+
end
43+
44+
def transform_header(header)
45+
-header[5..-1].split('_').each(&:capitalize!).join('-')
4146
end
4247
end
4348
end

lib/grape/router.rb

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require 'grape/router/route'
4+
require 'grape/util/cache'
45

56
module Grape
67
class Router
@@ -16,12 +17,20 @@ def initialize(pattern, regexp, index, **attributes)
1617
end
1718
end
1819

20+
class NormalizePathCache < Grape::Util::Cache
21+
def initialize
22+
@cache ||= Hash.new do |h, path|
23+
normalized_path = +"/#{path}"
24+
normalized_path.squeeze!('/')
25+
normalized_path.sub!(%r{/+\Z}, '')
26+
normalized_path = '/' if normalized_path.empty?
27+
h[path] = -normalized_path
28+
end
29+
end
30+
end
31+
1932
def self.normalize_path(path)
20-
path = +"/#{path}"
21-
path.squeeze!('/')
22-
path.sub!(%r{/+\Z}, '')
23-
path = '/' if path == ''
24-
path
33+
NormalizePathCache[path]
2534
end
2635

2736
def self.supported_methods
@@ -160,7 +169,7 @@ def greedy_match?(input)
160169
end
161170

162171
def call_with_allow_headers(env, methods, endpoint)
163-
env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ')
172+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ').freeze
164173
endpoint.call(env)
165174
end
166175

0 commit comments

Comments
 (0)