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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +9 -0
  4. data/.tm_properties +1 -0
  5. data/Gemfile +21 -1
  6. data/Makefile +9 -0
  7. data/README.md +87 -2
  8. data/Rakefile +5 -0
  9. data/VERSION +1 -1
  10. data/bin/simple-httpd +13 -0
  11. data/examples/README.md +41 -0
  12. data/examples/ex1/ex1_helpers.rb +5 -0
  13. data/examples/ex1/root.rb +11 -0
  14. data/examples/ex2/README.txt +1 -0
  15. data/examples/ex2/ex2_helpers.rb +5 -0
  16. data/examples/ex2/helpers.rb +15 -0
  17. data/examples/ex2/info.rb +4 -0
  18. data/examples/ex2/root.rb +3 -0
  19. data/examples/ex3/example_service.rb +13 -0
  20. data/examples/services/example_service.rb +25 -0
  21. data/examples/services/explicit_example_service.rb +18 -0
  22. data/examples/v2/api.js +1 -0
  23. data/examples/v2/jobs.rb +13 -0
  24. data/examples/v2/root.rb +3 -0
  25. data/examples/v2/v2_helpers.rb +5 -0
  26. data/lib/simple-service.rb +3 -0
  27. data/lib/simple/httpd.rb +99 -25
  28. data/lib/simple/httpd/base_controller.rb +2 -2
  29. data/lib/simple/httpd/base_controller/error_handling.rb +45 -17
  30. data/lib/simple/httpd/base_controller/json.rb +15 -8
  31. data/lib/simple/httpd/cli.rb +99 -0
  32. data/lib/simple/httpd/helpers.rb +54 -0
  33. data/lib/simple/httpd/mount_spec.rb +106 -0
  34. data/lib/simple/httpd/rack.rb +17 -0
  35. data/lib/simple/httpd/rack/dynamic_mount.rb +66 -0
  36. data/lib/simple/httpd/rack/merger.rb +28 -0
  37. data/lib/simple/httpd/rack/static_mount.rb +50 -0
  38. data/lib/simple/httpd/server.rb +69 -0
  39. data/lib/simple/httpd/service.rb +70 -0
  40. data/lib/simple/httpd/version.rb +1 -1
  41. data/lib/simple/service.rb +69 -0
  42. data/lib/simple/service/action.rb +78 -0
  43. data/lib/simple/service/context.rb +46 -0
  44. data/scripts/release +2 -0
  45. data/scripts/release.rb +91 -0
  46. data/simple-httpd.gemspec +9 -19
  47. data/spec/simple/httpd/base_controller/httpd_cors_spec.rb +15 -0
  48. data/spec/simple/httpd/base_controller/httpd_debug_spec.rb +11 -0
  49. data/spec/simple/httpd/base_controller/httpd_x_processing_copy.rb +15 -0
  50. data/spec/simple/httpd/base_spec.rb +16 -0
  51. data/spec/simple/httpd/dynamic_mounting_spec.rb +33 -0
  52. data/spec/simple/httpd/helpers_spec.rb +15 -0
  53. data/spec/simple/httpd/rspec_httpd_spec.rb +17 -0
  54. data/spec/simple/httpd/services/service_explicit_spec.rb +34 -0
  55. data/spec/simple/httpd/services/service_spec.rb +34 -0
  56. data/spec/simple/httpd/static_mounting_spec.rb +13 -0
  57. data/spec/spec_helper.rb +30 -6
  58. data/spec/support/004_simplecov.rb +3 -12
  59. metadata +61 -84
  60. data/lib/simple/httpd/app.rb +0 -84
  61. data/lib/simple/httpd/app/file_server.rb +0 -19
  62. data/spec/simple/httpd/version_spec.rb +0 -10
  63. data/tasks/release.rake +0 -104
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3273a1672a08dca30bf4541e2169bee7e5eb107b0cbf4f335c20c5c4d2190756
4
- data.tar.gz: c5262312bb70eb9e213688d6b85c05c4c1bd447d3e682eed5f1ba415d69a33b9
3
+ metadata.gz: e2cf16a40a84142155ccfdf2486e3f12d2a2be13429a942660136ba1c7ce5034
4
+ data.tar.gz: fe679b913fe4ea849945b797fd949ec4d4f9dc783d7ea252e358060037ec25e9
5
5
  SHA512:
