cannon 0.0.2

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/.bundle/config +2 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +5 -0
  6. data/README +3 -0
  7. data/Rakefile +20 -0
  8. data/bin/bundler +16 -0
  9. data/bin/coderay +16 -0
  10. data/bin/htmldiff +16 -0
  11. data/bin/ldiff +16 -0
  12. data/bin/mustache +16 -0
  13. data/bin/pry +16 -0
  14. data/bin/rspec +16 -0
  15. data/cannon.gemspec +29 -0
  16. data/lib/cannon.rb +16 -0
  17. data/lib/cannon/app.rb +196 -0
  18. data/lib/cannon/concerns/path_cache.rb +48 -0
  19. data/lib/cannon/concerns/signature.rb +14 -0
  20. data/lib/cannon/config.rb +58 -0
  21. data/lib/cannon/cookie_jar.rb +99 -0
  22. data/lib/cannon/handler.rb +25 -0
  23. data/lib/cannon/middleware.rb +47 -0
  24. data/lib/cannon/middleware/content_type.rb +15 -0
  25. data/lib/cannon/middleware/cookies.rb +18 -0
  26. data/lib/cannon/middleware/files.rb +33 -0
  27. data/lib/cannon/middleware/flush_and_benchmark.rb +20 -0
  28. data/lib/cannon/middleware/request_logger.rb +13 -0
  29. data/lib/cannon/middleware/router.rb +19 -0
  30. data/lib/cannon/request.rb +36 -0
  31. data/lib/cannon/response.rb +165 -0
  32. data/lib/cannon/route.rb +80 -0
  33. data/lib/cannon/route_action.rb +92 -0
  34. data/lib/cannon/version.rb +3 -0
  35. data/lib/cannon/views.rb +28 -0
  36. data/spec/app_spec.rb +20 -0
  37. data/spec/config_spec.rb +41 -0
  38. data/spec/environments_spec.rb +68 -0
  39. data/spec/features/action_types_spec.rb +154 -0
  40. data/spec/features/cookies_spec.rb +62 -0
  41. data/spec/features/files_spec.rb +17 -0
  42. data/spec/features/method_types_spec.rb +104 -0
  43. data/spec/features/requests_spec.rb +59 -0
  44. data/spec/features/views_spec.rb +31 -0
  45. data/spec/fixtures/public/background.jpg +0 -0
  46. data/spec/fixtures/views/render_test.html +1 -0
  47. data/spec/fixtures/views/test.html +1 -0
  48. data/spec/spec_helper.rb +98 -0
  49. data/spec/support/cannon_test.rb +108 -0
  50. metadata +219 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66079c33280a0b8397bb8e889127200e3afef3a9
