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