simple-httpd 0.0.4 → 0.3.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 +4 -4
 - data/.gitignore +1 -1
 - data/.rubocop.yml +9 -0
 - data/.tm_properties +1 -0
 - data/Gemfile +21 -1
 - data/Makefile +9 -0
 - data/README.md +87 -2
 - data/Rakefile +5 -0
 - data/VERSION +1 -1
 - data/bin/simple-httpd +13 -0
 - data/examples/README.md +41 -0
 - data/examples/ex1/ex1_helpers.rb +5 -0
 - data/examples/ex1/root.rb +11 -0
 - data/examples/ex2/README.txt +1 -0
 - data/examples/ex2/ex2_helpers.rb +5 -0
 - data/examples/ex2/helpers.rb +15 -0
 - data/examples/ex2/info.rb +4 -0
 - data/examples/ex2/root.rb +3 -0
 - data/examples/ex3/example_service.rb +13 -0
 - data/examples/services/example_service.rb +25 -0
 - data/examples/services/explicit_example_service.rb +18 -0
 - data/examples/v2/api.js +1 -0
 - data/examples/v2/jobs.rb +13 -0
 - data/examples/v2/root.rb +3 -0
 - data/examples/v2/v2_helpers.rb +5 -0
 - data/lib/simple-service.rb +3 -0
 - data/lib/simple/httpd.rb +99 -25
 - data/lib/simple/httpd/base_controller.rb +2 -2
 - data/lib/simple/httpd/base_controller/error_handling.rb +45 -17
 - data/lib/simple/httpd/base_controller/json.rb +15 -8
 - data/lib/simple/httpd/cli.rb +99 -0
 - data/lib/simple/httpd/helpers.rb +54 -0
 - data/lib/simple/httpd/mount_spec.rb +106 -0
 - data/lib/simple/httpd/rack.rb +17 -0
 - data/lib/simple/httpd/rack/dynamic_mount.rb +66 -0
 - data/lib/simple/httpd/rack/merger.rb +28 -0
 - data/lib/simple/httpd/rack/static_mount.rb +50 -0
 - data/lib/simple/httpd/server.rb +69 -0
 - data/lib/simple/httpd/service.rb +70 -0
 - data/lib/simple/httpd/version.rb +1 -1
 - data/lib/simple/service.rb +69 -0
 - data/lib/simple/service/action.rb +78 -0
 - data/lib/simple/service/context.rb +46 -0
 - data/scripts/release +2 -0
 - data/scripts/release.rb +91 -0
 - data/simple-httpd.gemspec +9 -19
 - data/spec/simple/httpd/base_controller/httpd_cors_spec.rb +15 -0
 - data/spec/simple/httpd/base_controller/httpd_debug_spec.rb +11 -0
 - data/spec/simple/httpd/base_controller/httpd_x_processing_copy.rb +15 -0
 - data/spec/simple/httpd/base_spec.rb +16 -0
 - data/spec/simple/httpd/dynamic_mounting_spec.rb +33 -0
 - data/spec/simple/httpd/helpers_spec.rb +15 -0
 - data/spec/simple/httpd/rspec_httpd_spec.rb +17 -0
 - data/spec/simple/httpd/services/service_explicit_spec.rb +34 -0
 - data/spec/simple/httpd/services/service_spec.rb +34 -0
 - data/spec/simple/httpd/static_mounting_spec.rb +13 -0
 - data/spec/spec_helper.rb +30 -6
 - data/spec/support/004_simplecov.rb +3 -12
 - metadata +61 -84
 - data/lib/simple/httpd/app.rb +0 -84
 - data/lib/simple/httpd/app/file_server.rb +0 -19
 - data/spec/simple/httpd/version_spec.rb +0 -10
 - data/tasks/release.rake +0 -104
 
| 
         @@ -12,8 +12,8 @@ class Simple::Httpd::BaseController < Sinatra::Base 
     | 
|
| 
       12 
12 
     | 
    
         
             
              # Rails development mode, but on the other hand it is much faster, and
         
     | 
| 
       13 
13 
     | 
    
         
             
              # probably useful 90% of the time.
         
     | 
| 
       14 
14 
     | 
    
         
             
              configure :development do
         
     | 
| 
       15 
     | 
    
         
            -
                require "sinatra/reloader"
         
     | 
