nyara 0.0.1.pre.5 → 0.0.1.pre.6

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/example/factorial.rb +19 -0
  3. data/ext/accept.c +2 -2
  4. data/ext/event.c +48 -23
  5. data/ext/extconf.rb +2 -0
  6. data/ext/hashes.c +28 -3
  7. data/ext/http-parser/http_parser.h +1 -0
  8. data/ext/nyara.c +20 -3
  9. data/ext/nyara.h +13 -2
  10. data/ext/request.c +90 -13
  11. data/ext/request.h +8 -2
  12. data/ext/request_parse.c +135 -6
  13. data/ext/route.cc +7 -10
  14. data/ext/test_response.c +155 -0
  15. data/ext/url_encoded.c +0 -5
  16. data/lib/nyara/config.rb +5 -0
  17. data/lib/nyara/controller.rb +91 -28
  18. data/lib/nyara/cookie.rb +7 -0
  19. data/lib/nyara/flash.rb +23 -0
  20. data/lib/nyara/hashes/header_hash.rb +2 -0
  21. data/lib/nyara/nyara.rb +14 -2
  22. data/lib/nyara/part.rb +156 -0
  23. data/lib/nyara/patches/array.rb +5 -0
  24. data/lib/nyara/patches/blank.rb +128 -0
  25. data/lib/nyara/patches/json.rb +15 -0
  26. data/lib/nyara/patches/mini_support.rb +6 -0
  27. data/lib/nyara/patches/string.rb +21 -0
  28. data/lib/nyara/patches/to_query.rb +113 -0
  29. data/lib/nyara/request.rb +13 -15
  30. data/lib/nyara/route.rb +15 -80
  31. data/lib/nyara/route_entry.rb +69 -2
  32. data/lib/nyara/session.rb +66 -21
  33. data/lib/nyara/test.rb +170 -0
  34. data/lib/nyara/view.rb +5 -6
  35. data/lib/nyara.rb +7 -6
  36. data/nyara.gemspec +2 -2
  37. data/rakefile +34 -4
  38. data/readme.md +8 -1
  39. data/spec/config_spec.rb +28 -0
  40. data/spec/cpu_counter_spec.rb +9 -0
  41. data/spec/evented_io_spec.rb +1 -0
  42. data/spec/flash_spec.rb +29 -0
  43. data/spec/hashes_spec.rb +8 -0
  44. data/spec/mini_support_spec.rb +54 -0
  45. data/spec/part_spec.rb +52 -0
  46. data/spec/path_helper_spec.rb +22 -14
  47. data/spec/request_delegate_spec.rb +19 -11
  48. data/spec/route_entry_spec.rb +55 -0
  49. data/spec/session_spec.rb +69 -7
  50. data/spec/spec_helper.rb +3 -0
  51. data/spec/test_spec.rb +58 -0
  52. data/tools/hello.rb +11 -3
  53. data/tools/memcheck.rb +33 -0
  54. data/tools/s.rb +11 -0
  55. metadata +23 -7
  56. data/example/design.rb +0 -62
  57. data/example/fib.rb +0 -15
  58. data/spec/route_spec.rb +0 -84
  59. /data/ext/inc/{status_codes.inc → status_codes.h} +0 -0
@@ -0,0 +1,23 @@
1
+ module Nyara
2
+ class Flash
3
+ def initialize session
4
+ # NOTE no need to convert hash type because Session uses ParamHash for json parsing
5
+ @now = session.delete('flash.next') || ParamHash.new
6
+ session['flash.next'] = @next = ParamHash.new
7
+ end
8
+ attr_reader :now, :next
9
+
10
+ def [] key
11
+ @now[key]
12
+ end
13
+
14
+ def []= key, value
15
+ @next[key] = value
16
+ end
17
+
18
+ def clear
19
+ @now.clear
20
+ @next.clear
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,6 @@
1
1
  module Nyara
2
+ # keys ignore case, and values all string<br>
3
+ # TODO check invalid chars in values
2
4
  class HeaderHash
3
5
  alias has_key? key?
4
6
 
data/lib/nyara/nyara.rb CHANGED
@@ -1,9 +1,11 @@
1
+ # patch core classes first
2
+ require_relative "patches/mini_support"
3
+
1
4
  # master require
