nyara 0.0.1.pre.2 → 0.0.1.pre.3

Sign up to get free protection for your applications and to get access to all the features.
data/ext/url_encoded.c CHANGED
@@ -1,4 +1,5 @@
1
- // parse path / query / url-encoded body
1
+ /* url-encoded parsing */
2
+
2
3
  #include "nyara.h"
3
4
  #include <ruby/encoding.h>
4
5
 
@@ -18,13 +19,13 @@ static char _half_octet(char c) {
18
19
  }
19
20
  }
20
21
 
21
- static long _decode_url_seg(VALUE path, const char*s, long len, char stop_char) {
22
+ static long _decode_url_seg(VALUE output, const char*s, long len, char stop_char) {
22
23
  const char* last_s = s;
23
24
  long last_len = 0;
24
25
 
25
26
  # define FLUSH_UNESCAPED\
26
27
  if (last_len) {\
27
- rb_str_cat(path, last_s, last_len);\
28
+ rb_str_cat(output, last_s, last_len);\
28
29
  last_s += last_len;\
29
30
  last_len = 0;\
30
31
  }
@@ -50,7 +51,7 @@ static long _decode_url_seg(VALUE path, const char*s, long len, char stop_char)
50
51
  unsigned char r = ((unsigned char)r1 << 4) | (unsigned char)r2;
51
52
  FLUSH_UNESCAPED;
52
53
  last_s += 3;
53
- rb_str_cat(path, (char*)&r, 1);
54
+ rb_str_cat(output, (char*)&r, 1);
54
55
 
55
56
  } else if (s[i] == stop_char) {
56
57
  i++;
@@ -58,7 +59,7 @@ static long _decode_url_seg(VALUE path, const char*s, long len, char stop_char)
58
59
 
59
60
  } else if (s[i] == '+') {
60
61
  FLUSH_UNESCAPED;
61
- rb_str_cat(path, " ", 1);
62
+ rb_str_cat(output, " ", 1);
62
63
 
63
64
  } else {
64
65
  last_len++;
@@ -70,9 +71,68 @@ static long _decode_url_seg(VALUE path, const char*s, long len, char stop_char)
70
71
  return i;
71
72
  }
72
73
 
74
+ // s should contain no space
73
75
  // return parsed len, s + return == start of query
76
+ // NOTE it's similar to _decode_url_seg, but:
77
+ // - "+" is not escaped
78
+ // - matrix uri params (segments starting with ";") are ignored
74
79
  long nyara_parse_path(VALUE output, const char* s, long len) {
75
- return _decode_url_seg(output, s, len, '?');
80
+ const char* last_s = s;
81
+ long last_len = 0;
82
+
83
+ # define FLUSH_UNESCAPED\
84
+ if (last_len) {\
85
+ rb_str_cat(output, last_s, last_len);\
86
+ last_s += last_len;\
87
+ last_len = 0;\
88
+ }
89
+
90
+ long i;
91
+ for (i = 0; i < len; i++) {
92
+ if (s[i] == '%') {
93
+ if (i + 2 >= len) {
94
+ last_len++;
95
+ continue;
96
+ }
97
+ char r1 = _half_octet(s[i + 1]);
98
+ if (r1 < 0) {
99
+ last_len++;
100
+ continue;
101
+ }
102
+ char r2 = _half_octet(s[i + 2]);
103
+ if (r2 < 0) {
104
+ last_len++;
105
+ continue;
106
+ }
107
+ i += 2;
108
+ unsigned char r = ((unsigned char)r1 << 4) | (unsigned char)r2;
109
+ FLUSH_UNESCAPED;
110
+ last_s += 3;
111
+ rb_str_cat(output, (char*)&r, 1);
112
+
113
+ } else if (s[i] == ';') {
114
+ // skip matrix uri params
115
+ i++;
116
+ for (; i < len; i++) {
117
+ if (s[i] == '?') {
118
+ i++;
119
+ break;
120
+ }
121
+ }
122
+ break;
123
+
124
+ } else if (s[i] == '?') {
125
+ i++;
126
+ break;
127
+
128
+ } else {
129
+ last_len++;
130
+ }
131
+ }
132
+ FLUSH_UNESCAPED;
133
+ # undef FLUSH_UNESCAPED
134
+
135
+ return i;
76
136
  }
77
137
 
78
138
  static VALUE ext_parse_path(VALUE self, VALUE output, VALUE input) {
data/hello.rb CHANGED
@@ -1,8 +1,14 @@
1
- # require_relative "lib/nyara"
1
+ require_relative "lib/nyara"
2
+ require "open-uri"
3
+ require "pry"
2
4
 
3
- require 'nyara'
5
+ configure do
6
+ # set :env, 'production'
7
+ # set :workers, 2
8
+ end
4
9
 
5
10
  get '/' do
11
+ open 'http://baidu.com', &:read
6
12
  send_string 'hello world'
7
13
  end
8
14
 
@@ -183,7 +183,7 @@ module Nyara
183
183
  def add_header_line h
184
184
  raise 'can not modify sent header' if request.response_header.frozen?
185
185
  h = h.sub /(?<![\r\n])\z/, "\r\n"
186
- request.response_header_extra_lines << s
186
+ request.response_header_extra_lines << h
187
187
  end
188
188
 
189
189
  # todo args helper
@@ -198,15 +198,34 @@ module Nyara
198
198
  end
199
199
  alias cookies cookie
200
200
 
201
- def set_cookie k, v=nil, opts
201
+ # Set cookie, if expires is +Time.now+, will remove the cookie entry
202
+ #
203
+ # :call-seq:
204
+ #
205
+ # set_cookie 'JSESSIONID', 'not-exist'
206
+ # set_cookie 'key-without-value'
207
+ #
208
+ # +opt: default_value+ are:
209
+ #
210
+ # expires: nil
211
+ # max_age: nil
212
+ # domain: nil
213
+ # path: nil
214
+ # secure: nil
215
+ # httponly: true
216
+ #
217
+ def set_cookie name, value=nil, opts={}
218
+ if value.is_a?(Hash)
219
+ raise ArgumentError, 'hash not allowed in cookie value, did you mean to use it as options?'
220
+ end
202
221
  # todo default domain ?
203
222
  opts = Hash[opts.map{|k,v| [k.to_sym,v]}]
204
- Cookie.output_set_cookie response.response_header_extra_lines, k, v, opts
223
+ Cookie.add_set_cookie request.response_header_extra_lines, name, value, opts
205
224
  end
206
225
 
207
- def delete_cookie k
226
+ def delete_cookie name
208
227
  # todo domain ? path ?
209
- set_cookie k, expires: Time.now, max_age: 0
228
+ set_cookie name, nil, expires: Time.now, max_age: 0
210
229
  end
211
230
 
212
231
  def clear_cookie
@@ -238,7 +257,7 @@ module Nyara
238
257
  r = request
239
258
  header = r.response_header
240
259
 
241
- Ext.send_data r, HTTP_STATUS_FIRST_LINES[r.status]
260
+ Ext.request_send_data r, HTTP_STATUS_FIRST_LINES[r.status]
242
261
 
243
262
  header.aset_content_type \
244
263
  r.response_content_type ||
@@ -254,7 +273,7 @@ module Nyara
254
273
  end
255
274
  data.concat r.response_header_extra_lines
256
275
  data << "\r\n"
257
- Ext.send_data r, data.join
276
+ Ext.request_send_data r, data.join
258
277
 
259
278
  # forbid further modification
260
279
  header.freeze
@@ -263,17 +282,17 @@ module Nyara
263
282
  # Send raw data, that is, not wrapped in chunked encoding<br>
264
283
  # NOTE: often you should call send_header before doing this.
265
284
  def send_data data
266
- Ext.send_data request, data.to_s
285
+ Ext.request_send_data request, data.to_s
267
286
  end
268
287
 
269
288
  # Send a data chunk, it can send_header first if header is not sent.
270
- #
289
+ #
271
290
  # :call-seq:
272
291
  #
273
292
  # send_chunk 'hello world!'
274
293
  def send_chunk data
275
294
  send_header unless request.response_header.frozen?
276
- Ext.send_chunk request, data.to_s
295
+ Ext.request_send_chunk request, data.to_s
277
296
  end
278
297
  alias send_string send_chunk
279
298
 
data/lib/nyara/cookie.rb CHANGED
@@ -11,8 +11,8 @@ module Nyara
11
11
  res
12
12
  end
13
13
 
14
- def add_set_cookie r, k, v, expires: nil, max_age: nil, domain: nil, path: nil, secure: nil, httponly: true
15
- r << "Set-Cookie: "
14
+ def add_set_cookie header_lines, k, v, expires: nil, max_age: nil, domain: nil, path: nil, secure: nil, httponly: true
15
+ r = "Set-Cookie: "
16
16
  if v.nil? or v == true
17
17
  r << "#{CGI.escape k.to_s}; "
18
18
  else
@@ -26,6 +26,7 @@ module Nyara
26
26
  r << "Secure; " if secure
27
27
  r << "HttpOnly; " if httponly
28
28
  r << "\r\n"
29
+ header_lines << r
29
30
  end
30
31
  end
31
32
  end
data/lib/nyara/nyara.rb CHANGED
@@ -8,9 +8,9 @@ require "socket"
8
8
  require "tilt"
9
9
 
10
10
  require_relative "../../ext/nyara"
11
- require_relative "param_hash"
12
- require_relative "header_hash"
13
- require_relative "config_hash"
11
+ require_relative "hashes/param_hash"
12
+ require_relative "hashes/header_hash"
13
+ require_relative "hashes/config_hash"
14
14
  require_relative "mime_types"
15
15
  require_relative "controller"
16
16
  require_relative "request"
@@ -43,40 +43,54 @@ module Nyara
43
43
 
44
44
  def start_server
45
45
  port = Config[:port] || 3000
46
- workers = Config[:workers] || (CpuCounter.count - 1)
47
46
 
48
47
  puts "starting #{Config[:env]} server at 0.0.0.0:#{port}"
49
48
  case Config[:env].to_s
50
-
51
49
  when 'production'
52
- server = TCPServer.new '0.0.0.0', port
53
- server.listen 1000
54
- trap :INT do
55
- @workers.each do |w|
56
- Process.kill :KILL, w
57
- end
58
- end
59
- GC.start
60
- @workers = workers.times.map do
61
- fork {
62
- Ext.init_queue
63
- Ext.run_queue server.fileno
64
- }
65
- end
66
- Process.waitall
67
-
50
+ patch_tcp_socket
51
+ start_production_server port
68
52
  when 'test'
69
53
  # don't
70
-
71
54
  else
72
- t = Thread.new do
73
- server = TCPServer.new '0.0.0.0', port
74
- server.listen 1000
55
+ patch_tcp_socket
56
+ start_development_server port
57
+ end
58
+ end
59
+
60
+ def patch_tcp_socket
61
+ puts "patching TCPSocket"
62
+ require_relative "patch_tcp_socket"
63
+ end
64
+
65
+ def start_production_server port
66
+ workers = Config[:workers] || ((CpuCounter.count + 1)/ 2)
67
+
68
+ puts "workers: #{workers}"
69
+ server = TCPServer.new '0.0.0.0', port
70
+ server.listen 1000
71
+ trap :INT do
72
+ @workers.each do |w|
73
+ Process.kill :KILL, w
74
+ end
75
+ end
76
+ GC.start
77
+ @workers = workers.times.map do
78
+ fork {
75
79
  Ext.init_queue
76
80
  Ext.run_queue server.fileno
77
- end
78
- t.join
81
+ }
82
+ end
83
+ Process.waitall
84
+ end
85
+
86
+ def start_development_server port
87
+ t = Thread.new do
88
+ server = TCPServer.new '0.0.0.0', port
89
+ server.listen 1000
90
+ Ext.init_queue
91
+ Ext.run_queue server.fileno
79
92
  end
93
+ t.join
80
94
  end
81
95
  end
82
96
  end
@@ -0,0 +1,22 @@
1
+ # patch TCPSocket to make operations synchrony
2
+ class TCPSocket
3
+ alias _orig_initialize initialize
4
+
5
+ def initialize *xs
6
+ _orig_initialize *xs
7
+ Nyara::Ext.set_nonblock fileno
8
+ Nyara::Ext.fd_watch fileno
9
+ end
10
+
11
+ def send data, flags, dest_addr=nil, &blk
12
+ if dest_addr
13
+ super
14
+ else
15
+ Nyara::Ext.fd_send fileno, data, flags, &blk
16
+ end
17
+ end
18
+
19
+ def recv max_len, flags=0
20
+ Nyara::Ext.fd_recv fileno, max_len, flags
21
+ end
22
+ end
data/lib/nyara/request.rb CHANGED
@@ -133,9 +133,7 @@ module Nyara
133
133
 
134
134
  # todo rename and move it into Ext
135
135
  def not_found
136
- puts "not found"
137
- Ext.send_data self, "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
138
- Ext.close self
136
+ Ext.request_send_data self, "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
139
137
  end
140
138
  end
141
139
  end
data/lib/nyara/view.rb CHANGED
@@ -128,6 +128,7 @@ module Nyara
128
128
  ENGINE_DEFAULT_CONTENT_TYPES[ext] = default_content_type
129
129
  end
130
130
 
131
+ # local keys are for first-time code generation, values not used
131
132
  # returns +[meth, ext_without_dot]+
132
133
  def template path, locals={}
133
134
  if File.extname(path).empty?
data/nyara.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "nyara"
3
- s.version = "0.0.1.pre.2"
3
+ s.version = "0.0.1.pre.3"
4
4
  s.author = "Zete Lui"
5
5
  s.email = "nobody@example.com"
6
6
  s.homepage = "https://github.com/luikore/nyara"
data/rakefile CHANGED
@@ -58,43 +58,6 @@ task :build => makefile do
58
58
  end
59
59
  end
60
60
 
61
- def term_color n
62
- print "\e[38;5;#{n}m"
63
- end
64
-
65
- def reset_color
66
- print "\e[00m"
67
- end
68
-
69
- desc "check arity of rb_define_method/rb_define_singleton_method"
70
- task :check_arity do
71
- Dir.glob 'ext/*.{c,cc}' do |f|
72
- puts "validatign #{f}"
73
- arities = {}
74
- data = File.read f
75
- data.scan /^(?:static )?VALUE (\w+)\((.+)\)/ do |func, params|
76
- arities[func] = params.count(',')
77
- puts " scan: #{func}/#{arities[func]}"
78
- end
79
- data.scan /rb_define(?:_singleton)?_method\(.*?(\w+)\s*\,\s*(\d+)\)/ do |func, arity|
80
- print " check: #{func}/#{arity} "
81
- if arities[func].nil?
82
- term_color 5
83
- print "UNSCANNED"
84
- reset_color
85
- puts
86
- elsif arities[func] != arity.to_i
87
- term_color 9
88
- print "MISMATCH #{arities[func]}"
89
- reset_color
90
- puts
91
- else
92
- puts "OK"
93
- end
94
- end
95
- end
96
- end
97
-
98
61
  desc "test"
99
62
  task :test => :build do
100
63
  sh 'rspec', '-c'
@@ -118,6 +81,7 @@ task :clean do
118
81
  sh 'rm', '-f', '*.gem'
119
82
  Dir.chdir 'ext' do
120
83
  sh 'make', 'clean'
84
+ sh 'rm', '-f', 'Makefile'
121
85
  end
122
86
  end
123
87
 
@@ -125,15 +89,72 @@ end
125
89
 
126
90
  desc "collect line stat"
127
91
  task :lines do
128
- c = 0
129
- Dir.glob('{**/*.rb,ext/*.{c,cc,h}}') do |f|
130
- c += (File.read(f).count "\n")
92
+ rb_c = 0
93
+ Dir.glob('**/*.rb') do |f|
94
+ rb_c += (File.read(f).count "\n")
95
+ end
96
+
97
+ c_c = 0
98
+ Dir.glob('ext/*.{c,cc,h}') do |f|
99
+ c_c += (File.read(f).count "\n")
100
+ end
101
+
102
+ spec_c = 0
103
+ Dir.glob('spec/**/*.rb') do |f|
104
+ spec_c += (File.read(f).count "\n")
131
105
  end
132
- puts "#{c} lines"
106
+
107
+ puts "c: #{c_c} lines"
108
+ puts "rb: #{rb_c - spec_c} lines"
109
+ puts "spec: #{spec_c} lines"
133
110
  end
134
111
 
135
112
  desc "list Nyara::Ext methods"
136
113
  task :list_ext do
137
114
  require_relative "lib/nyara/nyara"
138
- puts Nyara::Ext.methods - Module.methods
115
+ methods = (Nyara::Ext.methods - Module.methods).sort
116
+ [/queue/, /route/, /parse/, /request/].each do |r|
117
+ group = methods.grep r
118
+ puts group
119
+ puts
120
+ methods -= group
121
+ end
122
+ puts methods
123
+ end
124
+
125
+ def term_color n
126
+ print "\e[38;5;#{n}m"
127
+ end
128
+
129
+ def reset_color
130
+ print "\e[00m"
131
+ end
132
+
133
+ desc "audit arity of rb_define_method/rb_define_singleton_method"
134
+ task :audit_arity do
135
+ Dir.glob 'ext/*.{c,cc}' do |f|
136
+ puts "validatign #{f}"
137
+ arities = {}
138
+ data = File.read f
139
+ data.scan /^(?:static )?VALUE (\w+)\((.+)\)/ do |func, params|
140
+ arities[func] = params.count(',')
141
+ puts " scan: #{func}/#{arities[func]}"
142
+ end
143
+ data.scan /rb_define(?:_singleton)?_method\(.*?(\w+)\s*\,\s*(\d+)\)/ do |func, arity|
144
+ print " check: #{func}/#{arity} "
145
+ if arities[func].nil?
146
+ term_color 5
147
+ print "UNSCANNED"
148
+ reset_color
149
+ puts
150
+ elsif arities[func] != arity.to_i
151
+ term_color 9
152
+ print "MISMATCH #{arities[func]}"
153
+ reset_color
154
+ puts
155
+ else
156
+ puts "OK"
157
+ end
158
+ end
159
+ end
139
160
  end
data/readme.md CHANGED
@@ -8,7 +8,7 @@ Not Yet Another Ruby Async web framework and server. Not on rack nor rack-compat
8
8
 
9
9
  # Getting started
10
10
 
11
- Requires Ruby 2.0+, BSD/Linux/Mac OS X.
11
+ Requires Ruby 2.0+, BSD/Linux/Mac OS X, GCC4.7+ or Clang.
12
12
 
13
13
  Install
14
14
 
@@ -42,6 +42,33 @@ rake gen
42
42
  rake gem
43
43
  ```
44
44
 
45
+ If you have cloned the repo once, and want to update code
46
+
47
+ ```bash
48
+ git pull --recurse-submodules
49
+ git submodule foreach git fetch
50
+ ```
51
+
52
+ # Testing
53
+
54
+ Simply run the test
55
+
56
+ ```bash
57
+ rspec -c
58
+ ```
59
+
60
+ Test in GC.stress mode
61
+
62
+ ```bash
63
+ rspec -c -f d
64
+ ```
65
+
66
+ With coverage (generates *coverage/index.html*)
67
+
68
+ ```bash
69
+ COVERAGE=1 rspec -c
70
+ ```
71
+
45
72
  # Why fast
46
73
 
47
74
  ### Solid http parsers written in C
@@ -87,10 +114,12 @@ In Nyara, nested templates of Slim, ERB or Haml share the same output buffer, so
87
114
 
88
115
  # How fast
89
116
 
90
- Performance is feature, there are specs on (TODO):
117
+ Performance is feature, there are specs on:
91
118
 
92
- - Accept-* parse vs rack
93
- - MIME matching vs rack
119
+ - Accept-* parse vs sinatra
94
120
  - param parse vs ruby
95
121
  - layout rendering vs tilt
96
- - evented IO vs eventmachine
122
+
123
+ # License
124
+
125
+ BSD, see copying
@@ -0,0 +1,14 @@
1
+ # connect remote server, and read / write data
2
+
3
+ require_relative "../../lib/nyara"
4
+ require "pry"
5
+ require "open-uri"
6
+
7
+ configure do
8
+ port 3003
9
+ end
10
+
11
+ get '/' do
12
+ data = open "http://baidu.com", &:read
13
+ send_string data
14
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "spec_helper"
2
+
3
+ module Nyara
4
+ describe 'evented IO' do
5
+ before :all do
6
+ pid = Process.pid
7
+ @server = fork do
8
+ exec 'ruby', __dir__ + '/apps/connect.rb'
9
+ end
10
+ sleep 1 # wait for server startup
11
+ end
12
+
13
+ after :all do
14
+ Process.kill :KILL, @server
15
+ end
16
+
17
+ it "works" do
18
+ result1 = open "http://localhost:3003", &:read
19
+ result2 = open "http://baidu.com", &:read
20
+ assert_equal result2, result1
21
+ end
22
+ end
23
+ end
@@ -63,47 +63,66 @@ module Nyara
63
63
  @output = ''
64
64
  end
65
65
 
66
- it "parses" do
66
+ it "converts '%' bytes but not '+'" do
67
67
  i = '/%23+%24'
68
68
  assert_equal i.bytesize, parse(i)
69
- assert_equal "/\x23 \x24", @output
69
+ assert_equal "/\x23+\x24", @output
70
70
  end
71
71
 
72
- it "truncates ? after %" do
72
+ it "truncates '?' after %" do
73
73
  i = '/hello%f3%?world'
74
74
  len = parse i
75
75
  assert_equal '/hello%f3%?'.bytesize, len
76
76
  assert_equal "/hello\xf3%", @output
77
77
  end
78
78
 
79
- it "truncates ? after begin" do
79
+ it "truncates '?' after begin" do
80
80
  i = '?a'
81
81
  len = parse i
82
82
  assert_equal 1, len
83
83
  assert_equal '', @output
84
84
  end
85
85
 
86
- it "truncates ? before end" do
86
+ it "truncates '?' before end" do
87
87
  i = 'a?'
88
88
  len = parse i
89
89
  assert_equal 2, len
90
90
  assert_equal 'a', @output
91
91
  end
92
92
 
93
- it "truncates ? after unescaped char" do
93
+ it "truncates '?' after unescaped char" do
94
94
  i = 'a?a'
95
95
  len = parse i
96
96
  assert_equal 2, len
97
97
  assert_equal 'a', @output
98
98
  end
99
99
 
100
- it "truncates ? after escaped char" do
100
+ it "truncates '?' after escaped char" do
101
101
  i = '%40?'
102
102
  len = parse i
103
103
  assert_equal 4, len
104
104
  assert_equal "\x40", @output
105
105
  end
106
106
 
107
+ it "removes matrix uri params" do
108
+ i = '/a;matrix;matrix=3'
109
+ len = parse i
110
+ assert_equal i.size, len
111
+ assert_equal "/a", @output
112
+
113
+ @output = ''
114
+ i += '?'
115
+ len = parse i
116
+ assert_equal i.size, len
117
+ assert_equal "/a", @output
118
+
119
+ @output = ''
120
+ i += 'query'
121
+ len = parse i
122
+ assert_equal i.size - 'query'.size, len
123
+ assert_equal '/a', @output
124
+ end
125
+
107
126
  def parse input
108
127
  Ext.parse_path @output, input
109
128
  end