| 
       16 
     | 
    
         
            -
                register Sinatra::Reloader
         
     | 
| 
      
 15 
     | 
    
         
            +
                # require "sinatra/reloader"
         
     | 
| 
      
 16 
     | 
    
         
            +
                # register Sinatra::Reloader
         
     | 
| 
       17 
17 
     | 
    
         
             
              end
         
     | 
| 
       18 
18 
     | 
    
         
             
            end
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
         @@ -1,6 +1,10 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require_relative "./json"
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            # rubocop:disable Metrics/ClassLength
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
       3 
5 
     | 
    
         
             
            class Simple::Httpd::BaseController
         
     | 
| 
      
 6 
     | 
    
         
            +
              H = ::Simple::Httpd::Helpers
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
       4 
8 
     | 
    
         
             
              set :show_exceptions, false
         
     | 
| 
       5 
9 
     | 
    
         
             
              set :dump_errors, false
         
     | 
| 
       6 
10 
     | 
    
         
             
              set :raise_errors, false
         
     | 
| 
         @@ -64,27 +68,51 @@ class Simple::Httpd::BaseController 
     | 
|
| 
       64 
68 
     | 
    
         
             
                                  description: error_description(exc)
         
     | 
| 
       65 
69 
     | 
    
         
             
              end
         
     | 
| 
       66 
70 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
               
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
               
     | 
| 
       72 
     | 
    
         
            -
              #                title: "Not authorized",
         
     | 
| 
       73 
     | 
    
         
            -
              #                description: "You don't have necessary powers to access this page."
         
     | 
| 
       74 
     | 
    
         
            -
              # end
         
     | 
| 
      
 71 
     | 
    
         
            +
              error(::Simple::Service::ArgumentError) do |exc|
         
     | 
| 
      
 72 
     | 
    
         
            +
                render_error exc, status: 422,
         
     | 
| 
      
 73 
     | 
    
         
            +
                                  title: "Invalid input #{exc.message}",
         
     | 
| 
      
 74 
     | 
    
         
            +
                                  description: exc.message
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
       75 
76 
     | 
    
         | 
| 
       76 
     | 
    
         
            -
              #  
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
       78 
     | 
    
         
            -
               
     | 
| 
       79 
     | 
    
         
            -
               
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
       81 
     | 
    
         
            -
               
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
      
 77 
     | 
    
         
            +
              # -- not authorized.---------------------------------------------------------
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              class NotAuthorizedError < RuntimeError
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
              error(NotAuthorizedError) do
         
     | 
| 
      
 83 
     | 
    
         
            +
                render_error e, status: 403,
         
     | 
| 
      
 84 
     | 
    
         
            +
                                title: "Not authorized",
         
     | 
| 
      
 85 
     | 
    
         
            +
                                description: "You don't have necessary powers to access this page."
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
              def not_authorized!(msg)
         
     | 
| 
      
 89 
     | 
    
         
            +
                raise NotAuthorizedError, msg
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              # -- login required.---------------------------------------------------------
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
              class LoginRequiredError < RuntimeError
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
              def login_required!(msg)
         
     | 
| 
      
 98 
     | 
    
         
            +
                raise LoginRequiredError, msg
         
     | 
| 
      
 99 
     | 
    
         
            +
              end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              error(LoginRequiredError) do
         
     | 
| 
      
 102 
     | 
    
         
            +
                render_error e, status: 404,
         
     | 
| 
      
 103 
     | 
    
         
            +
                                title: "Not found",
         
     | 
| 
      
 104 
     | 
    
         
            +
                                description: "The server failed to recognize affiliation. Please provide a valid Session-Id."
         
     | 
| 
      
 105 
     | 
    
         
            +
              end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
              # -- resource not found.-----------------------------------------------------
         
     | 
| 
       84 
108 
     | 
    
         | 
| 
       85 
109 
     | 
    
         
             
              class NotFoundError < RuntimeError
         
     | 
| 
       86 
110 
     | 
    
         
             
              end
         
     | 
| 
       87 
111 
     | 
    
         | 
| 
      
 112 
     | 
    
         
            +
              def not_found!(msg)
         
     | 
| 
      
 113 
     | 
    
         
            +
                raise NotFoundError, msg
         
     | 
