net-http-server 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.
- data/.document +3 -0
- data/.gemtest +0 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +8 -0
- data/LICENSE.txt +20 -0
- data/README.md +48 -0
- data/Rakefile +36 -0
- data/gemspec.yml +16 -0
- data/lib/net/http/server.rb +4 -0
- data/lib/net/http/server/daemon.rb +123 -0
- data/lib/net/http/server/parser.rb +151 -0
- data/lib/net/http/server/requests.rb +129 -0
- data/lib/net/http/server/responses.rb +151 -0
- data/lib/net/http/server/server.rb +49 -0
- data/lib/net/http/server/version.rb +10 -0
- data/lib/rack/handler/http.rb +170 -0
- data/net-http-server.gemspec +10 -0
- data/spec/rack/handler/helpers/test_request.rb +69 -0
- data/spec/rack/handler/http_spec.rb +87 -0
- data/spec/rack/handler/images/image.jpg +0 -0
- data/spec/server/daemon_spec.rb +26 -0
- data/spec/server/parser_spec.rb +90 -0
- data/spec/server/requests_spec.rb +110 -0
- data/spec/server/responses_spec.rb +105 -0
- data/spec/server/server_spec.rb +5 -0
- data/spec/spec_helper.rb +2 -0
- metadata +155 -0
data/.document
ADDED
data/.gemtest
ADDED
File without changes
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title "net-http-server Documentation" --protected
|
data/ChangeLog.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Hal Brodigan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# net-http-server
|
2
|
+
|
3
|
+
* [Homepage](http://github.com/postmodern/net-http-server)
|
4
|
+
* [Issues](http://github.com/postmodern/net-http-server/issues)
|
5
|
+
* [Documentation](http://rubydoc.info/gems/net-http-server)
|
6
|
+
* Postmodern (postmodern.mod3 at gmail.com)
|
7
|
+
|
8
|
+
## Description
|
9
|
+
|
10
|
+
{Net::HTTP::Server} is a pure Ruby HTTP server.
|
11
|
+
|
12
|
+
## Features
|
13
|
+
|
14
|
+
* Pure Ruby.
|
15
|
+
* Provides a [Rack](http://rack.rubyforge.org/) Handler.
|
16
|
+
|
17
|
+
## Examples
|
18
|
+
|
19
|
+
Simple HTTP Server:
|
20
|
+
|
21
|
+
require 'net/http/server'
|
22
|
+
require 'pp'
|
23
|
+
|
24
|
+
Net::HTTP::Server.run(:port => 8080) do |request,socket|
|
25
|
+
pp request
|
26
|
+
|
27
|
+
[200, {'Content-Type' => 'text/html'}, ['Hello World']]
|
28
|
+
end
|
29
|
+
|
30
|
+
Use it with Rack:
|
31
|
+
|
32
|
+
require 'rack/handler/http'
|
33
|
+
|
34
|
+
Rack::Handler::HTTP.run app
|
35
|
+
|
36
|
+
## Requirements
|
37
|
+
|
38
|
+
* [parslet](http://rubygems.org/gems/parslet) ~> 1.0
|
39
|
+
|
40
|
+
## Install
|
41
|
+
|
42
|
+
$ gem install net-http-server
|
43
|
+
|
44
|
+
## Copyright
|
45
|
+
|
46
|
+
Copyright (c) 2011 Hal Brodigan
|
47
|
+
|
48
|
+
See {file:LICENSE.txt} for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
gem 'ore-tasks', '~> 0.3.0'
|
6
|
+
require 'ore/tasks'
|
7
|
+
|
8
|
+
Ore::Tasks.new
|
9
|
+
rescue LoadError => e
|
10
|
+
STDERR.puts e.message
|
11
|
+
STDERR.puts "Run `gem install ore-tasks` to install 'ore/tasks'."
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
gem 'rspec', '~> 2.4.0'
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
|
18
|
+
RSpec::Core::RakeTask.new
|
19
|
+
rescue LoadError => e
|
20
|
+
task :spec do
|
21
|
+
abort "Please run `gem install rspec` to install RSpec."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
task :test => :spec
|
25
|
+
task :default => :spec
|
26
|
+
|
27
|
+
begin
|
28
|
+
gem 'yard', '~> 0.6.0'
|
29
|
+
require 'yard'
|
30
|
+
|
31
|
+
YARD::Rake::YardocTask.new
|
32
|
+
rescue LoadError => e
|
33
|
+
task :yard do
|
34
|
+
abort "Please run `gem install yard` to install YARD."
|
35
|
+
end
|
36
|
+
end
|
data/gemspec.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
name: net-http-server
|
2
|
+
summary: A pure Ruby HTTP Server
|
3
|
+
description: A Rack compatible pure Ruby HTTP Server.
|
4
|
+
license: MIT
|
5
|
+
authors: Postmodern
|
6
|
+
email: postmodern.mod3@gmail.com
|
7
|
+
homepage: http://github.com/postmodern/net-http-server
|
8
|
+
has_yard: true
|
9
|
+
|
10
|
+
dependencies:
|
11
|
+
parslet: ~> 1.0
|
12
|
+
|
13
|
+
development_dependencies:
|
14
|
+
ore-tasks: ~> 0.3.0
|
15
|
+
rspec: ~> 2.4.0
|
16
|
+
yard: ~> 0.6.0
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'net/http/server/parser'
|
2
|
+
require 'net/http/server/requests'
|
3
|
+
require 'net/http/server/responses'
|
4
|
+
|
5
|
+
require 'net/protocol'
|
6
|
+
require 'gserver'
|
7
|
+
|
8
|
+
module Net
|
9
|
+
class HTTP < Protocol
|
10
|
+
module Server
|
11
|
+
class Daemon < GServer
|
12
|
+
|
13
|
+
include Requests
|
14
|
+
include Responses
|
15
|
+
|
16
|
+
# Default host to bind to.
|
17
|
+
DEFAULT_HOST = '0.0.0.0'
|
18
|
+
|
19
|
+
# Default port to listen on.
|
20
|
+
DEFAULT_PORT = 8080
|
21
|
+
|
22
|
+
# Maximum number of simultaneous connections.
|
23
|
+
MAX_CONNECTIONS = 256
|
24
|
+
|
25
|
+
# Creates a new HTTP Daemon.
|
26
|
+
#
|
27
|
+
# @param [Hash] options
|
28
|
+
# Options for the daemon.
|
29
|
+
#
|
30
|
+
# @option options [String] :host (DEFAULT_HOST)
|
31
|
+
# The host to run on.
|
32
|
+
#
|
33
|
+
# @option options [String] :port (DEFAULT_PORT)
|
34
|
+
# The port to listen on.
|
35
|
+
#
|
36
|
+
# @option options [Integer] :max_connections (MAX_CONNECTIONS)
|
37
|
+
# The maximum number of simultaneous connections.
|
38
|
+
#
|
39
|
+
# @option options [IO] :log (STDERR)
|
40
|
+
# The log to write errors to.
|
41
|
+
#
|
42
|
+
# @option options [#call] :handler
|
43
|
+
# The HTTP Request Handler object.
|
44
|
+
#
|
45
|
+
# @yield [request, socket]
|
46
|
+
# If a block is given, it will be used to process HTTP Requests.
|
47
|
+
#
|
48
|
+
# @yieldparam [Hash{Symbol => String,Array,Hash}] request
|
49
|
+
# The HTTP Request.
|
50
|
+
#
|
51
|
+
# @yieldparam [TCPSocket] socket
|
52
|
+
# The TCP socket of the client.
|
53
|
+
#
|
54
|
+
def initialize(options={},&block)
|
55
|
+
host = options.fetch(:host,DEFAULT_HOST)
|
56
|
+
port = options.fetch(:port,DEFAULT_PORT).to_i
|
57
|
+
max_connections = options.fetch(:max_connections,MAX_CONNECTIONS)
|
58
|
+
log = options.fetch(:log,STDERR)
|
59
|
+
|
60
|
+
super(port,host,max_connections,log,false,true)
|
61
|
+
|
62
|
+
handler(options[:handler],&block)
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Sets the HTTP Request Handler.
|
67
|
+
#
|
68
|
+
# @param [#call, nil] object
|
69
|
+
# The HTTP Request Handler object.
|
70
|
+
#
|
71
|
+
# @yield [request, socket]
|
72
|
+
# If a block is given, it will be used to process HTTP Requests.
|
73
|
+
#
|
74
|
+
# @yieldparam [Hash{Symbol => String,Array,Hash}] request
|
75
|
+
# The HTTP Request.
|
76
|
+
#
|
77
|
+
# @yieldparam [TCPSocket] socket
|
78
|
+
# The TCP socket of the client.
|
79
|
+
#
|
80
|
+
# @raise [ArgumentError]
|
81
|
+
# The HTTP Request Handler must respond to `#call`.
|
82
|
+
#
|
83
|
+
def handler(object=nil,&block)
|
84
|
+
if object
|
85
|
+
unless object.respond_to?(:call)
|
86
|
+
raise(ArgumentError,"HTTP Request Handler must respond to #call")
|
87
|
+
end
|
88
|
+
elsif block.nil?
|
89
|
+
raise(ArgumentError,"no HTTP Request Handler block given")
|
90
|
+
end
|
91
|
+
|
92
|
+
@handler = (object || block)
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Receives HTTP Requests and handles them.
|
97
|
+
#
|
98
|
+
# @param [TCPSocket] socket
|
99
|
+
# A new TCP connection.
|
100
|
+
#
|
101
|
+
def serve(socket)
|
102
|
+
if (raw_request = read_request(socket))
|
103
|
+
parser = Parser.new
|
104
|
+
|
105
|
+
begin
|
106
|
+
request = parser.parse(raw_request)
|
107
|
+
rescue Parslet::ParseFailed => error
|
108
|
+
return Responses::BAD_REQUEST
|
109
|
+
end
|
110
|
+
|
111
|
+
normalize_request(request)
|
112
|
+
|
113
|
+
# rack compliant
|
114
|
+
status, headers, body = @handler.call(request,socket)
|
115
|
+
|
116
|
+
write_response(socket,status,headers,body)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'net/protocol'
|
2
|
+
require 'parslet'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
class HTTP < Protocol
|
6
|
+
module Server
|
7
|
+
#
|
8
|
+
# Inspired by:
|
9
|
+
#
|
10
|
+
# * [Thin](https://github.com/macournoyer/thin/blob/master/ext/thin_parser/common.rl)
|
11
|
+
# * [Unicorn](https://github.com/defunkt/unicorn/blob/master/ext/unicorn_http/unicorn_http_common.rl)
|
12
|
+
# * [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616.html)
|
13
|
+
#
|
14
|
+
class Parser < Parslet::Parser
|
15
|
+
|
16
|
+
#
|
17
|
+
# Character Classes
|
18
|
+
#
|
19
|
+
rule(:digit) { match['0-9'] }
|
20
|
+
rule(:digits) { digit.repeat(1) }
|
21
|
+
rule(:xdigit) { digit | match['a-fA-F'] }
|
22
|
+
rule(:upper) { match['A-Z'] }
|
23
|
+
rule(:lower) { match['a-z'] }
|
24
|
+
rule(:alpha) { upper | lower }
|
25
|
+
rule(:alnum) { alpha | digit }
|
26
|
+
rule(:cntrl) { match['\x00-\x1f'] }
|
27
|
+
rule(:ascii) { match['\x00-\x7f'] }
|
28
|
+
|
29
|
+
rule(:lws) { match[" \t"] }
|
30
|
+
rule(:crlf) { str("\r\n") }
|
31
|
+
|
32
|
+
rule(:ctl) { cntrl | str("\x7f") }
|
33
|
+
rule(:text) { lws | (ctl.absnt? >> ascii) }
|
34
|
+
|
35
|
+
rule(:safe) { charset('$', '-', '_', '.') }
|
36
|
+
rule(:extra) { charset('!', '*', "'", '(', ')', ',') }
|
37
|
+
rule(:reserved) { charset(';', '/', '?', ':', '@', '&', '=', '+') }
|
38
|
+
rule(:sorta_safe) { charset('"', '<', '>') }
|
39
|
+
|
40
|
+
rule(:unsafe) { ctl | charset(' ', '#', '%') | sorta_safe }
|
41
|
+
rule(:national) {
|
42
|
+
(alpha | digit | reserved | extra | safe | unsafe).absnt? >> any
|
43
|
+
}
|
44
|
+
|
45
|
+
rule(:unreserved) { alpha | digit | safe | extra | national }
|
46
|
+
rule(:escape) { str("%u").maybe >> xdigit >> xdigit }
|
47
|
+
rule(:uchar) { unreserved | escape | sorta_safe }
|
48
|
+
rule(:pchar) { uchar | charset(':', '@', '&', '=', '+') }
|
49
|
+
rule(:separators) {
|
50
|
+
lws | charset(
|
51
|
+
'(', ')', '<', '>', '@', ',', ';', ':', "\\", '"', '/', '[', ']',
|
52
|
+
'?', '=', '{', '}'
|
53
|
+
)
|
54
|
+
}
|
55
|
+
|
56
|
+
#
|
57
|
+
# Elements
|
58
|
+
#
|
59
|
+
rule(:token) { (ctl | separators).absnt? >> ascii }
|
60
|
+
|
61
|
+
rule(:comment_text) { (str('(') | str(')')).absnt? >> text }
|
62
|
+
rule(:comment) { str('(') >> comment_text.repeat >> str(')') }
|
63
|
+
|
64
|
+
rule(:quoted_pair) { str("\\") >> ascii }
|
65
|
+
rule(:quoted_text) { quoted_pair | str('"').absnt? >> text }
|
66
|
+
rule(:quoted_string) { str('"') >> quoted_text >> str('"') }
|
67
|
+
|
68
|
+
#
|
69
|
+
# URI Elements
|
70
|
+
#
|
71
|
+
rule(:scheme) {
|
72
|
+
(alpha | digit | charset('+', '-', '.')).repeat
|
73
|
+
}
|
74
|
+
rule(:host_name) {
|
75
|
+
(alnum | charset('-', '_', '.')).repeat(1)
|
76
|
+
}
|
77
|
+
rule(:user_info) {
|
78
|
+
(
|
79
|
+
unreserved | escape | charset(';', ':', '&', '=', '+')
|
80
|
+
).repeat(1)
|
81
|
+
}
|
82
|
+
|
83
|
+
rule(:path) { pchar.repeat(1) >> (str('/') >> pchar.repeat).repeat }
|
84
|
+
rule(:query_string) { (uchar | reserved).repeat }
|
85
|
+
rule(:param) { (pchar | str('/')).repeat }
|
86
|
+
rule(:params) { param >> (str(';') >> param).repeat }
|
87
|
+
rule(:frag) { (uchar | reserved).repeat }
|
88
|
+
|
89
|
+
rule(:relative_path) {
|
90
|
+
path.maybe.as(:path) >>
|
91
|
+
(str(';') >> params.as(:params)).maybe >>
|
92
|
+
(str('?') >> query_string.as(:query)).maybe >>
|
93
|
+
(str('#') >> frag.as(:fragment)).maybe
|
94
|
+
}
|
95
|
+
rule(:absolute_path) { str('/').repeat(1) >> relative_path }
|
96
|
+
|
97
|
+
rule(:absolute_uri) {
|
98
|
+
scheme.as(:scheme) >> str(':') >> str('//').maybe >>
|
99
|
+
(user_info.as(:user_info) >> str('@')).maybe >>
|
100
|
+
host_name.as(:host) >>
|
101
|
+
(str(':') >> digits.as(:port)).maybe >>
|
102
|
+
absolute_path
|
103
|
+
}
|
104
|
+
|
105
|
+
rule(:request_uri) { str('*') | absolute_uri | absolute_path }
|
106
|
+
|
107
|
+
#
|
108
|
+
# HTTP Elements
|
109
|
+
#
|
110
|
+
rule(:request_method) { upper.repeat(1,20) | token.repeat(1) }
|
111
|
+
|
112
|
+
rule(:version_number) { digits >> str('.') >> digits }
|
113
|
+
rule(:http_version) { str('HTTP/') >> version_number.as(:version) }
|
114
|
+
rule(:request_line) {
|
115
|
+
request_method.as(:method) >>
|
116
|
+
str(' ') >> request_uri.as(:uri) >>
|
117
|
+
str(' ') >> http_version
|
118
|
+
}
|
119
|
+
|
120
|
+
rule(:header_name) { (str(':').absnt? >> token).repeat(1) }
|
121
|
+
rule(:header_value) {
|
122
|
+
(text | token | separators | quoted_string).repeat(1)
|
123
|
+
}
|
124
|
+
|
125
|
+
rule(:header) {
|
126
|
+
header_name.as(:name) >> str(':') >> lws.repeat(1) >>
|
127
|
+
header_value.as(:value) >> crlf
|
128
|
+
}
|
129
|
+
rule(:request) {
|
130
|
+
request_line >> crlf >>
|
131
|
+
header.repeat.as(:headers) >> crlf
|
132
|
+
}
|
133
|
+
|
134
|
+
root :request
|
135
|
+
|
136
|
+
protected
|
137
|
+
|
138
|
+
#
|
139
|
+
# Creates a matcher for the given characters.
|
140
|
+
#
|
141
|
+
# @param [Array<String>] chars
|
142
|
+
# The characters to match.
|
143
|
+
#
|
144
|
+
def charset(*chars)
|
145
|
+
match[chars.map { |c| Regexp.escape(c) }.join]
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'net/protocol'
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class HTTP < Protocol
|
5
|
+
module Server
|
6
|
+
module Requests
|
7
|
+
# Default ports for common URI schemes
|
8
|
+
DEFAULT_PORTS = {
|
9
|
+
'https' => 443,
|
10
|
+
'http' => 80
|
11
|
+
}
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
#
|
16
|
+
# Reads a HTTP Request from the stream.
|
17
|
+
#
|
18
|
+
# @param [IO] stream
|
19
|
+
# The stream to read from.
|
20
|
+
#
|
21
|
+
# @return [String, nil]
|
22
|
+
# The raw HTTP Request or `nil` if the Request was malformed.
|
23
|
+
#
|
24
|
+
def read_request(stream)
|
25
|
+
buffer = ''
|
26
|
+
|
27
|
+
request_line = stream.readline("\r\n")
|
28
|
+
|
29
|
+
# the request line must contain 'HTTP/'
|
30
|
+
return unless request_line.include?('HTTP/')
|
31
|
+
|
32
|
+
buffer << request_line
|
33
|
+
|
34
|
+
stream.each_line("\r\n") do |header|
|
35
|
+
buffer << header
|
36
|
+
|
37
|
+
# a header line must contain a ':' character followed by
|
38
|
+
# linear-white-space (either ' ' or "\t").
|
39
|
+
unless (header.include?(': ') || header.include?(":\t"))
|
40
|
+
# if this is not a header line, check if it is the end
|
41
|
+
# of the request
|
42
|
+
if header == "\r\n"
|
43
|
+
# end of the request
|
44
|
+
break
|
45
|
+
else
|
46
|
+
# invalid header line
|
47
|
+
return
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return buffer
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Normalizes the `:uri` part of the request.
|
57
|
+
#
|
58
|
+
# @param [Hash] request
|
59
|
+
# The unnormalized HTTP request.
|
60
|
+
#
|
61
|
+
def normalize_uri(request)
|
62
|
+
uri = request[:uri]
|
63
|
+
|
64
|
+
if uri.kind_of?(Hash)
|
65
|
+
if uri[:scheme]
|
66
|
+
uri[:port] = unless uri[:port]
|
67
|
+
DEFAULT_PORTS[uri[:scheme]]
|
68
|
+
else
|
69
|
+
uri[:port].to_i
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
unless uri[:path]
|
74
|
+
uri[:path] = '/'
|
75
|
+
else
|
76
|
+
uri[:path].insert(0,'/')
|
77
|
+
end
|
78
|
+
elsif uri == '*'
|
79
|
+
request[:uri] = {}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Normalizes the `:headers` part of the request.
|
85
|
+
#
|
86
|
+
# @param [Hash] request
|
87
|
+
# The unnormalized HTTP request.
|
88
|
+
#
|
89
|
+
def normalize_headers(request)
|
90
|
+
headers = request[:headers]
|
91
|
+
normalized_headers = {}
|
92
|
+
|
93
|
+
unless headers.empty?
|
94
|
+
headers.each do |header|
|
95
|
+
name = header[:name]
|
96
|
+
value = header[:value]
|
97
|
+
|
98
|
+
if normalized_headers.has_key?(name)
|
99
|
+
previous_value = normalized_headers[name]
|
100
|
+
|
101
|
+
if previous_value.kind_of?(Array)
|
102
|
+
previous_value << value
|
103
|
+
else
|
104
|
+
normalized_headers[name] = [previous_value, value]
|
105
|
+
end
|
106
|
+
else
|
107
|
+
normalized_headers[name] = value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
request[:headers] = normalized_headers
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Normalizes a HTTP request.
|
117
|
+
#
|
118
|
+
# @param [Hash] request
|
119
|
+
# The unnormalized HTTP request.
|
120
|
+
#
|
121
|
+
def normalize_request(request)
|
122
|
+
normalize_uri(request)
|
123
|
+
normalize_headers(request)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|