itsi-server 0.1.1 → 0.1.13
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.
Potentially problematic release.
This version of itsi-server might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +4417 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +94 -45
- data/ext/itsi_error/Cargo.toml +2 -0
- data/ext/itsi_error/src/from.rs +68 -0
- data/ext/itsi_error/src/lib.rs +18 -34
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +73 -13
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +100 -40
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
- data/ext/itsi_server/src/server/bind.rs +75 -31
- data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/cache_store.rs +74 -0
- data/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/ext/itsi_server/src/server/itsi_service.rs +172 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/listener.rs +332 -132
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
- data/ext/itsi_server/src/server/mod.rs +15 -2
- data/ext/itsi_server/src/server/process_worker.rs +229 -0
- data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +93 -0
- data/ext/itsi_server/src/server/static_file_server.rs +984 -0
- data/ext/itsi_server/src/server/thread_worker.rs +444 -0
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/tls.rs +187 -60
- data/ext/itsi_server/src/server/types.rs +43 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +225 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request.rb +87 -0
- data/lib/itsi/http_response.rb +39 -0
- data/lib/itsi/server/Itsi.rb +119 -0
- data/lib/itsi/server/config/dsl.rb +506 -0
- data/lib/itsi/server/config.rb +131 -0
- data/lib/itsi/server/default_app/default_app.rb +38 -0
- data/lib/itsi/server/default_app/index.html +91 -0
- data/lib/itsi/server/grpc_interface.rb +213 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +90 -9
- data/lib/itsi/standard_headers.rb +86 -0
- metadata +122 -31
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
| @@ -0,0 +1,506 @@ | |
| 1 | 
            +
            module Itsi
         | 
| 2 | 
            +
              class Server
         | 
| 3 | 
            +
                module Config
         | 
| 4 | 
            +
                  class DSL
         | 
| 5 | 
            +
                    attr_reader :parent, :children, :middleware, :controller_class, :routes, :methods, :protocols,
         | 
| 6 | 
            +
                                :hosts, :ports, :extensions, :content_types, :accepts, :options
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    def self.evaluate(config = Itsi::Server::Config.config_file_path)
         | 
| 9 | 
            +
                      new do
         | 
| 10 | 
            +
                        if config.is_a?(Proc)
         | 
| 11 | 
            +
                          instance_exec(&config)
         | 
| 12 | 
            +
                        else
         | 
| 13 | 
            +
                          code = IO.read(config)
         | 
| 14 | 
            +
                          instance_eval(code, config.to_s, 1)
         | 
| 15 | 
            +
                        end
         | 
| 16 | 
            +
                      end.options
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    def initialize(
         | 
| 20 | 
            +
                      parent = nil,
         | 
| 21 | 
            +
                      routes: [],
         | 
| 22 | 
            +
                      methods: [],
         | 
| 23 | 
            +
                      protocols: [],
         | 
| 24 | 
            +
                      hosts: [],
         | 
| 25 | 
            +
                      ports: [],
         | 
| 26 | 
            +
                      extensions: [],
         | 
| 27 | 
            +
                      content_types: [],
         | 
| 28 | 
            +
                      accepts: [],
         | 
| 29 | 
            +
                      controller: self,
         | 
| 30 | 
            +
                      &block
         | 
| 31 | 
            +
                    )
         | 
| 32 | 
            +
                      @parent           = parent
         | 
| 33 | 
            +
                      @children         = []
         | 
| 34 | 
            +
                      @middleware       = {}
         | 
| 35 | 
            +
                      @controller_class = nil
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      @controller = controller
         | 
| 38 | 
            +
                      # We'll store our array of route specs (strings or a single Regexp).
         | 
| 39 | 
            +
                      @routes = Array(routes).flatten
         | 
| 40 | 
            +
                      @methods = methods.map { |s| s.is_a?(Regexp) ? s : s.to_s }
         | 
| 41 | 
            +
                      @protocols = protocols.map { |s| s.is_a?(Regexp) ? s : s.to_s }
         | 
| 42 | 
            +
                      @hosts = hosts.map { |s| s.is_a?(Regexp) ? s : s.to_s }
         | 
| 43 | 
            +
                      @ports = ports.map { |s| s.is_a?(Regexp) ? s : s.to_s }
         | 
| 44 | 
            +
                      @extensions = extensions.map { |s| s.is_a?(Regexp) ? s : s.to_s }
         | 
| 45 | 
            +
                      @content_types = content_types.map { |s| s.is_a?(Regexp) ? s : s.to_s }
         | 
| 46 | 
            +
                      @accepts = accepts.map { |s| s.is_a?(Regexp) ? s : s.to_s }
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                      @options = {
         | 
| 49 | 
            +
                        middleware_loaders: [],
         | 
| 50 | 
            +
                        middleware_loader: lambda do
         | 
| 51 | 
            +
                          @options[:middleware_loaders].each(&:call)
         | 
| 52 | 
            +
                          flatten_routes
         | 
| 53 | 
            +
                        end
         | 
| 54 | 
            +
                      }
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      instance_exec(&block)
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
             | 