| 
      
 114 
     | 
    
         
            +
              end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
       88 
116 
     | 
    
         
             
              error(NotFoundError) do |exc|
         
     | 
| 
       89 
117 
     | 
    
         
             
                render_error exc, status: 404,
         
     | 
| 
       90 
118 
     | 
    
         
             
                                  title: "Not found",
         
     | 
| 
         @@ -117,7 +145,7 @@ class Simple::Httpd::BaseController 
     | 
|
| 
       117 
145 
     | 
    
         
             
              private
         
     | 
| 
       118 
146 
     | 
    
         | 
| 
       119 
147 
     | 
    
         
             
              def error_type(exc)
         
     | 
| 
       120 
     | 
    
         
            -
                "error/#{exc.class.name 
     | 
| 
      
 148 
     | 
    
         
            +
                "error/#{H.underscore exc.class.name}".gsub(/\/error$/, "")
         
     | 
| 
       121 
149 
     | 
    
         
             
              end
         
     | 
| 
       122 
150 
     | 
    
         | 
| 
       123 
151 
     | 
    
         
             
              def error_description(exc)
         
     | 
| 
         @@ -29,18 +29,25 @@ class Simple::Httpd::BaseController 
     | 
|
| 
       29 
29 
     | 
    
         | 
| 
       30 
30 
     | 
    
         
             
              public
         
     | 
| 
       31 
31 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
              def  
     | 
| 
       33 
     | 
    
         
            -
                @ 
     | 
| 
      
 32 
     | 
    
         
            +
              def parsed_body
         
     | 
| 
      
 33 
     | 
    
         
            +
                return @parsed_body if defined? @parsed_body
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                @parsed_body = parse_body
         
     | 
| 
      
 36 
     | 
    
         
            +
              rescue RuntimeError => e
         
     | 
| 
      
 37 
     | 
    
         
            +
                raise ArgumentError, e.to_s
         
     | 
| 
       34 
38 
     | 
    
         
             
              end
         
     | 
| 
       35 
39 
     | 
    
         | 
| 
       36 
40 
     | 
    
         
             
              private
         
     | 
| 
       37 
41 
     | 
    
         | 
| 
       38 
     | 
    
         
            -
              def  
     | 
| 
       39 
     | 
    
         
            -
                 
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
      
 42 
     | 
    
         
            +
              def parse_body
         
     | 
| 
      
 43 
     | 
    
         
            +
                case request.media_type
         
     | 
| 
      
 44 
     | 
    
         
            +
                when "application/json"
         
     | 
| 
      
 45 
     | 
    
         
            +
                  request.body.rewind
         
     | 
| 
      
 46 
     | 
    
         
            +
                  body = request.body.read
         
     | 
| 
      
 47 
     | 
    
         
            +
                  body == "" ? {} : JSON.parse(body)
         
     | 
| 
      
 48 
     | 
    
         
            +
                else
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # parses form data
         
     | 
| 
      
 50 
     | 
    
         
            +
                  request.POST
         
     | 
| 
       41 
51 
     | 
    
         
             
                end
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
                request.body.rewind
         
     | 
| 
       44 
     | 
    
         
            -
                JSON.parse(request.body.read)
         
     | 
| 
       45 
52 
     | 
    
         
             
              end
         
     | 
| 
       46 
53 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,99 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Simple
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Httpd
         
     | 
| 
      
 5 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_accessor :env
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
            end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            module Simple::Httpd::CLI
         
     | 
| 
      
 12 
     | 
    
         
            +
              include Simple::CLI
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              # Runs a simple httpd server
         
     | 
| 
      
 15 
     | 
    
         
            +
              #
         
     | 
| 
      
 16 
     | 
    
         
            +
              # A mount_spec is either the location of a directory, which would then be mounted
         
     | 
| 
      
 17 
     | 
    
         
            +
              # at the "/" HTTP location, or a directory followed by a colon and where to mount
         
     | 
| 
      
 18 
     | 
    
         
            +
              # the directory.
         
     | 
| 
      
 19 
     | 
    
         
            +
              #
         
     | 
| 
      
 20 
     | 
    
         
            +
              # Mounted directories might contain either ruby source code which is then executed
         
     | 
