uma 0.1.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: 0bfbeefbd2e6b80152cbc86a3f460eb687acdfd166e9b0237cf2defa772d6a47
4
+ data.tar.gz: ee08aa1f66d58d09bf7143b196e4b668c284295c0d2ed6bc4d773503104c9aa3
5
+ SHA512:
6
+ metadata.gz: fe7f63f78727969d3745d4ee28f45c14eb53d6531ef10efa23b712587c7272e6166e8095e0eb920866faf7a43c84876ef8162e4cdfcc8ad4a57cd190215e394f
7
+ data.tar.gz: 9edd172653ebcabd5dfc486c6fce8f1a0f39d1e509b089648d2a2a77a6911e08c653e6cf717a1d75b92936e35d04182b4aaf9d198ce8e595dda6ba90c8247d28
data/.gitignore ADDED
@@ -0,0 +1,56 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 2026-02-23 0.1.0
2
+
3
+ - First release
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://gem.coop'
2
+
3
+ gemspec
4
+
5
+ # gem "uringmachine", path: '../uringmachine'
6
+
7
+ group :development do
8
+ gem 'roda', '~>3.101'
9
+ gem 'minitest', '~>6.0'
10
+ gem 'rake'
11
+ gem 'yard'
12
+ gem 'irb'
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Digital Fabric
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ ```
2
+
3
+ ●╭───╮●
4
+ │UMA│ A modern web server for Ruby
5
+ ●╰───╯● https://uma.noteflakes.com/
6
+
7
+ ```
8
+
9
+ ## What is Uma?
10
+
11
+ Uma is a modern HTTP/1.1 server for Ruby/Rack apps.
12
+
13
+ ## Features
14
+
15
+ - Built on UringMachine
16
+ - Multi-process, multi-thread, multi-fiber concurrency model.
17
+
18
+ More information coming soon(ish)...
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake/clean'
4
+
5
+ task :default => :test
6
+ task :test do
7
+ exec 'ruby test/run.rb'
8
+ end
9
+
10
+ task :release do
11
+ require_relative './lib/uma/version'
12
+ version = Uma::VERSION
13
+
14
+ puts 'Building uma...'
15
+ `gem build uma.gemspec`
16
+
17
+ puts "Pushing uma #{version}..."
18
+ `gem push uma-#{version}.gem`
19
+
20
+ puts "Cleaning up..."
21
+ `rm *.gem`
22
+ end
data/TODO.md ADDED
@@ -0,0 +1,43 @@
1
+ ## Basic functionality
2
+
3
+ We work from the outside to the inside. Generally, we start from the script to
4
+ start the server, to the concurrency controls, to the actual HTTP server, to the
5
+ Rack interface.
6
+
7
+ - [v] bin script
8
+ - [v] `uma serve`
9
+
10
+ - [v] concurrency model
11
+ - M threads x N fibers
12
+ - Clear lifecycle management - start, run, stop
13
+ - Each thread sets up a UringMachine instance and a fiber scheduler
14
+ - Graceful stop
15
+
16
+ - [v] server
17
+
18
+ - [v] http rack control cycle
19
+
20
+ - [v] Rack hijack (full/partial)
21
+ - [v] More Rack tests. Where can they come from?
22
+ - [ ] Roda apps
23
+
24
+ - [v] application loading / bootstrapping
25
+ - [v] load .ru files
26
+ - [v] process warmup
27
+
28
+ - [v] load apps with `uma serve`
29
+
30
+ - [ ] uma serve - error if no bind address given
31
+
32
+ - [ ] logging
33
+ - [ ] In conformance with Rack spec
34
+ - [ ] JSON format
35
+
36
+ - [ ] benchmarks
37
+ - [ ] compare to falcon, puma
38
+ - [ ] using a Roda app with a few different endpoints representing different
39
+ types of requests:
40
+ - [ ] GET rendered template, simulate DB select query
41
+ - [ ] POST params, simulate DB update query, response redirects to another URL
42
+ - [ ] POST params, simulate DB update query, JSON response
43
+ - [ ] different concurrency levels: 10 50 100 500 1000 5000
data/bin/uma ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'uma/cli'
5
+
6
+ env = ENV.to_h.merge(
7
+ io_out: STDOUT,
8
+ io_err: STDERR
9
+ )
10
+ Uma::CLI.(ARGV, env)
data/lib/uma/app.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uma/http'
4
+ require 'uma/error'
5
+
6
+ module Uma
7
+ class App
8
+ def initialize(fn)
9
+ @fn = fn
10
+ eval_app_code
11
+ end
12
+
13
+ def run(proc)
14
+ @proc = proc
15
+ end
16
+
17
+ def connection_proc
18
+ ->(machine, fd) {
19
+ HTTP.http_connection(machine, { app: @proc }, fd)
20
+ }
21
+ end
22
+
23
+ def to_proc = @proc
24
+
25
+ private
26
+
27
+ def get_code
28
+ IO.read(@fn)
29
+ rescue SystemCallError
30
+ raise Uma::Error, "Could not load Rackup file"
31
+ end
32
+
33
+ def eval_app_code
34
+ eval(get_code, binding, @fn)
35
+ rescue SyntaxError, NameError => e
36
+ raise Uma::Error, e.message
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ module Uma
5
+ module CLI
6
+ module Error
7
+ class Base < StandardError; end
8
+
9
+ class NoCommand < Base; end
10
+ class InvalidCommand < Base; end
11
+ end
12
+ end
13
+ end
data/lib/uma/cli.rb ADDED
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uma/cli/error'
4
+ require 'uma/version'
5
+ require 'uma/server'
6
+ require 'uma/app'
7
+ require 'optparse'
8
+
9
+ module Uma
10
+ module CLI
11
+ def self.call(argv, env)
12
+ setup_controller(argv, env).tap {
13
+ it.run if !env[:norun]
14
+ }
15
+ end
16
+
17
+ def self.setup_controller(argv, env)
18
+ cmd = argv[0]
19
+ argv = argv[1..-1]
20
+
21
+ case cmd
22
+ when 'help'
23
+ Help.new(argv, env)
24
+ when 'serve'
25
+ Serve.new(argv, env)
26
+ when 'version'
27
+ Version.new(argv, env)
28
+ when '', nil
29
+ raise Error::NoCommand, ''
30
+ else
31
+ raise Error::InvalidCommand, "unrecognized command '#{cmd}'"
32
+ end
33
+ rescue => e
34
+ if env[:error_handler]
35
+ env[:error_handler].(e)
36
+ else
37
+ ErrorResponse.new(e, argv, env)
38
+ end
39
+ end
40
+
41
+ class Base
42
+ attr_reader :argv, :env
43
+
44
+ def initialize(argv, env)
45
+ @argv = argv
46
+ @env = env
47
+ end
48
+
49
+ def print_message(io, template, **)
50
+ io << "#{format(template, **)}\n"
51
+ end
52
+ end
53
+
54
+ CLI_HELP = <<~EOF
55
+
56
+
57
+ ●╭───╮●
58
+ │UMA│ A modern web server for Ruby
59
+ ●╰───╯● https://uma.noteflakes.com/
60
+
61
+
62
+ Uma version #{Uma::VERSION}
63
+
64
+ Usage: uma <COMMAND>
65
+
66
+ Commands:
67
+ serve Run a Rack application
68
+ help Print this message or the help of the given subcommand(s)
69
+ EOF
70
+
71
+ class Help < Base
72
+ def run
73
+ print_message(env[:io_out], CLI_HELP)
74
+ end
75
+ end
76
+
77
+ ERROR_RESPONSE_TEMPLATE = <<~EOF
78
+ Error: %<error_msg>s
79
+
80
+ Usage: uma <COMMAND>
81
+
82
+ Commands:
83
+ serve Run a Rack application
84
+ help Print this message or the help of the given subcommand(s)
85
+ EOF
86
+
87
+ class ErrorResponse < Base
88
+ def initialize(error, argv, env)
89
+ @error = error
90
+ @argv = argv
91
+ @env = env
92
+ end
93
+
94
+ def run
95
+ error_msg = @error.message
96
+ if error_msg.empty?
97
+ print_message(env[:io_err], CLI_HELP)
98
+ else
99
+ print_message(
100
+ env[:io_err], ERROR_RESPONSE_TEMPLATE,
101
+ error_msg:
102
+ )
103
+ end
104
+ end
105
+ end
106
+
107
+ class Version < Base
108
+ def run
109
+ print_message(env[:io_out], "Uma version #{Uma::VERSION}")
110
+ end
111
+ end
112
+
113
+ SERVE_BANNER = <<~EOF
114
+
115
+
116
+ ●╭───╮●
117
+ │UMA│ A modern web server for Ruby
118
+ ●╰───╯● https://uma.noteflakes.com/
119
+
120
+
121
+ Uma version #{Uma::VERSION}
122
+ EOF
123
+
124
+ class Serve < Base
125
+ attr_reader :server
126
+
127
+ def initialize(argv, env)
128
+ super
129
+ server_class = env[:server_class] || Uma::Server
130
+ @server = server_class.new(@env)
131
+ end
132
+
133
+ def run
134
+ parse_argv
135
+
136
+ print_message(@env[:io_err], SERVE_BANNER)
137
+ @server.start
138
+ rescue => e
139
+ if env[:error_handler]
140
+ env[:error_handler].(e)
141
+ else
142
+ ErrorResponse.new(e, @argv, @env).run
143
+ end
144
+ ensure
145
+ @server.stop
146
+ end
147
+
148
+ private
149
+
150
+ HELP_BANNER = <<~EOF
151
+
152
+
153
+ ●╭───╮●
154
+ │UMA│ A modern web server for Ruby
155
+ ●╰───╯● https://uma.noteflakes.com/
156
+
157
+
158
+ Usage: uma serve [OPTIONS] [location]
159
+
160
+ EOF
161
+
162
+ def parse_argv
163
+ parser = OptionParser.new do |o|
164
+ o.banner = HELP_BANNER
165
+
166
+ o.on('-b', '--bind BIND', String,
167
+ 'Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.') do
168
+ @env[:bind] ||= []
169
+ @env[:bind] << it
170
+ end
171
+
172
+ o.on('-s', '--silent', 'Silent mode') do
173
+ @env[:silent] = true
174
+ end
175
+
176
+ o.on('-h', '--help', 'Show this help message') do
177
+ puts o
178
+ exit
179
+ end
180
+
181
+ o.on('--no-server-headers', 'Don\'t include Server and Date headers') do
182
+ @env[:server_extensions] = false
183
+ end
184
+ end
185
+
186
+ parser.parse!(argv)
187
+ load_rackup_file(argv) if !@env[:connection_proc]
188
+ end
189
+
190
+ def load_rackup_file(argv)
191
+ location = File.expand_path(argv.first || '.')
192
+ if File.directory?(location)
193
+ fn = File.join(location, 'config.ru')
194
+ else
195
+ fn = location
196
+ end
197
+ app = App.new(fn)
198
+ @env[:connection_proc] = app.connection_proc
199
+ end
200
+ end
201
+ end
202
+ end
data/lib/uma/error.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uma
4
+ class Error < StandardError; end
5
+
6
+ class TimeoutError < Error; end
7
+ end