| 60 | 
            +
                    def workers(workers)
         | 
| 61 | 
            +
                      raise "Workers must be set at the root" unless @parent.nil?
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                      @options[:workers] = [workers.to_i, 1].max
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    def threads(threads)
         | 
| 67 | 
            +
                      raise "Threads must be set at the root" unless @parent.nil?
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      @options[:threads] = [threads.to_i, 1].max
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    def oob_gc_responses_threshold(threshold)
         | 
| 73 | 
            +
                      raise "OOB GC responses threshold must be set at the root" unless @parent.nil?
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                      @options[:oob_gc_responses_threshold] = threshold.to_i
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                    def log_level(level)
         | 
| 79 | 
            +
                      raise "Log level must be set at the root" unless @parent.nil?
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                      @options[:log_level] = level.to_s
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    def log_format(format)
         | 
| 85 | 
            +
                      raise "Log format must be set at the root" unless @parent.nil?
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      case format.to_s
         | 
| 88 | 
            +
                      when "auto" then nil
         | 
| 89 | 
            +
                      when "ansi" then ENV["ITSI_LOG_ANSI"] = "true"
         | 
| 90 | 
            +
                      when "json", "plain" then ENV["ITSI_LOG_PLAIN"] = "true"
         | 
| 91 | 
            +
                      else raise "Invalid log format '#{format}'"
         | 
| 92 | 
            +
                      end
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    def get(route, app_proc = nil, &blk)
         | 
| 96 | 
            +
                      endpoint(route, :get, app_proc, &blk)
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    def post(route, app_proc = nil, &blk)
         | 
| 100 | 
            +
                      endpoint(route, :post, app_proc, &blk)
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    def put(route, app_proc = nil, &blk)
         | 
| 104 | 
            +
                      endpoint(route, :put, app_proc, &blk)
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    def delete(route, app_proc = nil, &blk)
         | 
| 108 | 
            +
                      endpoint(route, :delete, app_proc, &blk)
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    def patch(route, app_proc = nil, &blk)
         | 
| 112 | 
            +
                      endpoint(route, :patch, app_proc, &blk)
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    def endpoint(route, method, app_proc = nil, &blk)
         | 
| 116 | 
            +
                      raise "You can't use both a block and an explicit handler in the same endpoint" if blk && app_proc
         | 
| 117 | 
            +
                      raise "You must provide either a block or an explicit handler for the endpoint" if app_proc.nil? && blk.nil?
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                      app_proc = @controller.method(app_proc).to_proc if app_proc.is_a?(Symbol)
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                      app_proc ||= blk
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                      location(route, methods: [method]) do
         | 
| 124 | 
            +
                        @middleware[:app] = { app_proc: app_proc }
         | 
| 125 | 
            +
                      end
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    def grpc(handler, **)
         | 
| 129 | 
            +
                      if @middleware[:app] && @middleware[:app][:request_type].to_s != "grpc"
         | 
| 130 | 
            +
                        raise "App has already been set. You can use only one of `run` and `rackup_file` or `grpc` per location"
         | 
| 131 | 
            +
                      end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                      @middleware[:app] ||= {
         | 
| 134 | 
            +
                        request_type: "grpc",
         | 
| 135 | 
            +
                        impls: []
         | 
| 136 | 
            +
                      }
         | 
| 137 | 
            +
                      @middleware[:app][:impls] << handler
         | 
| 138 | 
            +
                      @middleware[:app][:app_proc] = Itsi::Server::GrpcInterface.for(@middleware[:app][:impls])
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    def run(app, sendfile: true)
         | 
| 142 | 
            +
                      if @options[:app_loader]
         | 
| 143 | 
            +
                        raise "App has already been set. You can use only one of `run` and `rackup_file` per location"
         | 
| 144 | 
            +
                      end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                      if @parent.nil?
         | 
| 147 | 
            +
                        @options[:app_loader] = -> { { "app_proc" => Itsi::Server::RackInterface.for(app) } }
         | 
| 148 | 
            +
                      else
         | 
| 149 | 
            +
                        @middleware[:app] = { app_proc: Itsi::Server::RackInterface.for(app), sendfile: sendfile }
         | 
| 150 | 
            +
                      end
         | 
| 151 | 
            +
                    end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    def rackup_file(rackup_file)
         | 
| 154 | 
            +
                      if @options[:app_loader]
         | 
| 155 | 
            +
                        raise "App has already been set. You can use only one of `run` and `rackup_file` per location"
         | 
| 156 | 
            +
                      end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                      raise "Rackup file #{rackup_file} doesn't exist" unless File.exist?(rackup_file)
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                      if @parent.nil?
         | 
| 161 | 
            +
                        @options[:app_loader] = -> { { "app_proc" => Itsi::Server::RackInterface.for(rackup_file) } }
         | 
| 162 | 
            +
                      else
         | 
| 163 | 
            +
                        @middleware[:app] = { app_proc: Itsi::Server::RackInterface.for(rackup_file) }
         | 
