mlserver 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|