serverside 0.2.8 → 0.2.9
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.
- data/CHANGELOG +24 -0
- data/Rakefile +3 -8
- data/bin/serverside +1 -1
- data/lib/serverside/caching.rb +35 -12
- data/lib/serverside/controller.rb +67 -0
- data/lib/serverside/request.rb +10 -0
- data/lib/serverside/routing.rb +4 -4
- data/lib/serverside/static.rb +7 -1
- data/test/functional/static_server.rb +1 -1
- data/test/spec/caching_spec.rb +39 -1
- data/test/spec/controller_spec.rb +139 -0
- data/test/spec/request_spec.rb +288 -0
- data/test/unit/routing_test.rb +6 -6
- metadata +25 -22
    
        data/CHANGELOG
    CHANGED
    
    | @@ -1,3 +1,27 @@ | |
| 1 | 
            +
            ==0.2.9
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * Improved rake clean task.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * Added HTTP::Request.send_file method.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * Added basic rendering capabilities to ServerSide::Controller.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            * Added support for lambda rules in ServerSide::Controller.mount.
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * Start work on controller class.
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            * Renamed Router.route_default to default_route.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            * Fixed small errors in documentation.
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            * Changed ServerSide::Static.serve_static to look for index.html inside directory before serving the directory.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            * Added HTTP::Caching.cache_etags and cache_stamp methods.
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            * Fixed bug in doc_rforge rake task.
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            * Rewrote request unit test into request spec.
         | 
| 24 | 
            +
             | 
| 1 25 | 
             
            ==0.2.8
         | 
| 2 26 |  | 
| 3 27 | 
             
            * Refactored ServerSide::Static to use HTTP::Caching code.
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -7,8 +7,8 @@ require 'fileutils' | |
| 7 7 | 
             
            include FileUtils
         | 
| 8 8 |  | 
| 9 9 | 
             
            NAME = "serverside"
         | 
| 10 | 
            -
            VERS = "0.2. | 