| 164 | 
            +
                      end
         | 
| 165 | 
            +
                    end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                    def include(path)
         | 
| 168 | 
            +
                      code = IO.read("#{path}.rb")
         | 
| 169 | 
            +
                      instance_eval(code, "#{path}.rb", 1)
         | 
| 170 | 
            +
                    end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                    def bind(bind_str)
         | 
| 173 | 
            +
                      raise "Bind must be set at the root" unless @parent.nil?
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                      @options[:binds] ||= []
         | 
| 176 | 
            +
                      @options[:binds] << bind_str.to_s
         | 
| 177 | 
            +
                    end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                    def after_fork(&block)
         | 
| 180 | 
            +
                      raise "After fork must be set at the root" unless @parent.nil?
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                      @options[:hooks] ||= {}
         | 
| 183 | 
            +
                      @options[:hooks][:after_fork] = block
         | 
| 184 | 
            +
                    end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                    def before_fork(&block)
         | 
| 187 | 
            +
                      raise "Before fork must be set at the root" unless @parent.nil?
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                      @options[:hooks] ||= {}
         | 
| 190 | 
            +
                      @options[:hooks][:before_fork] = block
         | 
| 191 | 
            +
                    end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                    def after_memory_threshold_reached(&block)
         | 
| 194 | 
            +
                      raise "Before fork must be set at the root" unless @parent.nil?
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                      @options[:hooks] ||= {}
         | 
| 197 | 
            +
                      @options[:hooks][:after_memory_threshold_reached] = block
         | 
| 198 | 
            +
                    end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                    def worker_memory_limit(memory_limit)
         | 
| 201 | 
            +
                      raise "Worker memory limit must be set at the root" unless @parent.nil?
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                      @options[:worker_memory_limit] = memory_limit
         | 
| 204 | 
            +
                    end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                    def multithreaded_reactor(multithreaded)
         | 
| 207 | 
            +
                      raise "Multithreaded reactor must be set at the root" unless @parent.nil?
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                      @options[:multithreaded_reactor] = !!multithreaded
         | 
| 210 | 
            +
                    end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                    def watch(path, commands)
         | 
| 213 | 
            +
                      raise "Watch be set at the root" unless @parent.nil?
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                      @options[:notify_watchers] ||= []
         | 
| 216 | 
            +
                      @options[:notify_watchers] << [path, commands]
         | 
| 217 | 
            +
                    end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                    def fiber_scheduler(klass_name = true)
         | 
| 220 | 
            +
                      raise "Fiber scheduler must be set at the root" unless @parent.nil?
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                      klass_name = "Itsi::Scheduler" if klass_name == true
         | 
| 223 | 
            +
                      @options[:scheduler_class] = klass_name if klass_name
         | 
| 224 | 
            +
                    end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                    def preload(preload)
         | 
| 227 | 
            +
                      raise "Preload must be set at the root" unless @parent.nil?
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                      @options[:preload] = preload
         | 
| 230 | 
            +
                    end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                    def shutdown_timeout(shutdown_timeout)
         | 
| 233 | 
            +
                      raise "Shutdown timeout must be set at the root" unless @parent.nil?
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                      @options[:shutdown_timeout] = shutdown_timeout.to_f
         | 
| 236 | 
            +
                    end
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                    def script_name(script_name)
         | 
| 239 | 
            +
                      raise "Script name must be set at the root" unless @parent.nil?
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                      @options[:script_name] = script_name.to_s
         | 
| 242 | 
            +
                    end
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                    def stream_body(stream_body)
         | 
| 245 | 
            +
                      raise "Stream body must be set at the root" unless @parent.nil?
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                      @options[:stream_body] = !!stream_body
         | 
| 248 | 
            +
                    end
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                    def location(*routes, methods: [], protocols: [], hosts: [], ports: [], extensions: [], content_types: [],
         | 
| 251 | 
            +
                                 accepts: [], &block)
         | 
| 252 | 
            +
                      build_child = lambda {
         | 
| 253 | 
            +
                        @children << DSL.new(
         | 
| 254 | 
            +
                          self,
         | 
| 255 | 
            +
                          routes: routes,
         | 
| 256 | 
            +
                          methods: Array(methods) | self.methods,
         | 
| 257 | 
            +
                          protocols: Array(protocols) | self.protocols,
         | 
| 258 | 
            +
                          hosts: Array(hosts) | self.hosts,
         | 
| 259 | 
            +
                          ports: Array(ports) | self.ports,
         | 
| 260 | 
            +
                          extensions: Array(extensions) | self.extensions,
         | 
| 261 | 
            +
                          content_types: Array(content_types) | self.content_types,
         | 
| 262 | 
            +
                          accepts: Array(accepts) | self.accepts,
         | 
| 263 | 
            +
                          controller: @controller,
         | 
| 264 | 
            +
                          &block
         | 
| 265 | 
            +
                        )
         | 
| 266 | 
            +
                      }
         | 
| 267 | 
            +
                      if @parent.nil?
         | 
