serverside 0.4.1 → 0.4.3

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.
@@ -37,15 +37,8 @@ module ServerSide::HTTP
37
37
  end
38
38
  end
39
39
 
40
- # include the Parsing, Response and Caching modules.
41
- include ServerSide::HTTP::Parsing
42
- include ServerSide::HTTP::Response
43
- include ServerSide::HTTP::Caching
44
-
45
40
  # attribute readers
46
- attr_reader :request_line, :method, :uri, :query, :http_version, :params
47
- attr_reader :content_length, :persistent, :request_headers
48
- attr_reader :request_cookies, :request_body
41
+ attr_reader :request, :response_sent
49
42
 
50
43
  # post_init creates a new @in buffer and sets the connection state to
51
44
  # initial.
@@ -64,8 +57,7 @@ module ServerSide::HTTP
64
57
  @in << data
65
58
  send(@state)
66
59
  rescue => e
67
- # if an error is raised, we send an error response
68
- send_error_response(e) unless @state == :done
60
+ handle_error(e)
69
61
  end
70
62
 
71
63
  # set_state is called whenever a state transition occurs. It invokes the
@@ -74,24 +66,22 @@ module ServerSide::HTTP
74
66
  @state = s
75
67
  send(s)
76
68
  rescue => e
69
+ handle_error(e)
70
+ end
71
+
72
+ # Handle errors raised while processing a request
73
+ def handle_error(e)
77
74
  # if an error is raised, we send an error response
78
- send_error_response(e) unless @state == :done
75
+ unless @response_sent || @streaming
76
+ send_response(Response.error(e))
77
+ end
79
78
  end
80
79
 
81
- # state_initial initializes @request_headers, @request_header_count,
82
- # @request_cookies and @response_headers. It immediately transitions to the
83
- # request_line state.
80
+ # state_initial creates a new request instance and immediately transitions
81
+ # to the request_line state.
84
82
  def state_initial
85
- # initialize request and response variables
86
- @request_line = nil
83
+ @request = ServerSide::HTTP::Request.new(self)
87
84
  @response_sent = false
88
- @request_headers = {}
89
- @request_header_count = 0
90
- @request_cookies = {}
91
- @response_headers = []
92
- @content_length = nil
93
-
94
- # immediately transition to the request_line state
95
85
  set_state(:state_request_line)
96
86
  end
97
87
 
@@ -100,13 +90,11 @@ module ServerSide::HTTP
100
90
  # line supplies information including the
101
91
  def state_request_line
102
92
  # check request line size
103
- if @in.size > MAX_REQUEST_LINE_SIZE
104
- raise MalformedRequestError, "Invalid request size"
105
- end
106
93
  if line = @in.get_line
107
- parse_request_line(line)
108
- # HTTP 1.1 connections are persistent by default.
109
- @persistent = @http_version == VERSION_1_1
94
+ if line.size > MAX_REQUEST_LINE_SIZE
95
+ raise BadRequestError, "Invalid request size"
96
+ end
97
+ @request.parse_request_line(line)
110
98
  set_state(:state_request_headers)
111
99
  end
112
100
  end
@@ -118,18 +106,13 @@ module ServerSide::HTTP
118
106
  while line = @in.get_line
119
107
  # Check header size
120
108
  if line.size > MAX_HEADER_SIZE
121
- raise MalformedRequestError, "Invalid header size"
109
+ raise BadRequestError, "Invalid header size"
122
110
  # If the line empty then we move to the next state
123
111
  elsif line.empty?
124
- expecting_body = @content_length && (@content_length > 0)
112
+ expecting_body = @request.content_length.to_i > 0
125
113
  set_state(expecting_body ? :state_request_body : :state_response)
126
114
  else
127
- # check header count
128
- if (@request_header_count += 1) > MAX_HEADER_COUNT
129
- raise MalformedRequestError, "Too many headers"
130
- else
131
- parse_header(line)
132
- end
115
+ @request.parse_header(line)
133
116
  end
134
117
  end
135
118
  end
@@ -138,9 +121,8 @@ module ServerSide::HTTP
138
121
  # the body. Once the body is parsed, the connection transitions to the
