rhebok 0.2.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a896132dd677343c070f45f44902d760feeb3b08
4
- data.tar.gz: 7a5f20c3fc6fc30b13dbccbd820f675eae44a5f8
3
+ metadata.gz: 454acd81875129e8a6b70d0322faeded5a9b12ab
4
+ data.tar.gz: c2e879260c7505697b312f7ce07b4b91e137ccbb
5
5
  SHA512:
6
- metadata.gz: 534a23046a8d9207c9746175d25ad7a81bd005e670129c5074262fed719574a8b9fe3cea30de9c4cfe445dcbc17b118d96d640d95ede960bbab98b8186c32170
7
- data.tar.gz: 714f997e82ecede337cf86ff87018267e79b9dc4a1cef5af29ed6419abe45960ce279b73c6a92b65024a0c5934e60a3766ddca0f6f7ea240ecae58793df2ac14
6
+ metadata.gz: 0233481756e663f30ca815c7320e2bfc0bc88636a2577451dc8717c2e938c76fd68a71eec61c36da06e95d8f7ad985c8cb36fd566f6d92e6fc98191c0ff21066
7
+ data.tar.gz: a3297edf4bd16b7e331e7c068bb2bb0c1be641d145b9fca70c52b06a68599ce48488ea6d19b997eea69ece63aa46bb18be6ad1c5f625fd8b0a0bad4de993a78e
data/Changes CHANGED
@@ -1,3 +1,7 @@
1
+ 0.8.0 2015-01-22T10:18:30Z
2
+
3
+ - support HTTP/1.1
4
+
1
5
  0.2.3 2015-01-08T11:21:01Z
2
6
 
3
7
  - check ENV["SERVER_STARTER_PORT"] has "="
data/README.md CHANGED
@@ -9,7 +9,7 @@ Rhebok supports following features.
9
9
  - uses writev(2) for output responses