| 268 | 
            +
                        @options[:middleware_loaders] << build_child
         | 
| 269 | 
            +
                      else
         | 
| 270 | 
            +
                        build_child[]
         | 
| 271 | 
            +
                      end
         | 
| 272 | 
            +
                    end
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                    def log_requests(**args)
         | 
| 275 | 
            +
                      @middleware[:log_requests] = args
         | 
| 276 | 
            +
                    end
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                    def allow_list(**args)
         | 
| 279 | 
            +
                      args[:allowed_patterns] = Array(args[:allowed_patterns]).map do |pattern|
         | 
| 280 | 
            +
                        if pattern.is_a?(Regexp)
         | 
| 281 | 
            +
                          pattern.source
         | 
| 282 | 
            +
                        else
         | 
| 283 | 
            +
                          pattern
         | 
| 284 | 
            +
                        end
         | 
| 285 | 
            +
                      end
         | 
| 286 | 
            +
                      @middleware[:allow_list] = args
         | 
| 287 | 
            +
                    end
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                    def deny_list(**args)
         | 
| 290 | 
            +
                      args[:denied_patterns] = Array(args[:denied_patterns]).map do |pattern|
         | 
| 291 | 
            +
                        if pattern.is_a?(Regexp)
         | 
| 292 | 
            +
                          pattern.source
         | 
| 293 | 
            +
                        else
         | 
| 294 | 
            +
                          pattern
         | 
| 295 | 
            +
                        end
         | 
| 296 | 
            +
                      end
         | 
| 297 | 
            +
                      @middleware[:deny_list] = args
         | 
| 298 | 
            +
                    end
         | 
| 299 | 
            +
             | 
| 300 | 
            +
                    def controller(controller)
         | 
| 301 | 
            +
                      raise "`controller` must be set inside a location block" if @parent.nil?
         | 
| 302 | 
            +
             | 
| 303 | 
            +
                      @controller = controller
         | 
| 304 | 
            +
                    end
         | 
| 305 | 
            +
             | 
| 306 | 
            +
                    def auth_basic(**args)
         | 
| 307 | 
            +
                      raise "`auth_basic` must be set inside a location block" if @parent.nil?
         | 
| 308 | 
            +
             | 
| 309 | 
            +
                      @middleware[:auth_basic] = args
         | 
| 310 | 
            +
                    end
         | 
| 311 | 
            +
             | 
| 312 | 
            +
                    def redirect(**args)
         | 
| 313 | 
            +
                      raise "`redirect` must be set inside a location block" if @parent.nil?
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                      @middleware[:redirect] = args
         | 
| 316 | 
            +
                    end
         | 
| 317 | 
            +
             | 
| 318 | 
            +
                    def proxy(**args)
         | 
| 319 | 
            +
                      raise "`proxy` must be set inside a location block" if @parent.nil?
         | 
| 320 | 
            +
             | 
| 321 | 
            +
                      @middleware[:proxy] = args
         | 
| 322 | 
            +
                    end
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                    def auth_jwt(**args)
         | 
| 325 | 
            +
                      raise "`auth_jwt` must be set inside a location block" if @parent.nil?
         | 
| 326 | 
            +
             | 
| 327 | 
            +
                      @middleware[:auth_jwt] = args
         | 
| 328 | 
            +
                    end
         | 
| 329 | 
            +
             | 
| 330 | 
            +
                    def auth_api_key(**args)
         | 
| 331 | 
            +
                      raise "`auth_api_key` must be set inside a location block" if @parent.nil?
         | 
| 332 | 
            +
             | 
| 333 | 
            +
                      @middleware[:auth_api_key] = args
         | 
| 334 | 
            +
                    end
         | 
| 335 | 
            +
             | 
| 336 | 
            +
                    def compress(**args)
         | 
| 337 | 
            +
                      raise "`compress` must be set inside a location block" if @parent.nil?
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                      @middleware[:compression] = args
         | 
| 340 | 
            +
                    end
         | 
| 341 | 
            +
             | 
| 342 | 
            +
                    def request_headers(**args)
         | 
| 343 | 
            +
                      raise "`request_headers` must be set inside a location block" if @parent.nil?
         | 
| 344 | 
            +
             | 
| 345 | 
            +
                      @middleware[:request_headers] = args
         | 
| 346 | 
            +
                    end
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                    def response_headers(**args)
         | 
| 349 | 
            +
                      raise "`response_headers` must be set inside a location block" if @parent.nil?
         | 
| 350 | 
            +
             | 
| 351 | 
            +
                      @middleware[:response_headers] = args
         | 
| 352 | 
            +
                    end
         | 
| 353 | 
            +
             | 
| 354 | 
            +
                    def rate_limit(**args)
         | 
| 355 | 
            +
                      raise "`rate_limit` must be set inside a location block" if @parent.nil?
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                      @middleware[:rate_limit] = args
         | 
| 358 | 
            +
                    end
         | 
| 359 | 
            +
             | 
| 360 | 
            +
                    def cache_control(**args)
         | 