6
- metadata.gz: 44224855c4874348894465f0a56e4910a4d136a5fb675dd34dec7d4306affacdae9c161caccad1eb91122eddb9d1894811938c6f3888abba7e8090d8111d0657
7
- data.tar.gz: 6b4bc045304a398031f17471d74c3dca151b0b7ae3847f859fbdf91e80777c9f77b10946f380ebf12e5946a9e6caca7f60a79cdd274ce593a8c25f00e41fb963
6
+ metadata.gz: 36e023782c8602e0864b1787ee661b1b4c6df8b76ea28889a14acc77116b927c98dd7d720850337b011a7c32ad6694767a776ba92ed772953ebdf3960ff43932
7
+ data.tar.gz: 0011d3b37a962c6dd5b1781f5dfd934aef319b8e13033b370fc9f4b240fa921ef06475808d7780072cd6c82675db95103d6c856911dd8a61d4048f8a45430bd3
data/.gitignore CHANGED
@@ -1,7 +1,7 @@
1
1
  coverage
2
2
  rdoc
3
3
  pkg
4
- log/test.log
4
+ log/*.log
5
5
  .rspec.data
6
6
  Gemfile.lock
7
7
  .rake_t_cache
data/.rubocop.yml CHANGED
@@ -78,3 +78,12 @@ Style/TrailingUnderscoreVariable:
78
78
 
79
79
  Style/StderrPuts:
80
80
  Enabled: false
81
+
82
+ Style/NonNilCheck:
83
+ Enabled: false
84
+
85
+ Metrics/ParameterLists:
86
+ Enabled: false
87
+
88
+ Style/StringLiteralsInInterpolation:
89
+ Enabled: false
data/.tm_properties ADDED
@@ -0,0 +1 @@
1
+ excludeDirectories = "{_build,coverage,assets/node_modules,node_modules,deps,db,cover,priv/static,storage,github,vendor,arena,}"
data/Gemfile CHANGED
@@ -1,5 +1,25 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'byebug'
4
3
  # Specify your gem's dependencies in {gemname}.gemspec
5
4
  gemspec
5
+
6
+ # --- Local overrides for runtime dependencies ------------------------------
7
+
8
+ # gem "simple-cli", path: "../simple-cli"
9
+
10
+ # --- Development and test dependencies ------------------------------
11
+
12
+ group :development, :test do
13
+ gem "rspec-httpd", "~> 0.3.0"
14
+ # gem "rspec-httpd", path: "../rspec-httpd"
15
+
16
+ gem 'rake', '~> 11'
17
+ gem 'rspec', '~> 3.7'
18
+ # gem 'rubocop', '~> 0.61.1'
19
+ gem 'simplecov', '~> 0'
20
+ gem 'byebug'
21
+
22
+ if ENV["PRELOAD_SERVER_GEM"]
23
+ gem ENV["PRELOAD_SERVER_GEM"]
24
+ end
25
+ end
data/Makefile ADDED
@@ -0,0 +1,9 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ rspec
5
+
6
+ .PHONY: fulltest
7
+ fulltest:
8
+ rspec
9
+ PRELOAD_SERVER_GEM=puma rspec
data/README.md CHANGED
@@ -1,3 +1,88 @@
1
- # simple-httpd
1
+ # simple-httpd – serving HTTP made simpler.
2
2
 
3
- [TODO]
3
+ This ruby gem wraps around [sinatra](/) to provide an even simpler way of setting up http based backends. It is especially helpful to:
4
+
5
+ - bind loosely related pieces of code together: `simple-httpd` lets a developer lay out their code and assets in directories and trees of directories and can then serve these via HTTP.
6
+ - have an easy way to serve static assets via HTTP.
7
+ - allow existing applications, especially CLI tools, to easily start HTTP servers.
8
+
9
+ In some ways one might be reminded of the web's old days where one would throw a bunch of php scripts into a FTP location, and then an appache webserver (but, really, its php integration) would start serving requests via HTTPS. ***simple-httpd* is not like that.** This gem still supports the notion of an application; source files typically rely on other source files' existence and functionality.
10
+
11
+ Also, at least as of now, **simple-httpd** does not dynamically reload code parts on request. This might change in the future.
12
+
13
+ ## Is it useful?
14
+
15
+ At this point I don't know yet. We'll see. In any case this gem is used to test [rspec-httpd](github.com/radiospiel/rspec-httpd) (a rspec extension helping with testing HTTP endpoints), and is used within [postjob-httpd](github.com/radiospiel/postjob-httpd) where it is configured to glue HTTP endpoints to the [postjob](github.com/radiospiel/postjob) job queue system.
16
+
17
+ It has proven useful so far - but as it is a really lean wrapper around sinatra one might probably also use sinatra in most cases.
18
+
19
+ ## Mounting directories
20
+
21
+ `simple-httpd` lets a user of the gem "mount" directories onto "mount points". A "mount point" describes the location of the actions or static assets at the HTTP endpoint. Note that two or more directories can be mounted at the same mount point.
22
+
23
+ Files in a mounted directory fall into different categories:
24
+
25
+ ### Static assets
26
+
27
+ Static assets are files with a predefined set of file extensions, including `.txt` and `.js`. (compare the `static_mount.rb` source file for a full list.)
28
+
29
+ They become available at the location specified by their filename and extension.
30
+
31
+ ### Dynamic assets
32
+
33
+ Each mounted directory which contains ruby source files is converted into a sinatra application, which consists of a root configuration and controllers for each action file.
34
+
35
+ Ruby files ending in `_helpers.rb`, e.g. `examples/ex1/ex1_helpers.rb` are executed in the context of a directory tree's root controller and provide functionality available in all action files. Typically they do not implement HTTP handlers themselves.
36
+
37
+ All other ruby files implement HTTP handlers in typical sinatra fashion:
38
+
39
+ # in v2/jobs.rb
40
+ get "/queue/:id/events" do
41
+ events = [
42
+ { job_id: params[:id], id: "event1" },
43
+ { job_id: params[:id], id: "event2" }
44
+ ]
45
+
46
+ json events
47
+ end
48
+
49
+ If this snippet is contained in a file `v2/jobs.rb` and the `v2` directory is mounted into `api/v2`, the snipped implements the handler for, for example, `GET /api/v2/jobs/queue/123/events`. In other words, the handler implement in the source file works on paths relative to a path combining the mount location and the file name.
50
+
51
+ To implement a action on the mountpoint itself one uses the `root.rb` file. The following
52
+
53
+ # in v2/root.rb
54
+ get "/" do
55
+ json version: "123"
56
+ end
57
+
58
+ would implement `GET /api/v2`.
59
+
60
+ ## Command line usage
61
+
62
+ `simple-httpd` comes with a CLI tool, which lets one assemble multiple locations into a single HTTP backend: the following command serves the *./ex1* and *./ex2* directories at `http://0.0.0.0:12345` and the *./v2* directory at `http://0.0.0.0:12345/api/v2`.
63
+
64
+ simple-httpd --port=12345 ex1 ex2 v2:api/v2
65
+
66
+ The `v2:api/v2` argument asks the `v2` directory to be mounted into the web endpoint at `/api/v2`. All relevant content is therefore served below `http://0.0.0.0:12345/api/v2`.
67
+
68
+ The arguments `ex1` and `ex2` serve at the `/` location. This notation really is a shorthand for `ex1:/`
69
+
70
+ ## Integration
71
+
72
+ `simple-httpd` can be integrated into other ruby scripts. Example:
73
+
74
+ require "simple-httpd"
75
+
76
+ httpd_root_dir = File.join(__dir__, "httpd")
77
+ port = 12345
78
+
79
+ app = ::Simple::Httpd.build("/" => httpd_root_dir)
80
+ ::Simple::Httpd.listen! app, port: port,
81
+ logger: ::Logger.new(STDERR)
82
+
83
+
84
+ ## The example application
85
+
86
+ An example application is contained in ./examples. (Well, this example is probably not as *useful* for any purpose, but I hope it demonstrates all simple-httpd use cases, also it is used during tests.)
87
+
88
+ See [its readme](examples/README.md) for more details.
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  Dir.glob("tasks/*.rake").each { |r| import r }
2
+
3
+ task :release do
4
+ sh "scripts/release"
5
+ end
6
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.3.0
data/bin/simple-httpd ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ $: << "lib"
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+
7
+ # === setup and run Simple Httpd server =======================================
8
+ require "simple/cli"
9
+ require "simple/httpd/cli"
10
+
11
+ # run the :main command with these arguments. This switches simple-cli into
12
+ # non-subcommand mode.
13
+ Simple::Httpd::CLI.run!(:main)
@@ -0,0 +1,41 @@
1
+ # An example
2
+
3
+ This directory contains an example application. (Well, this example is probably not as *useful* for any purpose, but I hope it demonstrates all simple-httpd use cases, also it is used during tests.)
4
+
5
+ See [its readme](examples/README.md) for more details.
6
+
7
+ ## How to start the example application
8
+
9
+ Assuming you have installed the simple-httpd gem via `gem install simple-httpd` you should be able to start the server via
10
+
11
+ simple-httpd --port=12345 ex1 ex2 v2:api/v2
12
+
13
+ This starts a HTTPD server on `http://0.0.0.0:12345`. The server serves content from the `./ex1` and `./ex2` directories at the root URL (`http://0.0.0.0:12345/`) and content from the ./v2 directory below `http://0.0.0.0:12345/api/v2`.
14
+
15
+ The following explanations assume you started a server with the configuration mentioned above.
16
+
17
+ ## Files
18
+
19
+ This directory currently contains these files:
20
+
21
+ - ex1/root.rb
22
+ - ex1/ex1_helpers.rb
23
+ - ex2/helpers.rb
24
+ - ex2/root.rb
25
+ - ex2/info.rb
26
+ - ex2/ex2_helpers.rb
27
+ - ex2/README.txt
28
+ - v2/root.rb
29
+ - v2/jobs.rb
30
+ - v2/v2_helpers.rb
31
+ - v2/api.js
32
+
33
+ ## Some routes
34
+
35
+ The following lists some routes and where they are implemented:
36
+
37
+ GET "/" .. in ex1/root.rb
38
+ GET "/debug" .. in ex2/root.rb
39
+ GET "/info/inspect" .. in ex2/info.rb
40
+ GET "/api/v2/" .. in v2/root.rb
41
+ GET "/api/v2/jobs/:id/events" .. in v2/jobs.rb
@@ -0,0 +1,5 @@
1
+ helpers do
2
+ def ex1_helper
3
+ "ex1_helper"
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ get "/" do
2
+ "root"
3
+ end
4
+
5
+ get "/hello" do
6
+ "hello"
7
+ end
8
+
9
+ get "/exit" do
10
+ exit 1
11
+ end
@@ -0,0 +1 @@
1
+ This is a README file
@@ -0,0 +1,5 @@
1
+ helpers do
2
+ def ex2_helper
3
+ "ex2_helper"
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ get "/ex1" do
2
+ begin
3
+ ex1_helper
4
+ rescue NameError
5
+ not_found! "ex1_helper cannot be run"
6
+ end
7
+ end
8
+
9
+ get "/ex2" do
10
+ begin
11
+ ex2_helper
12
+ rescue NameError
13
+ not_found! "ex2_helper cannot be run"
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ get "/inspect" do
2
+ content_type :text
3
+ request.env.map { |key, value| "#{key}=#{value}\n" }.grep(/^[A-Z]/).sort.join
4
+ end
@@ -0,0 +1,3 @@
1
+ get "/debug" do
2
+ debug params
3
+ end
@@ -0,0 +1,13 @@
1
+ get "/check" do
2
+ "ok: explicit_service"
3
+ end
4
+
5
+ mount_service ExplicitService do |service|
6
+ # def echo(one, two, a:, b:)
7
+ post "/echo/:a" => :explicit_echo
8
+
9
+ put "/echo_context" do
10
+ # def echo_context
11
+ service.call(:echo_context, parsed_body, params, context: context)
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ # rubocop:disable Naming/UncommunicativeMethodParamName, Lint/UnusedMethodArgument
2
+
3
+ module Example; end
4
+ module Example::Service
5
+ include ::Simple::Service
6
+
7
+ def test(a:, b:)
8
+ # "this is a test; a is #{a.inspect}, b is #{b.inspect}"
9
+ "hello from ExampleService#test"
10
+ end
11
+
12
+ def echo(one, two, a:, b:)
13
+ "one: [#{one}]/two: [#{two}]/a: [#{a}]/b: [#{b}]"
14
+ end
15
+
16
+ def echo_context
17
+ ::Simple::Service.context.inspect
18
+ end
19
+
20
+ def this_is_a_helper!; end
21
+
22
+ private
23
+
24
+ def this_is_a_private_helper; end
25
+ end
@@ -0,0 +1,18 @@
1
+ # rubocop:disable Naming/UncommunicativeMethodParamName, Lint/UnusedMethodArgument
2
+
3
+ module ExplicitService
4
+ include ::Simple::Service
5
+
6
+ def explicit_test(a:, b:)
7
+ # "this is a test; a is #{a.inspect}, b is #{b.inspect}"
8
+ "hello from ExplicitService#test"
9
+ end
10
+
11
+ def explicit_echo(one, two, a:, b:)
12
+ "one: [#{one}]/two: [#{two}]/a: [#{a}]/b: [#{b}]"
13
+ end
14
+
15
+ def echo_context
16
+ ::Simple::Service.context.inspect
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ /* API example file */
@@ -0,0 +1,13 @@
1
+ get "/info" do
2
+ content_type :text
3
+ "info"
4
+ end
5
+
6
+ get "/:id/events" do
7
+ events = [
8
+ { job_id: params[:id], id: "event1" },
9
+ { job_id: params[:id], id: "event2" }
10
+ ]
11
+
12
+ json events
13
+ end
@@ -0,0 +1,3 @@
1
+ get "/" do
2
+ json version: "v2"
3
+ end
@@ -0,0 +1,5 @@
1
+ helpers do
2
+ def v2_helper
3
+ "v2_helper"
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ # rubocop:disable Naming/FileName
2
+
3
+ require "simple/service"
data/lib/simple/httpd.rb CHANGED
@@ -1,43 +1,117 @@
1
+ # rubocop:disable Style/TrivialAccessors
2
+
1
3
  module Simple
