rhebok 0.2.3 → 0.8.0

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 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