| 361 | 
            +
                      raise "`cache_control` must be set inside a location block" if @parent.nil?
         | 
| 362 | 
            +
             | 
| 363 | 
            +
                      @middleware[:cache_control] = args
         | 
| 364 | 
            +
                    end
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                    def etag(**args)
         | 
| 367 | 
            +
                      raise "`etag` must be set inside a location block" if @parent.nil?
         | 
| 368 | 
            +
             | 
| 369 | 
            +
                      @middleware[:etag] = args
         | 
| 370 | 
            +
                    end
         | 
| 371 | 
            +
             | 
| 372 | 
            +
                    def intrusion_protection(**args)
         | 
| 373 | 
            +
                      raise "`intrusion_protection` must be set inside a location block" if @parent.nil?
         | 
| 374 | 
            +
             | 
| 375 | 
            +
                      args[:banned_url_patterns] = Array(args[:banned_url_patterns]).map do |pattern|
         | 
| 376 | 
            +
                        if pattern.is_a?(Regexp)
         | 
| 377 | 
            +
                          pattern.source
         | 
| 378 | 
            +
                        else
         | 
| 379 | 
            +
                          pattern
         | 
| 380 | 
            +
                        end
         | 
| 381 | 
            +
                      end
         | 
| 382 | 
            +
                      @middleware[:intrusion_protection] = args
         | 
| 383 | 
            +
                    end
         | 
| 384 | 
            +
             | 
| 385 | 
            +
                    def cors(**args)
         | 
| 386 | 
            +
                      raise "`cors` must be set inside a location block" if @parent.nil?
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                      @middleware[:cors] = args
         | 
| 389 | 
            +
                    end
         | 
| 390 | 
            +
             | 
| 391 | 
            +
                    def static_assets(**args)
         | 
| 392 | 
            +
                      raise "`static_assets` must be set inside a location block" if @parent.nil?
         | 
| 393 | 
            +
             | 
| 394 | 
            +
                      root_dir = args[:root_dir] || "."
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                      if !File.exist?(root_dir)
         | 
| 397 | 
            +
                        warn "Warning: static_assets root_dir '#{root_dir}' does not exist!"
         | 
| 398 | 
            +
                      elsif !File.directory?(root_dir)
         | 
| 399 | 
            +
                        warn "Warning: static_assets root_dir '#{root_dir}' is not a directory!"
         | 
| 400 | 
            +
                      end
         | 
| 401 | 
            +
             | 
| 402 | 
            +
                      args[:relative_path] = true unless args.key?(:relative_path)
         | 
| 403 | 
            +
             | 
| 404 | 
            +
                      location(/(?<path_suffix>.*)/, extensions: args[:allowed_extensions] || []) do
         | 
| 405 | 
            +
                        @middleware[:static_assets] = args
         | 
| 406 | 
            +
                      end
         | 
| 407 | 
            +
                    end
         | 
| 408 | 
            +
             | 
| 409 | 
            +
                    def file_server(**args)
         | 
| 410 | 
            +
                      raise "`file_server` must be set inside a location block" if @parent.nil?
         | 
| 411 | 
            +
             | 
| 412 | 
            +
                      # Forward to static_assets for implementation
         | 
| 413 | 
            +
                      puts "Note: file_server is an alias for static_assets"
         | 
| 414 | 
            +
                      static_assets(**args)
         | 
| 415 | 
            +
                    end
         | 
| 416 | 
            +
             | 
| 417 | 
            +
                    def flatten_routes
         | 
| 418 | 
            +
                      result = []
         | 
| 419 | 
            +
                      result.concat(@children.flat_map(&:flatten_routes))
         | 
| 420 | 
            +
                      route_options = paths_from_parent
         | 
| 421 | 
            +
                      if route_options
         | 
| 422 | 
            +
                        result << deep_stringify_keys(
         | 
| 423 | 
            +
                          {
         | 
| 424 | 
            +
                            route: Regexp.new("^#{route_options}$"),
         | 
| 425 | 
            +
                            methods: @methods.any? ? @methods : nil,
         | 
| 426 | 
            +
                            protocols: @protocols.any? ? @protocols : nil,
         | 
| 427 | 
            +
                            hosts: @hosts.any? ? @hosts : nil,
         | 
| 428 | 
            +
                            ports: @ports.any? ? @ports : nil,
         | 
| 429 | 
            +
                            extensions: @extensions.any? ? @extensions : nil,
         | 
| 430 | 
            +
                            content_types: @content_types.any? ? @content_types : nil,
         | 
| 431 | 
            +
                            accepts: @accepts.any? ? @accepts : nil,
         | 
| 432 | 
            +
                            middleware: effective_middleware
         | 
| 433 | 
            +
                          }
         | 
| 434 | 
            +
                        )
         | 
| 435 | 
            +
                      end
         | 
| 436 | 
            +
                      result
         | 
| 437 | 
            +
                    end
         | 
| 438 | 
            +
             | 
| 439 | 
            +
                    def paths_from_parent
         | 
