zapp 0.1.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -0
- data/Gemfile.lock +5 -3
- data/bin/zapp +1 -2
- data/examples/config/app.rb +5 -0
- data/examples/config/puma.rb +10 -0
- data/examples/config/zapp.rb +13 -0
- data/lib/rack/handler/{zap.rb → zapp.rb} +4 -3
- data/lib/zapp/cli.rb +5 -0
- data/lib/zapp/configuration.rb +10 -2
- data/lib/zapp/http_context/context.rb +10 -11
- data/lib/zapp/http_context/request.rb +10 -6
- data/lib/zapp/http_context/response.rb +10 -5
- data/lib/zapp/logger/base.rb +103 -0
- data/lib/zapp/logger.rb +21 -47
- data/lib/zapp/pipe.rb +14 -0
- data/lib/zapp/server.rb +38 -16
- data/lib/zapp/socket_pipe/receiver.rb +24 -0
- data/lib/zapp/socket_pipe/sender.rb +17 -0
- data/lib/zapp/version.rb +1 -1
- data/lib/zapp/worker/request_processor.rb +109 -0
- data/lib/zapp/worker.rb +18 -69
- data/lib/zapp/worker_pool.rb +17 -18
- data/lib/zapp.rb +18 -11
- data/zapp.gemspec +2 -0
- metadata +26 -100
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/examples/rails-app/.browserslistrc +0 -1
- data/examples/rails-app/.gitattributes +0 -10
- data/examples/rails-app/.gitignore +0 -40
- data/examples/rails-app/.ruby-version +0 -1
- data/examples/rails-app/Gemfile +0 -58
- data/examples/rails-app/Gemfile.lock +0 -253
- data/examples/rails-app/Rakefile +0 -8
- data/examples/rails-app/app/assets/config/manifest.js +0 -2
- data/examples/rails-app/app/assets/images/.keep +0 -0
- data/examples/rails-app/app/assets/stylesheets/application.css +0 -15
- data/examples/rails-app/app/channels/application_cable/channel.rb +0 -6
- data/examples/rails-app/app/channels/application_cable/connection.rb +0 -6
- data/examples/rails-app/app/controllers/application_controller.rb +0 -4
- data/examples/rails-app/app/controllers/concerns/.keep +0 -0
- data/examples/rails-app/app/helpers/application_helper.rb +0 -4
- data/examples/rails-app/app/javascript/channels/consumer.js +0 -6
- data/examples/rails-app/app/javascript/channels/index.js +0 -5
- data/examples/rails-app/app/javascript/packs/application.js +0 -13
- data/examples/rails-app/app/jobs/application_job.rb +0 -9
- data/examples/rails-app/app/mailers/application_mailer.rb +0 -6
- data/examples/rails-app/app/models/application_record.rb +0 -5
- data/examples/rails-app/app/models/concerns/.keep +0 -0
- data/examples/rails-app/app/views/layouts/application.html.erb +0 -16
- data/examples/rails-app/app/views/layouts/mailer.html.erb +0 -13
- data/examples/rails-app/app/views/layouts/mailer.text.erb +0 -1
- data/examples/rails-app/babel.config.js +0 -82
- data/examples/rails-app/bin/bundle +0 -118
- data/examples/rails-app/bin/rails +0 -7
- data/examples/rails-app/bin/rake +0 -7
- data/examples/rails-app/bin/setup +0 -38
- data/examples/rails-app/bin/spring +0 -16
- data/examples/rails-app/bin/webpack +0 -21
- data/examples/rails-app/bin/webpack-dev-server +0 -21
- data/examples/rails-app/bin/yarn +0 -19
- data/examples/rails-app/bin/zapp +0 -1
- data/examples/rails-app/config/application.rb +0 -24
- data/examples/rails-app/config/boot.rb +0 -6
- data/examples/rails-app/config/cable.yml +0 -10
- data/examples/rails-app/config/credentials.yml.enc +0 -1
- data/examples/rails-app/config/database.yml +0 -25
- data/examples/rails-app/config/environment.rb +0 -7
- data/examples/rails-app/config/environments/development.rb +0 -78
- data/examples/rails-app/config/environments/production.rb +0 -122
- data/examples/rails-app/config/environments/test.rb +0 -62
- data/examples/rails-app/config/initializers/application_controller_renderer.rb +0 -9
- data/examples/rails-app/config/initializers/assets.rb +0 -16
- data/examples/rails-app/config/initializers/backtrace_silencers.rb +0 -10
- data/examples/rails-app/config/initializers/content_security_policy.rb +0 -31
- data/examples/rails-app/config/initializers/cookies_serializer.rb +0 -7
- data/examples/rails-app/config/initializers/filter_parameter_logging.rb +0 -8
- data/examples/rails-app/config/initializers/inflections.rb +0 -17
- data/examples/rails-app/config/initializers/mime_types.rb +0 -5
- data/examples/rails-app/config/initializers/permissions_policy.rb +0 -12
- data/examples/rails-app/config/initializers/wrap_parameters.rb +0 -16
- data/examples/rails-app/config/locales/en.yml +0 -33
- data/examples/rails-app/config/puma.rb +0 -45
- data/examples/rails-app/config/routes.rb +0 -5
- data/examples/rails-app/config/spring.rb +0 -8
- data/examples/rails-app/config/storage.yml +0 -34
- data/examples/rails-app/config/webpack/development.js +0 -5
- data/examples/rails-app/config/webpack/environment.js +0 -3
- data/examples/rails-app/config/webpack/production.js +0 -5
- data/examples/rails-app/config/webpack/test.js +0 -5
- data/examples/rails-app/config/webpacker.yml +0 -92
- data/examples/rails-app/config/zapp.rb +0 -10
- data/examples/rails-app/config.ru +0 -7
- data/examples/rails-app/db/seeds.rb +0 -8
- data/examples/rails-app/lib/assets/.keep +0 -0
- data/examples/rails-app/lib/tasks/.keep +0 -0
- data/examples/rails-app/log/.keep +0 -0
- data/examples/rails-app/package.json +0 -17
- data/examples/rails-app/postcss.config.js +0 -12
- data/examples/rails-app/public/404.html +0 -67
- data/examples/rails-app/public/422.html +0 -67
- data/examples/rails-app/public/500.html +0 -66
- data/examples/rails-app/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/rails-app/public/apple-touch-icon.png +0 -0
- data/examples/rails-app/public/favicon.ico +0 -0
- data/examples/rails-app/public/robots.txt +0 -1
- data/examples/rails-app/storage/.keep +0 -0
- data/examples/rails-app/test/application_system_test_case.rb +0 -7
- data/examples/rails-app/test/channels/application_cable/connection_test.rb +0 -15
- data/examples/rails-app/test/controllers/.keep +0 -0
- data/examples/rails-app/test/fixtures/files/.keep +0 -0
- data/examples/rails-app/test/helpers/.keep +0 -0
- data/examples/rails-app/test/integration/.keep +0 -0
- data/examples/rails-app/test/mailers/.keep +0 -0
- data/examples/rails-app/test/models/.keep +0 -0
- data/examples/rails-app/test/system/.keep +0 -0
- data/examples/rails-app/test/test_helper.rb +0 -17
- data/examples/rails-app/tmp/.keep +0 -0
- data/examples/rails-app/tmp/pids/.keep +0 -0
- data/examples/rails-app/vendor/.keep +0 -0
- data/examples/rails-app/yarn.lock +0 -6973
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b4a4b6bdf9ac52a419e899e41344faf5ff15fdde704f70e93fe71cee334e70e
|
4
|
+
data.tar.gz: 4bd738d7ca1919ceef29612c5bf9a07a26ad8bda944b9adfc52197435dbb1a0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26554e64959a88e35bf1b7f9657909da61de5a92c980550df415ae0b5564f899b25922e1fbb69d79a39a72506fe903654a007555c8e85ce3b437eb1cbbf8ccb3
|
7
|
+
data.tar.gz: c790f81f5fa7d1867ee6f33c3fff92f3df44ece8051e9dc3f9ed260724dd0236a3d3fd1814ba3dcf54f3743713782445a5001f54fcb275872943665055593b64
|
data/.rubocop.yml
CHANGED
@@ -5,12 +5,17 @@ AllCops:
|
|
5
5
|
Exclude:
|
6
6
|
- examples/**/*
|
7
7
|
|
8
|
+
Gemspec/RequireMFA:
|
9
|
+
Enabled: false
|
10
|
+
|
8
11
|
Style/DocumentationMethod:
|
9
12
|
Enabled: false
|
10
13
|
Style/MissingElse:
|
11
14
|
Enabled: false
|
12
15
|
Style/StringLiterals:
|
13
16
|
EnforcedStyle: double_quotes
|
17
|
+
Style/InlineComment:
|
18
|
+
Enabled: false
|
14
19
|
Style/Copyright:
|
15
20
|
Enabled: false
|
16
21
|
Style/ConstantVisibility:
|
data/Gemfile.lock
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
zapp (0.1
|
4
|
+
zapp (0.2.1)
|
5
5
|
concurrent-ruby (~> 1.1.9)
|
6
6
|
puma (~> 5.5.2)
|
7
7
|
rack (~> 2.2.3)
|
8
8
|
rake (~> 13.0)
|
9
9
|
rspec (~> 3.0)
|
10
|
+
webrick
|
10
11
|
|
11
12
|
GEM
|
12
13
|
remote: https://rubygems.org/
|
13
14
|
specs:
|
14
15
|
ast (2.4.2)
|
15
16
|
coderay (1.1.3)
|
16
|
-
concurrent-ruby (1.1.
|
17
|
+
concurrent-ruby (1.1.10)
|
17
18
|
diff-lcs (1.4.4)
|
18
19
|
docile (1.4.0)
|
19
20
|
ffi (1.15.4)
|
@@ -53,7 +54,7 @@ GEM
|
|
53
54
|
method_source (~> 1.0)
|
54
55
|
puma (5.5.2)
|
55
56
|
nio4r (~> 2.0)
|
56
|
-
rack (2.2.
|
57
|
+
rack (2.2.4)
|
57
58
|
rainbow (3.0.0)
|
58
59
|
rake (13.0.6)
|
59
60
|
rb-fsevent (0.11.0)
|
@@ -95,6 +96,7 @@ GEM
|
|
95
96
|
simplecov_json_formatter (0.1.3)
|
96
97
|
thor (1.1.0)
|
97
98
|
unicode-display_width (2.1.0)
|
99
|
+
webrick (1.7.0)
|
98
100
|
|
99
101
|
PLATFORMS
|
100
102
|
x86_64-linux
|
data/bin/zapp
CHANGED
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative("app")
|
2
|
+
|
3
|
+
max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
|
4
|
+
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
|
5
|
+
threads(min_threads_count, max_threads_count)
|
6
|
+
worker_timeout(3600) if ENV.fetch("RAILS_ENV", "development") == "development"
|
7
|
+
port(ENV.fetch("PORT", 3000))
|
8
|
+
environment(ENV.fetch("RAILS_ENV", "development"))
|
9
|
+
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
10
|
+
app(App)
|
@@ -6,11 +6,12 @@ module Rack
|
|
6
6
|
module Handler
|
7
7
|
# Rack handler for the Zapp web server
|
8
8
|
class Zapp
|
9
|
+
register(:zapp, Rack::Handler::Zapp)
|
10
|
+
|
9
11
|
def self.run(app)
|
10
|
-
Zapp
|
12
|
+
Zapp.config.app = app
|
13
|
+
Zapp::Server.new.run
|
11
14
|
end
|
12
|
-
|
13
|
-
register(:zapp, Rack::Handler::Zapp)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
data/lib/zapp/cli.rb
CHANGED
@@ -27,6 +27,11 @@ module Zapp
|
|
27
27
|
parse_config_file(location: file)
|
28
28
|
end
|
29
29
|
|
30
|
+
opts.on("-v", "--version", "Prints the version of Zapp currently running") do
|
31
|
+
puts("Zapp v#{Zapp::VERSION}")
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
30
35
|
opts.on("-h", "--help", "Prints this help") do
|
31
36
|
puts(opts)
|
32
37
|
exit
|
data/lib/zapp/configuration.rb
CHANGED
@@ -12,6 +12,7 @@ module Zapp
|
|
12
12
|
:parallelism,
|
13
13
|
:threads_per_worker,
|
14
14
|
:logger_class,
|
15
|
+
:logger_out_io,
|
15
16
|
:log_requests,
|
16
17
|
:log_uncaught_errors,
|
17
18
|
:host,
|
@@ -33,6 +34,7 @@ module Zapp
|
|
33
34
|
|
34
35
|
# Default logging behavior
|
35
36
|
logger_class: Zapp::Logger,
|
37
|
+
logger_out_io: $stdout,
|
36
38
|
log_requests: true,
|
37
39
|
log_uncaught_errors: true,
|
38
40
|
|
@@ -44,7 +46,7 @@ module Zapp
|
|
44
46
|
{
|
45
47
|
Rack::RACK_VERSION => Rack::VERSION,
|
46
48
|
Rack::RACK_ERRORS => $stderr,
|
47
|
-
Rack::RACK_MULTITHREAD =>
|
49
|
+
Rack::RACK_MULTITHREAD => true,
|
48
50
|
Rack::RACK_MULTIPROCESS => true,
|
49
51
|
Rack::RACK_RUNONCE => false,
|
50
52
|
Rack::RACK_URL_SCHEME => %w[yes on 1].include?(ENV["HTTPS"]) ? "https" : "http"
|
@@ -72,7 +74,7 @@ module Zapp
|
|
72
74
|
@app = new unless new.nil?
|
73
75
|
|
74
76
|
@app ||= begin
|
75
|
-
raise(Zapp::
|
77
|
+
raise(Zapp::ZappError, "Missing rackup file '#{rackup_file}'") unless File.exist?(rackup_file)
|
76
78
|
|
77
79
|
rack_app, = rack_builder.parse_file(rackup_file)
|
78
80
|
|
@@ -98,6 +100,12 @@ module Zapp
|
|
98
100
|
@logger_class = new
|
99
101
|
end
|
100
102
|
|
103
|
+
def logger_out_io(new = nil)
|
104
|
+
return @logger_out_io if new.nil?
|
105
|
+
|
106
|
+
@logger_out_io = new
|
107
|
+
end
|
108
|
+
|
101
109
|
def log_requests(new = nil)
|
102
110
|
return @log_requests if new.nil?
|
103
111
|
|
@@ -1,31 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require_relative("request")
|
4
|
+
require_relative("response")
|
5
5
|
|
6
6
|
module Zapp
|
7
7
|
module HTTPContext
|
8
8
|
# Context containing request and response
|
9
9
|
class Context
|
10
|
-
attr_reader(:req, :res)
|
10
|
+
attr_reader(:req, :res, :socket)
|
11
11
|
|
12
|
-
def initialize(socket:)
|
12
|
+
def initialize(socket:, logger: Zapp::Logger)
|
13
13
|
@socket = socket
|
14
14
|
@req = Zapp::HTTPContext::Request.new(socket: socket)
|
15
15
|
@res = Zapp::HTTPContext::Response.new(socket: socket)
|
16
|
+
rescue Puma::HttpParserError => e
|
17
|
+
res.write(data: "Invalid HTTP request", status: 400, headers: {})
|
18
|
+
logger.warn("Puma parser error: #{e}")
|
19
|
+
logger.debug("HTTP request raw: #{context.req.raw}")
|
16
20
|
end
|
17
21
|
|
18
22
|
def close
|
19
23
|
@socket.close
|
20
24
|
end
|
21
25
|
|
22
|
-
def
|
23
|
-
|
24
|
-
clone_context.instance_variable_set(:@req, @req.dup)
|
25
|
-
clone_context.instance_variable_set(:@res, @res.dup)
|
26
|
-
clone_context.instance_variable_set(:@socket, @socket.dup)
|
27
|
-
|
28
|
-
clone_context
|
26
|
+
def client_closed?
|
27
|
+
req.data["HTTP_CONNECTION"] == "close"
|
29
28
|
end
|
30
29
|
end
|
31
30
|
end
|
@@ -1,27 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require("webrick")
|
4
|
+
|
3
5
|
module Zapp
|
4
6
|
module HTTPContext
|
5
7
|
# Represents an HTTP Request to be processed by a worker
|
6
8
|
class Request
|
7
9
|
attr_reader(:raw, :data, :body)
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
+
# Request parsing is done threaded, but not in separate Ractors.
|
12
|
+
# So we allocate an HTTP parser per thread and assign it to this hash key in Thread.current
|
13
|
+
PARSER_THREAD_HASH_KEY = "PUMA_PARSER_INSTANCE"
|
11
14
|
|
15
|
+
def initialize(socket:)
|
12
16
|
# Max Request size of 8KB TODO: Make a config value for this setting
|
13
17
|
@raw = socket.readpartial(8192)
|
14
18
|
@data = {}
|
15
|
-
end
|
16
19
|
|
17
|
-
def parse!(parser: Puma::HttpParser.new)
|
18
20
|
parser.execute(data, raw, 0)
|
21
|
+
|
19
22
|
@body = Zapp::InputStream.new(string: parser.body)
|
23
|
+
|
20
24
|
parser.reset
|
21
25
|
end
|
22
26
|
|
23
|
-
def
|
24
|
-
|
27
|
+
def parser
|
28
|
+
Thread.current[PARSER_THREAD_HASH_KEY] ||= Puma::HttpParser.new
|
25
29
|
end
|
26
30
|
end
|
27
31
|
end
|
@@ -4,21 +4,26 @@ module Zapp
|
|
4
4
|
module HTTPContext
|
5
5
|
# Represents an HTTP response being sent back to a client
|
6
6
|
class Response
|
7
|
+
attr_reader(:status, :data, :headers)
|
8
|
+
|
7
9
|
def initialize(socket:)
|
8
10
|
@socket = socket
|
9
11
|
end
|
10
12
|
|
11
|
-
# TODO: Add headers argument
|
12
13
|
def write(data:, status:, headers:)
|
13
|
-
|
14
|
+
@status = status
|
15
|
+
@data = data
|
16
|
+
@headers = headers
|
17
|
+
|
18
|
+
response = +"HTTP/1.1 #{status}\n"
|
14
19
|
|
15
|
-
response
|
20
|
+
response << "Content-Length: #{data.size}\n" unless headers["Content-Length"]
|
16
21
|
|
17
22
|
headers.each do |k, v|
|
18
|
-
response
|
23
|
+
response << "#{k}: #{v}\n"
|
19
24
|
end
|
20
25
|
|
21
|
-
response
|
26
|
+
response << "\n#{data}\n"
|
22
27
|
|
23
28
|
@socket.write(response)
|
24
29
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zapp
|
4
|
+
class Logger
|
5
|
+
# Base contains all the logging functionality and is included both as class and instance methods of Zap::Logger
|
6
|
+
# This allows logging without creating new instances,
|
7
|
+
# while allowing Ractors to create their own instances for thread safety
|
8
|
+
module Base
|
9
|
+
attr_writer(:level, :prefix)
|
10
|
+
|
11
|
+
LEVELS = { TRACE: 0, DEBUG: 1, INFO: 2, WARN: 3, ERROR: 4 }.freeze
|
12
|
+
|
13
|
+
FROZEN_ENV = ENV.map { |k, v| [k.freeze, v.freeze] }
|
14
|
+
.to_h.freeze
|
15
|
+
|
16
|
+
# The hash key in Ractor.current that stores the mutex for writing to output
|
17
|
+
OUT_IO_MUTEX_KEY = "ZAPP_LOGGER_OUT_IO_MUTEX"
|
18
|
+
|
19
|
+
def trace(msg) = log("TRACE", msg)
|
20
|
+
|
21
|
+
def debug(msg) = log("DEBUG", msg)
|
22
|
+
|
23
|
+
def info(msg) = log("INFO", msg)
|
24
|
+
|
25
|
+
def warn(msg) = log("WARN", msg)
|
26
|
+
|
27
|
+
def error(msg) = log("ERROR", msg)
|
28
|
+
|
29
|
+
def level
|
30
|
+
@level ||= begin
|
31
|
+
log_level = FROZEN_ENV["LOG_LEVEL"]
|
32
|
+
|
33
|
+
if log_level == "" || log_level.nil?
|
34
|
+
LEVELS[:DEBUG]
|
35
|
+
else
|
36
|
+
resolved_level = LEVELS[log_level.upcase.to_sym]
|
37
|
+
|
38
|
+
if resolved_level.nil?
|
39
|
+
raise(
|
40
|
+
Zapp::ZappError,
|
41
|
+
"Invalid log level '#{log_level.upcase}', must be one of [#{LEVELS.keys.join(', ')}]"
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
resolved_level
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def log(current_level, msg, **_tags)
|
51
|
+
return unless level <= LEVELS[current_level.to_sym]
|
52
|
+
|
53
|
+
write("--- #{prefix} [#{current_level}] #{msg}\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def flush
|
57
|
+
writing_thread_pool.wait_for_termination(0.1)
|
58
|
+
|
59
|
+
out_io_mutex.synchronize do
|
60
|
+
out.flush
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param new_out [IO]
|
65
|
+
def out=(new_out)
|
66
|
+
@out = new_out
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
|
71
|
+
# @return [IO]
|
72
|
+
def out
|
73
|
+
@out ||= Zapp.config.logger_out_io
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [String]
|
77
|
+
def prefix = @prefix ||= Ractor.current.name
|
78
|
+
|
79
|
+
def write(msg)
|
80
|
+
writing_thread_pool.post do
|
81
|
+
out_io_mutex.synchronize do
|
82
|
+
out.print(msg)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# We really just use this as a queue
|
88
|
+
# TODO: There's probably a smarter way of doing this with less overhead,
|
89
|
+
# TODO: or maybe we should just actually write logs multi-threaded
|
90
|
+
def writing_thread_pool
|
91
|
+
@writing_thread_pool ||= Concurrent::ThreadPoolExecutor.new(
|
92
|
+
min_threads: 1,
|
93
|
+
max_threads: 1,
|
94
|
+
max_queue: 100
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
def out_io_mutex
|
99
|
+
Ractor.current[OUT_IO_MUTEX_KEY] ||= Thread::Mutex.new
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/zapp/logger.rb
CHANGED
@@ -1,63 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative("logger/base")
|
4
|
+
|
3
5
|
module Zapp
|
4
|
-
# The default logger for
|
6
|
+
# The default logger for Zapp
|
5
7
|
class Logger
|
6
|
-
|
7
|
-
# This allows logging without creating new instances,
|
8
|
-
# while allowing Ractors to create their own instances for thread safety
|
9
|
-
module Base
|
10
|
-
attr_writer(:level, :prefix)
|
11
|
-
|
12
|
-
LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 }.freeze
|
13
|
-
|
14
|
-
def debug(msg)
|
15
|
-
log("DEBUG", msg)
|
16
|
-
end
|
17
|
-
|
18
|
-
def info(msg)
|
19
|
-
log("INFO", msg)
|
20
|
-
end
|
8
|
+
include(Zapp::Logger::Base)
|
21
9
|
|
22
|
-
|
23
|
-
|
24
|
-
|
10
|
+
def initialize
|
11
|
+
yield(self) if block_given?
|
12
|
+
end
|
25
13
|
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
class << self
|
15
|
+
# The hash key in Ractor.current that stores the global Zapp::Logger instance
|
16
|
+
GLOBAL_INSTANCE_KEY = "ZAPP_LOGGER_INSTANCE"
|
29
17
|
|
30
|
-
def
|
31
|
-
|
32
|
-
if LEVELS[ENV["ZAPP_LOG_LEVEL"]].nil?
|
33
|
-
raise(
|
34
|
-
Zapp::ZappError,
|
35
|
-
"Invalid log level '#{ENV['ZAP_LOG_LEVEL']}', must be one of [#{LEVELS.keys.join(', ')}]"
|
36
|
-
)
|
37
|
-
else
|
38
|
-
LEVELS[ENV["ZAP_LOG_LEVEL"]]
|
39
|
-
end
|
40
|
-
else
|
41
|
-
LEVELS[:DEBUG]
|
42
|
-
end
|
18
|
+
def instance
|
19
|
+
Ractor.current[GLOBAL_INSTANCE_KEY] ||= new
|
43
20
|
end
|
44
21
|
|
45
22
|
private
|
46
23
|
|
47
|
-
def
|
48
|
-
|
24
|
+
def method_missing(symbol, *args)
|
25
|
+
if respond_to_missing?(symbol)
|
26
|
+
instance.public_send(symbol, *args)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
49
30
|
end
|
50
|
-
end
|
51
|
-
include(Zapp::Logger::Base)
|
52
|
-
|
53
|
-
def initialize
|
54
|
-
@prefix = "Zap"
|
55
|
-
yield(self) if block_given?
|
56
|
-
end
|
57
31
|
|
58
|
-
|
59
|
-
|
60
|
-
|
32
|
+
def respond_to_missing?(symbol, include_private = false)
|
33
|
+
instance.respond_to?(symbol) || super(symbol, include_private)
|
34
|
+
end
|
61
35
|
end
|
62
36
|
end
|
63
37
|
end
|
data/lib/zapp/pipe.rb
ADDED
data/lib/zapp/server.rb
CHANGED
@@ -3,53 +3,75 @@
|
|
3
3
|
module Zapp
|
4
4
|
# The Zap HTTP Server, listens on a TCP connection and processes incoming requests
|
5
5
|
class Server
|
6
|
-
attr_reader(:
|
6
|
+
attr_reader(:worker_pool, :socket_pipe_receiver)
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
@
|
10
|
-
@
|
9
|
+
@socket_pipe = Zapp::Pipe.new
|
10
|
+
@context_pipe = Zapp::Pipe.new
|
11
|
+
|
12
|
+
@socket_pipe_receiver = Zapp::SocketPipe::Receiver.new(pipe: @socket_pipe)
|
13
|
+
|
14
|
+
@worker_pool = Zapp::WorkerPool.new(socket_pipe: @socket_pipe, context_pipe: @context_pipe)
|
11
15
|
end
|
12
16
|
|
13
17
|
def run
|
14
|
-
parser = Puma::HttpParser.new
|
15
|
-
|
16
18
|
log_start
|
17
19
|
|
18
20
|
loop do
|
19
|
-
socket =
|
20
|
-
next if socket.eof?
|
21
|
+
socket = socket_pipe_receiver.take
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
context.req.parse!(parser: parser)
|
23
|
+
next if socket.eof?
|
25
24
|
|
26
|
-
|
25
|
+
parsing_thread_pool.post do
|
26
|
+
ctx = Zapp::HTTPContext::Context.new(socket: socket)
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
worker_pool.process(context: ctx) unless ctx.client_closed? # Parsing failed
|
29
|
+
end
|
30
|
+
rescue Errno::ECONNRESET
|
31
|
+
next
|
31
32
|
end
|
32
33
|
rescue SignalException, IRB::Abort => e
|
33
34
|
shutdown(e)
|
34
35
|
end
|
35
36
|
|
36
37
|
def shutdown(err = nil)
|
37
|
-
Zapp::Logger.info("Received signal #{err}") unless err.nil?
|
38
|
+
Zapp::Logger.info("Received signal #{err.class.name}") unless err.nil?
|
38
39
|
Zapp::Logger.info("Gracefully shutting down workers, allowing request processing to finish")
|
39
40
|
|
40
41
|
worker_pool.drain
|
41
42
|
|
42
43
|
Zapp::Logger.info("Done. See you next time!")
|
44
|
+
Zapp::Logger.flush
|
43
45
|
end
|
44
46
|
|
45
47
|
private
|
46
48
|
|
47
49
|
def log_start
|
48
|
-
Zapp::Logger.info(
|
50
|
+
Zapp::Logger.info(
|
51
|
+
"
|
52
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
|
53
|
+
⚡ ███████╗ █████╗ ██████╗ ██████╗ ⚡
|
54
|
+
⚡ ╚══███╔╝██╔══██╗██╔══██╗██╔══██╗ ⚡
|
55
|
+
⚡ ███╔╝ ███████║██████╔╝██████╔╝ ⚡
|
56
|
+
⚡ ███╔╝ ██╔══██║██╔═══╝ ██╔═══╝ ⚡
|
57
|
+
⚡ ███████╗██║ ██║██║ ██║ ⚡
|
58
|
+
⚡ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ⚡
|
59
|
+
⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡
|
60
|
+
"
|
61
|
+
)
|
62
|
+
Zapp::Logger.info("Zapp version: #{Zapp::VERSION}")
|
49
63
|
Zapp::Logger.info("Environment: #{Zapp.config.mode}")
|
50
64
|
Zapp::Logger.info("Serving: #{Zapp.config.env[Rack::RACK_URL_SCHEME]}://#{Zapp.config.host}:#{Zapp.config.port}")
|
51
65
|
Zapp::Logger.info("Parallel workers: #{Zapp.config.parallelism}")
|
52
66
|
Zapp::Logger.info("Ready to accept requests")
|
53
67
|
end
|
68
|
+
|
69
|
+
def parsing_thread_pool
|
70
|
+
@parsing_thread_pool ||= Concurrent::ThreadPoolExecutor.new(
|
71
|
+
min_threads: Zapp.config.parallelism,
|
72
|
+
max_threads: Zapp.config.parallelism,
|
73
|
+
max_queue: Zapp.config.parallelism * 1_000
|
74
|
+
)
|
75
|
+
end
|
54
76
|
end
|
55
77
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zapp
|
4
|
+
module SocketPipe
|
5
|
+
class Receiver
|
6
|
+
attr_reader(:pipe, :raw_tcp_pipe)
|
7
|
+
|
8
|
+
def initialize(pipe:)
|
9
|
+
@pipe = pipe
|
10
|
+
@raw_tcp_pipe = Ractor.new(Zapp.config, name: "raw-tcp-pipe") do |config|
|
11
|
+
server = TCPServer.new(config.host, config.port)
|
12
|
+
|
13
|
+
loop do
|
14
|
+
Ractor.yield(server.accept)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def take
|
20
|
+
Ractor.select(pipe, raw_tcp_pipe)[1]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/zapp/version.rb
CHANGED