quick_serve 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Marcin Bunsch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,37 @@
1
+ = quick_serve
2
+
3
+ quick_serve is a super-simple way of serving static files for development. It was made mainly for javascript and css development, but might have other uses.
4
+
5
+ === General usage
6
+
7
+ Type 'qs' or 'quick_serve'. It will start a mongrel web server using the current directory as docroot. By default it will use port 5000, but if you run 'qs -p 4000' it will use 4000 (or any other you specify). Type 'qs -h' for more options.
8
+
9
+ === Snapshot mode usage
10
+
11
+ When evoked with -s trailed with an url, quick_serve will fetch the url and serve it regardless of the path you call. It's useful for testing js/css as it serves the snapshot from memory, minimizing cpu/memory usage leaving the browser with reloading js/css.
12
+
13
+ === Rails usage
14
+
15
+ quick_serve has a Rails adapter which can be called by adding this in environment.rb:
16
+
17
+ require 'quick_serve'
18
+
19
+ It's aimed at giving you a boost of speed on resource hungry pages when you're working on js/css.
20
+ It will store all generated pages requested with GET and having no flash on it and free the snapshot collection when:
21
+
22
+ * Any object is saved or destroyed in ActiveRecord
23
+ * Any file is changed in the app/ directory
24
+
25
+ Keep in mind, it will run only in development mode.
26
+
27
+ == Note on Patches/Pull Requests
28
+
29
+ * Fork the project.
30
+ * Make your feature addition or bug fix.
31
+ * Commit, do not mess with rakefile, version, or history.
32
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
33
+ * Send me a pull request.
34
+
35
+ == Copyright
36
+
37
+ Copyright (c) 2009 Marcin Bunsch . See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "quick_serve"
8
+ gem.summary = %Q{Super simple web server mainly for javascript development}
9
+ gem.description = %Q{}
10
+ gem.email = "marcin@applicake.com"
11
+ gem.homepage = "http://github.com/marcinbunsch/quick_serve"
12
+ gem.authors = ["Marcin Bunsch"]
13
+ gem.bindir = 'bin'
14
+ gem.executables = ['quick_serve', 'qs']
15
+ gem.default_executable = 'qs'
16
+ gem.files = []
17
+ gem.files = FileList['*', '{bin,lib,images,spec}/**/*']
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
22
+ end
23
+
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new(:test) do |test|
26
+ test.libs << 'lib' << 'test'
27
+ test.pattern = 'test/**/*_test.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ begin
32
+ require 'rcov/rcovtask'
33
+ Rcov::RcovTask.new do |test|
34
+ test.libs << 'test'
35
+ test.pattern = 'test/**/*_test.rb'
36
+ test.verbose = true
37
+ end
38
+ rescue LoadError
39
+ task :rcov do
40
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
41
+ end
42
+ end
43
+
44
+ task :test => :check_dependencies
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ if File.exist?('VERSION')
51
+ version = File.read('VERSION')
52
+ else
53
+ version = ""
54
+ end
55
+
56
+ rdoc.rdoc_dir = 'rdoc'
57
+ rdoc.title = "quick_serve #{version}"
58
+ rdoc.rdoc_files.include('README*')
59
+ rdoc.rdoc_files.include('lib/**/*.rb')
60
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.4
data/bin/qs ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'quick_serve'
4
+ QuickServe::Server.new.start
data/bin/quick_serve ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'quick_serve'
4
+ QuickServe::Server.new.start
@@ -0,0 +1,74 @@
1
+ module QuickServe
2
+ module Handlers
3
+ class Directory < Mongrel::DirHandler
4
+
5
+ STYLESHEET = <<-stylesheet
6
+ html, body {
7
+ font-family: "Lucida Grande", Verdana, sans-serif;
8
+ font-size: 90%;
9
+ font-weight: normal;
10
+ line-height: auto;
11
+ }
12
+ html {
13
+ background-color: #F0F0F0;
14
+ }
15
+ #body {
16
+ -moz-border-radius-bottomleft:10px;
17
+ -moz-border-radius-bottomright:10px;
18
+ -moz-border-radius-topleft:10px;
19
+ -moz-border-radius-topright:10px;
20
+ background-color: #fff;
21
+ border:1px solid #E1E1E1;
22
+ color:-moz-fieldtext;
23
+ width: 70%;
24
+ margin:4em auto;
25
+ padding:3em;
26
+ }
27
+ h1 {
28
+ font-size: 130%;
29
+ border-bottom: 1px solid #999;
30
+ padding: 3px;
31
+ }
32
+ a {
33
+ color: #666;
34
+ text-decoration: none
35
+ }
36
+ a:hover { color: #000 }
37
+ h3 {
38
+ font-size: 115%;
39
+ margin-bottom: 10px;
40
+ }
41
+ stylesheet
42
+
43
+ def send_dir_listing(base, dir, response)
44
+ # take off any trailing / so the links come out right
45
+ base = Mongrel::HttpRequest.unescape(base)
46
+ base.chop! if base[-1] == "/"[-1]
47
+
48
+ if @listing_allowed
49
+ response.start(200) do |head,out|
50
+ head[Mongrel::Const::CONTENT_TYPE] = "text/html"
51
+
52
+ out << "<html><head><title>Directory Listing for #{dir}</title><style type=\"text/css\">#{STYLESHEET}</style></head><body><div id=\"body\"><h1>Directory Listing for #{dir}</h1>"
53
+ entries = Dir.entries(dir)
54
+ entries = entries - ['..']
55
+ out << "<h3><a href=\"#{base}/..\">Up to higher level directory</a></h3>"
56
+ entries.each do |child|
57
+ next if child == "."
58
+ out << "<a href=\"#{base}/#{ Mongrel::HttpRequest.escape(child)}\">"
59
+ out << child
60
+ out << "</a><br/>"
61
+ end
62
+ out << "</div></body></html>"
63
+ end
64
+ else
65
+ response.start(403) do |head,out|
66
+ out.write("Directory listings not allowed")
67
+ end
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+
@@ -0,0 +1,31 @@
1
+ module QuickServe
2
+ module Handlers
3
+ class Snapshot < Mongrel::HttpHandler
4
+
5
+ def initialize(url)
6
+ @url = url
7
+ @https = (url.match('https:') ? true : false)
8
+ @host = url.gsub(/(http)(s*)(\:\/\/)/, '').split('/').first
9
+ @snapshot = nil
10
+ fetch
11
+ super()
12
+ end
13
+
14
+ def fetch
15
+ require 'open-uri'
16
+ @snapshot = open(@url).read
17
+ host_root = (@https ? 'https://' : 'http://') + @host + '/'
18
+ @snapshot.gsub!(/(href=|src=)(['"])(\/)/, "\\1\\2#{host_root}\\4")
19
+ end
20
+
21
+ def process(request, response)
22
+ response.start do |head,out|
23
+ puts "quick_serve: served snapshot of #{@url}"
24
+ head["Content-Type"] = "text/html"
25
+ out << @snapshot
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # Here we hack mongrel do jump in front of the handler
2
+ module Mongrel
3
+ module Rails
4
+ class RailsHandler
5
+
6
+ alias_method :original_process, :process
7
+
8
+ def process(request, response)
9
+ # only serve GET
10
+ return original_process(request, response) if request.params['REQUEST_METHOD'] != 'GET'
11
+ url = 'http://' + request.params["HTTP_HOST"] + request.params["REQUEST_URI"]
12
+ snapshot = QuickServe::Rails::Snapshot.fetch(url)
13
+ if snapshot
14
+ response.start do |head,out|
15
+ puts "quick_serve: served snapshot of #{url}"
16
+ head["Content-Type"] = "text/html"
17
+ out << snapshot
18
+ end
19
+ else
20
+ original_process(request, response)
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module ActionController
2
+ class Base
3
+ after_filter :store_snapshot
4
+ def store_snapshot
5
+ puts "quick_serve: stored snapshot as #{request.url}"
6
+ # do not store redirects or pages with flash
7
+ QuickServe::Rails::Snapshot.store(request.url, response.body) if !response.redirected_to and flash == {}
8
+ end
9
+ end
10
+ end
11
+
12
+ module ActiveRecord
13
+ class Base
14
+
15
+ after_save :free_snapshots
16
+ after_destroy :free_snapshots
17
+
18
+ def free_snapshots
19
+ # if rails uses active_record store, it changes with each request, making quick_serve unusable
20
+ # this probably should be a config option (somehow)
21
+ return if self.class.to_s == 'CGI::Session::ActiveRecordStore::Session'
22
+ puts "quick_serve: detected change in #{self.class}"
23
+ QuickServe::Rails::Snapshot.reset
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,81 @@
1
+ require 'find'
2
+ #
3
+ # QuickServe::Listener
4
+ #
5
+ # The generic Listener, which polls the files after a specific interval
6
+ #
7
+ module QuickServe
8
+ module Rails
9
+ class Listener
10
+ attr_accessor :files, :interval
11
+
12
+ # constructor
13
+ def initialize
14
+ self.interval = 2 # decrease the CPU load by increasing the interval
15
+ end
16
+
17
+ # find files and start the listener
18
+ def start
19
+ puts "** quick_serve: scanning for files... "
20
+ # build a file collection
21
+ find_files
22
+ puts "** quick_serve: watching #{files.size} files for changes... "
23
+ wait
24
+ end
25
+
26
+ # wait for a specified interval and check files for changes
27
+ # source: ZenTest/autotest.rb
28
+ def wait
29
+ Kernel.sleep self.interval until check_files
30
+ end
31
+
32
+ # check files to find these that have changed
33
+ def check_files
34
+ updated = []
35
+ files.each do |filename, mtime|
36
+ begin
37
+ current_mtime = File.stat(filename).mtime
38
+ rescue Errno::ENOENT
39
+ # file was not found and was probably deleted
40
+ # remove the file from the file list
41
+ files.delete(filename)
42
+ next
43
+ end
44
+ if current_mtime != mtime
45
+ updated << filename
46
+ # update the mtime in file registry so we it's only send once
47
+ files[filename] = current_mtime
48
+ puts "quick_serve: spotted change in #{filename}"
49
+ end
50
+ end
51
+ QuickServe::Rails::Snapshot.reset if updated != []
52
+ false
53
+ end
54
+
55
+ ##
56
+ # Find the files to process, ignoring temporary files, source
57
+ # configuration management files, etc., and return a Hash mapping
58
+ # filename to modification time.
59
+ # source: ZenTest/autotest.rb
60
+ def find_files
61
+ result = {}
62
+ targets = ['app'] # start simple
63
+ targets.each do |target|
64
+ order = []
65
+ Find.find(target) do |f|
66
+ next if test ?d, f
67
+ next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
68
+ next if f =~ /(\.svn|\.git)$/ # subversion/git
69
+ next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
70
+ filename = f.sub(/^\.\//, '')
71
+
72
+ result[filename] = File.stat(filename).mtime rescue next
73
+ end
74
+ end
75
+
76
+ self.files = result
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ # Storage for snapshots of pages generated by rails
2
+ module QuickServe
3
+ module Rails
4
+ class Snapshot
5
+ @@snapshots = {}
6
+
7
+ # return collection
8
+ def self.dump
9
+ @@snapshots
10
+ end
11
+
12
+ # remove all snapshots
13
+ def self.reset
14
+ @@snapshots = {}
15
+ end
16
+
17
+ # store a snapshot under specified key
18
+ def self.store(key, snapshot)
19
+ @@snapshots[key] = snapshot
20
+ end
21
+
22
+ # fetch a snapshot stored under specified key
23
+ def self.fetch(key)
24
+ @@snapshots[key]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ require 'quick_serve/rails/snapshot'
2
+ require 'quick_serve/rails/listener'
3
+ require 'quick_serve/rails/ext/mongrel'
4
+ require 'quick_serve/rails/ext/rails'
5
+ puts "** quick_serve: attaching rails snapshot handler"
6
+ Thread.new { QuickServe::Rails::Listener.new.start }
@@ -0,0 +1,93 @@
1
+ module QuickServe
2
+ class Server
3
+
4
+ def initialize
5
+ @options = { :dir => '.', :port => 5000, :host => '0.0.0.0', :deamon => false, :url => nil }
6
+ parse
7
+ end
8
+
9
+ def start
10
+ if @options[:url]
11
+ puts "quick_serve: running in snapshot mode using #{@options[:url]} as source"
12
+ else
13
+ puts "quick_serve: mongrel running on port #{@options[:port]} with current directory as docroot"
14
+ end
15
+ begin
16
+ if @options[:deamon]
17
+ pid = fork do
18
+ $stderr, $stdout = StringIO.new, StringIO.new
19
+ serve
20
+ end
21
+ File.open(pidfile, 'w') {|f| f.write(pid) }
22
+ else
23
+ serve
24
+ end
25
+ rescue Errno::EADDRINUSE
26
+ puts "quick_serve: port #{@options[:port]} is used by another process. Please specify other port using the -p option"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def serve
33
+ options = @options
34
+ config = Mongrel::Configurator.new :host => options[:host], :port => options[:port] do
35
+ listener do
36
+ if options[:url]
37
+ require 'quick_serve/handlers/snapshot'
38
+ uri "/", :handler => QuickServe::Handlers::Snapshot.new(options[:url])
39
+ else
40
+ require 'quick_serve/handlers/directory'
41
+ uri "/", :handler => QuickServe::Handlers::Directory.new(options[:dir])
42
+ end
43
+ end
44
+ trap("INT") { stop }
45
+ run
46
+ end
47
+ config.join
48
+ end
49
+
50
+ def parse
51
+ require 'optparse'
52
+ OptionParser.new do |opts|
53
+ opts.banner = "Usage: qs [options]"
54
+
55
+ opts.on("-p PORT", "--port PORT", "Specify port (default: 5000)") { |value| @options[:port] = value; }
56
+ opts.on("--dir DIRECTORY", "Specify directory to act as docroot (default: current directory)") { |value| @options[:dir] = value; }
57
+ opts.on("--host HOST", "Specify host (default: 0.0.0.0)") { |value| @options[:host] = value; }
58
+ opts.on("-s URL", "--snapshot URL", "Specify url for snapshot") { |value| @options[:url] = value; }
59
+ opts.on("-q", "quit deamon (if present)") { quit }
60
+ opts.on("-d", "--deamon", "Run as a deamon process") { @options[:deamon] = true; }
61
+ opts.on_tail("-h", "--help", "Show this message") do
62
+ puts opts
63
+ exit
64
+ end
65
+ end.parse!
66
+ end
67
+
68
+ def quit
69
+
70
+ print "Stopping quick serve... "
71
+ die(" pid file not found") if !File.exists?(pidfile)
72
+ pid = File.read(pidfile)
73
+ begin
74
+ if Process.kill("INT", pid.to_i) == 1
75
+ puts ' ok'
76
+ File.delete(pidfile) if File.exist?(pidfile)
77
+ end
78
+ rescue Errno::ESRCH
79
+ puts ' not found'
80
+ end
81
+ exit
82
+ end
83
+
84
+ def pidfile
85
+ File.join(@options[:dir], "qs.#{@options[:port]}.pid")
86
+ end
87
+
88
+ def die(msg)
89
+ puts msg
90
+ exit(1)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,15 @@
1
+ if defined?(RAILS_ENV)
2
+ if RAILS_ENV == 'development'
3
+ require 'quick_serve/rails'
4
+ else
5
+ puts "** quick_serve: quick_serve rails adapter can run only in development environment"
6
+ end
7
+ else
8
+ begin
9
+ require 'mongrel'
10
+ rescue LoadError
11
+ puts "quick_serve requires mongrel. Install it with: gem install mongrel"
12
+ exit(1)
13
+ end
14
+ require 'quick_serve/server'
15
+ end
@@ -0,0 +1,58 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{quick_serve}
8
+ s.version = "0.3.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Marcin Bunsch"]
12
+ s.date = %q{2009-09-17}
13
+ s.default_executable = %q{qs}
14
+ s.description = %q{}
15
+ s.email = %q{marcin@applicake.com}
16
+ s.executables = ["quick_serve", "qs"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "bin/qs",
27
+ "bin/quick_serve",
28
+ "lib/quick_serve.rb",
29
+ "lib/quick_serve/handlers/directory.rb",
30
+ "lib/quick_serve/handlers/snapshot.rb",
31
+ "lib/quick_serve/rails.rb",
32
+ "lib/quick_serve/rails/ext/mongrel.rb",
33
+ "lib/quick_serve/rails/ext/rails.rb",
34
+ "lib/quick_serve/rails/listener.rb",
35
+ "lib/quick_serve/rails/snapshot.rb",
36
+ "lib/quick_serve/server.rb",
37
+ "quick_serve.gemspec"
38
+ ]
39
+ s.has_rdoc = true
40
+ s.homepage = %q{http://github.com/marcinbunsch/quick_serve}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.1}
44
+ s.summary = %q{Super simple web server mainly for javascript development}
45
+ s.test_files = [
46
+ "test/test_helper.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 2
52
+
53
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
54
+ else
55
+ end
56
+ else
57
+ end
58
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'quick_serve'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: quick_serve
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.4
5
+ platform: ruby
6
+ authors:
7
+ - Marcin Bunsch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-25 00:00:00 +02:00
13
+ default_executable: qs
14
+ dependencies: []
15
+
16
+ description: ""
17
+ email: marcin@applicake.com
18
+ executables:
19
+ - quick_serve
20
+ - qs
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - LICENSE
25
+ - README.rdoc
26
+ files:
27
+ - LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION
31
+ - bin/qs
32
+ - bin/quick_serve
33
+ - lib/quick_serve.rb
34
+ - lib/quick_serve/handlers/directory.rb
35
+ - lib/quick_serve/handlers/snapshot.rb
36
+ - lib/quick_serve/rails.rb
37
+ - lib/quick_serve/rails/ext/mongrel.rb
38
+ - lib/quick_serve/rails/ext/rails.rb
39
+ - lib/quick_serve/rails/listener.rb
40
+ - lib/quick_serve/rails/snapshot.rb
41
+ - lib/quick_serve/server.rb
42
+ - quick_serve.gemspec
43
+ has_rdoc: true
44
+ homepage: http://github.com/marcinbunsch/quick_serve
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.5
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Super simple web server mainly for javascript development
71
+ test_files:
72
+ - test/test_helper.rb