simple-httpd 0.0.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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