serverside 0.4.1 → 0.4.3

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