139
122
  # response state.
140
123
  def state_request_body
141
- if @in.size >= @content_length
142
- @request_body = @in.slice!(0...@content_length)
143
- parse_request_body(@request_body)
124
+ if @in.size >= @request.content_length
125
+ @request.parse_body(@in.slice!(0...@request.content_length))
144
126
  set_state(:state_response)
145
127
  end
146
128
  end
@@ -149,13 +131,23 @@ module ServerSide::HTTP
149
131
  # error is raised. After the response is sent, the connection is either
150
132
  # closed or goes back to the initial state.
151
133
  def state_response
152
- handle
153
- unless @response_sent || @streaming
154
- raise "No handler found for this URI (#{@uri})"
134
+ unless resp = handle(@request)
135
+ raise "No handler found for this URI (#{@request.url})"
155
136
  end
156
- ensure
157
- unless @streaming
158
- set_state(@persistent ? :state_initial : :state_done)
137
+ send_response(resp)
138
+ end
139
+
140
+ def send_response(resp)
141
+ persistent = @request.persistent && resp.persistent?
142
+ if !persistent
143
+ resp.headers << CONNECTION_CLOSE
144
+ end
145
+ send_data(resp.to_s)
146
+ @response_sent = true
147
+ if resp.streaming?
148
+ start_stream_loop(resp.stream_period, resp.stream_proc)
149
+ else
150
+ set_state(persistent ? :state_initial : :state_done)
159
151
  end
160
152
  end
161
153
 
@@ -164,30 +156,14 @@ module ServerSide::HTTP
164
156
  close_connection_after_writing
165
157
  end
166
158
 
167
- # periodically implements a periodical timer. The timer is invoked until
168
- # the supplied block returns false or nil.
169
- def periodically(period, &block)
170
- EventMachine::add_timer(period) do
171
- if block.call
172
- periodically(period, &block)
173
- end
174
- end
175
- end
176
-
177
- # periodically implements a periodical timer. The timer is invoked until
178
- # the supplied block returns false or nil.
179
- def streaming_periodically(period, &block)
159
+ # starts implements a periodical timer. The timer is invoked until
160
+ # the supplied block returns false or nil. When the
161
+ def start_stream_loop(period, block)
180
162
  @streaming = true
181
- if block.call # invoke block for the first time
182
- EventMachine::add_timer(period) do
183
- if block.call
184
- streaming_periodically(period, &block)
185
- else
186
- set_state(@persistent ? :state_initial : :state_done)
187
- end
188
- end
163
+ if block.call(self)
164
+ EventMachine::add_timer(period) {start_stream_loop(period, block)}
189
165
  else
190
- set_state(@persistent ? :state_initial : :state_done)
166
+ set_state(:state_done)
191
167
  end
192
168
  end
193
169
  end
@@ -13,32 +13,45 @@ module ServerSide::HTTP
13
13
  }
14
14
  MIME_TYPES.default = 'text/plain'.freeze
15
15
 
16
- CACHE_AGES = {}
17
- CACHE_AGES.default = 86400 # one day
16
+ CACHE_TTL = {}
17
+ CACHE_TTL.default = 86400 # one day
18
+
19
+ @@static_root = Dir.pwd
20
+
21
+ def self.static_root
22
+ @@static_root
23
+ end
24
+
25
+ def self.static_root=(dir)
26
+ @@static_root = dir
27
+ end
18
28
 
19
29
  INVALID_PATH_RE = /\.\./.freeze
20
30
 
21
31
  # Serves a static file or directory.
22
32
  def serve_static(fn)
33
+ full_path = @@static_root/fn
34
+
23
35
  if fn =~ INVALID_PATH_RE
24
- raise MalformedRequestError, "Invalid path specified (#{@uri})"
25
- elsif !File.exists?(fn)
26
- raise FileNotFoundError, "File not found (#{@uri})"
36
+ raise BadRequestError, "Invalid path specified (#{fn})"
37
+ elsif !File.exists?(full_path)
38
+ raise NotFoundError, "File not found (#{fn})"
27
39
  end