2
4
  end
3
5
 
4
- module Simple::Httpd
6
+ class Simple::Httpd
5
7
  end
6
8
 
7
- require "simple/httpd/version"
8
- require "simple/httpd/app"
9
+ require "simple/service"
10
+
11
+ require "simple/httpd/helpers"
9
12
  require "simple/httpd/base_controller"
13
+ require "simple/httpd/version"
14
+ require "simple/httpd/mount_spec"
15
+ require "simple/httpd/server"
10
16
 
11
- module Simple::Httpd
12
- extend self
17
+ require "simple/httpd/service"
18
+
19
+ class Simple::Httpd
20
+ SELF = self
21
+
22
+ def self.logger=(logger)
23
+ @logger = logger
24
+ end
13
25
 
14
- def build_rack(base_controller, logger:)
15
- App.new(base_controller, logger: logger)
26
+ def self.logger
27
+ @logger ||= ::Logger.new(STDERR, level: ::Logger::INFO)
16
28
  end
17
29
 
18
- def listen!(app, environment:, port:)
19
- expect! port => 80..60_000
30
+ # Converts the passed in args into a Simple::Httpd application.
31
+ #
32
+ # The passed in arguments are used to create a Simple::Httpd object.
33
+ # If the function receives a rack app (determined by the ability to
34
+ # respond to call/3) it redirects to <tt>Server.listen!</tt> right
35
+ # away - this way this method can be used as a helper method
36
+ # to easily start a Rack server.
37
+ def self.listen!(*mount_specs, environment: "development", host: nil, port:, logger: nil, &block)
38
+ # If there is no argument but a block use the block as a rack server
39
+ if block
40
+ raise ArgumentError, "Can't deal w/block *and* mount_specs" unless mount_specs.empty?
20
41
 
