syro 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +6 -0
- data/lib/syro.rb +207 -90
- data/syro.gemspec +1 -1
- data/test/all.rb +44 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42c57c65ec35da48059c8844925b6baa1a3a3897
|
4
|
+
data.tar.gz: cee94b2f71a5eeea0a07fee70d9d923d5720df7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79191f17a6cb4b4bdf8c2b4a8456e59ec15f5cc1ac33911502117103ad122f3a492fb80b359e6990754b8a1cd049b50da0a2b813355fd869d8e88052922e7a4f
|
7
|
+
data.tar.gz: 97ab83421c8cb0b3772404d91494f6e08389cc96d85542e4c1f355ac0bd2a9222654e530a7180902f184f4241b7085eaeee8ba6e05420d9420fe18b6b7103c54
|
data/CHANGELOG
CHANGED
data/lib/syro.rb
CHANGED
@@ -27,12 +27,34 @@ class Syro
|
|
27
27
|
INBOX = "syro.inbox".freeze
|
28
28
|
|
29
29
|
class Response
|
30
|
-
LOCATION = "Location".freeze
|
31
|
-
DEFAULT = "text/html".freeze
|
32
|
-
|
30
|
+
LOCATION = "Location".freeze # :nodoc:
|
31
|
+
DEFAULT = "text/html".freeze # :nodoc:
|
32
|
+
|
33
|
+
# The status of the response.
|
34
|
+
#
|
35
|
+
# res.status = 200
|
36
|
+
# res.status # => 200
|
37
|
+
#
|
33
38
|
attr_accessor :status
|
34
39
|
|
40
|
+
# Returns the body of the response.
|
41
|
+
#
|
42
|
+
# res.body
|
43
|
+
# # => []
|
44
|
+
#
|
45
|
+
# res.write("there is")
|
46
|
+
# res.write("no try")
|
47
|
+
#
|
48
|
+
# res.body
|
49
|
+
# # => ["there is", "no try"]
|
50
|
+
#
|
35
51
|
attr :body
|
52
|
+
|
53
|
+
# Returns a hash with the response headers.
|
54
|
+
#
|
55
|
+
# res.headers
|
56
|
+
# # => { "Content-Type" => "text/html", "Content-Length" => "42" }
|
57
|
+
#
|
36
58
|
attr :headers
|
37
59
|
|
38
60
|
def initialize(headers = {})
|
@@ -42,14 +64,37 @@ class Syro
|
|
42
64
|
@length = 0
|
43
65
|
end
|
44
66
|
|
67
|
+
# Returns the response header corresponding to `key`.
|
68
|
+
#
|
69
|
+
# res["Content-Type"] # => "text/html"
|
70
|
+
# res["Content-Length"] # => "42"
|
71
|
+
#
|
45
72
|
def [](key)
|
46
73
|
@headers[key]
|
47
74
|
end
|
48
75
|
|
76
|
+
# Sets the given `value` with the header corresponding to `key`.
|
77
|
+
#
|
78
|
+
# res["Content-Type"] = "application/json"
|
79
|
+
# res["Content-Type"] # => "application/json"
|
80
|
+
#
|
49
81
|
def []=(key, value)
|
50
82
|
@headers[key] = value
|
51
83
|
end
|
52
84
|
|
85
|
+
# Appends `str` to `body` and updates the `Content-Length` header.
|
86
|
+
#
|
87
|
+
# res.body # => []
|
88
|
+
#
|
89
|
+
# res.write("foo")
|
90
|
+
# res.write("bar")
|
91
|
+
#
|
92
|
+
# res.body
|
93
|
+
# # => ["foo", "bar"]
|
94
|
+
#
|
95
|
+
# res["Content-Length"]
|
96
|
+
# # => 6
|
97
|
+
#
|
53
98
|
def write(str)
|
54
99
|
s = str.to_s
|
55
100
|
|
@@ -58,11 +103,42 @@ class Syro
|
|
58
103
|
@body << s
|
59
104
|
end
|
60
105
|
|
106
|
+
# Sets the `Location` header to `path` and updates the status to
|
107
|
+
# `status`. By default, `status` is `302`.
|
108
|
+
#
|
109
|
+
# res.redirect("/path")
|
110
|
+
#
|
111
|
+
# res["Location"] # => "/path"
|
112
|
+
# res.status # => 302
|
113
|
+
#
|
114
|
+
# res.redirect("http://syro.ru", 303)
|
115
|
+
#
|
116
|
+
# res["Location"] # => "http://syro.ru"
|
117
|
+
# res.status # => 303
|
118
|
+
#
|
61
119
|
def redirect(path, status = 302)
|
62
120
|
@headers[LOCATION] = path
|
63
121
|
@status = status
|
64
122
|
end
|
65
123
|
|
124
|
+
# Returns an array with three elements: the status, headers and body.
|
125
|
+
# If the status is not set, the status is set to 404 if empty body,
|
126
|
+
# otherwise the status is set to 200 and updates the `Content-Type`
|
127
|
+
# header to `text/html`.
|
128
|
+
#
|
129
|
+
# res.status = 200
|
130
|
+
# res.finish
|
131
|
+
# # => [200, {}, []]
|
132
|
+
#
|
133
|
+
# res.status = nil
|
134
|
+
# res.finish
|
135
|
+
# # => [404, {}, []]
|
136
|
+
#
|
137
|
+
# res.status = nil
|
138
|
+
# res.write("syro")
|
139
|
+
# res.finish
|
140
|
+
# # => [200, { "Content-Type" => "text/html" }, ["syro"]]
|
141
|
+
#
|
66
142
|
def finish
|
67
143
|
if @status.nil?
|
68
144
|
if @body.empty?
|
@@ -76,142 +152,183 @@ class Syro
|
|
76
152
|
[@status, @headers, @body]
|
77
153
|
end
|
78
154
|
|
155
|
+
# Sets a cookie into the response.
|
156
|
+
#
|
157
|
+
# res.set_cookie("foo", "bar")
|
158
|
+
# res["Set-Cookie"] # => "foo=bar"
|
159
|
+
#
|
160
|
+
# res.set_cookie("foo2", "bar2")
|
161
|
+
# res["Set-Cookie"] # => "foo=bar\nfoo2=bar2"
|
162
|
+
#
|
163
|
+
# res.set_cookie("bar", {
|
164
|
+
# domain: ".example.com",
|
165
|
+
# path: "/",
|
166
|
+
# # max_age: 0,
|
167
|
+
# # expires: Time.now + 10_000,
|
168
|
+
# secure: true,
|
169
|
+
# httponly: true,
|
170
|
+
# value: "bar"
|
171
|
+
# })
|
172
|
+
#
|
173
|
+
# res["Set-Cookie"].split("\n").last
|
174
|
+
# # => "bar=bar; domain=.example.com; path=/; secure; HttpOnly
|
175
|
+
#
|
176
|
+
# **NOTE:** This method doesn't sign and/or encrypt the value of the cookie.
|
177
|
+
#
|
79
178
|
def set_cookie(key, value)
|
80
179
|
Rack::Utils.set_cookie_header!(@headers, key, value)
|
81
180
|
end
|
82
181
|
|
182
|
+
# Deletes cookie.
|
183
|
+
#
|
184
|
+
# res.set_cookie("foo", "bar")
|
185
|
+
# res["Set-Cookie"]
|
186
|
+
# # => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
|
187
|
+
#
|
83
188
|
def delete_cookie(key, value = {})
|
84
189
|
Rack::Utils.delete_cookie_header!(@headers, key, value)
|
85
190
|
end
|
86
191
|
end
|
87
192
|
|
88
193
|
class Deck
|
89
|
-
|
90
|
-
|
91
|
-
|
194
|
+
module API
|
195
|
+
def initialize(code)
|
196
|
+
@syro_code = code
|
197
|
+
end
|
92
198
|
|
93
|
-
|
94
|
-
|
95
|
-
|
199
|
+
def env
|
200
|
+
@syro_env
|
201
|
+
end
|
96
202
|
|
97
|
-
|
98
|
-
|
99
|
-
|
203
|
+
def req
|
204
|
+
@syro_req
|
205
|
+
end
|
100
206
|
|
101
|
-
|
102
|
-
|
103
|
-
|
207
|
+
def res
|
208
|
+
@syro_res
|
209
|
+
end
|
104
210
|
|
105
|
-
|
106
|
-
|
107
|
-
|
211
|
+
def path
|
212
|
+
@syro_path
|
213
|
+
end
|
108
214
|
|
109
|
-
|
110
|
-
|
111
|
-
|
215
|
+
def inbox
|
216
|
+
@syro_inbox
|
217
|
+
end
|
112
218
|
|
113
|
-
|
114
|
-
|
115
|
-
|
219
|
+
def default_headers
|
220
|
+
return {}
|
221
|
+
end
|
116
222
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
@syro_res = Syro::Response.new(default_headers)
|
121
|
-
@syro_path = Seg.new(env.fetch(Rack::PATH_INFO))
|
122
|
-
@syro_inbox = inbox
|
223
|
+
def request_class
|
224
|
+
Rack::Request
|
225
|
+
end
|
123
226
|
|
124
|
-
|
125
|
-
|
227
|
+
def response_class
|
228
|
+
Syro::Response
|
229
|
+
end
|
230
|
+
|
231
|
+
def call(env, inbox)
|
232
|
+
@syro_env = env
|
233
|
+
@syro_req = request_class.new(env)
|
234
|
+
@syro_res = response_class.new(default_headers)
|
235
|
+
@syro_path = Seg.new(env.fetch(Rack::PATH_INFO))
|
236
|
+
@syro_inbox = inbox
|
237
|
+
|
238
|
+
catch(:halt) do
|
239
|
+
instance_eval(&@syro_code)
|
126
240
|
|
127
|
-
|
241
|
+
@syro_res.finish
|
242
|
+
end
|
128
243
|
end
|
129
|
-
end
|
130
244
|
|
131
|
-
|
132
|
-
|
245
|
+
def run(app, inbox = {})
|
246
|
+
path, script = env[Rack::PATH_INFO], env[Rack::SCRIPT_NAME]
|
133
247
|
|
134
|
-
|
135
|
-
|
136
|
-
|
248
|
+
env[Rack::PATH_INFO] = @syro_path.curr
|
249
|
+
env[Rack::SCRIPT_NAME] = @syro_path.prev
|
250
|
+
env[Syro::INBOX] = inbox
|
137
251
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
252
|
+
halt(app.call(env))
|
253
|
+
ensure
|
254
|
+
env[Rack::PATH_INFO], env[Rack::SCRIPT_NAME] = path, script
|
255
|
+
end
|
142
256
|
|
143
|
-
|
144
|
-
|
145
|
-
|
257
|
+
def halt(response)
|
258
|
+
throw(:halt, response)
|
259
|
+
end
|
146
260
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
261
|
+
def match(arg)
|
262
|
+
case arg
|
263
|
+
when String then @syro_path.consume(arg)
|
264
|
+
when Symbol then @syro_path.capture(arg, inbox)
|
265
|
+
when true then true
|
266
|
+
else false
|
267
|
+
end
|
153
268
|
end
|
154
|
-
end
|
155
269
|
|
156
|
-
|
157
|
-
|
158
|
-
|
270
|
+
def on(arg)
|
271
|
+
if match(arg)
|
272
|
+
yield(inbox[arg])
|
159
273
|
|
160
|
-
|
274
|
+
halt(res.finish)
|
275
|
+
end
|
161
276
|
end
|
162
|
-
end
|
163
277
|
|
164
|
-
|
165
|
-
|
166
|
-
|
278
|
+
def root?
|
279
|
+
@syro_path.root?
|
280
|
+
end
|
167
281
|
|
168
|
-
|
169
|
-
|
170
|
-
|
282
|
+
def root
|
283
|
+
if root?
|
284
|
+
yield
|
171
285
|
|
172
|
-
|
286
|
+
halt(res.finish)
|
287
|
+
end
|
173
288
|
end
|
174
|
-
end
|
175
289
|
|
176
|
-
|
177
|
-
|
178
|
-
|
290
|
+
def get
|
291
|
+
if root? && req.get?
|
292
|
+
yield
|
179
293
|
|
180
|
-
|
294
|
+
halt(res.finish)
|
295
|
+
end
|
181
296
|
end
|
182
|
-
end
|
183
297
|
|
184
|
-
|
185
|
-
|
186
|
-
|
298
|
+
def put
|
299
|
+
if root? && req.put?
|
300
|
+
yield
|
187
301
|
|
188
|
-
|
302
|
+
halt(res.finish)
|
303
|
+
end
|
189
304
|
end
|
190
|
-
end
|
191
305
|
|
192
|
-
|
193
|
-
|
194
|
-
|
306
|
+
def post
|
307
|
+
if root? && req.post?
|
308
|
+
yield
|
195
309
|
|
196
|
-
|
310
|
+
halt(res.finish)
|
311
|
+
end
|
197
312
|
end
|
198
|
-
end
|
199
313
|
|
200
|
-
|
201
|
-
|
202
|
-
|
314
|
+
def patch
|
315
|
+
if root? && req.patch?
|
316
|
+
yield
|
203
317
|
|
204
|
-
|
318
|
+
halt(res.finish)
|
319
|
+
end
|
205
320
|
end
|
206
|
-
end
|
207
321
|
|
208
|
-
|
209
|
-
|
210
|
-
|
322
|
+
def delete
|
323
|
+
if root? && req.delete?
|
324
|
+
yield
|
211
325
|
|
212
|
-
|
326
|
+
halt(res.finish)
|
327
|
+
end
|
213
328
|
end
|
214
329
|
end
|
330
|
+
|
331
|
+
include API
|
215
332
|
end
|
216
333
|
|
217
334
|
def initialize(deck = Deck, &code)
|
data/syro.gemspec
CHANGED
data/test/all.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "json"
|
2
|
+
|
1
3
|
class RackApp
|
2
4
|
def call(env)
|
3
5
|
[200, {"Content-Type" => "text/html"}, ["GET /rack"]]
|
@@ -17,6 +19,28 @@ class DefaultHeaders < Syro::Deck
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
class CustomRequestAndResponse < Syro::Deck
|
23
|
+
class JSONRequest < Rack::Request
|
24
|
+
def params
|
25
|
+
JSON.parse(body.read)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class JSONResponse < Syro::Response
|
30
|
+
def write(s)
|
31
|
+
super(JSON.generate(s))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def request_class
|
36
|
+
JSONRequest
|
37
|
+
end
|
38
|
+
|
39
|
+
def response_class
|
40
|
+
JSONResponse
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
20
44
|
textual = Syro.new(TextualDeck) {
|
21
45
|
get {
|
22
46
|
text("GET /textual")
|
@@ -25,6 +49,14 @@ textual = Syro.new(TextualDeck) {
|
|
25
49
|
|
26
50
|
default_headers = Syro.new(DefaultHeaders) { }
|
27
51
|
|
52
|
+
json = Syro.new(CustomRequestAndResponse) {
|
53
|
+
root {
|
54
|
+
params = req.params
|
55
|
+
|
56
|
+
res.write(params)
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
28
60
|
admin = Syro.new {
|
29
61
|
get {
|
30
62
|
res.write("GET /admin")
|
@@ -155,6 +187,10 @@ app = Syro.new {
|
|
155
187
|
on("headers") {
|
156
188
|
run(default_headers)
|
157
189
|
}
|
190
|
+
|
191
|
+
on("json") {
|
192
|
+
run(json)
|
193
|
+
}
|
158
194
|
}
|
159
195
|
|
160
196
|
setup do
|
@@ -270,3 +306,11 @@ test "default headers" do |f|
|
|
270
306
|
|
271
307
|
assert_equal "text/html", f.last_response.headers["Content-Type"]
|
272
308
|
end
|
309
|
+
|
310
|
+
test "custom request and response class" do |f|
|
311
|
+
params = JSON.generate(foo: "foo")
|
312
|
+
|
313
|
+
f.post("/json", params)
|
314
|
+
|
315
|
+
assert_equal params, f.last_response.body
|
316
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syro
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michel Martens
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: seg
|