minver 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/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +85 -0
- data/Rakefile +2 -0
- data/lib/minver/base.rb +145 -0
- data/lib/minver/parser.rb +72 -0
- data/lib/minver/request.rb +43 -0
- data/lib/minver/request_error.rb +13 -0
- data/lib/minver/response.rb +43 -0
- data/lib/minver/version.rb +3 -0
- data/lib/minver.rb +4 -0
- data/minver.gemspec +22 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3d4c1636a148271f9102e1c53bbb4eb9c6404523
|
4
|
+
data.tar.gz: 6d8aee43656fb56755daecf1e51c83ff49cc94a0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9d63a5e430c927c335d5693206a928f6055b1d46869e1cffe739c47cf4216e2d21d030082351b944872636432aa5cda2844699218957d4898354b40397505ebf
|
7
|
+
data.tar.gz: fe2978bf5237e338584c54210d06a1dace55f5360b8ee94b2b7707812e0ae8eb07180eab00284c58119a8616b973adc5604c0d008a21c2c360f0000a8d1f85cc
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Danyel Bayraktar
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Minver
|
2
|
+
|
3
|
+
This gem provides a minimal HTTP server solution with two key features:
|
4
|
+
|
5
|
+
1. Graceful shutdown of the server
|
6
|
+
2. The caller can retrieve a value that is generated from the route handler
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'minver'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install minver
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
This gem allows you to wait for user input via HTTP requests. For example:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'minver'
|
28
|
+
|
29
|
+
# initialize server. default port is 18167, default bind is '::'
|
30
|
+
server = Minver::Base.new port: 3000
|
31
|
+
|
32
|
+
# Define your routes:
|
33
|
+
|
34
|
+
server.post "/name" do |request|
|
35
|
+
name = request.params["name"]
|
36
|
+
# pass this param to the caller
|
37
|
+
pass name: name
|
38
|
+
"Thanks, #{name}, your personal information was submitted."
|
39
|
+
end
|
40
|
+
|
41
|
+
server.post "/age" do |request|
|
42
|
+
age = request.params["age"].to_i
|
43
|
+
# pass this param to the caller
|
44
|
+
if age < 5
|
45
|
+
[400, {}, "Hey there, fella. You better ask your parents to use this app instead."]
|
46
|
+
else
|
47
|
+
pass age: age
|
48
|
+
"Thank you, your age has been submitted!"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# We're ready to go! Instantiate the hash where we store the info:
|
53
|
+
|
54
|
+
personal_info = {}
|
55
|
+
|
56
|
+
# And listen for requests!
|
57
|
+
|
58
|
+
loop do
|
59
|
+
$stdout.puts "Provide your personal information over http://localhost:3000/name and /age"
|
60
|
+
personal_info.merge!(server.run)
|
61
|
+
break if personal_info.key?(:name) && personal_info.key?(:age)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Now go to your terminal and make a request!
|
65
|
+
# e.g.:
|
66
|
+
# curl -XPOST -H"Content-Type: application/json" -d'{"name": "Danyel Bayraktar"}' localhost:3000/name
|
67
|
+
# or:
|
68
|
+
# curl -XPOST -d'age=3' localhost:3000/age
|
69
|
+
|
70
|
+
|
71
|
+
# Do something with this information!
|
72
|
+
puts "Hey #{personal_info[:name]}, #{personal_info[:age]} years is the best age to be starring my repo!"
|
73
|
+
|
74
|
+
# Don't forget to shut down the server
|
75
|
+
# (you can also call `stop` instead of `pass` from the route handler while still providing a value):
|
76
|
+
server.stop
|
77
|
+
```
|
78
|
+
|
79
|
+
## Contributing
|
80
|
+
|
81
|
+
1. Fork it ( https://github.com/[my-github-username]/minver/fork )
|
82
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
83
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
84
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
85
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/minver/base.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'minver/request'
|
3
|
+
require 'minver/response'
|
4
|
+
require 'minver/request_error'
|
5
|
+
|
6
|
+
module Minver
|
7
|
+
class Base
|
8
|
+
|
9
|
+
HTTP_VERSION = "1.1"
|
10
|
+
HTTP_METHODS = %w(GET POST PATCH PUT DELETE HEAD)
|
11
|
+
HTTP_CODES = {
|
12
|
+
100 => "Continue",
|
13
|
+
101 => "Switching Protocols",
|
14
|
+
103 => "Checkpoint",
|
15
|
+
200 => "OK",
|
16
|
+
201 => "Created",
|
17
|
+
202 => "Accepted",
|
18
|
+
203 => "Non-Authoritative Information",
|
19
|
+
204 => "No Content",
|
20
|
+
205 => "Reset Content",
|
21
|
+
206 => "Partial Content",
|
22
|
+
300 => "Multiple Choices",
|
23
|
+
301 => "Moved Permanently",
|
24
|
+
302 => "Found",
|
25
|
+
303 => "See Other",
|
26
|
+
304 => "Not Modified",
|
27
|
+
306 => "Switch Proxy",
|
28
|
+
307 => "Temporary Redirect",
|
29
|
+
308 => "Resume Incomplete",
|
30
|
+
400 => "Bad Request",
|
31
|
+
401 => "Unauthorized",
|
32
|
+
402 => "Payment Required",
|
33
|
+
403 => "Forbidden",
|
34
|
+
404 => "Not Found",
|
35
|
+
405 => "Method Not Allowed",
|
36
|
+
406 => "Not Acceptable",
|
37
|
+
407 => "Proxy Authentication Required",
|
38
|
+
408 => "Request Timeout",
|
39
|
+
409 => "Conflict",
|
40
|
+
410 => "Gone",
|
41
|
+
411 => "Length Required",
|
42
|
+
412 => "Precondition Failed",
|
43
|
+
413 => "Request Entity Too Large",
|
44
|
+
414 => "Request-URI Too Long",
|
45
|
+
415 => "Unsupported Media Type",
|
46
|
+
416 => "Requested Range Not Satisfiable",
|
47
|
+
417 => "Expectation Failed",
|
48
|
+
422 => "Unprocessable Entity",
|
49
|
+
423 => "Locked",
|
50
|
+
424 => "Failed Dependency",
|
51
|
+
500 => "Internal Server Error",
|
52
|
+
501 => "Not Implemented",
|
53
|
+
502 => "Bad Gateway",
|
54
|
+
503 => "Service Unavailable",
|
55
|
+
504 => "Gateway Timeout",
|
56
|
+
505 => "HTTP Version Not Supported",
|
57
|
+
511 => "Network Authentication Required"
|
58
|
+
}
|
59
|
+
|
60
|
+
def initialize(**options, &block)
|
61
|
+
@bind = options.fetch(:bind, '::')
|
62
|
+
@port = options.fetch(:port, 18167)
|
63
|
+
@clients = []
|
64
|
+
instance_eval(&block) if block_given?
|
65
|
+
end
|
66
|
+
|
67
|
+
def server
|
68
|
+
@server ||= TCPServer.new @bind, @port
|
69
|
+
end
|
70
|
+
|
71
|
+
def on(method, uri, &block)
|
72
|
+
triggers[method][normalize_path(uri)] = block
|
73
|
+
end
|
74
|
+
|
75
|
+
HTTP_METHODS.each do |method|
|
76
|
+
define_method method.downcase do |uri, &block|
|
77
|
+
on(method, uri, &block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def run(**options)
|
82
|
+
$stderr.puts "Listening on #{@bind}:#{@port}." if $DEBUG
|
83
|
+
loop do
|
84
|
+
result = IO.select([server, *@clients], *([nil, nil, 0] if options[:nonblock]))
|
85
|
+
return unless result
|
86
|
+
result.first.each do |client|
|
87
|
+
# it's possible that "client" here is the server so we extract the client from it.
|
88
|
+
@clients << (client = client.accept) if client.respond_to?(:accept)
|
89
|
+
if client.eof?
|
90
|
+
@clients.delete(client).close
|
91
|
+
next
|
92
|
+
end
|
93
|
+
begin
|
94
|
+
request = Request.new(client)
|
95
|
+
$stderr.puts request.data.lines.map{|l| "< #{l}"} if $DEBUG
|
96
|
+
block = triggers[request.http_method][normalize_path(request.path)]
|
97
|
+
response = if block
|
98
|
+
begin
|
99
|
+
Response.from(instance_exec(request, &block))
|
100
|
+
rescue => e
|
101
|
+
raise RequestError.new(
|
102
|
+
"An error occurred. Check the logs or ask the administrator.",
|
103
|
+
500,
|
104
|
+
cause: e
|
105
|
+
)
|
106
|
+
end
|
107
|
+
else
|
108
|
+
raise RequestError.new("The resource you were looking for does not exist.", 404)
|
109
|
+
end
|
110
|
+
if @should_pass
|
111
|
+
@should_pass = false
|
112
|
+
return @return_value
|
113
|
+
end
|
114
|
+
rescue RequestError => e
|
115
|
+
response = Response.from([e.code, e.headers, e.message])
|
116
|
+
raise e.cause || e if (500..599).include? e.code
|
117
|
+
ensure
|
118
|
+
$stderr.puts response.data.lines.map{|l| "> #{l}"} if $DEBUG
|
119
|
+
client.write(response.data)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def stop return_value=nil
|
126
|
+
pass return_value
|
127
|
+
server.close
|
128
|
+
end
|
129
|
+
|
130
|
+
def pass return_value
|
131
|
+
@should_pass = true
|
132
|
+
@return_value = return_value
|
133
|
+
end
|
134
|
+
|
135
|
+
protected
|
136
|
+
|
137
|
+
def normalize_path(path)
|
138
|
+
path.squeeze("/").chomp "/"
|
139
|
+
end
|
140
|
+
|
141
|
+
def triggers
|
142
|
+
@triggers ||= HTTP_METHODS.inject({}){ |h, m| h.merge(m => {}) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Minver
|
4
|
+
class Parser
|
5
|
+
def initialize(stream_or_string)
|
6
|
+
@stream = if stream_or_string.is_a? String
|
7
|
+
StringIO.new(stream_or_string)
|
8
|
+
else
|
9
|
+
stream_or_string
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def http_method
|
14
|
+
@http_method ||= request_match[1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def request_url
|
18
|
+
@request_url ||= request_match[2]
|
19
|
+
end
|
20
|
+
|
21
|
+
def request_http_version
|
22
|
+
@request_http_version ||= request_match[3]
|
23
|
+
end
|
24
|
+
|
25
|
+
def headers
|
26
|
+
@headers ||= header_lines.inject({}) do |h, line|
|
27
|
+
h.merge(Hash[[line.rstrip.split(': ', 2)]])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](key)
|
32
|
+
headers[key]
|
33
|
+
end
|
34
|
+
|
35
|
+
def data
|
36
|
+
@data ||= [*head, body].join
|
37
|
+
end
|
38
|
+
|
39
|
+
def body
|
40
|
+
@body ||= @stream.read(headers["Content-Length"].to_i)
|
41
|
+
end
|
42
|
+
|
43
|
+
def path
|
44
|
+
@path ||= request_url.split('?')[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
def query_string
|
48
|
+
@query_string ||= request_url.split('?')[1] || ''
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
def request_match
|
53
|
+
@request_match ||= request_line.match(/(\S+)\s+(\S+)(?:\s+HTTP\/(\S+)?)/)
|
54
|
+
end
|
55
|
+
|
56
|
+
def request_line
|
57
|
+
@request_line ||= head.lines.first
|
58
|
+
end
|
59
|
+
|
60
|
+
def header_lines
|
61
|
+
@header_lines ||= head.lines[1...-1]
|
62
|
+
end
|
63
|
+
|
64
|
+
def head
|
65
|
+
@head ||= "".tap do |h|
|
66
|
+
begin
|
67
|
+
h << line = @stream.readline # TODO: non-blocking
|
68
|
+
end until ["\r\n", "\n"].include? line
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'minver/parser'
|
2
|
+
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Minver
|
6
|
+
class Request
|
7
|
+
attr_reader :params
|
8
|
+
|
9
|
+
def initialize(client)
|
10
|
+
@client = client
|
11
|
+
@params = Hash[URI.decode_www_form(parser.query_string)].tap do |params|
|
12
|
+
begin
|
13
|
+
puts headers.to_yaml if $DEBUG
|
14
|
+
params.merge! case type = headers["Content-Type"]
|
15
|
+
when 'application/json'
|
16
|
+
require 'json'
|
17
|
+
JSON.parse(body)
|
18
|
+
when 'application/x-www-form-urlencoded'
|
19
|
+
Hash[URI.decode_www_form(body)]
|
20
|
+
else
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
rescue => e
|
24
|
+
raise RequestError.new(
|
25
|
+
"The given content-type is not recognized or the content data is malformed.",
|
26
|
+
400,
|
27
|
+
cause: e
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
[:http_method, :headers, :[], :path, :data, :body].each do |method|
|
34
|
+
define_method method do
|
35
|
+
parser.public_send(method)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parser
|
40
|
+
@parser ||= Minver::Parser.new(@client)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Minver
|
2
|
+
class RequestError < StandardError
|
3
|
+
attr_reader :code, :message, :headers, :cause
|
4
|
+
|
5
|
+
def initialize(message, code, headers: {}, cause: nil)
|
6
|
+
super(message)
|
7
|
+
@code = code
|
8
|
+
@message = message
|
9
|
+
@headers = headers
|
10
|
+
@cause = cause
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Minver::Response
|
2
|
+
DEFAULT_HEADERS = {
|
3
|
+
"Content-Type" => "text/html; charset=utf-8",
|
4
|
+
"Server" => "Minver/1.0",
|
5
|
+
"Connection" => "close"
|
6
|
+
}
|
7
|
+
|
8
|
+
def initialize status, headers, body
|
9
|
+
headers["Content-Length"] = body.length
|
10
|
+
@status = status
|
11
|
+
@headers = DEFAULT_HEADERS.merge(
|
12
|
+
"Date" => Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z")
|
13
|
+
).merge(headers)
|
14
|
+
@body = body
|
15
|
+
end
|
16
|
+
|
17
|
+
def body
|
18
|
+
@body
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
[status_line, *header_lines, '', body].join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def status_line
|
26
|
+
["HTTP/#{Minver::Base::HTTP_VERSION}", @status, Minver::Base::HTTP_CODES[@status]].join(' ')
|
27
|
+
end
|
28
|
+
|
29
|
+
def header_lines
|
30
|
+
@headers.map do |k, v|
|
31
|
+
[k, v].join(": ")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.from var
|
36
|
+
case var
|
37
|
+
when String
|
38
|
+
new(200, {}, var)
|
39
|
+
when Array
|
40
|
+
new(*var)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/minver.rb
ADDED
data/minver.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'minver/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "minver"
|
8
|
+
spec.version = Minver::VERSION
|
9
|
+
spec.authors = ["Danyel Bayraktar"]
|
10
|
+
spec.email = ["cydrop@gmail.com"]
|
11
|
+
spec.summary = %q{Minimal HTTP server with graceful shutdown & value passing}
|
12
|
+
spec.homepage = "https://github.com/muja/minver"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: minver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Danyel Bayraktar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- cydrop@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- lib/minver.rb
|
54
|
+
- lib/minver/base.rb
|
55
|
+
- lib/minver/parser.rb
|
56
|
+
- lib/minver/request.rb
|
57
|
+
- lib/minver/request_error.rb
|
58
|
+
- lib/minver/response.rb
|
59
|
+
- lib/minver/version.rb
|
60
|
+
- minver.gemspec
|
61
|
+
homepage: https://github.com/muja/minver
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.5.1
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Minimal HTTP server with graceful shutdown & value passing
|
85
|
+
test_files: []
|