21
- logger = app.logger
22
- logger.info "Starting httpd server on http://0.0.0.0:#{port}/"
42
+ app = block
43
+ elsif mount_specs.length == 1 && mount_specs.first.respond_to?(:call)
44
+ # there is one argument, and that looks like a Rack app: return that.
45
+ app = mount_specs.first
46
+ else
47
+ # Build a Httpd app, and listen
48
+ app = build(*mount_specs)
49
+ app.rack
50
+ end
23
51
 
24
- app = Rack::Lint.new(app) if environment != "production"
52
+ Server.listen!(app, environment: environment, host: host, port: port, logger: logger)
53
+ end
54
+
55
+ # Converts the passed in arguments into a Simple::Httpd application.
56
+ #
57
+ # For a description of mounts see <tt>#add</tt>
58
+ def self.build(*mount_specs)
59
+ new(*mount_specs)
60
+ end
61
+
62
+ private
63
+
64
+ # Builds a Simple::Httpd application.
65
+ def initialize(*mount_specs)
66
+ @mount_specs = []
67
+ mount_specs.map do |mount_spec|
68
+ mount(mount_spec, at: nil)
69
+ end
70
+ end
71
+
72
+ public
73
+
74
+ # Adds one or more mount_points
75
+ #
76
+ # Each entry in mounts can be either:
77
+ #
78
+ # - a mount_point <tt>[ mount_point, path ]</tt>, e.g. <tt>[ "path/to/root", "/"]</tt>
79
+ # - a string denoting a mount_point, e.g. "path/to/root:/")
80
+ # - a string denoting a "/" mount_point (e.g. "path", which is shorthand for "path:/")
81
+ #
82
+ def mount(mount_spec, at: nil)
83
+ raise ArgumentError, "Cannot mount onto an already built app" if built?
84
+
85
+ @mount_specs << MountSpec.build(mount_spec, at: at)
86
+ end
87
+
88
+ extend Forwardable
89
+ delegate :call => :rack # rubocop:disable Style/HashSyntax
90
+
91
+ def rack
92
+ @rack ||= build_rack
93
+ end
94
+
95
+ private
96
+
97
+ def build_rack
98
+ uri_map = {}
99
+
100
+ @mount_specs.group_by(&:mount_point).map do |mount_point, mount_specs|
101
+ apps = mount_specs.map(&:build_rack_apps).flatten
102
+ uri_map[mount_point] = Rack.merge(apps)
103
+ end
104
+
105
+ ::Rack::URLMap.new(uri_map)
106
+ end
25
107
 
26
- # re/AccessLog: the AccessLog setting points WEBrick's access logging to the
27
- # NullLogger object.
28
- #
29
- # Instead we'll use a combination of Rack::CommonLogger (see Simple::Httpd.app),
30
- # and sinatra's logger (see Simple::Httpd::BaseController).
31
- Rack::Server.start app: app,
32
- Port: port,
33
- environment: environment,
34
- Logger: logger,
35
- AccessLog: [[NullLogger, ""]]
108
+ def built?
109
+ @rack != nil
36
110
  end
37
111
 
38
- module NullLogger # :nodoc:
39
- extend self
112
+ public
40
113
 
41
- def <<(msg); end
114
+ def listen!(environment:, port:, logger:)
115
+ SELF.listen!(rack, environment: environment, port: port, logger: logger)
42
116
  end
43
117
  end