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