2
5
  require "fiber"
3
6
  require "cgi"
7
+ require "uri"
4
8
  require "openssl"
5
- require "json"
6
- require "base64"
7
9
  require "socket"
8
10
  require "tilt"
9
11
 
@@ -16,15 +18,19 @@ require_relative "controller"
16
18
  require_relative "request"
17
19
  require_relative "cookie"
18
20
  require_relative "session"
21
+ require_relative "flash"
19
22
  require_relative "config"
20
23
  require_relative "route"
21
24
  require_relative "route_entry"
22
25
  require_relative "view"
23
26
  require_relative "cpu_counter"
27
+ require_relative "part"
24
28
 
25
29
  module Nyara
26
30
  HTTP_STATUS_FIRST_LINES = Hash[HTTP_STATUS_CODES.map{|k,v|[k, "HTTP/1.1 #{k} #{v}\r\n".freeze]}].freeze
27
31
 
32
+ HTTP_REDIRECT_STATUS = [300, 301, 302, 303, 307]
33
+
28
34
  # base header response for 200
29
35
  # caveat: these entries can not be deleted
30
36
  OK_RESP_HEADER = HeaderHash.new
@@ -41,6 +47,12 @@ module Nyara
41
47
  Config
42
48
  end
43
49
 
50
+ def setup
51
+ Session.init
52
+ Route.compile
53
+ View.init
54
+ end
55
+
44
56
  def start_server
45
57
  port = Config[:port] || 3000
46
58
 