| 
      
 21 
     | 
    
         
            +
              # or static files to be delivered verbatim. See README.md for more details.
         
     | 
| 
      
 22 
     | 
    
         
            +
              #
         
     | 
| 
      
 23 
     | 
    
         
            +
              # Examples:
         
     | 
| 
      
 24 
     | 
    
         
            +
              #
         
     | 
| 
      
 25 
     | 
    
         
            +
              #   simple-httpd --port=8080 httpd/root --service=src/to/service.rb MyService:/ httpd/assets:assets
         
     | 
| 
      
 26 
     | 
    
         
            +
              #
         
     | 
| 
      
 27 
     | 
    
         
            +
              # serves the content of ./httpd/root on http://0.0.0.0/ and the content of httpd/assets
         
     | 
| 
      
 28 
     | 
    
         
            +
              # on http://0.0.0.0/assets.
         
     | 
| 
      
 29 
     | 
    
         
            +
              #
         
     | 
| 
      
 30 
     | 
    
         
            +
              # Options:
         
     | 
| 
      
 31 
     | 
    
         
            +
              #
         
     | 
| 
      
 32 
     | 
    
         
            +
              #   --environment=ENV         ... the environment setting, which adjusts configuration.
         
     | 
| 
      
 33 
     | 
    
         
            +
              #   --services=<path>,<path>  ... load these ruby file during startup. Used to define service objects.
         
     | 
| 
      
 34 
     | 
    
         
            +
              #
         
     | 
| 
      
 35 
     | 
    
         
            +
              # simple-httpd respects the HOST and PORT environment values to determine the interface
         
     | 
| 
      
 36 
     | 
    
         
            +
              # and port to listen to. Default values are "127.0.0.1" and 8181.
         
     | 
| 
      
 37 
     | 
    
         
            +
              #
         
     | 
| 
      
 38 
     | 
    
         
            +
              # Each entry in mounts can be either:
         
     | 
| 
      
 39 
     | 
    
         
            +
              #
         
     | 
| 
      
 40 
     | 
    
         
            +
              # - a mount_point <tt>[ mount_point, path ]</tt>, e.g. <tt>[ "path/to/root", "/"]</tt>
         
     | 
| 
      
 41 
     | 
    
         
            +
              # - a string denoting a mount_point, e.g. "path/to/root:/")
         
     | 
| 
      
 42 
     | 
    
         
            +
              # - a string denoting a "/" mount_point (e.g. "path", which is shorthand for "path:/")
         
     | 
| 
      
 43 
     | 
    
         
            +
              def main(*mount_specs, environment: "development", services: nil)
         
     | 
| 
      
 44 
     | 
    
         
            +
                ::Simple::Httpd.env = environment
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                start_simplecov if environment == "test"
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                mount_specs << "." if mount_specs.empty?
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                host = ENV["HOST"] || "127.0.0.1"
         
     | 
| 
      
 51 
     | 
    
         
            +
                port = Integer(ENV["PORT"] || 8181)
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                # late loading simple/httpd, for simplecov support
         
     | 
| 
      
 54 
     | 
    
         
            +
                require "simple/httpd"
         
     | 
| 
      
 55 
     | 
    
         
            +
                helpers = ::Simple::Httpd::Helpers
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                services&.split(",")&.each do |service_path|
         
     | 
| 
      
 58 
     | 
    
         
            +
                  paths = if Dir.exist?(service_path)
         
     | 
| 
      
 59 
     | 
    
         
            +
                            Dir.glob("#{service_path}/**/*.rb").sort
         
     | 
| 
      
 60 
     | 
    
         
            +
                          else
         
     | 
| 
      
 61 
     | 
    
         
            +
                            [service_path]
         
     | 
| 
      
 62 
     | 
    
         
            +
                          end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  paths.each do |path|
         
     | 
| 
      
 65 
     | 
    
         
            +
                    logger.info "Loading service(s) from #{helpers.shorten_path path}"
         
     | 
| 
      
 66 
     | 
    
         
            +
                    load path
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                ::Simple::Httpd.listen!(*mount_specs, environment: environment,
         
     | 
| 
      
 71 
     | 
    
         
            +
                                                      host: host,
         
     | 
| 
      
 72 
     | 
    
         
            +
                                                      port: port,
         
     | 
| 
      
 73 
     | 
    
         
            +
                                                      logger: logger)
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              private
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              def stderr_logger
         
     | 