| 11 | 
            -
            CLEAN.include ['**/.*.sw?', ' | 
| 10 | 
            +
            VERS = "0.2.9"
         | 
| 11 | 
            +
            CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
         | 
| 12 12 | 
             
            RDOC_OPTS = ['--quiet', '--title', "ServerSide Documentation",
         | 
| 13 13 | 
             
              "--opname", "index.html",
         | 
| 14 14 | 
             
              "--line-numbers", 
         | 
| @@ -67,6 +67,7 @@ task :uninstall => [:clean] do | |
| 67 67 | 
             
              sh %{sudo gem uninstall #{NAME}}
         | 
| 68 68 | 
             
            end
         | 
| 69 69 |  | 
| 70 | 
            +
            desc 'Update docs and upload to rubyforge.org'
         | 
| 70 71 | 
             
            task :doc_rforge do
         | 
| 71 72 | 
             
              sh %{rake doc}
         | 
| 72 73 | 
             
              sh %{scp -r doc/rdoc/* ciconia@rubyforge.org:/var/www/gforge-projects/serverside}
         | 
| @@ -124,12 +125,6 @@ RCov::VerifyTask.new(:rcov_verify => :rcov) do |t| | |
| 124 125 | 
             
              t.index_html = 'doc/output/coverage/index.html'
         | 
| 125 126 | 
             
            end
         | 
| 126 127 |  | 
| 127 | 
            -
            desc 'Update docs and upload to rubyforge.org'
         | 
| 128 | 
            -
            task :doc_rforge do
         | 
| 129 | 
            -
              sh %{rake doc}
         | 
| 130 | 
            -
              sh "scp -C -r * ciconia@rubyforge.org:/var/www/gforge-projects/serverside/"
         | 
| 131 | 
            -
            end
         | 
| 132 | 
            -
             | 
| 133 128 | 
             
            ##############################################################################
         | 
| 134 129 | 
             
            # Statistics
         | 
| 135 130 | 
             
            ##############################################################################
         | 
    
        data/bin/serverside
    CHANGED
    
    
    
        data/lib/serverside/caching.rb
    CHANGED
    
    | @@ -14,7 +14,30 @@ module ServerSide | |
| 14 14 | 
             
                  NOT_MODIFIED_CLOSE = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nConnection: close\r\nLast-Modified: %s\r\nETag: \"%s\"\r\nCache-Control: max-age=%d\r\n\r\n".freeze
         | 
| 15 15 | 
             
                  NOT_MODIFIED_PERSIST = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nLast-Modified: %s\r\nETag: \"%s\"\r\nCache-Control: max-age=%d\r\n\r\n".freeze
         | 
| 16 16 | 
             
                  MAX_AGE = 'max-age=%d'.freeze
         | 
| 17 | 
            +
                  IF_NONE_MATCH_REGEXP = /^"?([^"]+)"?$/.freeze
         | 
| 17 18 |  | 
| 19 | 
            +
                  # Returns an array containing all etags specified by the client in the
         | 
| 20 | 
            +
                  # If-None-Match header.
         | 
| 21 | 
            +
                  def cache_etags
         | 
| 22 | 
            +
                    h = @headers[IF_NONE_MATCH]
         | 
| 23 | 
            +
                    return [] unless h
         | 
| 24 | 
            +
                    h.split(',').inject([]) do |m, i|
         | 
| 25 | 
            +
                      i.strip =~ IF_NONE_MATCH_REGEXP ? (m << $1) : m
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                  
         | 
| 29 | 
            +
                  # Returns the cache stamp specified by the client in the 
         | 
| 30 | 
            +
                  # If-Modified-Since header. If no stamp is specified, returns nil.
         | 
| 31 | 
            +
                  def cache_stamp
         | 
| 32 | 
            +
                    (h = @headers[IF_MODIFIED_SINCE]) ? Time.httpdate(h) : nil
         | 
| 33 | 
            +
                  rescue
         | 
| 34 | 
            +
                    nil
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  # Checks the request headers for validators and returns true if the
         | 
| 38 | 
            +
                  # client cache is valid. The validators can be either etags (specified
         | 
| 39 | 
            +
                  # in the If-None-Match header), or a modification stamp (specified in the
         | 
| 40 | 
            +
                  # If-Modified-Since header.)
         | 
| 18 41 | 
             
                  def valid_client_cache?(etag, http_stamp)
         | 
| 19 42 | 
             
                    none_match = @headers[IF_NONE_MATCH]
         | 
| 20 43 | 
             
                    modified_since = @headers[IF_MODIFIED_SINCE]
         | 
| @@ -22,6 +45,15 @@ module ServerSide | |
| 22 45 | 
             
                      (modified_since && (modified_since == http_stamp))
         | 
| 23 46 | 
             
                  end
         | 
| 24 47 |  | 
| 48 | 
            +
                  # Validates the client cache by checking any supplied validators in the
         | 
| 49 | 
            +
                  # request. If the client cache is not valid, the specified block is
         | 
| 50 | 
            +
                  # executed. This method also makes sure the correct validators are 
         | 
| 51 | 
            +
                  # included in the response - along with a Cache-Control header, to allow
         | 
| 52 | 
            +
                  # the client to cache the response. A possible usage:
         | 
| 53 | 
            +
                  # 
         | 
| 54 | 
            +
                  #   validate_cache("1234-5678", Time.now, 360) do
         | 
| 55 | 
            +
                  #     send_response(200, "text/html", body)
         | 
| 56 | 
            +
                  #   end
         | 
| 25 57 | 
             
                  def validate_cache(etag, stamp, max_age = DEFAULT_MAX_AGE, &block)
         | 
| 26 58 | 
             
                    http_stamp = stamp.httpdate
         | 
| 27 59 | 
             
                    if valid_client_cache?(etag, http_stamp)
         | 
| @@ -34,23 +66,14 @@ module ServerSide | |
| 34 66 | 
             
                    end
         | 
| 35 67 | 
             
                  end
         | 
| 36 68 |  | 
| 69 | 
            +
                  # Sends a 304 HTTP response, along with etag and stamp validators, and a
         | 
| 70 | 
            +
                  # Cache-Control header.
         | 
| 37 71 | 
             
                  def send_not_modified(etag, http_time, max_age = DEFAULT_MAX_AGE)
         | 
| 38 72 | 
             
                    @socket << ((@persistent ? NOT_MODIFIED_PERSIST : NOT_MODIFIED_CLOSE) % 
         | 
| 39 73 | 
             
                      [Time.now.httpdate, http_time, etag, max_age])
         | 
| 40 74 | 
             
                  end
         | 
| 41 | 
            -
                end | 
| 75 | 
            +
                end
         | 
| 42 76 | 
             
              end
         | 
| 43 77 | 
             
            end
         | 
| 44 78 |  | 
| 45 79 | 
             
            __END__
         | 
| 46 | 
            -
             | 
| 47 | 
            -
            Reality::Controller.new(:node_history) do
         | 
| 48 | 
            -
              default_flavor :html
         | 
| 49 | 
            -
              valid_method :get
         | 
| 50 | 
            -
              
         | 
| 51 | 
            -
              def html
         | 
| 52 | 
            -
                validate_cache(etag, http_time) {|cache_headers|
         | 
| 53 | 
            -
                  render_template(cache_headers)
         | 
| 54 | 
            -
                }
         | 
| 55 | 
            -
              end
         | 
| 56 | 
            -
            end
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            require File.join(File.dirname(__FILE__), 'routing')
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ServerSide
         | 
| 4 | 
            +
              class Controller
         | 
| 5 | 
            +
                def self.mount(rule = nil, &block)
         | 
| 6 | 
            +
                  rule ||= block
         | 
| 7 | 
            +
                  raise ArgumentError, "No routing rule specified." if rule.nil?
         | 
| 8 | 
            +
                  c = Class.new(self) {}
         | 
| 9 | 
            +
                  ServerSide::Router.route(rule) {c.new(self)}
         | 
| 10 | 
            +
                  c
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                def initialize(request)
         | 
| 14 | 
            +
                  @request = request
         | 
| 15 | 
            +
                  @path = request.path
         | 
| 16 | 
            +
                  @parameters = request.parameters
         | 
| 17 | 
            +
                  process
         | 
| 18 | 
            +
                  render_default if not @rendered
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
                
         | 
| 21 | 
            +
                def process
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
                
         | 
| 24 | 
            +
                def render_default
         | 
| 25 | 
            +
                  @request.send_response(200, 'text/plain', 'no response.')
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
                
         | 
| 28 | 
            +
                def render(body, content_type)
         | 
| 29 | 
            +
                  @request.send_response(200, content_type, body)
         | 
| 30 | 
            +
                  @rendered = true
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            __END__
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            require 'rubygems'
         | 
| 38 | 
            +
            require 'active_support/inflector'
         | 
| 39 | 
            +
            require 'metaid'
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            class ActionController
         | 
| 42 | 
            +
              def self.default_routing_rule
         | 
| 43 | 
            +
                if name.split('::').last =~ /(.+)Controller$/
         | 
| 44 | 
            +
                  controller = Inflector.underscore($1) 
         | 
| 45 | 
            +
                  {:path => ["/#{controller}/:action", "/#{controller}/:action/:id"]}
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def self.inherited(c)
         | 
| 50 | 
            +
                routing_rule = c.respond_to?(:routing_rule) ?
         | 
| 51 | 
            +
                  c.routing_rule : c.default_routing_rule
         | 
| 52 | 
            +
                if routing_rule
         | 
| 53 | 
            +
                  ServerSide::Router.route(routing_rule) {c.new(self)}
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
              
         | 
| 57 | 
            +
              def self.route(arg = nil, &block)
         | 
| 58 | 
            +
                rule = arg || block
         | 
| 59 | 
            +
                meta_def(:get_route) {rule}
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            class MyController < ActionController
         | 
| 64 | 
            +
              route "hello"
         | 
| 65 | 
            +
            end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            p MyController.get_route
         | 
    
        data/lib/serverside/request.rb
    CHANGED
    
    | @@ -165,6 +165,16 @@ module ServerSide | |
| 165 165 | 
             
                    @persistent = false
         | 
| 166 166 | 
             
                  end
         | 
| 167 167 |  | 
| 168 | 
            +
                  CONTENT_DISPOSITION = 'Content-Disposition'.freeze
         | 
| 169 | 
            +
                  CONTENT_DESCRIPTION = 'Content-Description'.freeze
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  def send_file(content, content_type, disposition = :inline, filename = nil, description = nil)
         | 
| 172 | 
            +
                    disposition = filename ? "#{disposition}; filename=#{filename}" : "#{disposition}"
         | 
| 173 | 
            +
                    @response_headers[CONTENT_DISPOSITION] = disposition
         | 
| 174 | 
            +
                    @response_headers[CONTENT_DESCRIPTION] = description if description
         | 
| 175 | 
            +
                    send_response(200, content_type, content)
         | 
| 176 | 
            +
                  end
         | 
| 177 | 
            +
                  
         | 
| 168 178 | 
             
                  # Send a redirect response.
         | 
| 169 179 | 
             
                  def redirect(location, permanent = false)
         | 
| 170 180 | 
             
                    @socket << (STATUS_REDIRECT % 
         | 
    
        data/lib/serverside/routing.rb
    CHANGED
    
    | @@ -1,8 +1,8 @@ | |
| 1 1 | 
             
            require File.join(File.dirname(__FILE__), 'request')
         | 
| 2 2 |  | 
| 3 3 | 
             
            module ServerSide
         | 
| 4 | 
            -
              # The Router class  | 
| 5 | 
            -
              #  | 
| 4 | 
            +
              # The Router class is a subclass of HTTP::Request that can invoke
         | 
| 5 | 
            +
              # different handlers based on rules that can be specified either by
         | 
| 6 6 | 
             
              # lambdas or by hashes that contain variable names corresponding to patterns.
         | 
| 7 7 | 
             
              #
         | 
| 8 8 | 
             
              # The simplest form of a routing rule specifies a path pattern:
         | 
| @@ -19,7 +19,7 @@ module ServerSide | |
| 19 19 | 
             
              #
         | 
| 20 20 | 
             
              #   ServerSide.route(lambda {@headers['Agent'] =~ /Moz/}) {serve_static('moz'/@path)}
         | 
| 21 21 | 
             
              #
         | 
| 22 | 
            -
              # Routing rules are evaluated  | 
| 22 | 
            +
              # Routing rules are evaluated backwards, so the rules should be ordered
         | 
| 23 23 | 
             
              # from the general to the specific.
         | 
| 24 24 | 
             
              class Router < HTTP::Request
         | 
| 25 25 | 
             
                # Returns true if routes were defined.
         | 
| @@ -116,7 +116,7 @@ module ServerSide | |
| 116 116 | 
             
                end
         | 
| 117 117 |  | 
| 118 118 | 
             
                # Sets the default handler for incoming requests.
         | 
| 119 | 
            -
                def self. | 
| 119 | 
            +
                def self.default_route(&block)
         | 
| 120 120 | 
             
                  define_method(:default_handler, &block)
         | 
| 121 121 | 
             
                  compile_rules
         | 
| 122 122 | 
             
                end
         | 
    
        data/lib/serverside/static.rb
    CHANGED
    
    | @@ -76,7 +76,13 @@ module ServerSide | |
| 76 76 | 
             
                  elsif serve_template(path)
         | 
| 77 77 | 
             
                    return
         | 
| 78 78 | 
             
                  elsif File.directory?(path)
         | 
| 79 | 
            -
                     | 
| 79 | 
            +
                    if File.file?(path/'index.html')
         | 
| 80 | 
            +
                      serve_file(path/'index.html')
         | 
| 81 | 
            +
                    elsif File.file?(path/'index.rhtml')
         | 
| 82 | 
            +
                      serve_template(path/'index.rhtml')
         | 
| 83 | 
            +
                    else
         | 
| 84 | 
            +
                      serve_dir(path)
         | 
| 85 | 
            +
                    end
         | 
| 80 86 | 
             
                  else
         | 
| 81 87 | 
             
                    send_response(404, 'text', FILE_NOT_FOUND)
         | 
| 82 88 | 
             
                  end
         | 
    
        data/test/spec/caching_spec.rb
    CHANGED
    
    | @@ -98,4 +98,42 @@ context "Caching::valid_client_cache?" do | |
| 98 98 | 
             
                r.headers['If-Modified-Since'] = t.httpdate
         | 
| 99 99 | 
             
                r.valid_client_cache?('eeee', t.httpdate).should_not_be false
         | 
| 100 100 | 
             
              end
         | 
| 101 | 
            -
            end
         | 
| 101 | 
            +
            end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            context "Caching::cache_etags" do
         | 
| 104 | 
            +
              specify "should return an empty array if no If-None-Match header is included" do
         | 
| 105 | 
            +
                r = DummyRequest.new
         | 
| 106 | 
            +
                r.cache_etags.should_be_a_kind_of Array
         | 
| 107 | 
            +
                r.cache_etags.should_equal []
         | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
              
         | 
| 110 | 
            +
              specify "should return all etags included in the If-None-Match header" do
         | 
| 111 | 
            +
                r = DummyRequest.new
         | 
| 112 | 
            +
                r.headers['If-None-Match'] = '*'
         | 
| 113 | 
            +
                r.cache_etags.should_equal ['*']
         | 
| 114 | 
            +
                r.headers['If-None-Match'] = '*, "XXX-YYY","AAA-BBB"'
         | 
| 115 | 
            +
                r.cache_etags.should_equal ['*', 'XXX-YYY', 'AAA-BBB']
         | 
| 116 | 
            +
                r.headers['If-None-Match'] = '"abcd-EFGH"'
         | 
| 117 | 
            +
                r.cache_etags.should_equal ['abcd-EFGH']
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
            end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            context "Caching::cache_stamps" do
         | 
| 122 | 
            +
              specify "should return nil if no If-Modified-Since header is included" do
         | 
| 123 | 
            +
                r = DummyRequest.new
         | 
| 124 | 
            +
                r.cache_stamp.should_be_nil
         | 
| 125 | 
            +
              end
         | 
| 126 | 
            +
              
         | 
| 127 | 
            +
              specify "should return nil if no an invalid stamp is specified" do
         | 
| 128 | 
            +
                r = DummyRequest.new
         | 
| 129 | 
            +
                r.headers['If-Modified-Since'] = 'invalid stamp'
         | 
| 130 | 
            +
                r.cache_stamp.should_be_nil
         | 
| 131 | 
            +
              end
         | 
| 132 | 
            +
              
         | 
| 133 | 
            +
              specify "should return the stamp specified in the If-Modified-Since header" do
         | 
| 134 | 
            +
                r = DummyRequest.new
         | 
| 135 | 
            +
                t = Time.now
         | 
| 136 | 
            +
                r.headers['If-Modified-Since'] = t.httpdate
         | 
| 137 | 
            +
                r.cache_stamp.to_i.should_equal t.to_i
         | 
| 138 | 
            +
              end
         | 
| 139 | 
            +
            end
         | 
| @@ -0,0 +1,139 @@ | |
| 1 | 
            +
            require File.join(File.dirname(__FILE__), '../../lib/serverside')
         | 
| 2 | 
            +
            require 'stringio'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class ServerSide::Router
         | 
| 5 | 
            +
              def self.rules
         | 
| 6 | 
            +
                @@rules
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
              
         | 
| 9 | 
            +
              def self.reset_rules
         | 
| 10 | 
            +
                @@rules = []
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
              
         | 
| 13 | 
            +
              attr_accessor :path
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            context "ServerSide::Controller.mount" do
         | 
| 17 | 
            +
              specify "should accept a routing rule as argument" do
         | 
| 18 | 
            +
                proc {ServerSide::Controller.mount}.should_raise ArgumentError
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
              
         | 
| 21 | 
            +
              specify "should return a subclass of ServerSide::Controller" do
         | 
| 22 | 
            +
                c = ServerSide::Controller.mount(:path => '/test')
         | 
| 23 | 
            +
                c.should_be_a_kind_of Class
         | 
| 24 | 
            +
                c.superclass.should_be ServerSide::Controller
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
              
         | 
| 27 | 
            +
              specify "should add a routing rule using ServerSide::Router.route" do
         | 
| 28 | 
            +
                ServerSide::Router.reset_rules
         | 
| 29 | 
            +
                rule = {:path => '/test'}
         | 
| 30 | 
            +
                c = ServerSide::Controller.mount(rule)
         | 
| 31 | 
            +
                r = ServerSide::Router.rules.first
         | 
| 32 | 
            +
                r.first.should_equal rule
         | 
| 33 | 
            +
                r.last.should_be_a_kind_of Proc
         | 
| 34 | 
            +
                c.module_eval do
         | 
| 35 | 
            +
                  define_method(:initialize) {|req| $req = req}
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                res = r.last.call
         | 
| 38 | 
            +
                res.should_be_a_kind_of c
         | 
| 39 | 
            +
                
         | 
| 40 | 
            +
                r = ServerSide::Router.new(StringIO.new)
         | 
| 41 | 
            +
                r.path = '/test'
         | 
| 42 | 
            +
                r.respond
         | 
| 43 | 
            +
                $req.should_be_a_kind_of ServerSide::Router
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              specify "should accept either an argument or block as the rule" do
         | 
| 47 | 
            +
                ServerSide::Router.reset_rules
         | 
| 48 | 
            +
                rule = {:path => '/test'}
         | 
| 49 | 
            +
                c = ServerSide::Controller.mount(rule)
         | 
| 50 | 
            +
                r = ServerSide::Router.rules.first
         | 
| 51 | 
            +
                r.first.should_be rule
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                ServerSide::Router.reset_rules
         | 
| 54 | 
            +
                rule = proc {true}
         | 
| 55 | 
            +
                c = ServerSide::Controller.mount(&rule)
         | 
| 56 | 
            +
                r = ServerSide::Router.rules.first
         | 
| 57 | 
            +
                r.first.should_be rule
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
            end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            class ServerSide::Controller
         | 
| 62 | 
            +
              attr_accessor :request, :path, :parameters, :rendered
         | 
| 63 | 
            +
            end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            class ServerSide::HTTP::Request
         | 
| 66 | 
            +
              attr_accessor :path, :parameters
         | 
| 67 | 
            +
            end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            require 'metaid'
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            class DummyController < ServerSide::Controller
         | 
| 72 | 
            +
              attr_reader :process_called
         | 
| 73 | 
            +
              
         | 
| 74 | 
            +
              def process
         | 
| 75 | 
            +
                @process_called = true
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
              
         | 
| 78 | 
            +
              def render_default
         | 
| 79 | 
            +
                @rendered = :default
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
            end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            context "ServerSide::Controller new instance" do
         | 
| 84 | 
            +
              specify "should set @request, @path, and @parameters instance variables" do
         | 
| 85 | 
            +
                req = ServerSide::HTTP::Request.new(StringIO.new)
         | 
| 86 | 
            +
                req.path = '/aa/bb/cc'
         | 
| 87 | 
            +
                req.parameters = {:q => 'node_state', :f => 'xml'}
         | 
| 88 | 
            +
                c = ServerSide::Controller.new(req)
         | 
| 89 | 
            +
                c.request.should_be req
         | 
| 90 | 
            +
                c.path.should_be req.path
         | 
| 91 | 
            +
                c.parameters.should_be req.parameters
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
              
         | 
| 94 | 
            +
              specify "should invoke the process method" do
         | 
| 95 | 
            +
                req = ServerSide::HTTP::Request.new(StringIO.new)
         | 
| 96 | 
            +
                c = DummyController.new(req)
         | 
| 97 | 
            +
                c.process_called.should_be true
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
              
         | 
| 100 | 
            +
              specify "should invoke render_default unless @rendered" do
         | 
| 101 | 
            +
                req = ServerSide::HTTP::Request.new(StringIO.new)
         | 
| 102 | 
            +
                c = DummyController.new(req)
         | 
| 103 | 
            +
                c.rendered.should_be :default
         | 
| 104 | 
            +
                
         | 
| 105 | 
            +
                c_class = Class.new(DummyController) do
         | 
| 106 | 
            +
                  define_method(:process) {@rendered = true}
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
                c = c_class.new(req)
         | 
| 109 | 
            +
                c.rendered.should_be true
         | 
| 110 | 
            +
              end
         | 
| 111 | 
            +
            end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            context "ServerSide::Controller.render_default" do
         | 
| 114 | 
            +
              specify "should render a default 200 response" do
         | 
| 115 | 
            +
                req = ServerSide::HTTP::Request.new(StringIO.new)
         | 
| 116 | 
            +
                c = ServerSide::Controller.new(req)
         | 
| 117 | 
            +
                req.socket.rewind
         | 
| 118 | 
            +
                resp = req.socket.read
         | 
| 119 | 
            +
                resp.should_match /HTTP\/1\.1\s200/
         | 
| 120 | 
            +
              end
         | 
| 121 | 
            +
            end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            context "ServerSide::Controller.render" do
         | 
| 124 | 
            +
              specify "should render a 200 response with body and content type arguments" do
         | 
| 125 | 
            +
                req = ServerSide::HTTP::Request.new(StringIO.new)
         | 
| 126 | 
            +
                c = ServerSide::Controller.new(req)
         | 
| 127 | 
            +
                c.render('hello world', 'text/plain')
         | 
| 128 | 
            +
                req.socket.rewind
         | 
| 129 | 
            +
                resp = req.socket.read
         | 
| 130 | 
            +
                resp.should_match /Content-Type:\stext\/plain\r\n/
         | 
| 131 | 
            +
              end
         | 
| 132 | 
            +
              
         | 
| 133 | 
            +
              specify "should set @rendered to true" do
         | 
| 134 | 
            +
                req = ServerSide::HTTP::Request.new(StringIO.new)
         | 
| 135 | 
            +
                c = ServerSide::Controller.new(req)
         | 
| 136 | 
            +
                c.render('hello world', 'text/plain')
         | 
| 137 | 
            +
                c.rendered.should_equal true
         | 
| 138 | 
            +
              end
         | 
| 139 | 
            +
            end
         | 
| @@ -0,0 +1,288 @@ | |
| 1 | 
            +
            require File.join(File.dirname(__FILE__), '../../lib/serverside')
         | 
| 2 | 
            +
            require 'stringio'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class DummyRequest2 < ServerSide::HTTP::Request
         | 
| 5 | 
            +
              attr_accessor :calls, :parse_result, :persistent
         | 
| 6 | 
            +
              
         | 
| 7 | 
            +
              def parse
         | 
| 8 | 
            +
                @calls ||= []
         | 
| 9 | 
            +
                @calls << :parse
         | 
| 10 | 
            +
                @parse_result
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
              
         | 
| 13 | 
            +
              def respond
         | 
| 14 | 
            +
                @calls ||= []
         | 
| 15 | 
            +
                @calls << :respond
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            context "HTTP::Request.process" do
         | 
| 20 | 
            +
              specify "should call parse and and short-circuit if the result is nil" do
         | 
| 21 | 
            +
                r = DummyRequest2.new(nil)
         | 
| 22 | 
            +
                r.process.should_be_nil
         | 
| 23 | 
            +
                r.calls.should_equal [:parse]
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                r.calls = []
         | 
| 26 | 
            +
                r.parse_result = false
         | 
| 27 | 
            +
                r.process.should_be false
         | 
| 28 | 
            +
                r.calls.should_equal [:parse]
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
              
         | 
| 31 | 
            +
              specify "should follow parse with respond and return @persistent" do
         | 
| 32 | 
            +
                r = DummyRequest2.new(nil)
         | 
| 33 | 
            +
                r.parse_result = true
         | 
| 34 | 
            +
                r.process.should_be_nil
         | 
| 35 | 
            +
                r.calls.should_equal [:parse, :respond]
         | 
| 36 | 
            +
                
         | 
| 37 | 
            +
                r.calls = []
         | 
| 38 | 
            +
                r.persistent = 'mau'
         | 
| 39 | 
            +
                r.process.should_equal 'mau'
         | 
| 40 | 
            +
                r.calls.should_equal [:parse, :respond]
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            class ServerSide::HTTP::Request
         | 
| 45 | 
            +
              attr_writer :socket, :persistent, :response_headers
         | 
| 46 | 
            +
            end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            context "HTTP::Request.parse" do
         | 
| 49 | 
            +
              specify "should return nil for invalid requests" do
         | 
| 50 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 51 | 
            +
                r.socket = StringIO.new('POST /test')
         | 
| 52 | 
            +
                r.parse.should_be_nil
         | 
| 53 | 
            +
                r.socket = StringIO.new('invalid string')
         | 
| 54 | 
            +
                r.parse.should_be_nil
         | 
| 55 | 
            +
                r.socket = StringIO.new('GET HTTP/1.1')
         | 
| 56 | 
            +
                r.parse.should_be_nil
         | 
| 57 | 
            +
                r.socket = StringIO.new('GET /test http')
         | 
| 58 | 
            +
                r.parse.should_be_nil
         | 
| 59 | 
            +
                r.socket = StringIO.new('GET /test HTTP')
         | 
| 60 | 
            +
                r.parse.should_be_nil
         | 
| 61 | 
            +
                r.socket = StringIO.new('GET /test HTTP/')
         | 
| 62 | 
            +
                r.parse.should_be_nil
         | 
| 63 | 
            +
                r.socket = StringIO.new('POST /test HTTP/1.1')
         | 
| 64 | 
            +
                r.parse.should_be_nil
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
              
         | 
| 67 | 
            +
              specify "should parse valid requests and return request headers" do
         | 
| 68 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 69 | 
            +
                r.socket = StringIO.new(
         | 
| 70 | 
            +
                  "POST /test HTTP/1.1\r\nContent-Type: text/html\r\n\r\n")
         | 
| 71 | 
            +
                r.parse.should_be r.headers
         | 
| 72 | 
            +
                r.method.should_equal :post
         | 
| 73 | 
            +
                r.path.should_equal '/test'
         | 
| 74 | 
            +
                r.query.should_be_nil
         | 
| 75 | 
            +
                r.version.should_equal '1.1'
         | 
| 76 | 
            +
                r.parameters.should_equal({})
         | 
| 77 | 
            +
                r.headers.should_equal({'Content-Type' => 'text/html'})
         | 
| 78 | 
            +
                r.cookies.should_equal({})
         | 
| 79 | 
            +
                r.response_cookies.should_be_nil
         | 
| 80 | 
            +
                r.persistent.should_equal true
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
              
         | 
| 83 | 
            +
              specify "should correctly handle trailing slash in path" do
         | 
| 84 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 85 | 
            +
                r.socket = StringIO.new("POST /test/asdf/qw/ HTTP/1.1\r\n\r\n")
         | 
| 86 | 
            +
                r.parse.should_not_be_nil
         | 
| 87 | 
            +
                r.path.should_equal '/test/asdf/qw'
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                r.socket = StringIO.new(
         | 
| 90 | 
            +
                  "POST /test/asdf/qw/?time=24%20hours HTTP/1.1\r\n\r\n")
         | 
| 91 | 
            +
                r.parse.should_not_be_nil
         | 
| 92 | 
            +
                r.path.should_equal '/test/asdf/qw'
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
              
         | 
| 95 | 
            +
              specify "should parse URL-encoded parameters" do
         | 
| 96 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 97 | 
            +
                r.socket = StringIO.new(
         | 
| 98 | 
            +
                  "POST /test?q=node_history&time=24%20hours HTTP/1.1\r\n\r\n")
         | 
| 99 | 
            +
                r.parse.should_not_be_nil
         | 
| 100 | 
            +
                r.parameters.size.should_equal 2
         | 
| 101 | 
            +
                r.parameters[:time].should_equal '24 hours'
         | 
| 102 | 
            +
                r.parameters[:q].should_equal 'node_history'
         | 
| 103 | 
            +
              end
         | 
| 104 | 
            +
              
         | 
| 105 | 
            +
              specify "should correctly parse the HTTP version" do
         | 
| 106 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 107 | 
            +
                r.socket = StringIO.new(
         | 
| 108 | 
            +
                  "POST / HTTP/1.0\r\n\r\n")
         | 
| 109 | 
            +
                r.parse.should_not_be_nil
         | 
| 110 | 
            +
                r.version.should_equal '1.0'
         | 
| 111 | 
            +
                r.socket = StringIO.new(
         | 
| 112 | 
            +
                  "POST / HTTP/3.2\r\n\r\n")
         | 
| 113 | 
            +
                r.parse.should_not_be_nil
         | 
| 114 | 
            +
                r.version.should_equal '3.2'
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
              
         | 
| 117 | 
            +
              specify "should set @persistent correctly" do
         | 
| 118 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 119 | 
            +
                r.socket = StringIO.new("POST / HTTP/1.0\r\n\r\n")
         | 
| 120 | 
            +
                r.parse.should_not_be_nil
         | 
| 121 | 
            +
                r.persistent.should_equal false
         | 
| 122 | 
            +
                r.socket = StringIO.new("POST / HTTP/1.1\r\n\r\n")
         | 
| 123 | 
            +
                r.parse.should_not_be_nil
         | 
| 124 | 
            +
                r.persistent.should_equal true
         | 
| 125 | 
            +
                r.socket = StringIO.new("POST / HTTP/0.6\r\n\r\n")
         | 
| 126 | 
            +
                r.parse.should_not_be_nil
         | 
| 127 | 
            +
                r.persistent.should_equal false
         | 
| 128 | 
            +
                r.socket = StringIO.new("POST / HTTP/1.1\r\nConnection: close\r\n\r\n")
         | 
| 129 | 
            +
                r.parse.should_not_be_nil
         | 
| 130 | 
            +
                r.persistent.should_equal false
         | 
| 131 | 
            +
                r.socket = StringIO.new("POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")
         | 
| 132 | 
            +
                r.parse.should_not_be_nil
         | 
| 133 | 
            +
                r.persistent.should_equal true
         | 
| 134 | 
            +
              end
         | 
| 135 | 
            +
              
         | 
| 136 | 
            +
              specify "should parse cookies" do
         | 
| 137 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 138 | 
            +
                r.socket = StringIO.new(
         | 
| 139 | 
            +
                  "POST / HTTP/1.0\r\nCookie: abc=1342; def=7%2f4\r\n\r\n")
         | 
| 140 | 
            +
                r.parse.should_not_be_nil
         | 
| 141 | 
            +
                r.headers['Cookie'].should_equal 'abc=1342; def=7%2f4'
         | 
| 142 | 
            +
                r.cookies.size.should_equal 2
         | 
| 143 | 
            +
                r.cookies[:abc].should_equal '1342'
         | 
| 144 | 
            +
                r.cookies[:def].should_equal '7/4'
         | 
| 145 | 
            +
              end
         | 
| 146 | 
            +
              
         | 
| 147 | 
            +
              specify "should parse the post body" do
         | 
| 148 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 149 | 
            +
                r.socket = StringIO.new(
         | 
| 150 | 
            +
                  "POST /?q=node_history HTTP/1.0\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 15\r\n\r\ntime=24%20hours")
         | 
| 151 | 
            +
                r.parse.should_not_be_nil
         | 
| 152 | 
            +
                r.parameters.size.should_equal 2
         | 
| 153 | 
            +
                r.parameters[:q].should_equal 'node_history'
         | 
| 154 | 
            +
                r.parameters[:time].should_equal '24 hours'
         | 
| 155 | 
            +
              end
         | 
| 156 | 
            +
            end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
            context "HTTP::Request.send_response" do
         | 
| 159 | 
            +
              specify "should format a response with status and body" do
         | 
| 160 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 161 | 
            +
                r.socket = StringIO.new
         | 
| 162 | 
            +
                r.send_response(200, 'text', 'Hello there!')
         | 
| 163 | 
            +
                r.socket.rewind
         | 
| 164 | 
            +
                r.socket.read.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!"
         | 
| 165 | 
            +
              end
         | 
| 166 | 
            +
              
         | 
| 167 | 
            +
              specify "should format a response without connect-close when persistent" do
         | 
| 168 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 169 | 
            +
                r.socket = StringIO.new
         | 
| 170 | 
            +
                r.persistent = true
         | 
| 171 | 
            +
                r.send_response(200, 'text', 'Hello there!')
         | 
| 172 | 
            +
                r.socket.rewind
         | 
| 173 | 
            +
                r.socket.read.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!"
         | 
| 174 | 
            +
              end
         | 
| 175 | 
            +
              
         | 
| 176 | 
            +
              specify "should format a response without content-length for streaming response" do
         | 
| 177 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 178 | 
            +
                r.socket = StringIO.new
         | 
| 179 | 
            +
                r.persistent = true
         | 
| 180 | 
            +
                r.send_response(200, 'text')
         | 
| 181 | 
            +
                r.socket.rewind
         | 
| 182 | 
            +
                r.socket.read.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\n\r\n"
         | 
| 183 | 
            +
                r.stream('hey there')
         | 
| 184 | 
            +
                r.socket.rewind
         | 
| 185 | 
            +
                r.socket.read.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\n\r\nhey there"
         | 
| 186 | 
            +
              end
         | 
| 187 | 
            +
              
         | 
| 188 | 
            +
              specify "should include response_headers and headers in the response" do
         | 
| 189 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 190 | 
            +
                r.socket = StringIO.new
         | 
| 191 | 
            +
                r.persistent = true
         | 
| 192 | 
            +
                r.response_headers['XXX'] = 'Test'
         | 
| 193 | 
            +
                r.send_response(200, 'text')
         | 
| 194 | 
            +
                r.socket.rewind
         | 
| 195 | 
            +
                r.socket.read.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nXXX: Test\r\n\r\n"
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 198 | 
            +
                r.socket = StringIO.new
         | 
| 199 | 
            +
                r.persistent = true
         | 
| 200 | 
            +
                r.send_response(200, 'text', nil, nil, {'YYY' => 'TTT'})
         | 
| 201 | 
            +
                r.socket.rewind
         | 
| 202 | 
            +
                r.socket.read.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nYYY: TTT\r\n\r\n"
         | 
| 203 | 
            +
              end
         | 
| 204 | 
            +
              
         | 
| 205 | 
            +
              specify "should set persistent to false if exception is raised" do
         | 
| 206 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 207 | 
            +
                r.persistent = true
         | 
| 208 | 
            +
                proc {r.send_response(200, 'text', 'Hello there!')}.should_not_raise
         | 
| 209 | 
            +
                r.persistent.should_equal false
         | 
| 210 | 
            +
              end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
              specify "should include cookies in the response" do
         | 
| 213 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 214 | 
            +
                r.socket = StringIO.new
         | 
| 215 | 
            +
                t = Time.now + 360
         | 
| 216 | 
            +
                r.set_cookie(:session, "ABCDEFG", t)
         | 
| 217 | 
            +
                r.response_cookies.should_equal "Set-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\n" 
         | 
| 218 | 
            +
                r.send_response(200, 'text', 'Hi!')
         | 
| 219 | 
            +
                r.socket.rewind
         | 
| 220 | 
            +
                r.socket.read.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nSet-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\nContent-Length: 3\r\n\r\nHi!" 
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 223 | 
            +
                r.socket = StringIO.new
         | 
| 224 | 
            +
                r.delete_cookie(:session)
         | 
| 225 | 
            +
                r.response_cookies.should_equal "Set-Cookie: session=; path=/; expires=#{Time.at(0).rfc2822}\r\n"
         | 
| 226 | 
            +
                r.send_response(200, 'text', 'Hi!')
         | 
| 227 | 
            +
                r.socket.rewind
         | 
| 228 | 
            +
                r.socket.read.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nSet-Cookie: session=; path=/; expires=#{Time.at(0).rfc2822}\r\nContent-Length: 3\r\n\r\nHi!"
         | 
| 229 | 
            +
              end
         | 
| 230 | 
            +
            end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
            context "HTTP::Request.send_file" do
         | 
| 233 | 
            +
              specify "should include a disposition header in the response" do
         | 
| 234 | 
            +
                socket = StringIO.new
         | 
| 235 | 
            +
                r = ServerSide::HTTP::Request.new(socket)
         | 
| 236 | 
            +
                r.send_file('text', 'text/plain', :attachment)
         | 
| 237 | 
            +
                socket.rewind
         | 
| 238 | 
            +
                socket.read.should_match /Content-Disposition: attachment\r\n/
         | 
| 239 | 
            +
              end
         | 
| 240 | 
            +
              
         | 
| 241 | 
            +
              specify "should use :inline as default disposition" do
         | 
| 242 | 
            +
                socket = StringIO.new
         | 
| 243 | 
            +
                r = ServerSide::HTTP::Request.new(socket)
         | 
| 244 | 
            +
                r.send_file('text', 'text/plain')
         | 
| 245 | 
            +
                socket.rewind
         | 
| 246 | 
            +
                socket.read.should_match /Content-Disposition: inline\r\n/
         | 
| 247 | 
            +
              end
         | 
| 248 | 
            +
              
         | 
| 249 | 
            +
              specify "should include filename parameter if specified" do
         | 
| 250 | 
            +
                socket = StringIO.new
         | 
| 251 | 
            +
                r = ServerSide::HTTP::Request.new(socket)
         | 
| 252 | 
            +
                r.send_file('text', 'text/plain', :attachment, 'text.txt')
         | 
| 253 | 
            +
                socket.rewind
         | 
| 254 | 
            +
                socket.read.should_match /Content-Disposition: attachment; filename=text.txt\r\n/
         | 
| 255 | 
            +
              end
         | 
| 256 | 
            +
              
         | 
| 257 | 
            +
              specify "should include a description header if specified" do
         | 
| 258 | 
            +
                socket = StringIO.new
         | 
| 259 | 
            +
                r = ServerSide::HTTP::Request.new(socket)
         | 
| 260 | 
            +
                r.send_file('text', 'text/plain', :attachment, 'text.txt')
         | 
| 261 | 
            +
                socket.rewind
         | 
| 262 | 
            +
                socket.read.should_not_match /Content-Description:\s/
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                socket = StringIO.new
         | 
| 265 | 
            +
                r = ServerSide::HTTP::Request.new(socket)
         | 
| 266 | 
            +
                r.send_file('text', 'text/plain', :attachment, 'text.txt', 'this is text.')
         | 
| 267 | 
            +
                socket.rewind
         | 
| 268 | 
            +
                socket.read.should_match /Content-Description:\sthis is text\./
         | 
| 269 | 
            +
              end
         | 
| 270 | 
            +
            end
         | 
| 271 | 
            +
             | 
| 272 | 
            +
            context "HTTP::Request.redirect" do
         | 
| 273 | 
            +
              specify "should send a 302 response for temporary redirect" do
         | 
| 274 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 275 | 
            +
                r.socket = StringIO.new
         | 
| 276 | 
            +
                r.redirect('http://mau.com/132')
         | 
| 277 | 
            +
                r.socket.rewind
         | 
| 278 | 
            +
                r.socket.read.should_equal "HTTP/1.1 302\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n"
         | 
| 279 | 
            +
              end
         | 
| 280 | 
            +
              
         | 
| 281 | 
            +
              specify "should send a 301 response for permanent redirect" do
         | 
| 282 | 
            +
                r = ServerSide::HTTP::Request.new(nil)
         | 
| 283 | 
            +
                r.socket = StringIO.new
         | 
| 284 | 
            +
                r.redirect('http://mau.com/132', true)
         | 
| 285 | 
            +
                r.socket.rewind
         | 
| 286 | 
            +
                r.socket.read.should_equal "HTTP/1.1 301\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n"
         | 
| 287 | 
            +
              end
         | 
| 288 | 
            +
            end
         | 
    
        data/test/unit/routing_test.rb
    CHANGED
    
    | @@ -69,7 +69,7 @@ class RoutingTest < Test::Unit::TestCase | |
| 69 69 | 
             
                assert_equal nil, R.new(StringIO.new).respond
         | 
| 70 70 | 
             
                R.rules << [{:t => 'abc'}, lambda{1}]
         | 
| 71 71 | 
             
                R.rules << [{:t => 'def'}, lambda{2}]
         | 
| 72 | 
            -
                R. | 
| 72 | 
            +
                R.default_route {3}
         | 
| 73 73 | 
             
                R.compile_rules
         | 
| 74 74 | 
             
                r = R.new(StringIO.new)
         | 
| 75 75 | 
             
                r.t = 'abc'
         | 
| @@ -148,11 +148,11 @@ class RoutingTest < Test::Unit::TestCase | |
| 148 148 | 
             
                assert_equal c, eval("R::#{tag}")
         | 
| 149 149 | 
             
              end
         | 
| 150 150 |  | 
| 151 | 
            -
              def  | 
| 152 | 
            -
                R. | 
| 151 | 
            +
              def test_default_route
         | 
| 152 | 
            +
                R.default_route {'mau m'}
         | 
| 153 153 | 
             
                assert_equal 'mau m', R.new(StringIO.new).default_handler
         | 
| 154 154 |  | 
| 155 | 
            -
                R. | 
| 155 | 
            +
                R.default_route {654321}
         | 
| 156 156 | 
             
                assert_equal 654321, R.new(StringIO.new).default_handler
         | 
| 157 157 | 
             
              end
         | 
| 158 158 |  | 
| @@ -164,8 +164,8 @@ class RoutingTest < Test::Unit::TestCase | |
| 164 164 | 
             
                assert_equal 2, R.rules[0][1].call
         | 
| 165 165 | 
             
              end
         | 
| 166 166 |  | 
| 167 | 
            -
              def  | 
| 168 | 
            -
                ServerSide::Router. | 
| 167 | 
            +
              def test_serverside_default_route
         | 
| 168 | 
            +
                ServerSide::Router.default_route {1234}
         | 
| 169 169 | 
             
                assert_equal 1234, R.new(StringIO.new).default_handler
         | 
| 170 170 | 
             
              end
         | 
| 171 171 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -3,8 +3,8 @@ rubygems_version: 0.9.0 | |
| 3 3 | 
             
            specification_version: 1
         | 
| 4 4 | 
             
            name: serverside
         | 
| 5 5 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 6 | 
            -
              version: 0.2. | 
| 7 | 
            -
            date: 2006-10- | 
| 6 | 
            +
              version: 0.2.9
         | 
| 7 | 
            +
            date: 2006-10-25 00:00:00 +02:00
         | 
| 8 8 | 
             
            summary: Performance-oriented web framework.
         | 
| 9 9 | 
             
            require_paths: 
         | 
| 10 10 | 
             
            - lib
         | 
| @@ -33,43 +33,46 @@ files: | |
| 33 33 | 
             
            - README
         | 
| 34 34 | 
             
            - Rakefile
         | 
| 35 35 | 
             
            - bin/serverside
         | 
| 36 | 
            +
            - test/unit
         | 
| 36 37 | 
             
            - test/functional
         | 
| 37 38 | 
             
            - test/spec
         | 
| 38 39 | 
             
            - test/test_helper.rb
         | 
| 39 | 
            -
            - test/unit
         | 
| 40 | 
            +
            - test/unit/connection_test.rb
         | 
| 41 | 
            +
            - test/unit/cluster_test.rb
         | 
| 42 | 
            +
            - test/unit/daemon_test.rb
         | 
| 43 | 
            +
            - test/unit/server_test.rb
         | 
| 44 | 
            +
            - test/unit/static_test.rb
         | 
| 45 | 
            +
            - test/unit/routing_test.rb
         | 
| 46 | 
            +
            - test/unit/core_ext_test.rb
         | 
| 47 | 
            +
            - test/unit/request_test.rb
         | 
| 48 | 
            +
            - test/unit/template_test.rb
         | 
| 49 | 
            +
            - test/functional/routing_server.rb
         | 
| 50 | 
            +
            - test/functional/static_server.rb
         | 
| 40 51 | 
             
            - test/functional/primitive_static_server_test.rb
         | 
| 41 52 | 
             
            - test/functional/request_body_test.rb
         | 
| 42 | 
            -
            - test/functional/routing_server.rb
         | 
| 43 | 
            -
            - test/functional/routing_server_test.rb
         | 
| 44 53 | 
             
            - test/functional/static_profile.rb
         | 
| 45 54 | 
             
            - test/functional/static_rfuzz.rb
         | 
| 46 | 
            -
            - test/functional/static_server.rb
         | 
| 47 55 | 
             
            - test/functional/static_server_test.rb
         | 
| 56 | 
            +
            - test/functional/routing_server_test.rb
         | 
| 57 | 
            +
            - test/spec/controller_spec.rb
         | 
| 58 | 
            +
            - test/spec/core_ext_spec.rb
         | 
| 59 | 
            +
            - test/spec/request_spec.rb
         | 
| 48 60 | 
             
            - test/spec/caching_spec.rb
         | 
| 49 61 | 
             
            - test/spec/connection_spec.rb
         | 
| 50 | 
            -
            - test/spec/core_ext_spec.rb
         | 
| 51 | 
            -
            - test/unit/cluster_test.rb
         | 
| 52 | 
            -
            - test/unit/connection_test.rb
         | 
| 53 | 
            -
            - test/unit/core_ext_test.rb
         | 
| 54 | 
            -
            - test/unit/daemon_test.rb
         | 
| 55 | 
            -
            - test/unit/request_test.rb
         | 
| 56 | 
            -
            - test/unit/routing_test.rb
         | 
| 57 | 
            -
            - test/unit/server_test.rb
         | 
| 58 | 
            -
            - test/unit/static_test.rb
         | 
| 59 | 
            -
            - test/unit/template_test.rb
         | 
| 60 62 | 
             
            - lib/serverside
         | 
| 61 63 | 
             
            - lib/serverside.rb
         | 
| 62 | 
            -
            - lib/serverside/application.rb
         | 
| 63 | 
            -
            - lib/serverside/caching.rb
         | 
| 64 | 
            -
            - lib/serverside/cluster.rb
         | 
| 65 64 | 
             
            - lib/serverside/connection.rb
         | 
| 66 | 
            -
            - lib/serverside/ | 
| 65 | 
            +
            - lib/serverside/cluster.rb
         | 
| 67 66 | 
             
            - lib/serverside/daemon.rb
         | 
| 68 | 
            -
            - lib/serverside/ | 
| 69 | 
            -
            - lib/serverside/ | 
| 67 | 
            +
            - lib/serverside/application.rb
         | 
| 68 | 
            +
            - lib/serverside/controller.rb
         | 
| 70 69 | 
             
            - lib/serverside/server.rb
         | 
| 71 70 | 
             
            - lib/serverside/static.rb
         | 
| 71 | 
            +
            - lib/serverside/routing.rb
         | 
| 72 | 
            +
            - lib/serverside/core_ext.rb
         | 
| 73 | 
            +
            - lib/serverside/request.rb
         | 
| 72 74 | 
             
            - lib/serverside/template.rb
         | 
| 75 | 
            +
            - lib/serverside/caching.rb
         | 
| 73 76 | 
             
            - CHANGELOG
         | 
| 74 77 | 
             
            test_files: []
         | 
| 75 78 |  |