protocol-http 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.
- checksums.yaml +7 -0
- data/.editorconfig +6 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +19 -0
- data/Gemfile +4 -0
- data/README.md +91 -0
- data/Rakefile +6 -0
- data/lib/protocol/http.rb +21 -0
- data/lib/protocol/http/cgi.rb +44 -0
- data/lib/protocol/http/error.rb +50 -0
- data/lib/protocol/http/headers.rb +230 -0
- data/lib/protocol/http/methods.rb +39 -0
- data/lib/protocol/http/reference.rb +151 -0
- data/lib/protocol/http/url.rb +122 -0
- data/lib/protocol/http/version.rb +25 -0
- data/protocol-http.gemspec +25 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 13445d901a8d8f1b18193aaf50e6db7f44bbc4a211a731737d85de1aebb47f2f
|
4
|
+
data.tar.gz: bddfef52d76c90c55443ce2de2e3f21586e430c48f0b0abe3c0a50ee3df10d42
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9f1550f22ece5045d1dfb20414baea631ae14f4ca62fd8936e5d2fc4c440ec6dc880ec961ae75c7edf9497101d04856c3145274276af2e3e453fd0fdb7eccc64
|
7
|
+
data.tar.gz: 1c004df3985a25c449736652b23ac11221fdded57515e8ac716f9630f409e43b345901873ecf3a19c7b1a48d33bc86afd2dd307f83a91252a5adf843d47033a2
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
language: ruby
|
2
|
+
dist: xenial
|
3
|
+
cache: bundler
|
4
|
+
|
5
|
+
matrix:
|
6
|
+
include:
|
7
|
+
- rvm: 2.4
|
8
|
+
- rvm: 2.5
|
9
|
+
- rvm: 2.6
|
10
|
+
- rvm: 2.6
|
11
|
+
env: COVERAGE=PartialSummary,Coveralls
|
12
|
+
- rvm: truffleruby
|
13
|
+
- rvm: jruby-head
|
14
|
+
env: JRUBY_OPTS="--debug -X+O"
|
15
|
+
- rvm: ruby-head
|
16
|
+
allow_failures:
|
17
|
+
- rvm: truffleruby
|
18
|
+
- rvm: ruby-head
|
19
|
+
- rvm: jruby-head
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# Protocol::HTTP
|
2
|
+
|
3
|
+
Provides abstractions for working with the HTTP protocol with a focus on on HTTP/2.
|
4
|
+
|
5
|
+
[](http://travis-ci.com/socketry/protocol-http)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'protocol-http'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install protocol-http
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Headers
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'protocol/http/headers'
|
29
|
+
|
30
|
+
headers = Protocol::HTTP::Headers.new
|
31
|
+
|
32
|
+
headers['Content-Type'] = "image/jpeg"
|
33
|
+
|
34
|
+
headers['content-type']
|
35
|
+
# => "image/jpeg"
|
36
|
+
```
|
37
|
+
|
38
|
+
### Reference
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
require 'protocol/http/reference'
|
42
|
+
|
43
|
+
reference = Protocol::HTTP::Reference.new("/search", q: 'kittens')
|
44
|
+
|
45
|
+
reference.to_s
|
46
|
+
# => "/search?q=kittens"
|
47
|
+
```
|
48
|
+
|
49
|
+
### URL
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
require 'protocol/http/url'
|
53
|
+
|
54
|
+
reference = Protocol::HTTP::Reference.parse("/search?q=kittens")
|
55
|
+
|
56
|
+
parameters = Protocol::HTTP::URL.decode(reference.query_string)
|
57
|
+
# => {"q"=>"kittens"}
|
58
|
+
```
|
59
|
+
|
60
|
+
## Contributing
|
61
|
+
|
62
|
+
1. Fork it
|
63
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
64
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
65
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
66
|
+
5. Create new Pull Request
|
67
|
+
|
68
|
+
## License
|
69
|
+
|
70
|
+
Released under the MIT license.
|
71
|
+
|
72
|
+
Copyright, 2019, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
73
|
+
Copyright, 2013, by Ilya Grigorik.
|
74
|
+
|
75
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
76
|
+
of this software and associated documentation files (the "Software"), to deal
|
77
|
+
in the Software without restriction, including without limitation the rights
|
78
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
79
|
+
copies of the Software, and to permit persons to whom the Software is
|
80
|
+
furnished to do so, subject to the following conditions:
|
81
|
+
|
82
|
+
The above copyright notice and this permission notice shall be included in
|
83
|
+
all copies or substantial portions of the Software.
|
84
|
+
|
85
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
86
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
87
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
88
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
89
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
90
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
91
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative "http/version"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Protocol
|
22
|
+
module HTTP
|
23
|
+
# CGI keys (https://tools.ietf.org/html/rfc3875#section-4.1)
|
24
|
+
module CGI
|
25
|
+
AUTH_TYPE = "AUTH_TYPE".freeze
|
26
|
+
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
27
|
+
CONTENT_TYPE = "CONTENT_TYPE".freeze
|
28
|
+
GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
|
29
|
+
PATH_INFO = "PATH_INFO".freeze
|
30
|
+
PATH_TRANSLATED = "PATH_TRANSLATED".freeze
|
31
|
+
QUERY_STRING = "QUERY_STRING".freeze
|
32
|
+
REMOTE_ADDR = "REMOTE_ADDR".freeze
|
33
|
+
REMOTE_HOST = "REMOTE_HOST".freeze
|
34
|
+
REMOTE_IDENT = "REMOTE_IDENT".freeze
|
35
|
+
REMOTE_USER = "REMOTE_USER".freeze
|
36
|
+
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
37
|
+
SCRIPT_NAME = "SCRIPT_NAME".freeze
|
38
|
+
SERVER_NAME = "SERVER_NAME".freeze
|
39
|
+
SERVER_PORT = "SERVER_PORT".freeze
|
40
|
+
SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
|
41
|
+
SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
# Copyright, 2013, by Ilya Grigorik.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
|
22
|
+
module Protocol
|
23
|
+
module HTTP
|
24
|
+
class Error < StandardError
|
25
|
+
end
|
26
|
+
|
27
|
+
# The request was invalid/malformed in some way.
|
28
|
+
class BadRequest < Error
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised if connection header is missing or invalid indicating that
|
32
|
+
# this is an invalid HTTP 2.0 request - no frames are emitted and the
|
33
|
+
# connection must be aborted.
|
34
|
+
class HandshakeError < Error
|
35
|
+
end
|
36
|
+
|
37
|
+
# Raised by stream or connection handlers, results in GOAWAY frame
|
38
|
+
# which signals termination of the current connection. You *cannot*
|
39
|
+
# recover from this exception, or any exceptions subclassed from it.
|
40
|
+
class ProtocolError < Error
|
41
|
+
def initialize(message, code = nil)
|
42
|
+
super(message)
|
43
|
+
|
44
|
+
@code = code
|
45
|
+
end
|
46
|
+
|
47
|
+
attr :code
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Protocol
|
22
|
+
module HTTP
|
23
|
+
class Headers
|
24
|
+
class Split < Array
|
25
|
+
COMMA = /\s*,\s*/
|
26
|
+
|
27
|
+
def initialize(value)
|
28
|
+
super(value.split(COMMA))
|
29
|
+
end
|
30
|
+
|
31
|
+
def << value
|
32
|
+
super value.split(COMMA)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
join(", ")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Multiple < Array
|
41
|
+
def initialize(value)
|
42
|
+
super()
|
43
|
+
|
44
|
+
self << value
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
join("\n")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.[] hash
|
53
|
+
self.new(hash.to_a)
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(fields = nil, indexed = nil)
|
57
|
+
if fields
|
58
|
+
@fields = fields.dup
|
59
|
+
else
|
60
|
+
@fields = []
|
61
|
+
end
|
62
|
+
|
63
|
+
if indexed
|
64
|
+
@indexed = indexed.dup
|
65
|
+
else
|
66
|
+
@indexed = self.to_h
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def dup
|
71
|
+
self.class.new(@fields, @indexed)
|
72
|
+
end
|
73
|
+
|
74
|
+
attr :fields
|
75
|
+
|
76
|
+
def freeze
|
77
|
+
return if frozen?
|
78
|
+
|
79
|
+
@indexed = to_h
|
80
|
+
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
def empty?
|
85
|
+
@fields.empty?
|
86
|
+
end
|
87
|
+
|
88
|
+
def each(&block)
|
89
|
+
@fields.each(&block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def include? key
|
93
|
+
self[key] != nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def slice!(keys)
|
97
|
+
_, @fields = @fields.partition do |field|
|
98
|
+
keys.include?(field.first.downcase)
|
99
|
+
end
|
100
|
+
|
101
|
+
keys.each do |key|
|
102
|
+
@indexed.delete(key)
|
103
|
+
end
|
104
|
+
|
105
|
+
return self
|
106
|
+
end
|
107
|
+
|
108
|
+
def slice(keys)
|
109
|
+
self.dup.slice!(keys)
|
110
|
+
end
|
111
|
+
|
112
|
+
def add(key, value)
|
113
|
+
self[key] = value
|
114
|
+
end
|
115
|
+
|
116
|
+
def merge!(headers)
|
117
|
+
headers.each do |key, value|
|
118
|
+
self[key] = value
|
119
|
+
end
|
120
|
+
|
121
|
+
return self
|
122
|
+
end
|
123
|
+
|
124
|
+
def merge(headers)
|
125
|
+
self.dup.merge!(headers)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Append the value to the given key. Some values can be appended multiple times, others can only be set once.
|
129
|
+
# @param key [String] The header key.
|
130
|
+
# @param value The header value.
|
131
|
+
def []= key, value
|
132
|
+
merge_into(@indexed, key.downcase, value)
|
133
|
+
|
134
|
+
@fields << [key, value]
|
135
|
+
end
|
136
|
+
|
137
|
+
MERGE_POLICY = {
|
138
|
+
# Headers which may only be specified once.
|
139
|
+
'content-type' => false,
|
140
|
+
'content-disposition' => false,
|
141
|
+
'content-length' => false,
|
142
|
+
'user-agent' => false,
|
143
|
+
'referer' => false,
|
144
|
+
'host' => false,
|
145
|
+
'authorization' => false,
|
146
|
+
'proxy-authorization' => false,
|
147
|
+
'if-modified-since' => false,
|
148
|
+
'if-unmodified-since' => false,
|
149
|
+
'from' => false,
|
150
|
+
'location' => false,
|
151
|
+
'max-forwards' => false,
|
152
|
+
|
153
|
+
'connection' => Split,
|
154
|
+
|
155
|
+
# Headers specifically for proxies:
|
156
|
+
'via' => Split,
|
157
|
+
'x-forwarded-for' => Split,
|
158
|
+
|
159
|
+
# Headers which may be specified multiple times, but which can't be concatenated.
|
160
|
+
'set-cookie' => Multiple,
|
161
|
+
'www-authenticate' => Multiple,
|
162
|
+
'proxy-authenticate' => Multiple
|
163
|
+
}.tap{|hash| hash.default = Split}
|
164
|
+
|
165
|
+
# Delete all headers with the given key, and return the merged value.
|
166
|
+
def delete(key)
|
167
|
+
_, @fields = @fields.partition do |field|
|
168
|
+
field.first.downcase == key
|
169
|
+
end
|
170
|
+
|
171
|
+
return @indexed.delete(key)
|
172
|
+
end
|
173
|
+
|
174
|
+
protected def merge_into(hash, key, value)
|
175
|
+
if policy = MERGE_POLICY[key]
|
176
|
+
if current_value = hash[key]
|
177
|
+
current_value << value
|
178
|
+
else
|
179
|
+
hash[key] = policy.new(value)
|
180
|
+
end
|
181
|
+
else
|
182
|
+
raise ArgumentError, "Header #{key} can only be set once!" if hash.include?(key)
|
183
|
+
|
184
|
+
# We can't merge these, we only expose the last one set.
|
185
|
+
hash[key] = value
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def [] key
|
190
|
+
@indexed[key]
|
191
|
+
end
|
192
|
+
|
193
|
+
def to_h
|
194
|
+
@fields.inject({}) do |hash, (key, value)|
|
195
|
+
merge_into(hash, key.downcase, value)
|
196
|
+
|
197
|
+
hash
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def == other
|
202
|
+
if other.is_a? Hash
|
203
|
+
to_h == other
|
204
|
+
else
|
205
|
+
@fields == other.fields
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Used for merging objects into a sequential list of headers. Normalizes header keys and values.
|
210
|
+
class Merged
|
211
|
+
def initialize(*all)
|
212
|
+
@all = all
|
213
|
+
end
|
214
|
+
|
215
|
+
def << headers
|
216
|
+
@all << headers
|
217
|
+
end
|
218
|
+
|
219
|
+
# @yield [String, String] header key (lower case) and value (as string).
|
220
|
+
def each(&block)
|
221
|
+
@all.each do |headers|
|
222
|
+
headers.each do |key, value|
|
223
|
+
yield key.downcase, value.to_s
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Protocol
|
22
|
+
module HTTP
|
23
|
+
# HTTP method verbs
|
24
|
+
module Methods
|
25
|
+
GET = 'GET'.freeze
|
26
|
+
POST = 'POST'.freeze
|
27
|
+
PUT = 'PUT'.freeze
|
28
|
+
PATCH = 'PATCH'.freeze
|
29
|
+
DELETE = 'DELETE'.freeze
|
30
|
+
HEAD = 'HEAD'.freeze
|
31
|
+
OPTIONS = 'OPTIONS'.freeze
|
32
|
+
LINK = 'LINK'.freeze
|
33
|
+
UNLINK = 'UNLINK'.freeze
|
34
|
+
TRACE = 'TRACE'.freeze
|
35
|
+
|
36
|
+
# Use Methods.constants to get all constants.
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'url'
|
22
|
+
|
23
|
+
module Protocol
|
24
|
+
module HTTP
|
25
|
+
# A relative reference, excluding any authority.
|
26
|
+
class Reference
|
27
|
+
# Generate a reference from a path and user parameters. The path may contain a `#fragment` or `?query=parameters`.
|
28
|
+
def self.parse(path = '/', parameters = nil)
|
29
|
+
base, fragment = path.split('#', 2)
|
30
|
+
path, query_string = base.split('?', 2)
|
31
|
+
|
32
|
+
self.new(path, query_string, fragment, parameters)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(path, query_string = nil, fragment = nil, parameters = nil)
|
36
|
+
@path = path
|
37
|
+
@query_string = query_string
|
38
|
+
@fragment = fragment
|
39
|
+
@parameters = parameters
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.[] reference
|
43
|
+
if reference.is_a? self
|
44
|
+
return reference
|
45
|
+
else
|
46
|
+
return self.parse(reference)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# The path component, e.g. /foo/bar/index.html
|
51
|
+
attr :path
|
52
|
+
|
53
|
+
# The un-parsed query string, e.g. 'x=10&y=20'
|
54
|
+
attr :query_string
|
55
|
+
|
56
|
+
# A fragment, the part after the '#'
|
57
|
+
attr :fragment
|
58
|
+
|
59
|
+
# User supplied parameters that will be appended to the query part.
|
60
|
+
attr :parameters
|
61
|
+
|
62
|
+
def parameters?
|
63
|
+
@parameters and !@parameters.empty?
|
64
|
+
end
|
65
|
+
|
66
|
+
def query_string?
|
67
|
+
@query_string and !@query_string.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
def fragment?
|
71
|
+
@fragment and !@fragment.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
def append(buffer)
|
75
|
+
if query_string?
|
76
|
+
buffer << URL.escape_path(@path) << '?' << @query_string
|
77
|
+
buffer << '&' << URL.encode(@parameters) if parameters?
|
78
|
+
else
|
79
|
+
buffer << URL.escape_path(@path)
|
80
|
+
buffer << '?' << URL.encode(@parameters) if parameters?
|
81
|
+
end
|
82
|
+
|
83
|
+
if fragment?
|
84
|
+
buffer << '#' << URL.escape(@fragment)
|
85
|
+
end
|
86
|
+
|
87
|
+
return buffer
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_str
|
91
|
+
append(String.new)
|
92
|
+
end
|
93
|
+
|
94
|
+
alias to_s to_str
|
95
|
+
|
96
|
+
def + other
|
97
|
+
other = self.class[other]
|
98
|
+
|
99
|
+
self.class.new(
|
100
|
+
expand_path(self.path, other.path),
|
101
|
+
other.query_string,
|
102
|
+
other.fragment,
|
103
|
+
other.parameters,
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def [] parameters
|
108
|
+
self.dup(nil, parameters)
|
109
|
+
end
|
110
|
+
|
111
|
+
def dup(path = nil, parameters = nil, merge = true)
|
112
|
+
if @parameters and merge
|
113
|
+
if parameters
|
114
|
+
parameters = @parameters.merge(parameters)
|
115
|
+
else
|
116
|
+
parameters = @parameters
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if path
|
121
|
+
path = expand_path(@path, path)
|
122
|
+
else
|
123
|
+
path = @path
|
124
|
+
end
|
125
|
+
|
126
|
+
self.class.new(path, @query_string, @fragment, parameters)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def expand_path(base, relative)
|
132
|
+
if relative.start_with? '/'
|
133
|
+
return relative
|
134
|
+
else
|
135
|
+
path = base.split('/')
|
136
|
+
parts = relative.split('/')
|
137
|
+
|
138
|
+
parts.each do |part|
|
139
|
+
if part == '..'
|
140
|
+
path.pop
|
141
|
+
else
|
142
|
+
path << part
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
return path.join('/')
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Protocol
|
22
|
+
module HTTP
|
23
|
+
module URL
|
24
|
+
# Escapes a generic string, using percent encoding.
|
25
|
+
def self.escape(string, encoding = string.encoding)
|
26
|
+
string.b.gsub(/([^a-zA-Z0-9_.\-]+)/) do |m|
|
27
|
+
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
|
28
|
+
end.force_encoding(encoding)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.unescape(string, encoding = string.encoding)
|
32
|
+
string.b.gsub(/%(\h\h)/) do |hex|
|
33
|
+
Integer(hex, 16).chr
|
34
|
+
end.force_encoding(encoding)
|
35
|
+
end
|
36
|
+
|
37
|
+
# According to https://tools.ietf.org/html/rfc3986#section-3.3, we escape non-pchar.
|
38
|
+
NON_PCHAR = /([^a-zA-Z0-9_\-\.~!$&'()*+,;=:@\/]+)/.freeze
|
39
|
+
|
40
|
+
# Escapes a path
|
41
|
+
def self.escape_path(path)
|
42
|
+
encoding = path.encoding
|
43
|
+
path.b.gsub(NON_PCHAR) do |m|
|
44
|
+
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
|
45
|
+
end.force_encoding(encoding)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Encodes a hash or array into a query string
|
49
|
+
def self.encode(value, prefix = nil)
|
50
|
+
case value
|
51
|
+
when Array
|
52
|
+
return value.map {|v|
|
53
|
+
self.encode(v, "#{prefix}[]")
|
54
|
+
}.join("&")
|
55
|
+
when Hash
|
56
|
+
return value.map {|k, v|
|
57
|
+
self.encode(v, prefix ? "#{prefix}[#{escape(k.to_s)}]" : escape(k.to_s))
|
58
|
+
}.reject(&:empty?).join('&')
|
59
|
+
when nil
|
60
|
+
return prefix
|
61
|
+
else
|
62
|
+
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
63
|
+
|
64
|
+
return "#{prefix}=#{escape(value.to_s)}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.scan(string)
|
69
|
+
# TODO Ruby 2.6 doesn't require `.each`
|
70
|
+
string.split('&').each do |assignment|
|
71
|
+
key, value = assignment.split('=', 2)
|
72
|
+
|
73
|
+
yield unescape(key), unescape(value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.split(name)
|
78
|
+
name.scan(/([^\[]+)|(?:\[(.*?)\])/).flatten!.compact!
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.assign(keys, value, parent)
|
82
|
+
top, *middle = keys
|
83
|
+
|
84
|
+
middle.each_with_index do |key, index|
|
85
|
+
if key.nil? or key.empty?
|
86
|
+
parent = (parent[top] ||= Array.new)
|
87
|
+
top = parent.size
|
88
|
+
|
89
|
+
if nested = middle[index+1] and last = parent.last
|
90
|
+
top -= 1 unless last.include?(nested)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
parent = (parent[top] ||= Hash.new)
|
94
|
+
top = key
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
parent[top] = value
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.decode(string, maximum = 8, symbolize_keys: false)
|
102
|
+
parameters = {}
|
103
|
+
|
104
|
+
self.scan(string) do |name, value|
|
105
|
+
keys = self.split(name)
|
106
|
+
|
107
|
+
if keys.count > maximum
|
108
|
+
raise ArgumentError, "Key length exceeded limit!"
|
109
|
+
end
|
110
|
+
|
111
|
+
if symbolize_keys
|
112
|
+
keys.collect!{|key| key.empty? ? nil : key.to_sym}
|
113
|
+
end
|
114
|
+
|
115
|
+
self.assign(keys, value, parameters)
|
116
|
+
end
|
117
|
+
|
118
|
+
return parameters
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module Protocol
|
22
|
+
module HTTP
|
23
|
+
VERSION = "0.1.0"
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
require_relative "lib/protocol/http/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "protocol-http"
|
6
|
+
spec.version = Protocol::HTTP::VERSION
|
7
|
+
spec.authors = ["Samuel Williams"]
|
8
|
+
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
9
|
+
|
10
|
+
spec.summary = "Provides abstractions to handle HTTP protocols."
|
11
|
+
spec.homepage = "https://github.com/socketry/protocol-http"
|
12
|
+
|
13
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
14
|
+
f.match(%r{^(test|spec|features)/})
|
15
|
+
end
|
16
|
+
|
17
|
+
spec.required_ruby_version = '>= 2.4.0'
|
18
|
+
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "covered"
|
22
|
+
spec.add_development_dependency "bundler", ">= 1.16"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: protocol-http
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: covered
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- samuel.williams@oriontransfer.co.nz
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".editorconfig"
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- ".travis.yml"
|
80
|
+
- Gemfile
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/protocol/http.rb
|
84
|
+
- lib/protocol/http/cgi.rb
|
85
|
+
- lib/protocol/http/error.rb
|
86
|
+
- lib/protocol/http/headers.rb
|
87
|
+
- lib/protocol/http/methods.rb
|
88
|
+
- lib/protocol/http/reference.rb
|
89
|
+
- lib/protocol/http/url.rb
|
90
|
+
- lib/protocol/http/version.rb
|
91
|
+
- protocol-http.gemspec
|
92
|
+
homepage: https://github.com/socketry/protocol-http
|
93
|
+
licenses: []
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.4.0
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubygems_version: 3.0.2
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Provides abstractions to handle HTTP protocols.
|
114
|
+
test_files: []
|