28
40
 
29
- if File.directory?(fn)
30
- send_directory_representation(fn)
41
+ if File.directory?(full_path)
42
+ set_directory_representation(full_path, fn)
31
43
  else
32
- send_file_representation(fn)
44
+ set_file_representation(full_path, fn)
33
45
  end
34
46
  end
35
47
 
36
48
  # Sends a file representation, setting caching-related headers.
37
- def send_file_representation(fn)
38
- ext = File.extension(fn)
39
- expires = Time.now + CACHE_AGES[ext]
40
- cache :etag => File.etag(fn), :expires => expires, :last_modified => File.mtime(fn) do
41
- send_file(STATUS_OK, MIME_TYPES[ext], fn)
49
+ def set_file_representation(full_path, fn)
50
+ ext = File.extension(full_path)
51
+ ttl = CACHE_TTL[ext]
52
+ validate_cache :etag => File.etag(full_path), :ttl => CACHE_TTL[ext], :last_modified => File.mtime(full_path) do
53
+ add_header(CONTENT_TYPE, MIME_TYPES[ext])
54
+ @body = IO.read(full_path)
42
55
  end
43
56
  end
44
57
 
@@ -46,14 +59,15 @@ module ServerSide::HTTP
46
59
  DIR_LISTING = '<li><a href="%s">%s</a><br/></li>'.freeze
47
60
 
48
61
  # Sends a directory representation.
49
- def send_directory_representation(dir)
50
- entries = Dir.entries(dir)
51
- entries.reject! {|fn| fn =~ /^\./}
52
- entries.unshift('..') if dir != './'
62
+ def set_directory_representation(full_path, fn)
63
+ entries = Dir.entries(full_path)
64
+ entries.reject! {|f| f =~ /^\./}
65
+ entries.unshift('..') if fn != '/'
53
66
 
54
- list = entries.map {|e| DIR_LISTING % [@uri/e, e]}.join
55
- html = DIR_TEMPLATE % [dir, dir, list]
56
- send_representation(STATUS_OK, MIME_TYPES[:html], html)
67
+ list = entries.map {|e| DIR_LISTING % [fn/e, e]}.join
68
+ html = DIR_TEMPLATE % [fn, fn, list]
69
+ add_header(CONTENT_TYPE, MIME_TYPES[:html])
70
+ @body = html
57
71
  end
58
72
  end
59
73
  end
@@ -8,7 +8,8 @@ http_dir = File.dirname(__FILE__)/'http'
8
8
  require http_dir/'const'
9
9
  require http_dir/'error'
10
10
  require http_dir/'parsing'
11
- require http_dir/'response'
11
+ require http_dir/'request'
12
12
  require http_dir/'caching'
13
13
  require http_dir/'static'
14
+ require http_dir/'response'
14
15
  require http_dir/'server'
data/lib/serverside/js.rb CHANGED
@@ -27,6 +27,7 @@ module ServerSide
27
27
  value = @stack.pop.__content
28
28
  else
29
29
  value = args.first
30
+ value = value.__content if value.respond_to?(:__content)
30
31
  end
31
32
  @stack.last.__add_hash_value(key, value)
32
33
  self
@@ -55,7 +56,7 @@ module ServerSide
55
56
  NULL = 'null'.freeze
56
57
 
57
58
  # Serializes the specified object into JS/JSON format.
58
- def __serialize(obj, quote_keys)
59
+ def __serialize(obj)
59
60
  case obj
60
61
  when Time: JSON.generate(obj.to_f)
61
62
  else
@@ -66,108 +67,11 @@ module ServerSide
66
67
  # Returns the document content as a literal Javascript object. If a callback was specified,
67
68
  # the object is wrapped in a Javascript function call.
68
69
  def to_s
69
- j = __serialize(@content, false)
70
+ j = __serialize(@content)
70
71
  @callback ? "#{@callback}(#{j});" : j
71
72
  end
72
73
 