| 440 | 
            +
                      return nil unless @routes.any?
         | 
| 441 | 
            +
             | 
| 442 | 
            +
                      route_or_str = @routes.map do |seg|
         | 
| 443 | 
            +
                        case seg
         | 
| 444 | 
            +
                        when Regexp
         | 
| 445 | 
            +
                          seg.source
         | 
| 446 | 
            +
                        when /^:([A-Za-z_]\w*)(?:\(([^)]*)\))?$/
         | 
| 447 | 
            +
                          param_name = Regexp.last_match(1)
         | 
| 448 | 
            +
                          custom     = Regexp.last_match(2)
         | 
| 449 | 
            +
                          if custom && !custom.empty?
         | 
| 450 | 
            +
                            "(?<#{param_name}>#{custom})"
         | 
| 451 | 
            +
                          else
         | 
| 452 | 
            +
                            "(?<#{param_name}>[^/]+)"
         | 
| 453 | 
            +
                          end
         | 
| 454 | 
            +
                        when /\*/
         | 
| 455 | 
            +
                          seg.gsub(/\*/, ".*")
         | 
| 456 | 
            +
                        else
         | 
| 457 | 
            +
                          Regexp.escape(seg).gsub(%r{/$}, ".*")
         | 
| 458 | 
            +
                        end
         | 
| 459 | 
            +
                      end.join("|")
         | 
| 460 | 
            +
             | 
| 461 | 
            +
                      if parent.paths_from_parent && parent.paths_from_parent != "(?:/.*)"
         | 
