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.
- data/README +0 -26
- data/Rakefile +10 -5
- data/bin/serverside +4 -3
- data/lib/serverside/core_ext.rb +5 -0
- data/lib/serverside/http/caching.rb +35 -27
- data/lib/serverside/http/const.rb +8 -2
- data/lib/serverside/http/error.rb +8 -3
- data/lib/serverside/http/parsing.rb +43 -61
- data/lib/serverside/http/request.rb +75 -0
- data/lib/serverside/http/response.rb +94 -62
- data/lib/serverside/http/server.rb +44 -68
- data/lib/serverside/http/static.rb +34 -20
- data/lib/serverside/http.rb +2 -1
- data/lib/serverside/js.rb +4 -100
- data/lib/serverside/xml.rb +4 -4
- data/spec/http_spec.rb +94 -92
- data/spec/js_spec.rb +86 -0
- data/spec/request_spec.rb +133 -0
- data/spec/response_spec.rb +287 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/template_spec.rb +15 -17
- data/spec/xml_spec.rb +75 -0
- metadata +20 -15
@@ -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 :
|
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
|
-
|
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
|
-
|
75
|
+
unless @response_sent || @streaming
|
76
|
+
send_response(Response.error(e))
|
77
|
+
end
|
79
78
|
end
|
80
79
|
|
81
|
-
# state_initial
|
82
|
-
#
|
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
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
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
|
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
|
112
|
+
expecting_body = @request.content_length.to_i > 0
|
125
113
|
set_state(expecting_body ? :state_request_body : :state_response)
|
126
114
|
else
|
127
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
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
|
-
#
|
168
|
-
# the supplied block returns false or nil.
|
169
|
-
def
|
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
|
182
|
-
EventMachine::add_timer(period)
|
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(
|
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
|
-
|
17
|
-
|
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
|
25
|
-
elsif !File.exists?(
|
26
|
-
raise
|
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?(
|
30
|
-
|
41
|
+
if File.directory?(full_path)
|
42
|
+
set_directory_representation(full_path, fn)
|
31
43
|
else
|
32
|
-
|
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
|
38
|
-
ext = File.extension(
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
50
|
-
entries = Dir.entries(
|
51
|
-
entries.reject! {|
|
52
|
-
entries.unshift('..') if
|
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 % [
|
55
|
-
html = DIR_TEMPLATE % [
|
56
|
-
|
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
|
data/lib/serverside/http.rb
CHANGED
@@ -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/'
|
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
|
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
|
70
|
+
j = __serialize(@content)
|
70
71
|
@callback ? "#{@callback}(#{j});" : j
|
71
72
|
end
|
72
73
|
|
73
|
-
|
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
|
data/lib/serverside/xml.rb
CHANGED
@@ -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 ||
|
75
|
+
__instruct(atts || INSTRUCT_DEFAULT)
|
76
76
|
end
|
77
77
|
|
78
78
|
def to_s
|