tp2 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 +7 -0
- data/.gitignore +56 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +64 -0
- data/README.md +5 -0
- data/Rakefile +24 -0
- data/TODO.md +1 -0
- data/examples/simple.rb +26 -0
- data/lib/tp2/http.rb +188 -0
- data/lib/tp2/request_extensions.rb +11 -0
- data/lib/tp2/server.rb +33 -0
- data/lib/tp2/version.rb +3 -0
- data/lib/tp2.rb +4 -0
- data/tp2.gemspec +25 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0722207c7f88a7f070a40e0257c41c557a1e59c7c3dc7768eefc9aed52fd7c04
|
4
|
+
data.tar.gz: 80003148244f0278b56cf4fa99f966d9d4cfc88a72b69744c8120e91c70f74e6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1efa96a8ae2adfd2a4cd46bb0086e927cbe5c54819d68ff2051096130ecbdae89b7ff73501d7c058962bce931471961e74bcc3c53b190f07b83b5ddc6d3a36ec
|
7
|
+
data.tar.gz: fd111961e0c152b30cafcf22b718c541c8d977e7bf6bd0219b5a17fc6bcae55b9d95d9b2ed3d123d9711ec0111e56aeb913675c06de235da2a8853b3d08fd739
|
data/.gitignore
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
# Ignore Byebug command history file.
|
17
|
+
.byebug_history
|
18
|
+
|
19
|
+
## Specific to RubyMotion:
|
20
|
+
.dat*
|
21
|
+
.repl_history
|
22
|
+
build/
|
23
|
+
*.bridgesupport
|
24
|
+
build-iPhoneOS/
|
25
|
+
build-iPhoneSimulator/
|
26
|
+
|
27
|
+
## Specific to RubyMotion (use of CocoaPods):
|
28
|
+
#
|
29
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
30
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
31
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
32
|
+
#
|
33
|
+
# vendor/Pods/
|
34
|
+
|
35
|
+
## Documentation cache and generated files:
|
36
|
+
/.yardoc/
|
37
|
+
/_yardoc/
|
38
|
+
/doc/
|
39
|
+
/rdoc/
|
40
|
+
|
41
|
+
## Environment normalization:
|
42
|
+
/.bundle/
|
43
|
+
/vendor/bundle
|
44
|
+
/lib/bundler/man/
|
45
|
+
|
46
|
+
# for a library or gem, you might want to ignore these files since the code is
|
47
|
+
# intended to run in multiple environments; otherwise, check them in:
|
48
|
+
# Gemfile.lock
|
49
|
+
# .ruby-version
|
50
|
+
# .ruby-gemset
|
51
|
+
|
52
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
53
|
+
.rvmrc
|
54
|
+
|
55
|
+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
56
|
+
# .rubocop-https?--*
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
tp2 (0.1)
|
5
|
+
http_parser.rb (= 0.8.0)
|
6
|
+
qeweney (= 0.21)
|
7
|
+
uringmachine (= 0.5.1)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
docile (1.4.1)
|
13
|
+
escape_utils (1.3.0)
|
14
|
+
ffi (1.17.1-aarch64-linux-gnu)
|
15
|
+
ffi (1.17.1-aarch64-linux-musl)
|
16
|
+
ffi (1.17.1-arm-linux-gnu)
|
17
|
+
ffi (1.17.1-arm-linux-musl)
|
18
|
+
ffi (1.17.1-arm64-darwin)
|
19
|
+
ffi (1.17.1-x86_64-darwin)
|
20
|
+
ffi (1.17.1-x86_64-linux-gnu)
|
21
|
+
ffi (1.17.1-x86_64-linux-musl)
|
22
|
+
http_parser.rb (0.8.0)
|
23
|
+
listen (3.9.0)
|
24
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
25
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
26
|
+
minitest (5.25.4)
|
27
|
+
qeweney (0.21)
|
28
|
+
escape_utils (= 1.3.0)
|
29
|
+
rake (13.2.1)
|
30
|
+
rake-compiler (1.2.9)
|
31
|
+
rake
|
32
|
+
rb-fsevent (0.11.2)
|
33
|
+
rb-inotify (0.11.1)
|
34
|
+
ffi (~> 1.0)
|
35
|
+
simplecov (0.22.0)
|
36
|
+
docile (~> 1.1)
|
37
|
+
simplecov-html (~> 0.11)
|
38
|
+
simplecov_json_formatter (~> 0.1)
|
39
|
+
simplecov-html (0.13.1)
|
40
|
+
simplecov_json_formatter (0.1.4)
|
41
|
+
uringmachine (0.5.1)
|
42
|
+
yard (0.9.37)
|
43
|
+
|
44
|
+
PLATFORMS
|
45
|
+
aarch64-linux-gnu
|
46
|
+
aarch64-linux-musl
|
47
|
+
arm-linux-gnu
|
48
|
+
arm-linux-musl
|
49
|
+
arm64-darwin
|
50
|
+
x86_64-darwin
|
51
|
+
x86_64-linux-gnu
|
52
|
+
x86_64-linux-musl
|
53
|
+
|
54
|
+
DEPENDENCIES
|
55
|
+
listen (= 3.9.0)
|
56
|
+
minitest (= 5.25.4)
|
57
|
+
rake (= 13.2.1)
|
58
|
+
rake-compiler (= 1.2.9)
|
59
|
+
simplecov (= 0.22.0)
|
60
|
+
tp2!
|
61
|
+
yard (= 0.9.37)
|
62
|
+
|
63
|
+
BUNDLED WITH
|
64
|
+
2.6.2
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake/testtask"
|
4
|
+
|
5
|
+
task :default => [:test]
|
6
|
+
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << "test"
|
9
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
task :release do
|
13
|
+
require_relative './lib/tp2/version'
|
14
|
+
version = TP2::VERSION
|
15
|
+
|
16
|
+
puts 'Building tp2...'
|
17
|
+
`gem build tp2.gemspec`
|
18
|
+
|
19
|
+
puts "Pushing tp2 #{version}..."
|
20
|
+
`gem push tp2-#{version}.gem`
|
21
|
+
|
22
|
+
puts "Cleaning up..."
|
23
|
+
`rm *.gem`
|
24
|
+
end
|
data/TODO.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- Write some tests.
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
gem 'tp2', path: '.'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'tp2'
|
10
|
+
|
11
|
+
app = ->(req) {
|
12
|
+
req.respond('foobar', ':status' => Qeweney::Status::TEAPOT)
|
13
|
+
}
|
14
|
+
|
15
|
+
machine = UM.new
|
16
|
+
server = TP2::Server.new(machine, '0.0.0.0', 1234, &app)
|
17
|
+
|
18
|
+
machine.spin { server.run }
|
19
|
+
|
20
|
+
main = Fiber.current
|
21
|
+
trap('SIGINT') { machine.schedule(main, nil) }
|
22
|
+
|
23
|
+
puts "Running... (pid: #{Process.pid})"
|
24
|
+
STDOUT.flush
|
25
|
+
machine.yield # wait for termination
|
26
|
+
puts
|
data/lib/tp2/http.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'http/parser'
|
4
|
+
require 'qeweney'
|
5
|
+
|
6
|
+
module TP2
|
7
|
+
# Encapsulates a HTTP/1 connection
|
8
|
+
class HTTP1Connection # rubocop:disable Metrics/ClassLength
|
9
|
+
SEND_FLAGS = UM::MSG_NOSIGNAL | UM::MSG_WAITALL
|
10
|
+
|
11
|
+
def initialize(machine, fd, bgid, &app) # rubocop:disable Naming/MethodParameterName
|
12
|
+
@machine = machine
|
13
|
+
@fd = fd
|
14
|
+
@bgid = bgid
|
15
|
+
@parser = Http::Parser.new(self)
|
16
|
+
@app = app
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_headers_complete(headers) # rubocop:disable Metrics/MethodLength
|
20
|
+
headers = normalize_headers(headers)
|
21
|
+
headers[':path'] = @parser.request_url
|
22
|
+
headers[':method'] = @parser.http_method.downcase
|
23
|
+
headers[':scheme'] = get_scheme_from_headers(headers)
|
24
|
+
headers[':protocol'] = "http/#{@parser.http_major}.#{@parser.http_minor}"
|
25
|
+
@request = Qeweney::Request.new(headers, self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_scheme_from_headers(headers)
|
29
|
+
if (proto = headers['x-forwarded-proto'])
|
30
|
+
proto.downcase
|
31
|
+
else
|
32
|
+
'http'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def normalize_headers(headers)
|
37
|
+
headers.each_with_object({}) do |(k, v), h|
|
38
|
+
k = k.downcase
|
39
|
+
hk = h[k]
|
40
|
+
if hk
|
41
|
+
hk = h[k] = [hk] unless hk.is_a?(Array)
|
42
|
+
v.is_a?(Array) ? hk.concat(v) : hk << v
|
43
|
+
else
|
44
|
+
h[k] = v
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_body(chunk)
|
50
|
+
@request.buffer_body_chunk(chunk)
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_message_complete
|
54
|
+
@done = @request.headers[':protocol'] != 'http/1.1'
|
55
|
+
@app.call(@request)
|
56
|
+
end
|
57
|
+
|
58
|
+
def run
|
59
|
+
@machine.recv_each(@fd, @bgid, 0) do |buf|
|
60
|
+
@parser << buf
|
61
|
+
break if @done
|
62
|
+
end
|
63
|
+
rescue SystemCallError
|
64
|
+
# do nothing
|
65
|
+
ensure
|
66
|
+
@machine.close(@fd)
|
67
|
+
end
|
68
|
+
|
69
|
+
# response API
|
70
|
+
|
71
|
+
CRLF = "\r\n"
|
72
|
+
ZERO_CRLF_CRLF = "0\r\n\r\n"
|
73
|
+
CRLF_ZERO_CRLF_CRLF = "\r\n0\r\n\r\n"
|
74
|
+
|
75
|
+
# Sends response including headers and body. Waits for the request to complete
|
76
|
+
# if not yet completed. The body is sent using chunked transfer encoding.
|
77
|
+
# @param request [Qeweney::Request] HTTP request
|
78
|
+
# @param body [String] response body
|
79
|
+
# @param headers
|
80
|
+
def respond(_request, body, headers)
|
81
|
+
formatted_headers = format_headers(headers, body, false)
|
82
|
+
# request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
|
83
|
+
if body
|
84
|
+
buf = formatted_headers + body
|
85
|
+
@machine.send(@fd, buf, buf.bytesize, SEND_FLAGS)
|
86
|
+
# handle_write(formatted_headers + body)
|
87
|
+
else
|
88
|
+
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Sends response headers. If empty_response is truthy, the response status
|
93
|
+
# code will default to 204, otherwise to 200.
|
94
|
+
# @param request [Qeweney::Request] HTTP request
|
95
|
+
# @param headers [Hash] response headers
|
96
|
+
# @param empty_response [boolean] whether a response body will be sent
|
97
|
+
# @param chunked [boolean] whether to use chunked transfer encoding
|
98
|
+
# @return [void]
|
99
|
+
def send_headers(request, headers, empty_response: false, chunked: true)
|
100
|
+
formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
|
101
|
+
# request.tx_incr(formatted_headers.bytesize)
|
102
|
+
#
|
103
|
+
@machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Sends a response body chunk. If no headers were sent, default headers are
|
107
|
+
# sent using #send_headers. if the done option is true(thy), an empty chunk
|
108
|
+
# will be sent to signal response completion to the client.
|
109
|
+
# @param request [Qeweney::Request] HTTP request
|
110
|
+
# @param chunk [String] response body chunk
|
111
|
+
# @param done [boolean] whether the response is completed
|
112
|
+
# @return [void]
|
113
|
+
def send_chunk(_request, chunk, done: false)
|
114
|
+
data = +''
|
115
|
+
data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
|
116
|
+
data << "0\r\n\r\n" if done
|
117
|
+
return if data.empty?
|
118
|
+
|
119
|
+
# request.tx_incr(data.bytesize)
|
120
|
+
@machine.send(@fd, data, data.bytesize, SEND_FLAGS)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Finishes the response to the current request. If no headers were sent,
|
124
|
+
# default headers are sent using #send_headers.
|
125
|
+
# @return [void]
|
126
|
+
def finish(_request)
|
127
|
+
# request.tx_incr(5)
|
128
|
+
@machine.send(@fd, ZERO_CRLF_CRLF, ZERO_CRLF_CRLF.bytesize, SEND_FLAGS)
|
129
|
+
end
|
130
|
+
|
131
|
+
def http1_1?(request)
|
132
|
+
request.headers[':protocol'] == 'http/1.1'
|
133
|
+
end
|
134
|
+
|
135
|
+
INTERNAL_HEADER_REGEXP = /^:/.freeze
|
136
|
+
|
137
|
+
# Formats response headers into an array. If empty_response is true(thy),
|
138
|
+
# the response status code will default to 204, otherwise to 200.
|
139
|
+
# @param headers [Hash] response headers
|
140
|
+
# @param body [boolean] whether a response body will be sent
|
141
|
+
# @param chunked [boolean] whether to use chunked transfer encoding
|
142
|
+
# @return [String] formatted response headers
|
143
|
+
def format_headers(headers, body, chunked)
|
144
|
+
status = headers[':status']
|
145
|
+
status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
|
146
|
+
lines = format_status_line(body, status, chunked)
|
147
|
+
headers.each do |k, v|
|
148
|
+
next if k =~ INTERNAL_HEADER_REGEXP
|
149
|
+
|
150
|
+
collect_header_lines(lines, k, v)
|
151
|
+
end
|
152
|
+
lines << CRLF
|
153
|
+
lines
|
154
|
+
end
|
155
|
+
|
156
|
+
def format_status_line(body, status, chunked)
|
157
|
+
if !body
|
158
|
+
empty_status_line(status)
|
159
|
+
else
|
160
|
+
with_body_status_line(status, body, chunked)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def empty_status_line(status)
|
165
|
+
if status == 204
|
166
|
+
+"HTTP/1.1 #{status}\r\n"
|
167
|
+
else
|
168
|
+
+"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def with_body_status_line(status, body, chunked)
|
173
|
+
if chunked
|
174
|
+
+"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
|
175
|
+
else
|
176
|
+
+"HTTP/1.1 #{status}\r\nContent-Length: #{body.is_a?(String) ? body.bytesize : body.to_i}\r\n"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def collect_header_lines(lines, key, value)
|
181
|
+
if value.is_a?(Array)
|
182
|
+
value.inject(lines) { |_, item| lines << "#{key}: #{item}\r\n" }
|
183
|
+
else
|
184
|
+
lines << "#{key}: #{value}\r\n"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
data/lib/tp2/server.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tp2/http'
|
4
|
+
require 'tp2/request_extensions'
|
5
|
+
|
6
|
+
module TP2
|
7
|
+
class Server
|
8
|
+
def initialize(machine, hostname, port, &app)
|
9
|
+
@machine = machine
|
10
|
+
@hostname = hostname
|
11
|
+
@port = port
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
17
|
+
@machine.setsockopt(@server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
|
18
|
+
@machine.bind(@server_fd, @hostname, @port)
|
19
|
+
@machine.listen(@server_fd, UM::SOMAXCONN)
|
20
|
+
|
21
|
+
@bgid = @machine.setup_buffer_ring(4096, 1024)
|
22
|
+
puts "Listening on #{@hostname}:#{@port}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
setup
|
27
|
+
@machine.accept_each(@server_fd) do |fd|
|
28
|
+
conn = HTTP1Connection.new(@machine, fd, @bgid, &@app)
|
29
|
+
@machine.spin(conn) { it.run }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/tp2/version.rb
ADDED
data/lib/tp2.rb
ADDED
data/tp2.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative './lib/tp2/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'tp2'
|
5
|
+
s.summary = ''
|
6
|
+
s.version = TP2::VERSION
|
7
|
+
s.licenses = ['MIT']
|
8
|
+
s.author = 'Sharon Rosner'
|
9
|
+
s.email = 'sharon@noteflakes.com'
|
10
|
+
s.files = `git ls-files`.split
|
11
|
+
s.homepage = 'https://github.com/noteflakes/tp2'
|
12
|
+
s.metadata = {
|
13
|
+
'homepage_uri' => 'https://github.com/noteflakes/tp2',
|
14
|
+
'documentation_uri' => 'https://www.rubydoc.info/gems/tp2',
|
15
|
+
'changelog_uri' => 'https://github.com/noteflakes/tp2/blob/master/CHANGELOG.md'
|
16
|
+
}
|
17
|
+
s.rdoc_options = ['--title', 'TP2', '--main', 'README.md']
|
18
|
+
s.extra_rdoc_files = ['README.md']
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
s.required_ruby_version = '>= 3.4'
|
21
|
+
|
22
|
+
s.add_dependency 'http_parser.rb', '0.8.0'
|
23
|
+
s.add_dependency 'uringmachine', '0.5.1'
|
24
|
+
s.add_dependency 'qeweney', '0.21'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tp2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sharon Rosner
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-02-22 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: http_parser.rb
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - '='
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 0.8.0
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - '='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.8.0
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: uringmachine
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - '='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.5.1
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.5.1
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: qeweney
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0.21'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.21'
|
54
|
+
email: sharon@noteflakes.com
|
55
|
+
executables: []
|
56
|
+
extensions: []
|
57
|
+
extra_rdoc_files:
|
58
|
+
- README.md
|
59
|
+
files:
|
60
|
+
- ".gitignore"
|
61
|
+
- Gemfile
|
62
|
+
- Gemfile.lock
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- TODO.md
|
66
|
+
- examples/simple.rb
|
67
|
+
- lib/tp2.rb
|
68
|
+
- lib/tp2/http.rb
|
69
|
+
- lib/tp2/request_extensions.rb
|
70
|
+
- lib/tp2/server.rb
|
71
|
+
- lib/tp2/version.rb
|
72
|
+
- tp2.gemspec
|
73
|
+
homepage: https://github.com/noteflakes/tp2
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata:
|
77
|
+
homepage_uri: https://github.com/noteflakes/tp2
|
78
|
+
documentation_uri: https://www.rubydoc.info/gems/tp2
|
79
|
+
changelog_uri: https://github.com/noteflakes/tp2/blob/master/CHANGELOG.md
|
80
|
+
rdoc_options:
|
81
|
+
- "--title"
|
82
|
+
- TP2
|
83
|
+
- "--main"
|
84
|
+
- README.md
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '3.4'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.6.2
|
99
|
+
specification_version: 4
|
100
|
+
summary: ''
|
101
|
+
test_files: []
|