server.rb 0.1.1

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