data/lib/nyara/part.rb ADDED
@@ -0,0 +1,156 @@
1
+ module Nyara
2
+ # a part in multipart<br>
3
+ # for an easy introduction, http://msdn.microsoft.com/en-us/library/ms526943(v=exchg.10).aspx
4
+ #
5
+ # - todo make it possible to store data into /tmp (this requires memory threshold counting)
6
+ # - todo nested multipart?
7
+ class Part < ParamHash
8
+ MECHANISMS = %w[base64 quoted-printable 7bit 8bit binary].freeze
9
+ MECHANISMS.each &:freeze
10
+
11
+ # rfc2616
12
+ #
13
+ # token := 1*<any CHAR except CTLs or separators>
14
+ # separators := "(" | ")" | "<" | ">" | "@"
15
+ # | "," | ";" | ":" | "\" | <">
16
+ # | "/" | "[" | "]" | "?" | "="
17
+ # | "{" | "}" | " " | "\t"
18
+ # CTL := <any US-ASCII control character
19
+ # (octets 0 - 31) and DEL (127)>
20
+ #
21
+ TOKEN = /[^\x00-\x1f\x7f()<>@,;:\\"\/\[\]?=\{\}\ \t]+/ni
22
+
23
+ # rfc5978
24
+ #
25
+ # attr-char := ALPHA / DIGIT ; rfc5234
26
+ # / "!" / "#" / "$" / "&" / "+" / "-" / "."
27
+ # / "^" / "_" / "`" / "|" / "~"
28
+ #
29
+ ATTR_CHAR = /[a-z0-9!#$&+\-\.\^_`|~]/ni
30
+
31
+ # rfc5978 (NOTE rfc2231 param continuations is not recommended)
32
+ #
33
+ # value-chars := pct-encoded / attr-char
34
+ # pct-encoded := "%" HEXDIG HEXDIG
35
+ #
36
+ EX_PARAM = /\s*;\s*(filename|name)\s*(?:
37
+ = \s* "((?>\\"|[^"])*)" # quoted string - 2
38
+ | = \s* (#{TOKEN}) # token - 3
39
+ | \*= \s* ([\w\-]+) # charset - 4
40
+ '[\w\-]+' # language
41
+ ((?>%\h\h|#{ATTR_CHAR})+) # value-chars - 5
42
+ )/xni
43
+
44
+ # analyse given +head+ and build a param hash representing the part
45
+ #
46
+ # [head] header
47
+ # [mechanism] 7bit, 8bit, binary, base64, or quoted-printable
48
+ # [type] mime type
49
+ # [data] decoded data (incomplete before Part#final called)
50
+ # [filename] basename of uploaded data
51
+ # [name] param name
52
+ #
53
+ def initialize head
54
+ self['head'] = head
55
+ if mechanism = head['Content-Transfer-Encoding']
56
+ self['mechanism'] = mechanism.strip.downcase
57
+ end
58
+ if self['type'] = head['Content-Type']
59
+ self['type'] = self['type'][/.*?(?=;|$)/]
60
+ end
61
+ self['data'] = ''.force_encoding('binary')
62
+
63
+ disposition = head['Content-Disposition']
64
+ if disposition
65
+ # skip first token
66
+ ex_params = disposition.sub TOKEN, ''
67
+
68
+ # store values not so specific as encoded value
69
+ tmp_values = {}
70
+ ex_params.scan EX_PARAM do |name, v1, v2, enc, v3|
71
+ name.downcase!
72
+ if enc
73
+ # value with charset and lang is more specific
74
+ self[name] ||= enc_unescape enc, v3
75
+ else
76
+ tmp_values[name] ||= (v1 || (CGI.unescape(v2) rescue nil))
77
+ end
78
+ end
79
+ self['filename'] ||= tmp_values['filename']
80
+ self['name'] ||= tmp_values['name']
81
+ end
82
+ if self['filename']
83
+ self['filename'] = File.basename self['filename']
84
+ end
85
+ self['name'] ||= head['Content-Id']
86
+ end
87
+
88
+ # prereq: +raw+ in binary encoding
89
+ def update raw
90
+ case self['mechanism']
91
+ when 'base64'
92
+ # rfc2045#section-6.8
93
+ raw.gsub! /\s+/n, ''
94
+ if self['tmp']
95
+ raw = (self['tmp'] << raw)
96
+ end
97
+ # last part can be at most 4 bytes and 2 '='s
98
+ size = raw.bytesize - 6
99
+ if size >= 4
100
+ size = size / 4 * 4
101
+ self['data'] << raw.slice!(0...size).unpack('m').first
102
+ end
103
+ self['tmp'] = raw
104
+
105
+ when 'quoted-printable'
106
+ # http://en.wikipedia.org/wiki/Quoted-printable
107
+ if self['tmp']
108
+ raw = (self['tmp'] << raw)
109
+ end
110
+ if i = raw.rindex("\r\n")
111
+ s = raw.slice! i
112
+ s.gsub!(/=(?:(\h\h)|\r\n)/n) do
113
+ [$1].pack 'H*'
114
+ end
115
+ self['data'] << s
116
+ end
117
+ self['tmp'] = raw
118
+
119
+ else # '7bit', '8bit', 'binary', ...
120
+ self['data'] << raw
121
+ end
122
+ end
123
+
124
+ def final
125
+ case self['mechanism']
126
+ when 'base64'
127
+ if tmp = self['tmp']
128
+ self['data'] << tmp.unpack('m').first
129
+ delete 'tmp'
130
+ end
131
+
132
+ when 'quoted-printable'
133
+ if tmp = self['tmp']
134
+ self['data'] << tmp.gsub(/=(\h\h)|=\r\n/n) do
135
+ [$1].pack 'H*'
136
+ end
137
+ delete 'tmp'
138
+ end
139
+ end
140
+ self
141
+ end
142
+
143
+ # ---
144
+ # private
145
+ # +++
146
+
147
+ def enc_unescape enc, v
148
+ enc = (Encoding.find enc rescue nil)
149
+ v = CGI.unescape v
150
+ v.force_encoding(enc).encode!('utf-8') if enc
151
+ v
152
+ rescue
153
+ nil
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def sum
3
+ inject 0, :+
4
+ end
5
+ end
@@ -0,0 +1,128 @@
1
+ # copied from activesupport
2
+
3
+ =begin
4
+ Copyright (c) 2005-2013 David Heinemeier Hansson
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ =end
25
+
26
+ class Object
27
+ # An object is blank if it's false, empty, or a whitespace string.
28
+ # For example, '', ' ', +nil+, [], and {} are all blank.
29
+ #
30
+ # This simplifies:
31
+ #
32
+ # if address.nil? || address.empty?
33
+ #
34
+ # ...to:
35
+ #
36
+ # if address.blank?
37
+ def blank?
38
+ respond_to?(:empty?) ? empty? : !self
39
+ end
40
+
41
+ # An object is present if it's not <tt>blank?</tt>.
42
+ def present?
43
+ !blank?
44
+ end
45
+
46
+ # Returns object if it's <tt>present?</tt> otherwise returns +nil+.
47
+ # <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
48
+ #
49
+ # This is handy for any representation of objects where blank is the same
50
+ # as not present at all. For example, this simplifies a common check for
51
+ # HTTP POST/query parameters:
52
+ #
53
+ # state = params[:state] if params[:state].present?
54
+ # country = params[:country] if params[:country].present?
55
+ # region = state || country || 'US'
56
+ #
57
+ # ...becomes:
58
+ #
59
+ # region = params[:state].presence || params[:country].presence || 'US'
60
+ def presence
61
+ self if present?
62
+ end
63
+ end
64
+
65
+ class NilClass
66
+ # +nil+ is blank:
67
+ #
68
+ # nil.blank? # => true
69
+ def blank?
70
+ true
71
+ end
72
+ end
73
+
74
+ class FalseClass
75
+ # +false+ is blank:
76
+ #
77
+ # false.blank? # => true
78
+ def blank?
79
+ true
80
+ end
81
+ end
82
+
83
+ class TrueClass
84
+ # +true+ is not blank:
85
+ #
86
+ # true.blank? # => false
87
+ def blank?
88
+ false
89
+ end
90
+ end
91
+
92
+ class Array
93
+ # An array is blank if it's empty:
94
+ #
95
+ # [].blank? # => true
96
+ # [1,2,3].blank? # => false
97
+ alias_method :blank?, :empty?
98
+ end
99
+
100
+ class Hash
101
+ # A hash is blank if it's empty:
102
+ #
103
+ # {}.blank? # => true
104
+ # { key: 'value' }.blank? # => false
105
+ alias_method :blank?, :empty?
106
+ end
107
+
108
+ class String
109
+ # A string is blank if it's empty or contains whitespaces only:
110
+ #
111
+ # ''.blank? # => true
112
+ # ' '.blank? # => true
113
+ # ' '.blank? # => true
114
+ # ' something here '.blank? # => false
115
+ def blank?
116
+ self !~ /[^[:space:]]/
117
+ end
118
+ end
119
+
120
+ class Numeric #:nodoc:
121
+ # No number is blank:
122
+ #
123
+ # 1.blank? # => false
124
+ # 0.blank? # => false
125
+ def blank?
126
+ false
127
+ end
128
+ end
@@ -0,0 +1,15 @@
1
+ require "json"
2
+
3
+ # +JSON.parse '{"json_class":"MyClass"}'+ should not create object as MyClass
4
+ JSON.load_default_options.merge! create_additions: false
5
+ JSON::GenericObject.json_creatable = false
6
+
7
+ [Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
8
+ klass.class_eval do
9
+ # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
10
+ # "</" is escaped for convenience use in javascript
11
+ def to_json(options = nil)
12
+ super(options).gsub '</', "<\\/"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ require_relative "array"
2
+ require_relative "blank"
3
+ require_relative "json"
4
+ require_relative "string"
5
+ require_relative "to_query"
6
+ # except tcp_socket
@@ -0,0 +1,21 @@
1
+ # patch with 2.1 methods if not defined
2
+ class String
3
+ unless defined? b
4
+ def b
5
+ dup.force_encoding 'binary'
6
+ end
7
+ end
8
+
9
+ unless defined? scrub
10
+ # NOTE: block unsupported
11
+ def scrub replacement=nil
12
+ if replacement
13
+ replacement = replacement.encode 'UTF-16BE'
14
+ else
15
+ replacement = "\xFF\xFD".force_encoding 'UTF-16BE'
16
+ end
17
+ r = encode("UTF-16BE", undef: :replace, invalid: :replace, replace: replacement)
18
+ r.encode("UTF-8").gsub("\0".encode("UTF-8"), '')
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,113 @@
1
+ # copied from activesupport
2
+ require "cgi"
3
+
4
+ =begin
5
+ Copyright (c) 2005-2013 David Heinemeier Hansson
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ =end
26
+
27
+ class Object
28
+ # Alias of <tt>to_s</tt>.
29
+ def to_param
30
+ to_s
31
+ end
32
+ end
33
+
34
+ class String
35
+ alias to_param to_s
36
+ end
37
+
38
+ class NilClass
39
+ # Returns +self+.
40
+ def to_param
41
+ self
42
+ end
43
+ end
44
+
45
+ class TrueClass
46
+ # Returns +self+.
47
+ def to_param
48
+ self
49
+ end
50
+ end
51
+
52
+ class FalseClass
53
+ # Returns +self+.
54
+ def to_param
55
+ self
56
+ end
57
+ end
58
+
59
+ class Array
60
+ # Calls <tt>to_param</tt> on all its elements and joins the result with
61
+ # slashes. This is used by <tt>url_for</tt> in Action Pack.
62
+ def to_param
63
+ collect { |e| e.to_param }.join '/'
64
+ end
65
+ end
66
+
67
+ class Hash
68
+ # Returns a string representation of the receiver suitable for use as a URL
69
+ # query string:
70
+ #
71
+ # {name: 'David', nationality: 'Danish'}.to_param
72
+ # # => "name=David&nationality=Danish"
73
+ #
74
+ # An optional namespace can be passed to enclose the param names:
75
+ #
76
+ # {name: 'David', nationality: 'Danish'}.to_param('user')
77
+ # # => "user[name]=David&user[nationality]=Danish"
78
+ #
79
+ # The string pairs "key=value" that conform the query string
80
+ # are sorted lexicographically in ascending order.
81
+ #
82
+ # This method is also aliased as +to_query+.
83
+ def to_param(namespace = nil)
84
+ collect do |key, value|
85
+ value.to_query(namespace ? "#{namespace}[#{key}]" : key)
86
+ end.sort * '&'
87
+ end
88
+ end
89
+
90
+ class Object
91
+ # Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the
92
+ # param name.
93
+ #
94
+ # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work.
95
+ def to_query(key)
96
+ "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
97
+ end
98
+ end
99
+
100
+ class Array
101
+ # Converts an array into a string suitable for use as a URL query string,
102
+ # using the given +key+ as the param name.
103
+ #
104
+ # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
105
+ def to_query(key)
106
+ prefix = "#{key}[]"
107
+ collect { |value| value.to_query(prefix) }.join '&'
108
+ end
109
+ end
110
+
111
+ class Hash
112
+ alias_method :to_query, :to_param
113
+ end
data/lib/nyara/request.rb CHANGED
@@ -4,6 +4,7 @@ module Nyara
4
4
  # request and handler
5
5
  class Request
6
6
  # c-ext: http_method, scope, path, query, path_with_query format, accept, header
7
+ # cookie, session, flash
7
8
  # status, response_content_type, response_header, response_header_extra_lines
8
9
  # todo: body, move all underline methods into Ext
9
10
 
@@ -54,7 +55,7 @@ module Nyara
54
55
  if r
55
56
  r.split(':', 2).first
56
57
  else
57
- ''
58
+ Config['host'] || 'localhost'
58
59
  end
59
60
  end
60
61
  end
@@ -65,12 +66,19 @@ module Nyara
65
66
  if r
66
67
  r = r.split(':', 2).last
67
68
  end
68
- r ? r.to_i : 80 # or server running port?
69
+ r ? r.to_i : (Config['port'] || 80)
69
70
  end
70
71
  end
71
72
 
72
- def host
73
- header['Host']
73
+ def host_with_port
74
+ header['Host'] || begin
75
+ p = port
76
+ if p == 80
77
+ domain
78
+ else
79
+ "#{domain}:#{p}"
80
+ end
81
+ end
74
82
  end
75
83
 
76
84
  def xhr?
@@ -121,18 +129,8 @@ module Nyara
121
129
  end
122
130
  end
123
131
 
124
- def cookie
125
- @cookie ||= Cookie.decode header
126
- end
127
-
128
- def session
129
- @session ||= Session.decode cookie
130
- end
131
-
132
- # todo serialize the changed cookie
133
-
134
132
  # todo rename and move it into Ext
135
- def not_found
133
+ def not_found # :nodoc:
136
134
  Ext.request_send_data self, "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
137
135
  end
138
136
  end