| 
      
 79 
     | 
    
         
            +
                logger = ::Logger.new STDERR
         
     | 
| 
      
 80 
     | 
    
         
            +
                logger.level = ::Logger::INFO
         
     | 
| 
      
 81 
     | 
    
         
            +
                logger
         
     | 
| 
      
 82 
     | 
    
         
            +
              end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
              def start_simplecov
         
     | 
| 
      
 85 
     | 
    
         
            +
                require "simplecov"
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                SimpleCov.command_name "Integration Tests"
         
     | 
| 
      
 88 
     | 
    
         
            +
                SimpleCov.start do
         
     | 
| 
      
 89 
     | 
    
         
            +
                  # return true to remove src from coverage
         
     | 
| 
      
 90 
     | 
    
         
            +
                  add_filter do |src|
         
     | 
| 
      
 91 
     | 
    
         
            +
                    next true if src.filename =~ /\/spec\//
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                    false
         
     | 
| 
      
 94 
     | 
    
         
            +
                  end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                  # minimum_coverage 90
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Simple::Httpd::Helpers
         
     | 
| 
      
 2 
     | 
    
         
            +
              extend self
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              private
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def pwd
         
     | 
| 
      
 7 
     | 
    
         
            +
                @pwd ||= File.join(Dir.getwd, "/")
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def home
         
     | 
| 
      
 11 
     | 
    
         
            +
                @home ||= File.join(Dir.home, "/")
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              public
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              def shorten_path(path)
         
     | 
| 
      
 17 
     | 
    
         
            +
                path = File.absolute_path(path)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                if path.start_with?(pwd)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  path = path[pwd.length..-1]
         
     | 
| 
      
 21 
     | 
    
         
            +
                  path = File.join("./", path) if path =~ /\//
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                if path.start_with?(home)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  path = File.join("~/", path[home.length..-1])
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                path
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              def underscore(str)
         
     | 
| 
      
 32 
     | 
    
         
            +
                parts = str.split("::")
         
     | 
| 
      
 33 
     | 
    
         
            +
                parts = parts.map do |part|
         
     | 
| 
      
 34 
     | 
    
         
            +
                  part.gsub(/[A-Z]+/) { |ch| "_#{ch.downcase}" }.gsub(/^_/, "")
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
                parts.join("/")
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              # instance_eval zero or more paths in the context of obj
         
     | 
| 
      
 40 
     | 
    
         
            +
              def instance_eval_paths(obj, *paths)
         
     | 
| 
      
 41 
     | 
    
         
            +
                paths.each do |path|
         
     | 
| 
      
 42 
     | 
    
         
            +
                  # STDERR.puts "Loading #{path}"
         
     | 
| 
      
 43 
     | 
    
         
            +
                  obj.instance_eval File.read(path), path, 1
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
                obj
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              # subclass a klass with an optional description
         
     | 
| 
      
 49 
     | 
    
         
            +
              def subclass(klass, description: nil)
         
     | 
| 
      
 50 
     | 
    
         
            +
                subclass = Class.new(klass)
         
     | 
| 
      
 51 
     | 
    
         
            +
                subclass.define_method(:inspect) { description } if description
         
     | 
| 
      
 52 
     | 
    
         
            +
                subclass
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,106 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # rubocop:disable Metrics/AbcSize, Style/ParallelAssignment
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "simple-service"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "./rack"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class Simple::Httpd::MountSpec
         
     | 
| 
      
 7 
     | 
    
         
            +
              def self.build(arg, at:)
         
     | 
| 
      
 8 
     | 
    
         
            +
                if at
         
     | 
| 
      
 9 
     | 
    
         
            +
                  entity, mount_point = arg, at
         
     | 
| 
      
 10 
     | 
    
         
            +
                else
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # The regexp below uses negative lookbehind and negative lookahead to
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # only match single colons, but not double (or more) colons. See
         
     | 
| 
      
 13 
     | 
    
         
            +
                  # `ri Regexp` for details.
         
     | 
| 
      
 14 
     | 
    
         
            +
                  entity, mount_point = *arg.split(/(?<!:):(?!:)/, 2)
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                mount_point = normalize_and_verify_mount_point(mount_point)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                ServiceMountSpec.build(mount_point, service: entity) ||
         
     | 
