Skip to content

Commit 38a77ca

Browse files
committed
Merge pull request #197 from seancribbs/less-object-allocations
Reduced object allocation per request
2 parents 7ccf8c3 + ebccb7c commit 38a77ca

23 files changed

+265
-122
lines changed

lib/webmachine.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
require 'webmachine/configuration'
1+
require 'webmachine/configuration'
2+
require 'webmachine/constants'
23
require 'webmachine/cookie'
34
require 'webmachine/headers'
45
require 'webmachine/request'

lib/webmachine/adapters/httpkit.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
require 'webmachine/adapter'
2+
require 'webmachine/constants'
3+
require 'webmachine/version'
14
require 'httpkit'
2-
35
require 'webmachine/version'
4-
require 'webmachine/headers'
5-
require 'webmachine/request'
66
require 'webmachine/response'
7-
require 'webmachine/dispatcher'
8-
require 'webmachine/chunked_body'
7+
require 'webmachine/request'
8+
require 'webmachine/headers'
99

1010
module Webmachine
1111
module Adapters
1212
class HTTPkit < Adapter
13+
VERSION_STRING = "#{Webmachine::SERVER_STRING} HTTPkit/#{::HTTPkit::VERSION}".freeze
14+
1315
def options
1416
@options ||= {
1517
:address => application.configuration.ip,
@@ -49,8 +51,8 @@ def convert_request(request)
4951

5052
# Converts Webmachine::Response to HTTPkit::Response
5153
def convert_response(response)
52-
response.headers["Server"] =
53-
Webmachine::SERVER_STRING + ' HTTPkit/' + ::HTTPkit::VERSION
54+
response.headers[SERVER] = VERSION_STRING
55+
5456

5557
::HTTPkit::Response.new(
5658
response.code.to_i,

lib/webmachine/adapters/lazy_request_body.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
2-
module Webmachine
1+
module Webmachine
32
module Adapters
43
# Wraps a request body so that it can be passed to
54
# {Request} while still lazily evaluating the body.

lib/webmachine/adapters/rack.rb

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
require 'webmachine/adapter'
12
require 'rack'
2-
require 'webmachine/version'
3+
require 'webmachine/constants'
34
require 'webmachine/headers'
45
require 'webmachine/request'
56
require 'webmachine/response'
6-
require 'webmachine/dispatcher'
7+
require 'webmachine/version'
78
require 'webmachine/chunked_body'
89

910
module Webmachine
@@ -36,6 +37,10 @@ class Rack < Adapter
3637
# Used to override default Rack server options (useful in testing)
3738
DEFAULT_OPTIONS = {}
3839

40+
REQUEST_URI = 'REQUEST_URI'.freeze
41+
VERSION_STRING = "#{Webmachine::SERVER_STRING} Rack/#{::Rack.version}".freeze
42+
NEWLINE = "\n".freeze
43+
3944
# Start the Rack adapter
4045
def run
4146
options = DEFAULT_OPTIONS.merge({
@@ -55,17 +60,17 @@ def call(env)
5560

5661
rack_req = ::Rack::Request.new env
5762
request = Webmachine::Request.new(rack_req.request_method,
58-
env['REQUEST_URI'],
63+
env[REQUEST_URI],
5964
headers,
6065
RequestBody.new(rack_req))
6166

6267
response = Webmachine::Response.new
6368
application.dispatcher.dispatch(request, response)
6469

65-
response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")
70+
response.headers[SERVER] = VERSION_STRING
6671

6772
rack_status = response.code
68-
rack_headers = response.headers.flattened("\n")
73+
rack_headers = response.headers.flattened(NEWLINE)
6974
rack_body = case response.body
7075
when String # Strings are enumerable in ruby 1.8
7176
[response.body]
@@ -76,7 +81,7 @@ def call(env)
7681
Webmachine::ChunkedBody.new(Array(response.body.call))
7782
elsif response.body.respond_to?(:each)
7883
# This might be an IOEncoder with a Content-Length, which shouldn't be chunked.
79-
if response.headers["Transfer-Encoding"] == "chunked"
84+
if response.headers[TRANSFER_ENCODING] == "chunked"
8085
Webmachine::ChunkedBody.new(response.body)
8186
else
8287
response.body
@@ -91,15 +96,17 @@ def call(env)
9196
end
9297

9398
class RackResponse
99+
ONE_FIVE = '1.5'.freeze
100+
94101
def initialize(body, status, headers)
95102
@body = body
96103
@status = status
97104
@headers = headers
98105
end
99106

100107
def finish
101-
@headers['Content-Type'] ||= 'text/html' if rack_release_enforcing_content_type
102-
@headers.delete('Content-Type') if response_without_body
108+
@headers[CONTENT_TYPE] ||= TEXT_HTML if rack_release_enforcing_content_type
109+
@headers.delete(CONTENT_TYPE) if response_without_body
103110
[@status, @headers, @body]
104111
end
105112

@@ -110,7 +117,7 @@ def response_without_body
110117
end
111118

112119
def rack_release_enforcing_content_type
113-
::Rack.release < '1.5'
120+
::Rack.release < ONE_FIVE
114121
end
115122
end
116123

lib/webmachine/adapters/reel.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
require 'webmachine/adapter'
2+
require 'webmachine/constants'
3+
require 'set'
14
require 'reel'
2-
require 'webmachine/version'
35
require 'webmachine/headers'
46
require 'webmachine/request'
57
require 'webmachine/response'
6-
require 'webmachine/dispatcher'
7-
require 'set'
88

99
module Webmachine
1010
module Adapters
@@ -55,7 +55,7 @@ def process(connection)
5555
# state machine. Do the "Railsy" thing and handle them like POSTs
5656
# with a magical parameter
5757
if @extra_verbs.include?(request.method)
58-
method = "POST"
58+
method = POST_METHOD
5959
param = "_method=#{request.method}"
6060
uri = request_uri(request.url, request.headers, param)
6161
else

lib/webmachine/adapters/webrick.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
require 'webmachine/adapter'
12
require 'webrick'
2-
require 'webmachine/version'
3+
require 'webmachine/constants'
34
require 'webmachine/headers'
5+
require 'webmachine/adapters/lazy_request_body'
46
require 'webmachine/request'
57
require 'webmachine/response'
6-
require 'webmachine/dispatcher'
8+
require 'webmachine/version'
79

810
module Webmachine
911
module Adapters
@@ -25,6 +27,7 @@ def run
2527

2628
# WEBRick::HTTPServer that is run by the WEBrick adapter.
2729
class Server < ::WEBrick::HTTPServer
30+
2831
def initialize(options)
2932
@application = options[:application]
3033
super(options)
@@ -49,12 +52,12 @@ def service(wreq, wres)
4952
cookies = [response.headers['Set-Cookie'] || []].flatten
5053
cookies.each { |c| wres.cookies << c }
5154

52-
wres['Server'] = [Webmachine::SERVER_STRING, wres.config[:ServerSoftware]].join(" ")
55+
wres[SERVER] = [Webmachine::SERVER_STRING, wres.config[:ServerSoftware]].join(" ")
5356
case response.body
5457
when String
5558
wres.body << response.body
5659
when Enumerable
57-
wres.chunked = response.headers['Transfer-Encoding'] == 'chunked'
60+
wres.chunked = response.headers[TRANSFER_ENCODING] == 'chunked'
5861
response.body.each {|part| wres.body << part }
5962
else
6063
if response.body.respond_to?(:call)

lib/webmachine/chunked_body.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'webmachine/constants'
2+
13
module Webmachine
24
# {ChunkedBody} is used to wrap an {Enumerable} object (like an enumerable
35
# {Response#body}) so it yields proper chunks for chunked transfer encoding.
@@ -13,11 +15,8 @@ module Webmachine
1315
#
1416
# This is needed for Ruby webservers which don't do the chunking themselves.
1517
class ChunkedBody
16-
# Delimiter for chunked encoding
17-
CRLF = "\r\n"
18-
1918
# Final chunk in any chunked-encoding response
20-
FINAL_CHUNK = "0#{CRLF}#{CRLF}"
19+
FINAL_CHUNK = "0#{CRLF}#{CRLF}".freeze
2120

2221
# Creates a new {ChunkedBody} from the given {Enumerable}.
2322
# @param [Enumerable] body the enumerable response body

lib/webmachine/constants.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
module Webmachine
2+
# Universal HTTP delimiter
3+
CRLF = "\r\n".freeze
4+
5+
# HTTP Content-Type
6+
CONTENT_TYPE = 'Content-Type'.freeze
7+
8+
# Default Content-Type
9+
TEXT_HTML = 'text/html'.freeze
10+
11+
# HTTP Date
12+
DATE = 'Date'.freeze
13+
14+
# HTTP Transfer-Encoding
15+
TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
16+
17+
# HTTP Content-Length
18+
CONTENT_LENGTH = 'Content-Length'.freeze
19+
20+
# A underscore
21+
UNDERSCORE = '_'.freeze
22+
23+
# A dash
24+
DASH = '-'.freeze
25+
26+
# A Slash
27+
SLASH = '/'.freeze
28+
29+
MATCHES_ALL = '*/*'.freeze
30+
31+
GET_METHOD = "GET"
32+
HEAD_METHOD = "HEAD"
33+
POST_METHOD = "POST"
34+
PUT_METHOD = "PUT"
35+
DELETE_METHOD = "DELETE"
36+
OPTIONS_METHOD = "OPTIONS"
37+
TRACE_METHOD = "TRACE"
38+
CONNECT_METHOD = "CONNECT"
39+
40+
STANDARD_HTTP_METHODS = [
41+
GET_METHOD, HEAD_METHOD, POST_METHOD,
42+
PUT_METHOD, DELETE_METHOD, TRACE_METHOD,
43+
CONNECT_METHOD, OPTIONS_METHOD
44+
].map!(&:freeze)
45+
STANDARD_HTTP_METHODS.freeze
46+
47+
# A colon
48+
COLON = ':'.freeze
49+
50+
# http string
51+
HTTP = 'http'.freeze
52+
53+
# Host string
54+
HOST = 'Host'.freeze
55+
56+
# HTTP Content-Encoding
57+
CONTENT_ENCODING = 'Content-Encoding'.freeze
58+
59+
# Charset string
60+
CHARSET = 'Charset'.freeze
61+
62+
# Semicolon split match
63+
SPLIT_SEMI = /\s*,\s*/.freeze
64+
65+
# Star Character
66+
STAR = '*'.freeze
67+
68+
# HTTP Location
69+
LOCATION = 'Location'.freeze
70+
71+
# identity Encoding
72+
IDENTITY = 'identity'.freeze
73+
74+
SERVER = 'Server'.freeze
75+
end

lib/webmachine/decision/conneg.rb

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require 'webmachine/constants'
12
require 'webmachine/translation'
23
require 'webmachine/media_type'
34

@@ -13,7 +14,7 @@ module Conneg
1314
# appropriate media type.
1415
# @api private
1516
def choose_media_type(provided, header)
16-
types = Array(header).map{|h| h.split(/\s*,\s*/) }.flatten
17+
types = Array(header).map{|h| h.split(SPLIT_SEMI) }.flatten
1718
requested = MediaTypeList.build(types)
1819
provided = provided.map do |p| # normalize_provided
1920
MediaType.parse(p)
@@ -31,9 +32,9 @@ def choose_media_type(provided, header)
3132
# @api private
3233
def choose_encoding(provided, header)
3334
encodings = provided.keys
34-
if encoding = do_choose(encodings, header, "identity")
35-
response.headers['Content-Encoding'] = encoding unless encoding == 'identity'
36-
metadata['Content-Encoding'] = encoding
35+
if encoding = do_choose(encodings, header, IDENTITY)
36+
response.headers[CONTENT_ENCODING] = encoding unless encoding == IDENTITY
37+
metadata[CONTENT_ENCODING] = encoding
3738
end
3839
end
3940

@@ -44,7 +45,7 @@ def choose_charset(provided, header)
4445
if provided && !provided.empty?
4546
charsets = provided.map {|c| c.first }
4647
if charset = do_choose(charsets, header, HAS_ENCODING ? Encoding.default_external.name : kcode_charset)
47-
metadata['Charset'] = charset
48+
metadata[CHARSET] = charset
4849
end
4950
else
5051
true
@@ -56,8 +57,8 @@ def choose_charset(provided, header)
5657
# @api private
5758
def choose_language(provided, header)
5859
if provided && !provided.empty?
59-
requested = PriorityList.build(header.split(/\s*,\s*/))
60-
star_priority = requested.priority_of("*")
60+
requested = PriorityList.build(header.split(SPLIT_SEMI))
61+
star_priority = requested.priority_of(STAR)
6162
any_ok = star_priority && star_priority > 0.0
6263
accepted = requested.find do |priority, range|
6364
if priority == 0.0
@@ -98,9 +99,9 @@ def language_match(range, tag)
9899
# @api private
99100
def do_choose(choices, header, default)
100101
choices = choices.dup.map {|s| s.downcase }
101-
accepted = PriorityList.build(header.split(/\s*,\s*/))
102+
accepted = PriorityList.build(header.split(SPLIT_SEMI))
102103
default_priority = accepted.priority_of(default)
103-
star_priority = accepted.priority_of("*")
104+
star_priority = accepted.priority_of(STAR)
104105
default_ok = (default_priority.nil? && star_priority != 0.0) || default_priority
105106
any_ok = star_priority && star_priority > 0.0
106107
chosen = accepted.find do |priority, acceptable|
@@ -118,7 +119,7 @@ def do_choose(choices, header, default)
118119

119120
private
120121
# Matches acceptable items that include 'q' values
121-
CONNEG_REGEX = /^\s*(\S+);\s*q=(\S*)\s*$/
122+
CONNEG_REGEX = /^\s*(\S+);\s*q=(\S*)\s*$/.freeze
122123

123124
# Matches the requested media type (with potential modifiers)
124125
# against the provided types (with potential modifiers).

0 commit comments

Comments
 (0)