4
+ data.tar.gz: c43167297db90ba74f2dbacbad97e16b0ce663e8
5
+ SHA512:
6
+ metadata.gz: d9b3c0a33b211e8ca0480f197e6edb8d0a45f9bf31bdf01bcea9b6cd7bc095a51ef6435770286c961e83ed1c48ce9e9d5f4d18ade2f3974127b830dc682d6baf
7
+ data.tar.gz: ef901f8d5be2112104a7167cfa87e2a26884f4bbd6ee74133103148b609047b6f1d387815179fed3567e520b3e7d75f9ff3d92d52f0d52f93233d61b6b118082
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_BIN: bin
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ .DS_Store
3
+ spec/examples.txt
4
+ pkg
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'eventmachine_httpserver', git: 'https://github.com/joeosburn/evma_httpserver.git'
data/README ADDED
@@ -0,0 +1,3 @@
1
+ # cannon
2
+
3
+ Simple EventMachine based application server. WIP.
@@ -0,0 +1,20 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'bundler'
10
+ Bundler::GemHelper.install_tasks
11
+
12
+ require 'rake'
13
+ require 'rspec/core/rake_task'
14
+
15
+ desc "Run all examples"
16
+ RSpec::Core::RakeTask.new(:spec) do |t|
17
+ t.rspec_opts = %w[--color]
18
+ end
19
+
20
+ task :default => [:spec]
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'bundler' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("bundler", "bundler")
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'coderay' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("coderay", "coderay")
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'htmldiff' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("diff-lcs", "htmldiff")
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'ldiff' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("diff-lcs", "ldiff")
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'mustache' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("mustache", "mustache")
data/bin/pry ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'pry' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("pry", "pry")
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rspec-core", "rspec")
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cannon/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'cannon'
8
+ spec.version = Cannon::VERSION
9
+ spec.authors = ['Joe Osburn']
10
+ spec.email = ['joe@jnodev.com']
11
+
12
+ spec.summary = %q{Cannon is a fast web framework}
13
+ spec.homepage = 'https://github.com/joeosburn/cannon'
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ spec.bindir = 'exe'
16
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
17
+ spec.require_paths = ['lib']
18
+ spec.test_files = Dir['spec/**/*']
19
+
20
+ spec.add_dependency 'eventmachine', '~> 1.0.8'
21
+ spec.add_dependency 'eventmachine_httpserver'
22
+ spec.add_dependency 'mime-types', '~> 2.6.2'
23
+ spec.add_dependency 'mustache', '~> 1.0.2'
24
+ spec.add_dependency 'pry', '~> 0.10.3'
25
+ spec.add_dependency 'msgpack', '~> 0.7.1'
26
+
27
+ spec.add_development_dependency 'rspec', '~> 3.3.0'
28
+ spec.add_development_dependency 'http-cookie', '~> 1.0.2'
29
+ end
@@ -0,0 +1,16 @@
1
+ require 'eventmachine'
2
+ require 'evma_httpserver'
3
+ require 'logger'
4
+
5
+ require 'cannon/concerns/path_cache'
6
+ require 'cannon/concerns/signature'
7
+ require 'cannon/config'
8
+ require 'cannon/views'
9
+ require 'cannon/request'
10
+ require 'cannon/cookie_jar'
11
+ require 'cannon/response'
12
+ require 'cannon/middleware'
13
+ require 'cannon/route_action'
14
+ require 'cannon/route'
15
+ require 'cannon/handler'
16
+ require 'cannon/app'
@@ -0,0 +1,196 @@
1
+ require 'mime/types'
2
+ require 'pry'
3
+
4
+ module Cannon
5
+ class AlreadyListening < StandardError; end
6
+
7
+ class App
8
+ attr_reader :routes, :app_binding, :cache
9
+
10
+ def initialize(app_binding, port: nil, ip_address: nil, &block)
11
+ @app_binding = app_binding
12
+ @routes = []
13
+ @load_environment = block
14
+ @cache = {}
15
+
16
+ config.port = port unless port.nil?
17
+ config.ip_address = ip_address unless ip_address.nil?
18
+
19
+ define_cannon_environment
20
+ define_cannon_root
21
+ define_cannon_mime_type
22
+ define_cannon_config
23
+ define_cannon_configure_method
24
+ define_cannon_logger
25
+ define_cannon_cache
26
+ end
27
+
28
+ %w{get post put patch delete head all}.each do |http_method|
29
+ define_method(http_method) do |path, action: nil, actions: nil, redirect: nil, &block|
30
+ add_route(path, method: http_method.to_sym, action: action, actions: actions, redirect: redirect, &block)
31
+ end
32
+ end
33
+
34
+ def listen(port: config.port, ip_address: config.ip_address, async: false)
35
+ cannon_app = self
36
+
37
+ if ENV['CONSOLE']
38
+ command_set = Pry::CommandSet.new {}
39
+ Pry.start binding, commands: command_set
40
+ exit
41
+ end
42
+
43
+ raise AlreadyListening, 'App is currently listening' unless @server_thread.nil?
44
+
45
+ Cannon::Handler.define_singleton_method(:app) { cannon_app }
46
+
47
+ $LOAD_PATH << Cannon.root
48
+ reload_environment unless config.reload_on_request
49
+
50
+ server_block = ->(notifier) do
51
+ EventMachine::run {
52
+ server = EventMachine::start_server(ip_address, port, Cannon::Handler)
53
+ notifier << server unless notifier.nil? # notify the calling thread that the server started if async
54
+ Cannon.logger.info "Cannon listening on port #{port}..."
55
+ }
56
+ end
57
+
58
+ if async
59
+ notification = Queue.new
60
+ Thread.abort_on_exception = true
61
+ @server_thread = Thread.new { server_block.call(notification) }
62
+ notification.pop
63
+ else
64
+ server_block.call(nil)
65
+ end
66
+ end
67
+
68
+ def stop
69
+ return if @server_thread.nil?
70
+ EventMachine::stop_event_loop
71
+ @server_thread.join(10)
72
+ @server_thread.kill unless @server_thread.stop?
73
+ Thread.abort_on_exception = false
74
+ @server_thread = nil
75
+ Cannon.logger.info "Cannon no longer listening"
76
+ end
77
+
78
+ def reload_environment
79
+ @load_environment.call unless @load_environment.nil?
80
+ end
81
+
82
+ def configure(*environments, &block)
83
+ environments.each do |environment|
84
+ define_env_helper(environment)
85
+ yield config if Cannon.env == environment.to_s
86
+ end
87
+ end
88
+
89
+ def config
90
+ @config ||= Config.new
91
+ end
92
+
93
+ def env
94
+ @env ||= detect_env
95
+ end
96
+
97
+ def middleware_runner
98
+ @middleware_runner ||= build_middleware_runner(prepared_middleware_stack)
99
+ end
100
+
101
+ private
102
+
103
+ def prepared_middleware_stack
104
+ stack = config.middleware.dup
105
+ stack << 'FlushAndBenchmark'
106
+ end
107
+
108
+ def build_middleware_runner(middleware, callback: nil)
109
+ return callback if middleware.size < 1
110
+
111
+ middleware_runner = MiddlewareRunner.new(middleware.pop, callback: callback, app: self)
112
+ build_middleware_runner(middleware, callback: middleware_runner)
113
+ end
114
+
115
+ def add_route(path, method:, action:, actions:, redirect:, &block)
116
+ route = Route.new(self, method: method, path: path, actions: [block, action, actions].flatten.compact, redirect: redirect)
117
+ routes << route
118
+ extra_router(route)
119
+ end
120
+
121
+ def detect_env
122
+ ENV['CANNON_ENV'] ? ENV['CANNON_ENV'].dup : 'development'
123
+ end
124
+
125
+ def define_cannon_environment
126
+ cannon_method(:env, self.env)
127
+ define_env_helper(self.env)
128
+ end
129
+
130
+ def define_env_helper(env)
131
+ helper_method = "#{env}?"
132
+ return if Cannon.env.respond_to?(helper_method)
133
+ Cannon.env.singleton_class.send(:define_method, helper_method) { Cannon.env == env }
134
+ end
135
+
136
+ def define_cannon_config
137
+ cannon_method(:config, self.config)
138
+ end
139
+
140
+ def define_cannon_configure_method
141
+ app = self
142
+ Cannon.send(:define_method, :configure, ->(*environments, &block) { app.configure(*environments, &block) })
143
+ Cannon.send(:module_function, :configure)
144
+ end
145
+
146
+ def define_cannon_root
147
+ cannon_method(:root, @app_binding.eval('File.expand_path(File.dirname(__FILE__))'))
148
+ end
149
+
150
+ def define_cannon_cache
151
+ cannon_method(:cache, self.cache)
152
+ end
153
+
154
+ def define_cannon_logger
155
+ app = self
156
+ Cannon.send(:define_method, :logger, -> { app.config.logger })
157
+ Cannon.send(:module_function, :logger)
158
+ end
159
+
160
+ def define_cannon_mime_type
161
+ Cannon.send(:define_method, :mime_type, ->(filepath) { MIME::Types.type_for(filepath.split('/').last).first })
162
+ Cannon.send(:module_function, :mime_type)
163
+ end
164
+
165
+ def cannon_method(name, value)
166
+ Cannon.send(:define_method, name.to_sym, -> { value })
167
+ Cannon.send(:module_function, name.to_sym)
168
+ end
169
+
170
+ def extra_router(route)
171
+ ExtraRouter.new(self, route)
172
+ end
173
+
174
+ class ExtraRouter
175
+ def initialize(app, route)
176
+ @app = app
177
+ @route = route
178
+ end
179
+
180
+ def handle(&block)
181
+ @route.add_route_action(block)
182
+ self
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ module Kernel
189
+ def reload(lib)
190
+ if old = $LOADED_FEATURES.find{ |path| path=~/#{Regexp.escape lib}(\.rb)?\z/ }
191
+ load old
192
+ else
193
+ require lib
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,48 @@
1
+ module PathCache
2
+ def self.included(base)
3
+ base.send(:attr_accessor, :base_path, :cache)
4
+ base.send(:attr_reader, :path_array)
5
+ end
6
+
7
+ private
8
+
9
+ def file_and_content_type(filepath)
10
+ if @app.cache[cache].include?(filepath)
11
+ @app.cache[cache][filepath]
12
+ else
13
+ @app.cache[cache][filepath] = read_file_and_content_type(filepath)
14
+ end
15
+ end
16
+
17
+ def read_file_and_content_type(filepath)
18
+ [IO.binread(filepath), Cannon.mime_type(filepath)]
19
+ end
20
+
21
+ def outdated_cache?
22
+ if @app.config.reload_on_request
23
+ @last_path_signature != current_path_signature
24
+ else
25
+ @last_path_signature.nil?
26
+ end
27
+ end
28
+
29
+ def reload_cache
30
+ @last_path_signature = current_path_signature
31
+ @path_array = build_path_array
32
+ @app.cache[cache] = {}
33
+ end
34
+
35
+ def current_path_signature
36
+ Dir.glob("#{base_path}/**/*").map do |name|
37
+ [name, File.mtime(name)].to_s
38
+ end.inject(Digest::SHA512.new) do |digest, x|
39
+ digest.update x
40
+ end.to_s
41
+ end
42
+
43
+ def build_path_array
44
+ Dir.glob("#{base_path}/**/*").reject { |file| File.directory?(file) }.collect do |name|
45
+ name.gsub(/^#{base_path}/, '')
46
+ end
47
+ end
48
+ end