| 
      
 20 
     | 
    
         
            +
                  PathMountSpec.build(mount_point, path: entity) ||
         
     | 
| 
      
 21 
     | 
    
         
            +
                  raise(ArgumentError, "#{mount_point}: don't know how to mount #{entity.inspect}")
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              def self.normalize_and_verify_mount_point(mount_point)
         
     | 
| 
      
 25 
     | 
    
         
            +
                mount_point ||= "/"                           # fall back to "/"
         
     | 
| 
      
 26 
     | 
    
         
            +
                mount_point = File.join("/", mount_point)     # make sure we start at "/"
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                canary_url = "http://0.0.0.0#{mount_point}"   # verify mount_point: can it be used to build a URL?
         
     | 
| 
      
 29 
     | 
    
         
            +
                URI.parse canary_url
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                mount_point
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              attr_reader :mount_point
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              class PathMountSpec < ::Simple::Httpd::MountSpec
         
     | 
| 
      
 37 
     | 
    
         
            +
                Rack = ::Simple::Httpd::Rack
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                attr_reader :path, :mount_point
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def self.build(mount_point, path:)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  path = path.gsub(/\/$/, "") # remove trailing "/"
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  raise ArgumentError, "You probably don't want to mount your root directory, check mount_spec" if path == ""
         
     | 
| 
      
 45 
     | 
    
         
            +
                  return unless Dir.exist?(path)
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  new(mount_point, path)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def initialize(mount_point, path)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  @mount_point, @path = mount_point, path
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def build_rack_apps
         
     | 
| 
      
 55 
     | 
    
         
            +
                  [
         
     | 
| 
      
 56 
     | 
    
         
            +
                    Rack::DynamicMount.build(mount_point, path),
         
     | 
| 
      
 57 
     | 
    
         
            +
                    Rack::StaticMount.build(mount_point, path)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  ].compact
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              class ServiceMountSpec < ::Simple::Httpd::MountSpec
         
     | 
| 
      
 63 
     | 
    
         
            +
                H = ::Simple::Httpd::Helpers
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                attr_reader :service
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def self.build(mount_point, service:)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  service = ::Simple::Service.resolve(service)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  return unless service
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  new(mount_point, service)
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def initialize(mount_point, service)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  @mount_point, @service = mount_point, service
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                def build_rack_apps
         
     | 
| 
      
 79 
     | 
    
         
            +
                  [build_controller]
         
     | 
| 
      
 80 
     | 
    
         
            +
                end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                private
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                # wraps all helpers into a Simple::Httpd::BaseController subclass
         
     | 
| 
      
 85 
     | 
    
         
            +
                def build_controller
         
     | 
| 
      
 86 
     | 
    
         
            +
                  controller = H.subclass(::Simple::Httpd::BaseController)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  setup_action_routes! controller
         
     | 
| 
      
 88 
     | 
    
         
            +
                  controller
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                def setup_action_routes!(controller)
         
     | 
| 
      
 92 
     | 
    
         
            +
                  action_names = service.actions.keys
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                  controller.mount_service(service) do |service|
         
     | 
| 
      
 95 
     | 
    
         
            +
                    action_names.each do |action_name|
         
     | 
| 
      
 96 
     | 
    
         
            +
                      ::Simple::Httpd.logger.debug "#{mount_point}/#{action_name} -> #{service.name}##{action_name}"
         
     | 
| 
      
 97 
     | 
    
         
            +
                      controller.post "/#{action_name}" => action_name
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                  ::Simple::Httpd.logger.info do
         
     | 
| 
      
 102 
     | 
    
         
            +
                    "#{mount_point}: mounting #{action_names.count} actions(s) from #{service.name} service"
         
     | 
| 
      
 103 
     | 
    
         
            +
                  end
         
     | 
| 
      
 104 
     | 
    
         
            +
                end
         
     | 
| 
      
 105 
     | 
    
         
            +
              end
         
     | 
| 
      
 106 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Simple::Httpd::Rack
         
     | 
| 
      
 2 
     | 
    
         
            +
            end
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "rack/static_mount"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative "rack/dynamic_mount"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require_relative "rack/merger"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module Simple::Httpd::Rack
         
     | 
