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 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,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "MLserver"
@@ -0,0 +1,11 @@
1
+ module MLserver
2
+ class ClientHandler
3
+ def initialize(&block)
4
+ @block = block
5
+ end
6
+
7
+ def run(request, client)
8
+ @block.call(request, client)
9
+ end
10
+ end
11
+ end
@@ -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,10 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Error [ecode]</title>
5
+ </head>
6
+
7
+ <body>
8
+ <h1>[emsg]</h1>
9
+ </body>
10
+ </html>
@@ -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: []