server.rb 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a1e9f252405fa8dfefb506be2f364ab0397e219b73de71de64d0b44d3afac915
4
+ data.tar.gz: 8ea9be708ac06fa1c6828376948f2cd54e2743229dd4bdcf1643765e8fe82ba0
5
+ SHA512:
6
+ metadata.gz: c591537a32ca5e8f8fb3808df201b68a1f554005f09a2668701a2aab57cb7077a7f3c134916ded8f52c6eeae149142ab8979cd55db014cb79ece01235e48aa20
7
+ data.tar.gz: 1adc90d672fba8a1e6b57e9dc49aa3b7b90723b17d57304fadf82cee3cc42757ddb01bd1259dbe309cbb54ebf11f15e85625903d89ae1710471d8af778c26e16
data/lib/server/dir.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # A rack application that serves the contents
5
+ # of a directory over HTTP.
6
+ class Server::Dir
7
+ prepend Server::Gzip
8
+
9
+ def initialize(root)
10
+ @root = File.realpath(root)
11
+ @mime_types = {".ttf" => "font/ttf"}.freeze
12
+ end
13
+
14
+ def call(env)
15
+ catch(:redirect) { finish Rack::Request.new(env) }
16
+ rescue Errno::EPERM, Errno::EACCES
17
+ body = "Permission denied"
18
+ [403, {"content-length" => body.bytesize, "content-type" => "text/plain"}, [body]]
19
+ rescue Errno::ENOENT
20
+ body = "The requested URL was not found"
21
+ [404, {"content-length" => body.bytesize, "content-type" => "text/plain"}, [body]]
22
+ rescue => ex
23
+ body = "Internal server error (#{ex.class})"
24
+ [500, {"content-length" => body.bytesize, "content-type" => "text/plain"}, [body]]
25
+ end
26
+
27
+ def finish(req)
28
+ path = resolve_path(req)
29
+ body = File.binread(path)
30
+ extn = File.extname(path)
31
+ [
32
+ 200,
33
+ {"content-type" => mime_types[extn] || Rack::Mime.mime_type(extn),
34
+ "content-length" => body.bytesize},
35
+ body.each_line.to_a
36
+ ]
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :root, :mime_types
42
+
43
+ def resolve_path(req)
44
+ path = File.join root, File.expand_path(req.path)
45
+ return path unless File.directory?(path)
46
+ if req.path.end_with?("/")
47
+ File.join(path, "index.html")
48
+ else
49
+ throw(:redirect, [301, {"Location" => "#{req.path}/"}, [""]])
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # A middleware that adds ETag support.
5
+ class Server::ETag < Rack::ETag
6
+ ETAGS = {}
7
+
8
+ ##
9
+ # @param [#call] app
10
+ # A rack app.
11
+ #
12
+ # @return [Server::ETag]
13
+ # Reurns an instance of {Server::ETag Server::ETag}.
14
+ def initialize(app)
15
+ @app = app
16
+ end
17
+
18
+ ##
19
+ # @param [Hash] env
20
+ # An environment hash.
21
+ #
22
+ # @return [Array<Number, Hash, #each>]
23
+ def call(env)
24
+ status, headers, body = super(env)
25
+ if headers["etag"] && headers["etag"] == env["HTTP_IF_NONE_MATCH"]
26
+ [304, headers, [""]]
27
+ else
28
+ [status, headers, body]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # A mixin that serves a compressed copy of a file.
5
+ # Similar to the nginx module
6
+ # [gzip_static](http://nginx.org/en/docs/http/ngx_http_gzip_static_module.html).
7
+ module Server::Gzip
8
+ def finish(request)
9
+ path = gzip_path(request)
10
+ if path
11
+ body = File.binread(path)
12
+ extn = File.extname(path[0..-4])
13
+ [
14
+ 200,
15
+ {"content-type" => mime_types[extn] || Rack::Mime.mime_type(extn),
16
+ "content-encoding" => "gzip",
17
+ "content-length" => body.bytesize},
18
+ body.each_line
19
+ ]
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def gzip_path(request)
28
+ return unless request.get_header("accept-encoding")
29
+ &.include?("gzip")
30
+ path = "#{find_path(request)}.gz"
31
+ File.exist?(path) ? path : nil
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "puma"
4
+ require "puma/events"
5
+
6
+ class Puma::Server
7
+ public :binder
8
+ end
@@ -0,0 +1,3 @@
1
+ class Server
2
+ VERSION = "0.1.1"
3
+ end
data/lib/server.rb ADDED
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Server
4
+ require "rack"
5
+ require_relative "server/puma"
6
+ require_relative "server/gzip"
7
+ require_relative "server/etag"
8
+ require_relative "server/dir"
9
+
10
+ ##
11
+ # @param [String] path
12
+ # The path to a directory.
13
+ #
14
+ # @return [Rack::Builder]
15
+ # Returns a Rack app.
16
+ def self.app(path)
17
+ Rack::Builder.app do
18
+ use Server::ETag
19
+ run Server::Dir.new(path)
20
+ end
21
+ end
22
+ private_class_method :app
23
+
24
+ ##
25
+ # @param [String] path
26
+ # The path to a directory.
27
+ #
28
+ # @param [Hash] options
29
+ # A hash of options.
30
+ #
31
+ # @return [Server]
32
+ # Returns an instance of {Server Server}.
33
+ def self.dir(path, options = {})
34
+ host = options.delete(:host) || options.delete("host") || "127.0.0.1"
35
+ port = options.delete(:port) || options.delete("port") || 3000
36
+ unix = options.delete(:unix) || options.delete("unix") || nil
37
+ new app(path), options.merge!(
38
+ binds: [unix ? "unix://#{unix}" : "tcp://#{host}:#{port}"]
39
+ )
40
+ end
41
+
42
+ ##
43
+ # @param [Rack::Builder] app
44
+ # A rack app.
45
+ #
46
+ # @param [Hash] options
47
+ # A hash of options.
48
+ #
49
+ # @return [Server]
50
+ # Returns an instance of {Server Server}.
51
+ def initialize(app, options = {})
52
+ @app = app
53
+ @options = default_options.merge!(options)
54
+ @events = Puma::Events.new
55
+ @server = Puma::Server.new(@app, @events, @options)
56
+ end
57
+
58
+ ##
59
+ # Starts the web server.
60
+ #
61
+ # @param [Boolean] block
62
+ # When given as true, this method will block.
63
+ #
64
+ # @return [Thread]
65
+ # Returns a thread.
66
+ def start(block: false)
67
+ @server.binder.parse(@options[:binds])
68
+ thr = @server.run
69
+ block ? thr.join : thr
70
+ end
71
+
72
+ ##
73
+ # Stops the web server.
74
+ #
75
+ # @return [void]
76
+ def stop
77
+ @server.stop
78
+ end
79
+
80
+ private
81
+
82
+ def default_options
83
+ {
84
+ min_threads: 1,
85
+ max_threads: 5,
86
+ workers: 1
87
+ }
88
+ end
89
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: server.rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - '0x1eef'
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: puma
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: standard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.24'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.24'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack-test
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.5'
83
+ description: A static file web server
84
+ email:
85
+ - 0x1eef@protonmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - lib/server.rb
91
+ - lib/server/dir.rb
92
+ - lib/server/etag.rb
93
+ - lib/server/gzip.rb
94
+ - lib/server/puma.rb
95
+ - lib/server/version.rb
96
+ homepage: https://github.com/0x1eef/server.rb#readme
97
+ licenses:
98
+ - 0BSD
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubygems_version: 3.5.3
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: A static file web server
119
+ test_files: []