| 
      
 9 
     | 
    
         
            +
              def self.merge(apps)
         
     | 
| 
      
 10 
     | 
    
         
            +
                Merger.build(apps)
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def self.error(status, message = nil)
         
     | 
| 
      
 14 
     | 
    
         
            +
                message ||= "Error #{status}"
         
     | 
| 
      
 15 
     | 
    
         
            +
                [status, {}, [message]]
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,66 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "expectation"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # The Simple::Httpd::Mountpoint.build returns a Rack compatible app, which
         
     | 
| 
      
 4 
     | 
    
         
            +
            # serves HTTP requests according to a set of dynamic ruby scripts and some
         
     | 
| 
      
 5 
     | 
    
         
            +
            # existing static files.
         
     | 
| 
      
 6 
     | 
    
         
            +
            class Simple::Httpd::Rack::DynamicMount
         
     | 
| 
      
 7 
     | 
    
         
            +
              H = ::Simple::Httpd::Helpers
         
     | 
| 
      
 8 
     | 
    
         
            +
              Rack = ::Simple::Httpd::Rack
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def self.build(mount_point, path)
         
     | 
| 
      
 11 
     | 
    
         
            +
                expect! path => String
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                url_map = new(mount_point, path).build_url_map
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                ::Rack::URLMap.new(url_map)
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              attr_reader :path
         
     | 
| 
      
 19 
     | 
    
         
            +
              attr_reader :mount_point
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def initialize(mount_point, path)
         
     | 
| 
      
 22 
     | 
    
         
            +
                @mount_point = mount_point
         
     | 
| 
      
 23 
     | 
    
         
            +
                @path = path
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                # determine source_paths and controller_paths
         
     | 
| 
      
 26 
     | 
    
         
            +
                source_paths = Dir.glob("#{path}/**/*.rb")
         
     | 
| 
      
 27 
     | 
    
         
            +
                helper_paths, @controller_paths = source_paths.partition { |str| /_helpers\.rb$/ =~ str }
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # build root_controller
         
     | 
| 
      
 30 
     | 
    
         
            +
                @root_controller = build_root_controller(helper_paths)
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              # rubocop:disable Metrics/AbcSize
         
     | 
| 
      
 34 
     | 
    
         
            +
              def build_url_map
         
     | 
| 
      
 35 
     | 
    
         
            +
                @controller_paths.sort.each_with_object({}) do |absolute_path, url_map|
         
     | 
| 
      
 36 
     | 
    
         
            +
                  relative_path = absolute_path[(path.length)..-1]
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  relative_mount_point = relative_path == "/root.rb" ? "/" : relative_path.gsub(/\.rb$/, "")
         
     | 
| 
      
 39 
     | 
    
         
            +
                  controller_class = load_controller absolute_path
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  ::Simple::Httpd.logger.info do
         
     | 
| 
      
 42 
     | 
    
         
            +
                    absolute_mount_point = File.join(mount_point, relative_mount_point)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    routes_count = controller_class.routes.reject { |verb, _| verb == "HEAD" }.values.sum(&:count)
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    "#{absolute_mount_point}: mounting #{routes_count} route(s) from #{H.shorten_path absolute_path}"
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  url_map.update relative_mount_point => controller_class
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              # wraps all helpers into a Simple::Httpd::BaseController subclass
         
     | 
| 
      
 53 
     | 
    
         
            +
              def build_root_controller(helper_paths)
         
     | 
| 
      
 54 
     | 
    
         
            +
                klass = H.subclass ::Simple::Httpd::BaseController,
         
     | 
| 
      
 55 
     | 
    
         
            +
                                   description: "root controller at #{path} w/#{helper_paths.count} helpers"
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                H.instance_eval_paths klass, *helper_paths.sort
         
     | 
| 
      
 58 
     | 
    
         
            +
                klass
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
              # wraps the source file in path into a root_controller
         
     | 
| 
      
 62 
     | 
    
         
            +
              def load_controller(path)
         
     | 
| 
      
 63 
     | 
    
         
            +
                klass = H.subclass @root_controller, description: "controller at #{path}"
         
     | 
| 
      
 64 
     | 
    
         
            +
                H.instance_eval_paths klass, path
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
            end
         
     |