73
- # Returns the content in JSON format.
74
- def to_json
75
- __serialize(@content, true)
76
- end
77
-
78
- alias_method :inspect, :to_s
79
- end
80
- end
81
-
82
- __END__
83
-
84
- module ServerSide
85
- # Serializes data into a Javscript literal hash format. For example:
86
- # ServerSide::JS.new {|j| j}
87
- class JS
88
- # blank slate
89
- instance_methods.each do |m|
90
- undef_method m unless (m =~ /^__|instance_eval|meta|respond_to|nil|is_a/)
91
- end
92
-
93
- # Initializes a new document. A callback function name can be supplied to
94
- # wrap the hash.
95
- def initialize(callback = nil, &block)
96
- @callback = callback
97
- @stack = [self]
98
- block.call(self) if block
99
- end
100
-
101
- # Catches calls to define keys and creates methods on the fly.
102
- def method_missing(key, *args, &block)
103
- value = nil
104
- if block
105
- @stack.push JS.new
106
- block.call(self)
107
- value = @stack.pop.__content
108
- else
109
- value = args.first
110
- end
111
- @stack.last.__add_hash_value(key, value)
112
- self
113
- end
114
-
115
- def __add_hash_value(key, value)
116
- @content ||= {}
117
- @content[key] = value
118
- end
119
-
120
- def __add_array_value(value)
121
- @content ||= []
122
- @content << value
123
- end
124
-
125
- def <<(value)
126
- value = value.__content if value.respond_to?(:__js)
127
- @stack.last.__add_array_value(value)
128
- end
129
-
130
- # Returns the current document content.
131
- def __content
132
- @content
133
- end
134
-
135
- NULL = 'null'.freeze
136
-
137
- # Serializes the specified object into JS/JSON format.
138
- def __serialize(obj, quote_keys)
139
- if obj.nil?
140
- NULL
141
- elsif obj.is_a? Array
142
- "[#{obj.map{|v| __serialize(v, quote_keys)}.join(', ')}]"
143
- elsif obj.is_a? Hash
144
- if quote_keys
145
- fields = obj.to_a.map{|kv| "\"#{kv[0]}\": #{__serialize(kv[1], quote_keys)}"}
146
- else
147
- fields = obj.to_a.map{|kv| "#{kv[0]}: #{__serialize(kv[1], quote_keys)}"}
148
- end
149
- "{#{fields.join(', ')}}"
150
- elsif obj.is_a? Symbol
151
- obj.to_s
152
- elsif obj.is_a? Time
153
- obj.to_f
154
- else
155
- obj.inspect
156
- end
157
- end
158
-
159
- # Returns the document content as a literal Javascript object. If a callback was specified,
160
- # the object is wrapped in a Javascript function call.
161
- def to_s
162
- j = __serialize(@content, false)
163
- @callback ? "#{@callback}(#{j});" : j
164
- end
165
-
166
- # Returns the content in JSON format.
167
- def to_json
168
- __serialize(@content, true)
169
- end
170
-
74
+ alias_method :to_json, :to_s
171
75
  alias_method :inspect, :to_s
172
76
  end
173
77
  end
@@ -24,7 +24,7 @@ module ServerSide
24
24
  end
25
25
 
26
26
  def __value(value)
27
- @doc << value.to_s
27
+ @doc << value.to_s.html_escape
28
28
  end
29
29
 
30
30
  INSTRUCT_LEFT = '<?xml'.freeze
@@ -36,8 +36,6 @@ module ServerSide
36
36
  @doc << INSTRUCT_RIGHT
37
37
  end
38
38
 
39
- SPACE = ' '.freeze
40
-
41
39
  def __fmt_atts(atts)
42
40
  atts.inject('') {|m, i| m << " #{i[0]}=#{i[1].to_s.inspect}"}
43
41
  end
@@ -71,8 +69,10 @@ module ServerSide
71
69
  self
72
70
  end
73
71
 
72
+ INSTRUCT_DEFAULT = {:version => "1.0", :encoding => "UTF-8"}.freeze
73
+
74
74
  def instruct!(atts = nil)
75
- __instruct(atts || {:version => "1.0", :encoding => "UTF-8"})
75
+ __instruct(atts || INSTRUCT_DEFAULT)
76
76
  end
77
77
 
78
78
  def to_s