mlserver 1.0.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/bin/mlserver +3 -0
- data/lib/MLserver/client_handler.rb +11 -0
- data/lib/MLserver/error_response.rb +60 -0
- data/lib/MLserver/html/error_page.template.html +10 -0
- data/lib/MLserver/logger.rb +51 -0
- data/lib/MLserver/redirect_response.rb +13 -0
- data/lib/MLserver/request.rb +31 -0
- data/lib/MLserver/request_parser.rb +42 -0
- data/lib/MLserver/response.rb +41 -0
- data/lib/MLserver/server.rb +61 -0
- data/lib/MLserver/settings.rb +23 -0
- data/lib/MLserver.rb +18 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6b326cb18fd091f0272824b58d166980a1e3add7e87d508dd4895388eae223c8
|
4
|
+
data.tar.gz: 26e225d538c7791fe256aa31f1cd14e40b505ffcdb3217296ec602ad1318fe8d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2fc0d8d718f919271737cddc7fc361e47b5cdba17091c8b53684612dabbca8b5ed42aaf80c0f6f06fa5e4163ee41f8957a4f4ef3a27dbc3905a8f2c34512c286
|
7
|
+
data.tar.gz: 58c17b39a1fc34cc1aaf72242683d3fb22d3a177a362b235964d94ca961da435400c2f684239dfe7af67db6f410a245a882d8ef305675a982a7e2c81c6b5b97f
|
data/bin/mlserver
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module MLserver
|
2
|
+
class ErrorResponse
|
3
|
+
@@error_messages_by_code = {
|
4
|
+
400 => "bad request",
|
5
|
+
401 => "unauthorized",
|
6
|
+
403 => "forbidden",
|
7
|
+
404 => "page not found",
|
8
|
+
405 => "method not allowed",
|
9
|
+
406 => "not acceptable",
|
10
|
+
407 => "proxy authentication required",
|
11
|
+
408 => "request timeout",
|
12
|
+
409 => "conflict",
|
13
|
+
410 => "resource gone",
|
14
|
+
411 => "length required",
|
15
|
+
412 => "precondition failed",
|
16
|
+
413 => "payload too large",
|
17
|
+
414 => "URI too long",
|
18
|
+
415 => "unsupported media type",
|
19
|
+
416 => "range not satisfiable",
|
20
|
+
417 => "expectation failed",
|
21
|
+
418 => "I'm a teapot",
|
22
|
+
421 => "misdirected request",
|
23
|
+
422 => "unprocessable entity",
|
24
|
+
423 => "resource locked",
|
25
|
+
424 => "failed dependency",
|
26
|
+
425 => "too early",
|
27
|
+
426 => "upgrade required",
|
28
|
+
428 => "precondition required",
|
29
|
+
429 => "too many requests",
|
30
|
+
431 => "request header fields too large",
|
31
|
+
451 => "unavailable for legal reasons",
|
32
|
+
500 => "internal server error",
|
33
|
+
501 => "not implemented",
|
34
|
+
502 => "bad gateway",
|
35
|
+
503 => "service unavailable",
|
36
|
+
504 => "gateway timeout",
|
37
|
+
505 => "HTTP version not supported",
|
38
|
+
506 => "variant also negotiates",
|
39
|
+
507 => "insufficient storage",
|
40
|
+
508 => "loop detected",
|
41
|
+
510 => "not extended",
|
42
|
+
511 => "network authentication required"
|
43
|
+
}
|
44
|
+
|
45
|
+
def initialize(code, message: nil, httpver: "HTTP/1.0")
|
46
|
+
code = code.to_i
|
47
|
+
@code = code
|
48
|
+
@message = message ? message : "Error: #{code} (#{@@error_messages_by_code[code]})"
|
49
|
+
@httpver = httpver
|
50
|
+
end
|
51
|
+
|
52
|
+
def response
|
53
|
+
return Response.new(status: @code, data: html_page, content_type: "text/html", httpver: @httpver, headers: {Connection: "close"})
|
54
|
+
end
|
55
|
+
|
56
|
+
def html_page
|
57
|
+
return File.read(File.dirname(__FILE__) + "/html/error_page.template.html").gsub("[ecode]", @code.to_s).gsub("[emsg]", @message)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module MLserver
|
2
|
+
class Logger
|
3
|
+
@@log_levels = [:info, :warn, :error]
|
4
|
+
|
5
|
+
def initialize(out:, err:, log_colors: {}, outputs: {error: :err})
|
6
|
+
@out = out
|
7
|
+
@err = err
|
8
|
+
@log_colors = log_colors
|
9
|
+
@outputs = outputs
|
10
|
+
end
|
11
|
+
|
12
|
+
def log(message, level = :info)
|
13
|
+
out = @out
|
14
|
+
out = @err if @outputs[level] == :err
|
15
|
+
|
16
|
+
if @log_colors[level]
|
17
|
+
message = message.color @log_colors[level]
|
18
|
+
end
|
19
|
+
out.puts message
|
20
|
+
end
|
21
|
+
|
22
|
+
def format_ip_address(address)
|
23
|
+
ipv4_re = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/
|
24
|
+
ipv4_anywhere_re = /((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}/
|
25
|
+
ipv4_in_ipv6_re = /^(\:\:ffff\:)((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/
|
26
|
+
ipv6_re = /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/
|
27
|
+
|
28
|
+
return address if address.match?(ipv4_re)
|
29
|
+
if address.match?(ipv4_in_ipv6_re)
|
30
|
+
ipv4_address = address.match(ipv4_anywhere_re).to_s
|
31
|
+
return ipv4_address
|
32
|
+
end
|
33
|
+
if address.match?(ipv6_re)
|
34
|
+
return "[#{address}]"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def log_traffic(ip, direction, data)
|
39
|
+
symbol = (direction == :incoming ? "=>" : "<=")
|
40
|
+
|
41
|
+
log("#{format_ip_address ip} #{symbol} #{data}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
dlcolors = {
|
46
|
+
warn: :yellow,
|
47
|
+
error: :red
|
48
|
+
}
|
49
|
+
|
50
|
+
DefaultLogger = Logger.new(out: STDOUT, err: STDERR, log_colors: dlcolors)
|
51
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module MLserver
|
2
|
+
class RedirectResponse
|
3
|
+
def initialize(url, type: 302, httpver: "HTTP/1.1")
|
4
|
+
@type = type.to_i
|
5
|
+
@url = url.to_s
|
6
|
+
@httpver = httpver
|
7
|
+
end
|
8
|
+
|
9
|
+
def response
|
10
|
+
return Response.new(status: @type, headers: {Location: @url, Connection: "close"}, data: "Redirecting... If you don't get redirected, go to the following URL: #{@url}", httpver: @httpver)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MLserver
|
2
|
+
class Request
|
3
|
+
def initialize(headers: {}, request:, data: "", client:)
|
4
|
+
@headers = headers
|
5
|
+
@request = request
|
6
|
+
@data = data
|
7
|
+
@client = client
|
8
|
+
|
9
|
+
@request_split = @request.split(" ")
|
10
|
+
end
|
11
|
+
|
12
|
+
def method
|
13
|
+
@request_split[0].upcase
|
14
|
+
end
|
15
|
+
|
16
|
+
def path
|
17
|
+
@request_split[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def httpver
|
21
|
+
@request_split[2]
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond(response)
|
25
|
+
MLserver.settings.logger.log_traffic @client.peeraddr[2], :outgoing, "#{response.httpver} #{response.status}"
|
26
|
+
@client.puts response.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :headers, :request, :data
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module MLserver
|
2
|
+
module RequestParser
|
3
|
+
def self.parse_request(client)
|
4
|
+
keepReading = true
|
5
|
+
headers = {}
|
6
|
+
data = ""
|
7
|
+
|
8
|
+
req = client.gets.to_s
|
9
|
+
|
10
|
+
while req.gsub("\r\n", "") == "" do
|
11
|
+
req = client.gets.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
#Close if bad request
|
15
|
+
if req.to_s.length < 14
|
16
|
+
client.puts ErrorResponse.new(400).response.to_s
|
17
|
+
client.close
|
18
|
+
Thread.exit
|
19
|
+
end
|
20
|
+
|
21
|
+
#Get all headers
|
22
|
+
while keepReading do
|
23
|
+
x = client.gets.to_s
|
24
|
+
if x.chomp.length == 0
|
25
|
+
keepReading = false
|
26
|
+
else
|
27
|
+
if x.split(": ").length < 2
|
28
|
+
client.puts ErrorResponse.new(400).response.to_s
|
29
|
+
client.close
|
30
|
+
Thread.exit
|
31
|
+
end
|
32
|
+
headers[x.split(": ")[0].to_sym] = x.split(": ")[1].chomp
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#Get payload data
|
37
|
+
data = client.read(headers[:"Content-Length"].to_i)
|
38
|
+
|
39
|
+
return Request.new(headers: headers, request: req, data: data, client: client)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module MLserver
|
2
|
+
class Response
|
3
|
+
@@default_headers = {
|
4
|
+
Server: "MLserver #{MLserver.version}",
|
5
|
+
"Content-Type": "text/plain"
|
6
|
+
}
|
7
|
+
|
8
|
+
def initialize(status:200, headers:{}, data:"", content_type: nil, httpver: "HTTP/1.1")
|
9
|
+
@status = status
|
10
|
+
@data = data
|
11
|
+
@httpver = httpver
|
12
|
+
@headers = @@default_headers.merge(response_specific_headers).merge(headers)
|
13
|
+
@headers[:"Content-Type"] = content_type if content_type
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s(array:false)
|
17
|
+
status_line = "#{httpver} #{status}"
|
18
|
+
headers = @headers.map { |header|
|
19
|
+
k=header[0].to_s
|
20
|
+
v=header[1].to_s
|
21
|
+
header = "#{k}: #{v}"
|
22
|
+
}
|
23
|
+
headers = headers.join("\r\n") if !array
|
24
|
+
data = @data
|
25
|
+
|
26
|
+
return "#{status_line}\r\n#{headers}\r\n\r\n#{data}\r\n" if !array
|
27
|
+
return [status_line, headers, "", data, ""] if array
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :status, :headers, :data, :httpver
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def response_specific_headers
|
35
|
+
{
|
36
|
+
Date: Time.now.strftime("%a, %d %b %Y %H:%M:%S %Z"),
|
37
|
+
"Content-Length": @data.length,
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module MLserver
|
2
|
+
module Server
|
3
|
+
@@valid_http_versions = ["HTTP/1.0", "HTTP/1.1"]
|
4
|
+
|
5
|
+
def self.start
|
6
|
+
settings = MLserver.settings
|
7
|
+
|
8
|
+
host = settings.host
|
9
|
+
port = settings.port
|
10
|
+
handler = settings.handler
|
11
|
+
logger = settings.logger
|
12
|
+
|
13
|
+
logger.log "MLserver #{MLserver.version}"
|
14
|
+
|
15
|
+
server = TCPServer.new(host, port)
|
16
|
+
|
17
|
+
logger.log "Listening on #{logger.format_ip_address host}:#{port}"
|
18
|
+
|
19
|
+
loop do
|
20
|
+
Thread.start(server.accept) do |client|
|
21
|
+
loop do
|
22
|
+
r=RequestParser.parse_request(client)
|
23
|
+
|
24
|
+
logger.log_traffic client.peeraddr[2], :incoming, "#{r.method} #{r.path} #{r.httpver}"
|
25
|
+
|
26
|
+
if !@@valid_http_versions.include?(r.httpver)
|
27
|
+
client_ip = client.peeraddr[2]
|
28
|
+
|
29
|
+
resp = MLserver::ErrorResponse.new(505)
|
30
|
+
r.respond resp.response
|
31
|
+
client.close
|
32
|
+
|
33
|
+
Thread.exit
|
34
|
+
end
|
35
|
+
|
36
|
+
if r.httpver == "HTTP/1.1"
|
37
|
+
if !r.headers[:Host]
|
38
|
+
r.respond ErrorResponse.new(400).response
|
39
|
+
client.close
|
40
|
+
Thread.exit
|
41
|
+
elsif settings.force_host
|
42
|
+
if !settings.force_host.include?(r.headers[:Host])
|
43
|
+
r.respond ErrorResponse.new(400).response
|
44
|
+
client.close
|
45
|
+
Thread.exit
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
handler.run(r, client)
|
51
|
+
|
52
|
+
if r.httpver == "HTTP/1.0" || r.headers[:Connection] == "close"
|
53
|
+
client.close
|
54
|
+
Thread.exit
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MLserver
|
2
|
+
@@settings = nil
|
3
|
+
|
4
|
+
class Settings
|
5
|
+
def initialize(host: "0.0.0.0", port: "5555", handler:, logger:, force_host: false)
|
6
|
+
@host = host
|
7
|
+
@port = port.to_i
|
8
|
+
@handler = handler
|
9
|
+
@logger = logger
|
10
|
+
@force_host = force_host
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :host, :port, :handler, :logger, :force_host
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.settings
|
17
|
+
@@settings
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.settings=(x)
|
21
|
+
@@settings=x
|
22
|
+
end
|
23
|
+
end
|
data/lib/MLserver.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module MLserver
|
2
|
+
def self.version
|
3
|
+
"1.0.0"
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require "socket"
|
8
|
+
require "rbtext"
|
9
|
+
require "rbtext/string_methods"
|
10
|
+
require_relative "MLserver/request.rb"
|
11
|
+
require_relative "MLserver/request_parser.rb"
|
12
|
+
require_relative "MLserver/response.rb"
|
13
|
+
require_relative "MLserver/server.rb"
|
14
|
+
require_relative "MLserver/client_handler.rb"
|
15
|
+
require_relative "MLserver/logger.rb"
|
16
|
+
require_relative "MLserver/error_response.rb"
|
17
|
+
require_relative "MLserver/redirect_response.rb"
|
18
|
+
require_relative "MLserver/settings.rb"
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mlserver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthias Lee
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-04-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: argparse
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rbtext
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.3.3
|
41
|
+
description: A simple web server
|
42
|
+
email: matthias@matthiasclee.com
|
43
|
+
executables:
|
44
|
+
- mlserver
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- bin/mlserver
|
49
|
+
- lib/MLserver.rb
|
50
|
+
- lib/MLserver/client_handler.rb
|
51
|
+
- lib/MLserver/error_response.rb
|
52
|
+
- lib/MLserver/html/error_page.template.html
|
53
|
+
- lib/MLserver/logger.rb
|
54
|
+
- lib/MLserver/redirect_response.rb
|
55
|
+
- lib/MLserver/request.rb
|
56
|
+
- lib/MLserver/request_parser.rb
|
57
|
+
- lib/MLserver/response.rb
|
58
|
+
- lib/MLserver/server.rb
|
59
|
+
- lib/MLserver/settings.rb
|
60
|
+
homepage: https://github.com/Matthiasclee/MLServer
|
61
|
+
licenses: []
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubygems_version: 3.1.6
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: A simple web server
|
82
|
+
test_files: []
|