| 462 | 
            +
                        "#{parent.paths_from_parent}#{route_or_str != "" ? "(?:#{route_or_str})" : ""}"
         | 
| 463 | 
            +
                      else
         | 
| 464 | 
            +
                        route_or_str = "/#{route_or_str}" unless route_or_str.start_with?("/")
         | 
| 465 | 
            +
                        "(?:#{route_or_str})"
         | 
| 466 | 
            +
                      end
         | 
| 467 | 
            +
                    end
         | 
| 468 | 
            +
             | 
| 469 | 
            +
                    def effective_middleware
         | 
| 470 | 
            +
                      merged = merge_ancestor_middleware
         | 
| 471 | 
            +
                      merged.map { |k, v| { type: k.to_s, parameters: v } }
         | 
| 472 | 
            +
                    end
         | 
| 473 | 
            +
             | 
| 474 | 
            +
                    def merge_ancestor_middleware
         | 
| 475 | 
            +
                      chain = []
         | 
| 476 | 
            +
                      node = self
         | 
| 477 | 
            +
                      while node
         | 
| 478 | 
            +
                        chain << node
         | 
| 479 | 
            +
                        node = node.parent
         | 
| 480 | 
            +
                      end
         | 
| 481 | 
            +
                      chain.reverse!
         | 
| 482 | 
            +
             | 
| 483 | 
            +
                      merged = {}
         | 
| 484 | 
            +
                      chain.each do |n|
         | 
| 485 | 
            +
                        n.middleware.each do |k, v|
         | 
| 486 | 
            +
                          merged[k] = v
         | 
| 487 | 
            +
                        end
         | 
| 488 | 
            +
                      end
         | 
| 489 | 
            +
                      merged
         | 
| 490 | 
            +
                    end
         | 
| 491 | 
            +
             | 
| 492 | 
            +
                    def deep_stringify_keys(obj)
         | 
| 493 | 
            +
                      case obj
         | 
| 494 | 
            +
                      when Hash
         | 
| 495 | 
            +
                        obj.transform_keys!(&:to_s)
         | 
| 496 | 
            +
                        obj.transform_values! { |v| deep_stringify_keys(v) }
         | 
| 497 | 
            +
                      when Array
         | 
| 498 | 
            +
                        obj.map { |v| deep_stringify_keys(v) }
         | 
| 499 | 
            +
                      else
         | 
| 500 | 
            +
                        obj
         | 
| 501 | 
            +
                      end
         | 
| 502 | 
            +
                    end
         | 
| 503 | 
            +
                  end
         | 
| 504 | 
            +
                end
         | 
| 505 | 
            +
              end
         | 
| 506 | 
            +
            end
         | 
| @@ -0,0 +1,131 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Itsi
         | 
| 4 | 
            +
              class Server
         | 
| 5 | 
            +
                module Config
         | 
| 6 | 
            +
                  require_relative "config/dsl"
         | 
| 7 | 
            +
                  require_relative "default_app/default_app"
         | 
| 8 | 
            +
                  require "debug"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  ITSI_DEFAULT_CONFIG_FILE = "Itsi.rb"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def self.save_argv!
         | 
| 13 | 
            +
                    @argv ||= ARGV[0...ARGV.index("--listeners")]
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # The configuration used when launching the Itsi server are evaluated in the following precedence:
         | 
| 17 | 
            +
                  # 1. CLI Args.
         | 
| 18 | 
            +
                  # 2. Itsi.rb file.
         | 
| 19 | 
            +
                  # 3. Default values.
         | 
| 20 | 
            +
                  def self.build_config(args, config_file_path, builder_proc)
         | 
| 21 | 
            +
                    itsifile_config = \
         | 
| 22 | 
            +
                      if builder_proc
         | 
| 23 | 
            +
                        DSL.evaluate(builder_proc)
         | 
| 24 | 
            +
                      elsif File.exist?(config_file_path.to_s)
         | 
| 25 | 
            +
                        DSL.evaluate(config_file_path)
         | 
| 26 | 
            +
                      else
         | 
| 27 | 
            +
                        {}
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
                    args.transform_keys!(&:to_sym)
         | 
| 30 | 
            +
                    itsifile_config.transform_keys!(&:to_sym)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    # We'll preload while we load config, if enabled.
         | 
| 33 | 
            +
                    middleware_loader = itsifile_config.fetch(:middleware_loader, -> {})
         | 
| 34 | 
            +
                    default_app_loader = itsifile_config.fetch(:app_loader) do
         | 
| 35 | 
            +
                      rackup_file_path = args.fetch(:rackup_file, "./config.ru")
         | 
| 36 | 
            +
                      if File.exist?(rackup_file_path)
         | 
| 37 | 
            +
                        lambda {
         | 
| 38 | 
            +
                          { "app_proc" => Itsi::Server::RackInterface.for(rackup_file_path) }
         | 
| 39 | 
            +
                        }
         | 
| 40 | 
            +
                      else
         | 
| 41 | 
            +
                        DEFAULT_APP
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                    preload = args.fetch(:preload) { itsifile_config.fetch(:preload, false) }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    case preload
         | 
| 47 | 
            +
                    # If we preload everything, then we'll load middleware and default rack app ahead of time
         | 
| 48 | 
            +
                    when true
         | 
| 49 | 
            +
                      preloaded_middleware = middleware_loader.call
         | 
| 50 | 
            +
                      preloaded_app = default_app_loader.call
         | 
| 51 | 
            +
                      middleware_loader = -> { preloaded_middleware }
         | 
| 52 | 
            +
                      default_app_loader = -> { preloaded_app }
         | 
| 53 | 
            +
                    # If we're just preloading a specific gem group, we'll do that here too
         | 
| 54 | 
            +
                    when Symbol
         | 
| 55 | 
            +
                      Bundler.require(preload)
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    {
         | 
| 59 | 
            +
                      workers: args.fetch(:workers) { itsifile_config.fetch(:workers, Etc.nprocessors) },
         | 
| 60 | 
            +
                      worker_memory_limit: args.fetch(:worker_memory_limit) { itsifile_config.fetch(:worker_memory_limit, nil) },
         | 
| 61 | 
            +
                      silence: args.fetch(:silence) { itsifile_config.fetch(:silence, false) },
         | 
| 62 | 
            +
                      shutdown_timeout: args.fetch(:shutdown_timeout) { itsifile_config.fetch(:shutdown_timeout, 5) },
         | 
| 63 | 
            +
                      hooks: itsifile_config.fetch(:hooks, nil),
         | 
| 64 | 
            +
                      preload: !!preload,
         | 
| 65 | 
            +
                      notify_watchers: itsifile_config.fetch(:notify_watchers, nil),
         | 
| 66 | 
            +
                      threads: args.fetch(:threads) { itsifile_config.fetch(:threads, 1) },
         | 
| 67 | 
            +
                      script_name: args.fetch(:script_name) { itsifile_config.fetch(:script_name, "") },
         | 
| 68 | 
            +
                      streamable_body: args.fetch(:streamable_body) { itsifile_config.fetch(:streamable_body, false) },
         | 
| 69 | 
            +
                      multithreaded_reactor: args.fetch(:multithreaded_reactor) do
         | 
| 70 | 
            +
                        itsifile_config.fetch(:multithreaded_reactor, true)
         | 
| 71 | 
            +
                      end,
         | 
| 72 | 
            +
                      scheduler_class: args.fetch(:scheduler_class) { itsifile_config.fetch(:scheduler_class, nil) },
         | 
| 73 | 
            +
                      oob_gc_responses_threshold: args.fetch(:oob_gc_responses_threshold) do
         | 
| 74 | 
            +
                        itsifile_config.fetch(:oob_gc_responses_threshold, nil)
         | 
| 75 | 
            +
                      end,
         | 
| 76 | 
            +
                      log_level: args.fetch(:log_level) { itsifile_config.fetch(:log_level, nil) },
         | 
| 77 | 
            +
                      binds: args.fetch(:binds) { itsifile_config.fetch(:binds, ["http://0.0.0.0:3000"]) },
         | 
| 78 | 
            +
                      middleware_loader: middleware_loader,
         | 
| 79 | 
            +
                      default_app_loader: default_app_loader,
         | 
| 80 | 
            +
                      listeners: args.fetch(:listeners) { nil }
         | 
| 81 | 
            +
                    }.transform_keys(&:to_s)
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  # Reloads the entire process
         | 
| 85 | 
            +
                  # using exec, passing in any active file descriptors
         | 
| 86 | 
            +
                  # and previous invocation arguments
         | 
| 87 | 
            +
                  def self.reload_exec(listener_info)
         | 
| 88 | 
            +
                    if ENV["BUNDLE_BIN_PATH"]
         | 
| 89 | 
            +
                      exec "bundle", "exec", $PROGRAM_NAME, *@argv, "--listeners", listener_info
         | 
| 90 | 
            +
                    else
         | 
| 91 | 
            +
                      exec $PROGRAM_NAME, *@argv, "--listeners", listener_info
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  # Find config file path, if it exists.
         | 
| 96 | 
            +
                  def self.config_file_path(config_file_path = nil)
         | 
| 97 | 
            +
                    config_file_path ||= \
         | 
| 98 | 
            +
                      if File.exist?(ITSI_DEFAULT_CONFIG_FILE)
         | 
| 99 | 
            +
                        ITSI_DEFAULT_CONFIG_FILE
         | 
| 100 | 
            +
                      elsif File.exist?("config/#{ITSI_DEFAULT_CONFIG_FILE}")
         | 
| 101 | 
            +
                        "config/#{ITSI_DEFAULT_CONFIG_FILE}"
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                    # Options simply pass through unless we've specified a config file
         | 
| 104 | 
            +
                    return unless File.exist?(config_file_path.to_s)
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    config_file_path
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  def self.pid_file_path
         | 
| 110 | 
            +
                    if Dir.exist?("tmp")
         | 
| 111 | 
            +
                      File.join("tmp", "itsi.pid")
         | 
| 112 | 
            +
                    else
         | 
| 113 | 
            +
                      ".itsi.pid"
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  # Write a default config file, if one doesn't exist.
         | 
| 118 | 
            +
                  def self.write_default
         | 
| 119 | 
            +
                    if File.exist?(ITSI_DEFAULT_CONFIG_FILE)
         | 
| 120 | 
            +
                      puts "#{ITSI_DEFAULT_CONFIG_FILE} already exists."
         | 
| 121 | 
            +
                      return
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    puts "Writing default configuration..."
         | 
| 125 | 
            +
                    File.open(ITSI_DEFAULT_CONFIG_FILE, "w") do |file|
         | 
| 126 | 
            +
                      file.write(IO.read("#{__dir__}/Itsi.rb"))
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
              end
         | 
| 131 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            # When you Run Itsi without a Rack app,
         | 
| 2 | 
            +
            # we start a tiny little echo server, just so you can see it in action.
         | 
| 3 | 
            +
            DEFAULT_INDEX = IO.read("#{__dir__}/index.html").freeze
         | 
| 4 | 
            +
            DEFAULT_BINDS = ["http://0.0.0.0:3000"].freeze
         | 
| 5 | 
            +
            DEFAULT_APP = lambda {
         | 
| 6 | 
            +
              require "json"
         | 
| 7 | 
            +
              require "itsi/scheduler"
         | 
| 8 | 
            +
              Itsi.log_warn "No config.ru or Itsi.rb app detected. Running default app."
         | 
| 9 | 
            +
              {
         | 
| 10 | 
            +
                "app_proc" => Itsi::Server::RackInterface.for(lambda do |env|
         | 
| 11 | 
            +
                  headers, body = \
         | 
| 12 | 
            +
                    if env["itsi.response"].json?
         | 
| 13 | 
            +
                      [
         | 
| 14 | 
            +
                        { "Content-Type" => "application/json" },
         | 
| 15 | 
            +
                        [{ "message" => "You're running on Itsi!", "rack_env" => env,
         | 
| 16 | 
            +
                           "version" => Itsi::Server::VERSION }.to_json]
         | 
| 17 | 
            +
                      ]
         | 
| 18 | 
            +
                    else
         | 
| 19 | 
            +
                      [
         | 
| 20 | 
            +
                        { "Content-Type" => "text/html" },
         | 
| 21 | 
            +
                        [
         | 
| 22 | 
            +
                          format(
         | 
| 23 | 
            +
                            DEFAULT_INDEX,
         | 
| 24 | 
            +
                            REQUEST_METHOD: env["REQUEST_METHOD"],
         | 
| 25 | 
            +
                            PATH_INFO: env["PATH_INFO"],
         | 
| 26 | 
            +
                            SERVER_NAME: env["SERVER_NAME"],
         | 
| 27 | 
            +
                            SERVER_PORT: env["SERVER_PORT"],
         | 
| 28 | 
            +
                            REMOTE_ADDR: env["REMOTE_ADDR"],
         | 
| 29 | 
            +
                            HTTP_USER_AGENT: env["HTTP_USER_AGENT"]
         | 
| 30 | 
            +
                          )
         | 
| 31 | 
            +
                        ]
         | 
| 32 | 
            +
                      ]
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  [200, headers, body]
         | 
| 35 | 
            +
                end)
         | 
| 36 | 
            +
              }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            }
         |