10
10
  - prefork and graceful shutdown using [prefork_engine](https://rubygems.org/gems/prefork_engine)
11
11
  - hot deploy using [start_server](https://metacpan.org/release/Server-Starter) ([here](https://github.com/lestrrat/go-server-starter) is golang version by lestrrat-san)
12
- - only supports HTTP/1.0. But does not support Keepalive.
12
+ - supports HTTP/1.1. But does not have Keepalive
13
13
  - supports OobGC
14
14
 
15
15
  This server is suitable for running HTTP application servers behind a reverse proxy like nginx.
@@ -18,6 +18,7 @@
18
18
  #define MAX_HEADER_NAME_LEN 1024
19
19
  #define MAX_HEADERS 128
20
20
  #define BAD_REQUEST "HTTP/1.0 400 Bad Request\r\nConnection: close\r\n\r\n400 Bad Request\r\n"
21
+ #define READ_BUF 16384
21
22
  #define TOU(ch) (('a' <= ch && ch <= 'z') ? ch - ('a' - 'A') : ch)
22
23
 
23
24
  static const char *DoW[] = {
@@ -26,6 +27,8 @@ static const char *DoW[] = {
26
27
  static const char *MoY[] = {
27
28
  "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
28
29
  };
30
+ static const char xdigit[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
31
+
29
32
 
30
33
  /* stolen from HTTP::Status and Feersum */
31
34
  /* Unmarked codes are from RFC 2616 */
@@ -377,6 +380,24 @@ void str_i(char * dst, int * dst_len, int src, int fig) {
377
380
  *dst_len += fig;
378
381
  }
379
382
 
383
+ static
384
+ int _chunked_header(char *buf, ssize_t len) {
385
+ int dlen = 0, i;
386
+ ssize_t l = len;
387
+ while ( l > 0 ) {
388
+ dlen++;
389
+ l /= 16;
390
+ }
391
+ i = dlen;
392
+ buf[i++] = 13;
393
+ buf[i++] = 10;
394
+ while ( len > 0 ) {
395
+ buf[--dlen] = xdigit[len % 16];
396
+ len /= 16;
397
+ }
398
+ return i;
399
+ }
400
+
380
401
  static
381
402
  int _date_line(char * date_line) {
382
403
  struct tm gtm;
@@ -436,7 +457,7 @@ int _parse_http_request(char *buf, ssize_t buf_len, VALUE env) {
436
457
  rb_hash_aset(env, request_method_key, rb_str_new(method,method_len));
437
458
  rb_hash_aset(env, request_uri_key, rb_str_new(path, path_len));
438
459
  rb_hash_aset(env, script_name_key, vacant_string_val);
439
- rb_hash_aset(env, server_protocol_key, (minor_version > 1 || minor_version < 0 ) ? http10_val : http11_val);
460
+ rb_hash_aset(env, server_protocol_key, (minor_version == 1) ? http11_val : http10_val);
440
461
 
441
462
  /* PATH_INFO QUERY_STRING */
442
463
  path_len = find_ch(path, path_len, '#'); /* strip off all text after # after storing request_uri */
@@ -582,6 +603,8 @@ VALUE rhe_read_timeout(VALUE self, VALUE filenov, VALUE rbuf, VALUE lenv, VALUE
582
603
  timeout = NUM2DBL(timeoutv);
583
604
  offset = NUM2LONG(offsetv);
584
605
  len = NUM2LONG(lenv);
606
+ if ( len > READ_BUF )
607
+ len = READ_BUF;
585
608
  d = ALLOC_N(char, len);
586
609
  rv = _read_timeout(fileno, timeout, &d[offset], len);
587
610
  if ( rv > 0 ) {
@@ -665,7 +688,7 @@ VALUE rhe_close(VALUE self, VALUE fileno) {
665
688
  }
666
689
 
667
690
  static
668
- VALUE rhe_write_response(VALUE self, VALUE filenov, VALUE timeoutv, VALUE status_codev, VALUE headers, VALUE body) {
691
+ VALUE rhe_write_response(VALUE self, VALUE filenov, VALUE timeoutv, VALUE status_codev, VALUE headers, VALUE body, VALUE use_chunkedv) {
669
692
  ssize_t hlen = 0;
670
693
  ssize_t blen = 0;
671
694
 
@@ -687,12 +710,15 @@ VALUE rhe_write_response(VALUE self, VALUE filenov, VALUE timeoutv, VALUE status
687
710
  const char * message;
688
711
 
689
712
  const char * s;
690
- char* d;
713
+ char * d;
691
714
  ssize_t n;
692
715
 
716
+ char * chunked_header_buf;
717
+
693
718
  int fileno = NUM2INT(filenov);
694
719
  double timeout = NUM2DBL(timeoutv);
695
720
  int status_code = NUM2INT(status_codev);
721
+ int use_chunked = NUM2INT(use_chunkedv);
696
722
 
697
723
  harr = rb_ary_new();
698
724
  RB_GC_GUARD(harr);
@@ -700,6 +726,9 @@ VALUE rhe_write_response(VALUE self, VALUE filenov, VALUE timeoutv, VALUE status
700
726
  hlen = RARRAY_LEN(harr);
701
727
  blen = RARRAY_LEN(body);
702
728
  iovcnt = 10 + (hlen * 2) + blen;
729
+ if ( use_chunked )
730
+ iovcnt += blen*2;
731
+ chunked_header_buf = ALLOC_N(char, 32 * blen);
703
732
 
704
733
  {
705
734
  struct iovec v[iovcnt]; // Needs C99 compiler
@@ -713,7 +742,7 @@ VALUE rhe_write_response(VALUE self, VALUE filenov, VALUE timeoutv, VALUE status
713
742
  status_line[i++] = '/';
714
743
  status_line[i++] = '1';
715
744
  status_line[i++] = '.';
716
- status_line[i++] = '0';
745
+ status_line[i++] = '1';
717
746
  status_line[i++] = ' ';
718
747
  str_i(status_line,&i,status_code,3);
719
748
  status_line[i++] = ' ';
@@ -790,15 +819,38 @@ VALUE rhe_write_response(VALUE self, VALUE filenov, VALUE timeoutv, VALUE status
790
819
  v[1].iov_base = date_line;
791
820
  }
792
821
 
822
+ if ( use_chunked ) {
823
+ v[iovcnt].iov_base = "Transfer-Encoding: chunked\r\n";
824
+ v[iovcnt].iov_len = sizeof("Transfer-Encoding: chunked\r\n") - 1;
825
+ iovcnt++;
826
+ }
827
+
793
828
  v[iovcnt].iov_base = "Connection: close\r\n\r\n";
794
829
  v[iovcnt].iov_len = sizeof("Connection: close\r\n\r\n") - 1;
795
830
  iovcnt++;
796
831
 
832
+ ssize_t chb_offset = 0;
797
833
  for ( i=0; i<blen; i++) {
798
834
  val_obj = rb_ary_entry(body, i);
835
+ if ( use_chunked ) {
836
+ v[iovcnt].iov_len = _chunked_header(&chunked_header_buf[chb_offset],RSTRING_LEN(val_obj));
837
+ v[iovcnt].iov_base = &chunked_header_buf[chb_offset];
838
+ chb_offset += v[iovcnt].iov_len;
839
+ iovcnt++;
840
+ }
799
841
  v[iovcnt].iov_base = RSTRING_PTR(val_obj);
800
842
  v[iovcnt].iov_len = RSTRING_LEN(val_obj);
801
843
  iovcnt++;
844
+ if ( use_chunked ) {
845
+ v[iovcnt].iov_base = "\r\n";
846
+ v[iovcnt].iov_len = sizeof("\r\n") -1;
847
+ iovcnt++;
848
+ }
849
+ }
850
+ if ( use_chunked ) {
851
+ v[iovcnt].iov_base = "0\r\n\r\n";
852
+ v[iovcnt].iov_len = sizeof("0\r\n\r\n") - 1;
853
+ iovcnt++;
802
854
  }
803
855
 
804
856
  vec_offset = 0;
@@ -824,7 +876,7 @@ VALUE rhe_write_response(VALUE self, VALUE filenov, VALUE timeoutv, VALUE status
824
876
  }
825
877
  }
826
878
  }
827
-
879
+ xfree(chunked_header_buf);
828
880
  if ( rv < 0 ) {
829
881
  return Qnil;
830
882
  }
@@ -880,5 +932,5 @@ void Init_rhebok()
880
932
  rb_define_module_function(cRhebok, "write_timeout", rhe_write_timeout, 5);
881
933
  rb_define_module_function(cRhebok, "write_all", rhe_write_all, 4);
882
934
  rb_define_module_function(cRhebok, "close_rack", rhe_close, 1);
883
- rb_define_module_function(cRhebok, "write_response", rhe_write_response, 5);
935
+ rb_define_module_function(cRhebok, "write_response", rhe_write_response, 6);
884
936
  }
@@ -11,6 +11,7 @@ require 'io/nonblock'
11
11
  require 'prefork_engine'
12
12
  require 'rhebok'
13
13
  require 'rhebok/config'
14
+ require 'rhebok/buffered'
14
15
 
15
16
  $RACK_HANDLER_RHEBOK_GCTOOL = true
16
17
  begin
@@ -238,16 +239,15 @@ module Rack
238
239
  begin
239
240
  proc_req_count += 1
240
241
  @can_exit = false
242
+ # expect
243
+ if env.key?("HTTP_EXPECT") && env.delete("HTTP_EXPECT") == "100-continue"
244
+ ::Rhebok.write_all(connection, "HTTP/1.1 100 Continue\015\012\015\012", 0, @options[:Timeout])
245
+ end
241
246
  # handle request
247
+ is_chunked = env.key?("HTTP_TRANSFER_ENCODING") && env.delete("HTTP_TRANSFER_ENCODING") == 'chunked'
242
248
  if env.key?("CONTENT_LENGTH") && env["CONTENT_LENGTH"].to_i > 0
243
249
  cl = env["CONTENT_LENGTH"].to_i
244
- if cl > MAX_MEMORY_BUFFER_SIZE
245
- buffer = Tempfile.open('r')
246
- buffer.binmode
247
- buffer.set_encoding('BINARY')
248
- else
249
- buffer = StringIO.new("").set_encoding('BINARY')
250
- end
250
+ buffer = ::Rhebok::Buffered.new(cl,MAX_MEMORY_BUFFER_SIZE)
251
251
  while cl > 0
252
252
  chunk = ""
253
253
  if buf.bytesize > 0
@@ -259,30 +259,74 @@ module Rack
259
259
  return
260
260
  end
261
261
  end
262
- buffer << chunk
262
+ buffer.print(chunk)
263
263
  cl -= chunk.bytesize
264
264
  end
265
- buffer.rewind
266
- env["rack.input"] = buffer
265
+ env["rack.input"] = buffer.rewind
266
+ elsif is_chunked
267
+ buffer = ::Rhebok::Buffered.new(0,MAX_MEMORY_BUFFER_SIZE)
268
+ chunked_buffer = '';
269
+ complete = false
270
+ while !complete
271
+ chunk = ""
272
+ if buf.bytesize > 0
273
+ chunk = buf
274
+ buf = ""
275
+ else
276
+ readed = ::Rhebok.read_timeout(connection, chunk, 16384, 0, @options[:Timeout])
277
+ if readed == nil
278
+ return
279
+ end
280
+ end
281
+ chunked_buffer << chunk
282
+ while chunked_buffer.sub!(/^(([0-9a-fA-F]+).*\015\012)/,"") != nil
283
+ trailer = $1
284
+ chunked_len = $2.hex
285
+ if chunked_len == 0
286
+ complete = true
287
+ break
288
+ elsif chunked_buffer.bytesize < chunked_len + 2
289
+ chunked_buffer = trailer + chunked_buffer
290
+ break
291
+ end
292
+ buffer.print(chunked_buffer.byteslice(0,chunked_len))
293
+ chunked_buffer = chunked_buffer.byteslice(chunked_len,chunked_buffer.bytesize-chunked_len)
294
+ chunked_buffer.sub!(/^\015\012/,"")
295
+ end
296
+ break if complete
297
+ end
298
+ env["CONTENT_LENGTH"] = buffer.size.to_s
299
+ env["rack.input"] = buffer.rewind
267
300
  end
268
301
 
269
302
  status_code, headers, body = app.call(env)
303
+
304
+ use_chunked = env["SERVER_PROTOCOL"] != "HTTP/1.1" ||
305
+ headers.key?("Transfer-Encoding") ||
306
+ headers.key?("Content-Length") ? false : true
307
+
270
308
  if body.instance_of?(Array)
271
- ::Rhebok.write_response(connection, @options[:Timeout], status_code.to_i, headers, body)
309
+ ::Rhebok.write_response(connection, @options[:Timeout], status_code.to_i, headers, body, use_chunked ? 1 : 0)
272
310
  else
273
- ::Rhebok.write_response(connection, @options[:Timeout], status_code.to_i, headers, [])
311
+ ::Rhebok.write_response(connection, @options[:Timeout], status_code.to_i, headers, [], use_chunked ? 1 : 0)
274
312
  body.each do |part|
275
- ret = ::Rhebok.write_all(connection, part, 0, @options[:Timeout])
313
+ ret = nil
314
+ if use_chunked
315
+ ret = ::Rhebok.write_all(connection, part.bytesize.to_s(16) + "\015\012" + part + "\015\012", 0, @options[:Timeout])
316
+ else
317
+ ret = ::Rhebok.write_all(connection, part, 0, @options[:Timeout])
318
+ end
276
319
  if ret == nil
277
320
  break
278
321
  end
279
322
  end #body.each
323
+ ::Rhebok.write_all(connection, "0\015\012\015\012", 0, @options[:Timeout]) if use_chunked
280
324
  body.respond_to?(:close) and body.close
281
325
  end
282
326
  #p [env,status_code,headers,body]
283
327
  ensure
284
- if buffer.instance_of?(Tempfile)
285
- buffer.close!
328
+ if buffer != nil
329
+ buffer.close
286
330
  end
287
331
  ::Rhebok.close_rack(connection)
288
332
  # out of band gc
@@ -0,0 +1,46 @@
1
+ require 'stringio'
2
+ require 'tempfile'
3
+
4
+ class Rhebok
5
+ class Buffered
6
+ def initialize(length=0,memory_max=1048576)
7
+ @length = length
8
+ @memory_max = memory_max
9
+ @size = 0
10
+ if length > memory_max
11
+ @buffer = Tempfile.open('r')
12
+ @buffer.binmode
13
+ @buffer.set_encoding('BINARY')
14
+ else
15
+ @buffer = StringIO.new("").set_encoding('BINARY')
16
+ end
17
+ end
18
+
19
+ def print(buf)
20
+ if @size + buf.bytesize > @memory_max && @buffer.instance_of?(StringIO)
21
+ new_buffer = Tempfile.open('r')
22
+ new_buffer.binmode
23
+ new_buffer.set_encoding('BINARY')
24
+ new_buffer << @buffer.string
25
+ @buffer = new_buffer
26
+ end
27
+ @buffer << buf
28
+ @size += buf.bytesize
29
+ end
30
+
31
+ def size
32
+ @size
33
+ end
34
+
35
+ def rewind
36
+ @buffer.rewind
37
+ @buffer
38
+ end
39
+
40
+ def close
41
+ if @buffer.instance_of?(Tempfile)
42
+ @buffer.close!
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  class Rhebok
2
- VERSION = "0.2.3"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -0,0 +1,46 @@
1
+ require 'rack'
2
+ require File.expand_path('../testrequest', __FILE__)
3
+ require 'timeout'
4
+ require 'socket'
5
+ require 'rack/handler/rhebok'
6
+
7
+ describe Rhebok do
8
+ extend TestRequest::Helpers
9
+ begin
10
+
11
+ @host = '127.0.0.1'
12
+ @port = 9202
13
+ #@app = Rack::Lint.new(TestRequest.new) Lint requires Content-Length or Transfer-Eoncoding
14
+ @app = TestRequest.new
15
+ @pid = fork
16
+ if @pid == nil
17
+ #child
18
+ Rack::Handler::Rhebok.run(@app, :Host=>'127.0.0.1', :Port=>9202, :MaxWorkers=>1,
19
+ :BeforeFork => proc { ENV["TEST_FOO"] = "FOO" },
20
+ :AfterFork => proc { ENV["TEST_BAR"] = "BAR" },
21
+ )
22
+ exit!(true)
23
+ end
24
+
25
+ command = 'curl --stderr - -sv -X POST -T "'+File.expand_path('../testrequest.rb', __FILE__)+'" -H "Content-type: text/plain" --header "Transfer-Encoding: chunked" http://127.0.0.1:9202/remove_length'
26
+ curl_command(command)
27
+ should "with curl" do
28
+ @request["Transfer-Encoding"].should.equal "chunked"
29
+ @request["Expect"].should.equal "100-continue"
30
+ @response.key?("Transfer-Encoding").should.equal false
31
+ @response["CONTENT_LENGTH"].should.equal File.stat(File.expand_path('../testrequest.rb', __FILE__)).size.to_s
32
+ @response["test.postdata"].bytesize.should.equal File.stat(File.expand_path('../testrequest.rb', __FILE__)).size
33
+ @header["Transfer-Encoding"].should.equal "chunked"
34
+ @header["Connection"].should.equal "close"
35
+ @header.key?("HTTP/1.1 100 Continue").should.equal true
36
+ end
37
+
38
+ ensure
39
+ sleep 1
40
+ if @pid != nil
41
+ Process.kill(:TERM, @pid)
42
+ Process.wait()
43
+ end
44
+ end
45
+
46
+ end
@@ -23,7 +23,10 @@ class TestRequest
23
23
  ENV.has_key?("TEST_BAR") and minienv["TEST_BAR"] = ENV["TEST_BAR"]
24
24
  body = minienv.to_yaml
25
25
  size = body.respond_to?(:bytesize) ? body.bytesize : body.size
26
- res_header = {"Content-Type" => "text/yaml", "Content-Length" => size.to_s, "X-Foo" => "Foo\nBar", "X-Bar"=>"Foo\n\nBar", "X-Baz"=>"\nBaz", "X-Fuga"=>"Fuga\n"}
26
+ res_header = {"Content-Length" => size.to_s, "Content-Type" => "text/yaml", "X-Foo" => "Foo\nBar", "X-Bar"=>"Foo\n\nBar", "X-Baz"=>"\nBaz", "X-Fuga"=>"Fuga\n"}
27
+ if env["PATH_INFO"] =~ /remove_length/
28
+ res_header.delete("Content-Length")
29
+ end
27
30
  [status, res_header.merge(test_header), [body]]
28
31
  end
29
32
 
@@ -74,6 +77,36 @@ class TestRequest
74
77
  }
75
78
  }
76
79
  end
80
+
81
+ def curl_command(command)
82
+ body = ""
83
+ header = {}
84
+ request = {}
85
+ open("|" + command) { |f|
86
+ while (line = f.gets)
87
+ next if line.match(/^(\*|}|{) /)
88
+ if line.match(/^> /)
89
+ line.sub!(/^> /,"")
90
+ line.gsub!(/[\r\n]/,"")
91
+ key,val = line.split(/: /,2)
92
+ request[key.to_s] = val.to_s
93
+ next
94
+ end
95
+ if line.match(/^< /)
96
+ line.sub!(/^< /,"")
97
+ line.gsub!(/[\r\n]/,"")
98
+ key,val = line.split(/: /,2)
99
+ header[key.to_s] = val.to_s
100
+ next
101
+ end
102
+ line.sub!(/\* Closing connection #0\n/,"")
103
+ body += line
104
+ end
105
+ }
106
+ @request = request
107
+ @response = YAML.load(body)
108
+ @header = header
109
+ end
77
110
  end
78
111
  end
79
112
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rhebok
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masahiro Nagano
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-08 00:00:00.000000000 Z
11
+ date: 2015-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -109,6 +109,7 @@ files:
109
109
  - ext/rhebok/rhebok.c
110
110
  - lib/rack/handler/rhebok.rb
111
111
  - lib/rhebok.rb
112
+ - lib/rhebok/buffered.rb
112
113
  - lib/rhebok/config.rb
113
114
  - lib/rhebok/version.rb
114
115
  - rhebok.gemspec
@@ -117,6 +118,7 @@ files:
117
118
  - test/spec_03_unix.rb
118
119
  - test/spec_04_hook.rb
119
120
  - test/spec_05_config.rb
121
+ - test/spec_06_curl.rb
120
122
  - test/testconfig.rb
121
123
  - test/testrequest.rb
122
124
  homepage: https://github.com/kazeburo/rhebok
@@ -149,5 +151,6 @@ test_files:
149
151
  - test/spec_03_unix.rb
150
152
  - test/spec_04_hook.rb
151
153
  - test/spec_05_config.rb
154
+ - test/spec_06_curl.rb
152
155
  - test/testconfig.rb
153
156
  - test/testrequest.rb