nyara 0.0.1.pre
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.
- checksums.yaml +7 -0
- data/example/design.rb +62 -0
- data/example/fib.rb +15 -0
- data/example/hello.rb +5 -0
- data/example/stream.rb +10 -0
- data/ext/accept.c +133 -0
- data/ext/event.c +89 -0
- data/ext/extconf.rb +34 -0
- data/ext/hashes.c +130 -0
- data/ext/http-parser/AUTHORS +41 -0
- data/ext/http-parser/CONTRIBUTIONS +4 -0
- data/ext/http-parser/LICENSE-MIT +23 -0
- data/ext/http-parser/contrib/parsertrace.c +156 -0
- data/ext/http-parser/contrib/url_parser.c +44 -0
- data/ext/http-parser/http_parser.c +2175 -0
- data/ext/http-parser/http_parser.h +304 -0
- data/ext/http-parser/test.c +3425 -0
- data/ext/http_parser.c +1 -0
- data/ext/inc/epoll.h +60 -0
- data/ext/inc/kqueue.h +77 -0
- data/ext/inc/status_codes.inc +64 -0
- data/ext/inc/str_intern.h +66 -0
- data/ext/inc/version.inc +1 -0
- data/ext/mime.c +107 -0
- data/ext/multipart-parser-c/README.md +18 -0
- data/ext/multipart-parser-c/multipart_parser.c +309 -0
- data/ext/multipart-parser-c/multipart_parser.h +48 -0
- data/ext/multipart_parser.c +1 -0
- data/ext/nyara.c +56 -0
- data/ext/nyara.h +59 -0
- data/ext/request.c +474 -0
- data/ext/route.cc +325 -0
- data/ext/url_encoded.c +304 -0
- data/hello.rb +5 -0
- data/lib/nyara/config.rb +64 -0
- data/lib/nyara/config_hash.rb +51 -0
- data/lib/nyara/controller.rb +336 -0
- data/lib/nyara/cookie.rb +31 -0
- data/lib/nyara/cpu_counter.rb +65 -0
- data/lib/nyara/header_hash.rb +18 -0
- data/lib/nyara/mime_types.rb +612 -0
- data/lib/nyara/nyara.rb +82 -0
- data/lib/nyara/param_hash.rb +5 -0
- data/lib/nyara/request.rb +144 -0
- data/lib/nyara/route.rb +138 -0
- data/lib/nyara/route_entry.rb +43 -0
- data/lib/nyara/session.rb +104 -0
- data/lib/nyara/view.rb +317 -0
- data/lib/nyara.rb +25 -0
- data/nyara.gemspec +20 -0
- data/rakefile +91 -0
- data/readme.md +35 -0
- data/spec/ext_mime_match_spec.rb +27 -0
- data/spec/ext_parse_accept_value_spec.rb +29 -0
- data/spec/ext_parse_spec.rb +138 -0
- data/spec/ext_route_spec.rb +70 -0
- data/spec/hashes_spec.rb +71 -0
- data/spec/path_helper_spec.rb +77 -0
- data/spec/request_delegate_spec.rb +67 -0
- data/spec/request_spec.rb +56 -0
- data/spec/route_entry_spec.rb +12 -0
- data/spec/route_spec.rb +84 -0
- data/spec/session_spec.rb +66 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/view_spec.rb +87 -0
- data/tools/bench-cookie.rb +22 -0
- metadata +111 -0
data/lib/nyara/view.rb
ADDED
@@ -0,0 +1,317 @@
|
|
1
|
+
module Nyara
|
2
|
+
module Renderable
|
3
|
+
end
|
4
|
+
|
5
|
+
# A support class which provides:
|
6
|
+
# - layout / locals for rendering
|
7
|
+
# - template search
|
8
|
+
# - template default content-type mapping
|
9
|
+
# - template precompile
|
10
|
+
# - streaming
|
11
|
+
#
|
12
|
+
# Streaming is implemented in this way: when +Fiber.yield+ is called, we flush +View#out+ and send the data.
|
13
|
+
# This adds a bit limitations to the layouts.
|
14
|
+
# Consider this case (+friend+ fills into View#out, while +enemy+ doesn't):
|
15
|
+
#
|
16
|
+
# friend layout { enemy layout { friend page } }
|
17
|
+
#
|
18
|
+
# Friend layout and friend page shares one buffer, but enemy layout just concats +buffer.join+ before we flush friend layout.
|
19
|
+
# So the simple solution is: templates other than stream-friendly ones are not allowed to be a layout.
|
20
|
+
#
|
21
|
+
# Note on Erubis: to support streaming, Erubis is disabled even loaded.
|
22
|
+
class View
|
23
|
+
# ext (without dot) => most preferrable content type (e.g. "text/html")
|
24
|
+
ENGINE_DEFAULT_CONTENT_TYPES = ParamHash.new
|
25
|
+
|
26
|
+
# ext (without dot) => stream friendly
|
27
|
+
ENGINE_STREAM_FRIENDLY = ParamHash.new
|
28
|
+
|
29
|
+
class Buffer < Array
|
30
|
+
def join
|
31
|
+
r = super
|
32
|
+
clear
|
33
|
+
r
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def init
|
39
|
+
@root = Config['views']
|
40
|
+
@meth2ext = {} # meth => ext (without dot)
|
41
|
+
@meth2sig = {}
|
42
|
+
end
|
43
|
+
attr_reader :root
|
44
|
+
|
45
|
+
# +path+ needs extension
|
46
|
+
def on_delete path
|
47
|
+
meth = path2meth path
|
48
|
+
Renderable.class_eval do
|
49
|
+
undef meth
|
50
|
+
end
|
51
|
+
|
52
|
+
@meth2ext.delete meth
|
53
|
+
@meth2sig.delete meth
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_delete_all
|
57
|
+
meths = @meth2sig
|
58
|
+
Renderable.class_eval do
|
59
|
+
meths.each do |meth, _|
|
60
|
+
undef meth
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@meth2sig.clear
|
64
|
+
@meth2ext.clear
|
65
|
+
end
|
66
|
+
|
67
|
+
# +path+ needs extension
|
68
|
+
# returns dot_ext for further use
|
69
|
+
def on_update path
|
70
|
+
meth = path2meth path
|
71
|
+
return unless @meth2sig[meth] # has not been searched before, see also View.template
|
72
|
+
|
73
|
+
ext = File.extname(path)[1..-1]
|
74
|
+
return unless ext
|
75
|
+
src = precompile ext do
|
76
|
+
Dir.chdir(@root){ File.read path, encoding: 'utf-8' }
|
77
|
+
end
|
78
|
+
|
79
|
+
if src
|
80
|
+
sig = @meth2sig[meth].map{|k| "#{k}: nil" }.join ','
|
81
|
+
sig = '_={}' if sig.empty?
|
82
|
+
Renderable.class_eval <<-RUBY, path, 1
|
83
|
+
def render #{sig}
|
84
|
+
#{src}
|
85
|
+
end
|
86
|
+
alias :#{meth.inspect} render
|
87
|
+
RUBY
|
88
|
+
else
|
89
|
+
t = Dir.chdir @root do
|
90
|
+
# todo display template error
|
91
|
+
Tilt.new path rescue return
|
92
|
+
end
|
93
|
+
# partly precompiled
|
94
|
+
Renderable.send :define_method, meth do |locals=nil, &p|
|
95
|
+
t.render self, locals, &p
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@meth2ext[meth] = ext
|
100
|
+
end
|
101
|
+
|
102
|
+
# define inline render method and add Content-Type mapping
|
103
|
+
def register_engine ext, default_content_type, stream_friendly=false
|
104
|
+
# todo figure out fname and line
|
105
|
+
meth = engine2meth ext
|
106
|
+
file = "file".inspect
|
107
|
+
line = 1
|
108
|
+
|
109
|
+
if stream_friendly
|
110
|
+
Renderable.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
111
|
+
def render locals={}
|
112
|
+
@_nyara_locals = locals
|
113
|
+
src = locals.map{|k, _| "\#{k} = @_nyara_locals[:\#{k}];" }.join
|
114
|
+
src << View.precompile(#{ext.inspect}){ @_nyara_view.in }
|
115
|
+
instance_eval src, #{file}, #{line}
|
116
|
+
end
|
117
|
+
alias :#{meth.inspect} render
|
118
|
+
RUBY
|
119
|
+
ENGINE_STREAM_FRIENDLY[ext] = true
|
120
|
+
else
|
121
|
+
Renderable.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
122
|
+
def render locals=nil
|
123
|
+
Tilt[#{ext.inspect}].new(#{file}, #{line}){ @_nyara_view.in }.render self, locals
|
124
|
+
end
|
125
|
+
alias :#{meth.inspect} render
|
126
|
+
RUBY
|
127
|
+
end
|
128
|
+
ENGINE_DEFAULT_CONTENT_TYPES[ext] = default_content_type
|
129
|
+
end
|
130
|
+
|
131
|
+
# returns +[meth, ext_without_dot]+
|
132
|
+
def template path, locals={}
|
133
|
+
if File.extname(path).empty?
|
134
|
+
@ext_list ||= Tilt.mappings.keys.delete_if(&:empty?).join ','
|
135
|
+
Dir.chdir @root do
|
136
|
+
paths = Dir.glob("#{path}.{#@ext_list}")
|
137
|
+
if paths.size > 1
|
138
|
+
raise ArgumentError, "more than 1 matching views: #{paths.inspect}, add file extension to distinguish them"
|
139
|
+
end
|
140
|
+
path = paths.first
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
meth = path2meth path
|
145
|
+
ext = @meth2ext[meth]
|
146
|
+
return [meth, ext] if ext
|
147
|
+
|
148
|
+
@meth2sig[meth] = locals.keys
|
149
|
+
ext = on_update path
|
150
|
+
raise "template not found or not valid in Tilt: #{path}" unless ext
|
151
|
+
[meth, ext]
|
152
|
+
end
|
153
|
+
|
154
|
+
# private
|
155
|
+
|
156
|
+
# Block is lazy invoked when it's ok to read the template source.
|
157
|
+
def precompile ext
|
158
|
+
src_method =\
|
159
|
+
case ext
|
160
|
+
when 'slim'
|
161
|
+
:slim_src
|
162
|
+
when 'erb', 'rhtml'
|
163
|
+
:erb_src
|
164
|
+
when 'haml'
|
165
|
+
:haml_src
|
166
|
+
end
|
167
|
+
return unless src_method
|
168
|
+
|
169
|
+
send src_method, yield
|
170
|
+
end
|
171
|
+
|
172
|
+
def erb_src template
|
173
|
+
@erb_compiler ||= begin
|
174
|
+
c = ERB::Compiler.new '<>' # trim mode
|
175
|
+
c.pre_cmd = ["_erbout = @_nyara_view.out"]
|
176
|
+
c.put_cmd = "_erbout.push" # after newline
|
177
|
+
c.insert_cmd = "_erbout.push" # before newline
|
178
|
+
c.post_cmd = ["_erbout.join"]
|
179
|
+
c
|
180
|
+
end
|
181
|
+
src, enc = @erb_compiler.compile template
|
182
|
+
# todo do sth with enc?
|
183
|
+
src
|
184
|
+
end
|
185
|
+
|
186
|
+
def slim_src template
|
187
|
+
# todo pretty by env
|
188
|
+
t = Slim::Template.new(nil, nil, pretty: false){ template }
|
189
|
+
src = t.instance_variable_get :@src
|
190
|
+
if src.start_with?('_buf = []')
|
191
|
+
src.sub! '_buf = []', '_buf = @_nyara_view.out'
|
192
|
+
end
|
193
|
+
src
|
194
|
+
end
|
195
|
+
|
196
|
+
def haml_src template
|
197
|
+
e = Haml::Engine.new template
|
198
|
+
# todo trim mode
|
199
|
+
<<-RUBY
|
200
|
+
_hamlout = Haml::Buffer.new(nil, encoding: 'utf-8')
|
201
|
+
_hamlout.buffer = @_nyara_view.out
|
202
|
+
#{e.precompiled}
|
203
|
+
_hamlout.buffer.join
|
204
|
+
RUBY
|
205
|
+
end
|
206
|
+
|
207
|
+
def path2meth path
|
208
|
+
"!!#{path}"
|
209
|
+
end
|
210
|
+
|
211
|
+
def engine2meth engine
|
212
|
+
"!:#{engine}"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# NOTE this is the list used in View.precompile
|
217
|
+
%w[slim erb rhtml haml].each {|e| register_engine e, 'text/html', true }
|
218
|
+
|
219
|
+
%w[ad adoc asciidoc erubis builder liquid mab markdown mkd md
|
220
|
+
textile rdoc radius nokogiri wiki creole mediawiki mw
|
221
|
+
].each {|e| register_engine e, 'text/html' }
|
222
|
+
|
223
|
+
register_engine 'str', 'text/plain'
|
224
|
+
register_engine 'coffee', 'application/javascript'
|
225
|
+
register_engine 'yajl', 'application/javascript'
|
226
|
+
register_engine 'rcsv', 'application/csv'
|
227
|
+
register_engine 'sass', 'text/stylesheet'
|
228
|
+
register_engine 'scss', 'text/stylesheet'
|
229
|
+
register_engine 'less', 'text/stylesheet'
|
230
|
+
|
231
|
+
# If view_path not given, find template source in opts
|
232
|
+
def initialize instance, view_path, layout, locals, opts
|
233
|
+
locals ||= {}
|
234
|
+
if view_path
|
235
|
+
raise ArgumentError, "unkown options: #{opts.inspect}" unless opts.empty?
|
236
|
+
meth, ext = View.template(view_path, locals)
|
237
|
+
|
238
|
+
unless @deduced_content_type = ENGINE_DEFAULT_CONTENT_TYPES[ext]
|
239
|
+
raise ArgumentError, "unkown template engine: #{ext.inspect}"
|
240
|
+
end
|
241
|
+
|
242
|
+
@layouts = [[meth, ext]]
|
243
|
+
else
|
244
|
+
raise ArgumentError, "too many options, expected only 1: #{opts.inspect}" if opts.size > 1
|
245
|
+
ext, template = opts.first
|
246
|
+
meth = View.engine2meth ext
|
247
|
+
|
248
|
+
unless @deduced_content_type = ENGINE_DEFAULT_CONTENT_TYPES[ext]
|
249
|
+
raise ArgumentError, "unkown template engine: #{ext.inspect}"
|
250
|
+
end
|
251
|
+
|
252
|
+
@layouts = [meth]
|
253
|
+
@in = template
|
254
|
+
end
|
255
|
+
|
256
|
+
unless layout.is_a?(Array)
|
257
|
+
layout = layout ? [layout] : []
|
258
|
+
end
|
259
|
+
layout.each do |l|
|
260
|
+
pair = View.template(l)
|
261
|
+
# see notes on View
|
262
|
+
raise "can not use #{meth} as layout" unless ENGINE_STREAM_FRIENDLY[pair[1]]
|
263
|
+
@layouts << pair
|
264
|
+
end
|
265
|
+
|
266
|
+
@locals = locals
|
267
|
+
@instance = instance
|
268
|
+
@instance.instance_variable_set :@_nyara_view, self
|
269
|
+
@out = Buffer.new
|
270
|
+
end
|
271
|
+
attr_reader :deduced_content_type, :in, :out
|
272
|
+
|
273
|
+
def render
|
274
|
+
@rest_layouts = @layouts.dup
|
275
|
+
@instance.send_chunk _render
|
276
|
+
Fiber.yield :term_close
|
277
|
+
end
|
278
|
+
|
279
|
+
# :nodoc:
|
280
|
+
def _render
|
281
|
+
t, _ = @rest_layouts.pop
|
282
|
+
if @rest_layouts.empty?
|
283
|
+
@instance.send t, @locals
|
284
|
+
else
|
285
|
+
@instance.send t do
|
286
|
+
_render
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def stream
|
292
|
+
@rest_layouts = @layouts.dup
|
293
|
+
@fiber = Fiber.new do
|
294
|
+
@rest_result = _render
|
295
|
+
nil
|
296
|
+
end
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
def resume
|
301
|
+
r = @fiber.resume
|
302
|
+
Fiber.yield r if r
|
303
|
+
unless @out.empty?
|
304
|
+
@instance.send_chunk @out.join
|
305
|
+
@out.clear
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def end
|
310
|
+
while @fiber.alive?
|
311
|
+
resume
|
312
|
+
end
|
313
|
+
@instance.send_chunk @rest_result
|
314
|
+
Fiber.yield :term_close
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
data/lib/nyara.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# auto run
|
2
|
+
require_relative "nyara/nyara"
|
3
|
+
|
4
|
+
at_exit do
|
5
|
+
Nyara::Route.compile
|
6
|
+
Nyara::View.init
|
7
|
+
Nyara.start_server
|
8
|
+
end
|
9
|
+
|
10
|
+
module Nyara
|
11
|
+
class SimpleController < Controller
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
%w[on tag get post put delete patch options].each do |m|
|
16
|
+
eval <<-RUBY
|
17
|
+
def #{m} *xs, &blk
|
18
|
+
Nyara::SimpleController.#{m} *xs, &blk
|
19
|
+
end
|
20
|
+
RUBY
|
21
|
+
end
|
22
|
+
|
23
|
+
configure do
|
24
|
+
map '/', 'nyara::simple'
|
25
|
+
end
|
data/nyara.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "nyara"
|
3
|
+
s.version = "0.0.1.pre"
|
4
|
+
s.author = "Zete Lui"
|
5
|
+
s.email = "nobody@example.com"
|
6
|
+
s.homepage = "https://github.com/luikore/nyara"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.summary = "Fast, slim and fuzzy ruby web framework + server"
|
9
|
+
s.description = "Fast, slim and fuzzy ruby web framework + server, based on preforked event queue and Fiber. NO rack NOR eventmachine are used."
|
10
|
+
s.required_ruby_version = ">=2.0.0"
|
11
|
+
s.licenses = ['BSD']
|
12
|
+
|
13
|
+
s.files = Dir.glob('{rakefile,nyara.gemspec,readme.md,**/*.{rb,h,c,cc,inc}}')
|
14
|
+
s.files += Dir.glob('ext/http-parser/{AUTHORS,CONTRIBUTIONS,LICENSE-MIT}')
|
15
|
+
s.files += Dir.glob('ext/multipart-parser-c/README.md')
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.extensions = ["ext/extconf.rb"]
|
18
|
+
s.rubygems_version = '2.0.3'
|
19
|
+
s.has_rdoc = false
|
20
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require "rake"
|
2
|
+
|
3
|
+
Dir.chdir __dir__
|
4
|
+
|
5
|
+
status_file = "ext/inc/status_codes.inc"
|
6
|
+
version_file = "ext/inc/version.inc"
|
7
|
+
|
8
|
+
desc "code generate"
|
9
|
+
task :gen => [status_file, version_file]
|
10
|
+
|
11
|
+
desc "generate #{status_file}"
|
12
|
+
file status_file do
|
13
|
+
puts "generating: #{status_file}"
|
14
|
+
require "nokogiri"
|
15
|
+
require "open-uri"
|
16
|
+
f = File.open status_file, 'w'
|
17
|
+
f.puts "#define HTTP_STATUS_CODES(XX)\\"
|
18
|
+
Nokogiri::XML(open("http://www.iana.org/assignments/http-status-codes/http-status-codes.xml")).css("record").each do |r|
|
19
|
+
value = r.css('value').text
|
20
|
+
next if value.index '-'
|
21
|
+
description = r.css('description').text
|
22
|
+
f.puts %Q| XX(#{value}, "#{description}");\\|
|
23
|
+
end
|
24
|
+
f.puts "// end define"
|
25
|
+
f.close
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "generate #{version_file}"
|
29
|
+
file version_file => 'nyara.gemspec' do
|
30
|
+
puts "generating: #{version_file}"
|
31
|
+
lines = File.readlines('nyara.gemspec')
|
32
|
+
version = nil
|
33
|
+
lines.each do |line|
|
34
|
+
if line =~ /s\.version =/
|
35
|
+
version = line[/\d+(\.\d+)*(\.pre)?/]
|
36
|
+
break
|
37
|
+
end
|
38
|
+
end
|
39
|
+
abort 'version not found' unless version
|
40
|
+
File.open version_file, 'w' do |f|
|
41
|
+
f.puts %Q{#define NYARA_VERSION "#{version}"}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "build ext"
|
46
|
+
task :build do
|
47
|
+
Dir.chdir 'ext' do
|
48
|
+
sh 'make'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "test"
|
53
|
+
task :test => :build do
|
54
|
+
sh 'rspec', '-c'
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "build and test"
|
58
|
+
task :default => :test
|
59
|
+
|
60
|
+
desc "build and install gem"
|
61
|
+
task :gem do
|
62
|
+
sh 'rm', '-f', '*.gem'
|
63
|
+
sh 'gem', 'build', 'nyara.gemspec'
|
64
|
+
gem_package = Dir.glob('*.gem').first
|
65
|
+
sh 'gem', 'install', '--no-rdoc', '--no-ri', gem_package
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "clean"
|
69
|
+
task :clean do
|
70
|
+
sh 'rm', '-f', '*.gem'
|
71
|
+
Dir.chdir 'ext' do
|
72
|
+
sh 'make', 'clean'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# -- utils --
|
77
|
+
|
78
|
+
desc "collect line stat"
|
79
|
+
task :lines do
|
80
|
+
c = 0
|
81
|
+
Dir.glob('{**/*.rb,ext/*.{c,cc,h}}') do |f|
|
82
|
+
c += (File.read(f).count "\n")
|
83
|
+
end
|
84
|
+
puts "#{c} lines"
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "list Nyara::Ext methods"
|
88
|
+
task :list_ext do
|
89
|
+
require_relative "lib/nyara/nyara"
|
90
|
+
puts Nyara::Ext.methods - Module.methods
|
91
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
git submodule update --recursive
|
2
|
+
rake gen
|
3
|
+
rake gem
|
4
|
+
|
5
|
+
# Why fast
|
6
|
+
|
7
|
+
## Solid http parsers written in C
|
8
|
+
|
9
|
+
These evented parsers are used:
|
10
|
+
|
11
|
+
- http_parser with chunked encoding support
|
12
|
+
- multipart request parser
|
13
|
+
|
14
|
+
And these are implemented in addition:
|
15
|
+
|
16
|
+
- `Accept-*` parser
|
17
|
+
- mime type parser and content type matcher
|
18
|
+
- url-encoded parser for parsing path / query / request body
|
19
|
+
|
20
|
+
## Fast first-line routing and request class
|
21
|
+
|
22
|
+
## Thin evented IO layer built for *nix and Fiber
|
23
|
+
|
24
|
+
# How fast
|
25
|
+
|
26
|
+
Speed is feature, there are specs on:
|
27
|
+
|
28
|
+
- Accept-* parse vs rack
|
29
|
+
- MIME parse vs rack
|
30
|
+
- query parse vs rack
|
31
|
+
- query parse vs houdini
|
32
|
+
- layout engine vs tilt
|
33
|
+
- evented IO vs eventmachine
|
34
|
+
- helloworld vs sinatra
|
35
|
+
- pseudo real app speed vs jruby sinatra threaded mode
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
module Nyara
|
4
|
+
describe Ext, '.mime_match' do
|
5
|
+
it "#mime_match_seg" do
|
6
|
+
a = Ext.mime_match_seg 'text', 'text', 'html'
|
7
|
+
assert_equal true, a
|
8
|
+
|
9
|
+
a = Ext.mime_match_seg 'text/html', 'text', 'stylesheet'
|
10
|
+
assert_equal false, a
|
11
|
+
|
12
|
+
a = Ext.mime_match_seg '*', 'text', 'html'
|
13
|
+
assert_equal true, a
|
14
|
+
end
|
15
|
+
|
16
|
+
it "#mime_match works with wildcards" do
|
17
|
+
a = Ext.mime_match %w'*', [%w'text html html']
|
18
|
+
assert_equal 'html', a
|
19
|
+
|
20
|
+
a = Ext.mime_match %w'application/javascript text/*', [%w'some text txt', %w'text html html']
|
21
|
+
assert_equal 'html', a
|
22
|
+
|
23
|
+
a = Ext.mime_match %w'text/*', [%w'some text txt']
|
24
|
+
assert_nil a
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
module Nyara
|
4
|
+
describe Ext, ".parse_accept_value" do
|
5
|
+
it 'works' do
|
6
|
+
a = Ext.parse_accept_value ''
|
7
|
+
assert_equal [], a
|
8
|
+
|
9
|
+
a = Ext.parse_accept_value "text/plain; q=0.5, text/html,text/x-dvi; q=3.8, text/x-c"
|
10
|
+
assert_equal %w[text/html text/x-dvi text/x-c text/plain], a
|
11
|
+
end
|
12
|
+
|
13
|
+
it "ignores q <= 0" do
|
14
|
+
a = Ext.parse_accept_value "text/plain; q=0.0, text/html"
|
15
|
+
assert_equal(%w'text/html', a)
|
16
|
+
|
17
|
+
a = Ext.parse_accept_value "*, text/plain; q=-3"
|
18
|
+
assert_equal(%w'*', a)
|
19
|
+
|
20
|
+
a = Ext.parse_accept_value "text/plain; q=0, text/*"
|
21
|
+
assert_equal(%w'text/*', a)
|
22
|
+
end
|
23
|
+
|
24
|
+
it ".parse_accept_value should be robust" do
|
25
|
+
a = Ext.parse_accept_value 'q=0.1, text/html'
|
26
|
+
assert_equal 'text/html', a[1]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
module Nyara
|
4
|
+
describe Ext, "parse" do
|
5
|
+
# note: this method is only used in C code
|
6
|
+
context "#parse_url_encoded_seg" do
|
7
|
+
[false, true].each do |nested|
|
8
|
+
context (nested ? 'nested mode' : 'flat mode') do
|
9
|
+
it "normal parse" do
|
10
|
+
assert_equal({'a' => 'b'}, parse('a=b', nested))
|
11
|
+
end
|
12
|
+
|
13
|
+
it "param seg end with '='" do
|
14
|
+
assert_equal({'a' => ''}, parse('a=', nested))
|
15
|
+
end
|
16
|
+
|
17
|
+
it "param seg begin with '='" do
|
18
|
+
assert_equal({'' => 'b'}, parse('=b', nested))
|
19
|
+
end
|
20
|
+
|
21
|
+
it "param seg without value" do
|
22
|
+
assert_equal({'a' => ''}, parse('a', nested))
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises error" do
|
26
|
+
assert_raise ArgumentError do
|
27
|
+
parse 'a=&b'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "nested key" do
|
34
|
+
it "parses nested key" do
|
35
|
+
res = {"a"=>{"b"=>[[{"c"=>"1"}]]}}
|
36
|
+
assert_equal res, Ext.parse_url_encoded_seg({}, "a[b][][][c]=1", true)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'allows "[]" as input' do
|
40
|
+
res = {""=>[""]}
|
41
|
+
assert_equal res, Ext.parse_url_encoded_seg({}, "[]", true)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'ignores empty input' do
|
45
|
+
res = {}
|
46
|
+
assert_equal res, Ext.parse_url_encoded_seg({}, "", true)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "content hash is ParamHash" do
|
50
|
+
h = ParamHash.new
|
51
|
+
assert_equal ParamHash, Ext.parse_url_encoded_seg(h, "a[b]=c", true)[:a].class
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse str, nested
|
56
|
+
h = {}
|
57
|
+
Ext.parse_url_encoded_seg h, str, nested
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "#parse_path" do
|
62
|
+
before :each do
|
63
|
+
@output = ''
|
64
|
+
end
|
65
|
+
|
66
|
+
it "parses" do
|
67
|
+
i = '/%23+%24'
|
68
|
+
assert_equal i.bytesize, parse(i)
|
69
|
+
assert_equal "/\x23 \x24", @output
|
70
|
+
end
|
71
|
+
|
72
|
+
it "truncates ? after %" do
|
73
|
+
i = '/hello%f3%?world'
|
74
|
+
len = parse i
|
75
|
+
assert_equal '/hello%f3%?'.bytesize, len
|
76
|
+
assert_equal "/hello\xf3%", @output
|
77
|
+
end
|
78
|
+
|
79
|
+
it "truncates ? after begin" do
|
80
|
+
i = '?a'
|
81
|
+
len = parse i
|
82
|
+
assert_equal 1, len
|
83
|
+
assert_equal '', @output
|
84
|
+
end
|
85
|
+
|
86
|
+
it "truncates ? before end" do
|
87
|
+
i = 'a?'
|
88
|
+
len = parse i
|
89
|
+
assert_equal 2, len
|
90
|
+
assert_equal 'a', @output
|
91
|
+
end
|
92
|
+
|
93
|
+
it "truncates ? after unescaped char" do
|
94
|
+
i = 'a?a'
|
95
|
+
len = parse i
|
96
|
+
assert_equal 2, len
|
97
|
+
assert_equal 'a', @output
|
98
|
+
end
|
99
|
+
|
100
|
+
it "truncates ? after escaped char" do
|
101
|
+
i = '%40?'
|
102
|
+
len = parse i
|
103
|
+
assert_equal 4, len
|
104
|
+
assert_equal "\x40", @output
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse input
|
108
|
+
Ext.parse_path @output, input
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context ".parse_cookie" do
|
113
|
+
it "parses complex cookie" do
|
114
|
+
history = CGI.escape '历史'
|
115
|
+
cookie = "pgv_pvi; pgv_si= ; pgv_pvi=som; sid=1d6c75f0 ; PLHistory=<#{history}>;"
|
116
|
+
h = Ext.parse_cookie ParamHash.new, cookie
|
117
|
+
assert_equal '1d6c75f0', h['sid']
|
118
|
+
assert_equal '', h['pgv_si']
|
119
|
+
assert_equal '', h['pgv_pvi'] # left orverrides right
|
120
|
+
assert_equal '<历史>', h['PLHistory']
|
121
|
+
end
|
122
|
+
|
123
|
+
it "parses empty cookie" do
|
124
|
+
cookie = ''
|
125
|
+
h = Ext.parse_cookie ParamHash.new, cookie
|
126
|
+
assert_empty h
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context ".parse_param" do
|
131
|
+
it "parses param with non-utf-8 chars" do
|
132
|
+
bad_s = CGI.escape "\xE2"
|
133
|
+
h = Ext.parse_param ParamHash.new, bad_s
|
134
|
+
assert_equal "", h["\xE2"]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|