bossan 0.1.0 → 0.1.1

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.
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.o
19
+ Makefile
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bossan.gemspec
4
+ gemspec
@@ -0,0 +1,50 @@
1
+ # Bossan
2
+
3
+ Bossan is a high performance asynchronous rack web server.
4
+
5
+ ## Requirements
6
+
7
+ Bossan requires Ruby 1.9.x.
8
+
9
+ Bossan supports Linux only.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'bossan'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install bossan
24
+
25
+ ## Usage
26
+
27
+ simple rack app:
28
+
29
+ ``` ruby
30
+ require 'bossan'
31
+
32
+ Bossan.run('127.0.0.1', 8000, proc {|env|
33
+ [
34
+ 200, # Status code
35
+ { # Response headers
36
+ 'Content-Type' => 'text/html',
37
+ 'Content-Length' => '13',
38
+ },
39
+ ['hello, world!'] # Response body
40
+ ]
41
+ })
42
+ ```
43
+
44
+ ## Contributing
45
+
46
+ 1. Fork it
47
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
48
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
49
+ 4. Push to the branch (`git push origin my-new-feature`)
50
+ 5. Create new Pull Request
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ task :default => [:compile, :clean]
5
+
6
+ task :compile do
7
+ Dir.chdir File.expand_path("../ext/bossan", __FILE__)
8
+ system "ruby extconf.rb"
9
+ system "make"
10
+ end
11
+
12
+ task :clean do
13
+ Dir.chdir File.expand_path("../ext/bossan", __FILE__)
14
+ system "rm -f *.o Makefile"
15
+ end
@@ -0,0 +1,16 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/bossan/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "bossan"
6
+ gem.version = Bossan::VERSION
7
+ gem.authors = ["Hiroki Noda"]
8
+ gem.email = ["kubo39@gmail.com"]
9
+ gem.description = %q{high performance asynchronous rack web server}
10
+ gem.summary = gem.description
11
+ gem.homepage = "https://github.com/kubo39/bossan"
12
+
13
+ gem.files = `git ls-files`.split($/)
14
+ gem.extensions = ["ext/bossan/extconf.rb"]
15
+ gem.require_paths = ["lib", "ext"]
16
+ end
@@ -0,0 +1,12 @@
1
+ require 'bossan'
2
+
3
+ Bossan.run('127.0.0.1', 8000, proc {|env|
4
+ [
5
+ 200, # Status code
6
+ { # Response headers
7
+ 'Content-Type' => 'text/html',
8
+ 'Content-Length' => '13',
9
+ },
10
+ ['hello, world!'] # Response body
11
+ ]
12
+ })
@@ -0,0 +1,11 @@
1
+ require 'bossan'
2
+ require 'sinatra/base'
3
+
4
+ class App < Sinatra::Base
5
+ get '/' do
6
+ 'Hello world!'
7
+ end
8
+ end
9
+
10
+ Bossan.run('127.0.0.1', 8000, App)
11
+
@@ -0,0 +1,10 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title Hello, Bossan!
5
+ %body
6
+ #header
7
+ %h1 Hello, Bossan!
8
+ #content
9
+ %p
10
+ Hello, World!
@@ -0,0 +1,11 @@
1
+ require 'bossan'
2
+ require 'sinatra/base'
3
+ require 'haml'
4
+
5
+ class App < Sinatra::Base
6
+ get '/' do
7
+ haml :index
8
+ end
9
+ end
10
+
11
+ Bossan.run('127.0.0.1', 8000, App)
@@ -51,13 +51,13 @@
51
51
  #define CRLF "\r\n"
52
52
  #define DELIM ": "
53
53
 
54
- #define MSG_500 ("HTTP/1.0 500 Internal Server Error\r\nContent-Type: text/html\r\nServer: " SERVER "\r\n\r\n<html><head><title>500 Internal Server Error</head><h1>Internal Server Error</h1><p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p></html>\n")
54
+ #define MSG_500 ("HTTP/1.0 500 Internal Server Error\r\nContent-Type: text/html\r\nServer: " SERVER "\r\n\r\n<html><head><title>500 Internal Server Error</title></head><h1>Internal Server Error</h1><p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p></html>\n")
55
55
 
56
- #define MSG_400 ("HTTP/1.0 400 Bad Request\r\nContent-Type: text/html\r\nServer: " SERVER "\r\n\r\n<html><head><title>Bad Request</head><body><p>Bad Request.</p></body></html>")
56
+ #define MSG_400 ("HTTP/1.0 400 Bad Request\r\nContent-Type: text/html\r\nServer: " SERVER "\r\n\r\n<html><head><title>Bad Request</title></head><body><p>Bad Request.</p></body></html>")
57
57
 
58
- #define MSG_411 ("HTTP/1.0 411 Length Required\r\nContent-Type: text/html\r\nServer: " SERVER "\r\n\r\n<html><head><title>Length Required</head><body><p>Length Required.</p></body></html>")
58
+ #define MSG_411 ("HTTP/1.0 411 Length Required\r\nContent-Type: text/html\r\nServer: " SERVER "\r\n\r\n<html><head><title>Length Required</title></head><body><p>Length Required.</p></body></html>")
59
59
 
60
- #define MSG_413 ("HTTP/1.0 413 Request Entity Too Large\r\nContent-Type: text/html\r\nServer: " SERVER "\r\n\r\n<html><head><title>Request Entity Too Large</head><body><p>Request Entity Too Large.</p></body></html>")
60
+ #define MSG_413 ("HTTP/1.0 413 Request Entity Too Large\r\nContent-Type: text/html\r\nServer: " SERVER "\r\n\r\n<html><head><title>Request Entity Too Large</title></head><body><p>Request Entity Too Large.</p></body></html>")
61
61
 
62
62
  #define SERVER "bossan/0.1"
63
63
 
@@ -94,11 +94,14 @@ static VALUE rb_remote_port;
94
94
  static VALUE rack_input;
95
95
  static VALUE http_connection;
96
96
 
97
+ static VALUE empty_string;
98
+
97
99
  static VALUE http_user_agent;
98
100
 
99
101
  static VALUE i_keys;
100
102
  static VALUE i_call;
101
103
  static VALUE i_new;
104
+ static VALUE i_key;
102
105
 
103
106
  static char *server_name = "127.0.0.1";
104
107
  static short server_port = 8000;
@@ -132,10 +135,6 @@ typedef struct {
132
135
  size_t limit;
133
136
  } buffer;
134
137
 
135
- typedef struct {
136
- VALUE filelike;
137
- } FileWrapperObject;
138
-
139
138
  typedef enum {
140
139
  FIELD,
141
140
  VAL,
@@ -526,7 +525,7 @@ writev_bucket(write_bucket *data)
526
525
  }
527
526
 
528
527
  static inline int
529
- write_headers(client_t *client, char *data, size_t datalen)
528
+ write_headers(client_t *client)
530
529
  {
531
530
  if(client->header_done){
532
531
  return 1;
@@ -566,7 +565,10 @@ write_headers(client_t *client, char *data, size_t datalen)
566
565
  cache_time_update();
567
566
  add_header(bucket, "Date", 4, (char *)http_time, 29);
568
567
  if(client->keep_alive == 1){
569
- add_header(bucket, "Connection", 10, "keep-alive", 10);
568
+ // Keep-Alive
569
+ add_header(bucket, "Connection", 10, "Keep-Alive", 10);
570
+ } else {
571
+ add_header(bucket, "Connection", 10, "close", 5);
570
572
  }
571
573
  }
572
574
 
@@ -581,7 +583,7 @@ write_headers(client_t *client, char *data, size_t datalen)
581
583
  if (TYPE(client->headers)!=T_HASH){
582
584
  goto error;
583
585
  }
584
- VALUE tmp = rb_funcall(client->headers, rb_intern("key?"), 1, object1);
586
+ VALUE tmp = rb_funcall(client->headers, i_key, 1, object1);
585
587
  if (tmp == Qfalse){
586
588
  goto error;
587
589
  }
@@ -621,16 +623,10 @@ write_headers(client_t *client, char *data, size_t datalen)
621
623
  }
622
624
  set2bucket(bucket, CRLF, 2);
623
625
 
624
- if(data){
625
- set2bucket(bucket, data, datalen);
626
- }
627
626
  client->bucket = bucket;
628
627
  int ret = writev_bucket(bucket);
629
628
  if(ret != 0){
630
629
  client->header_done = 1;
631
- if(ret > 0 && data){
632
- client->write_bytes += datalen;
633
- }
634
630
  // clear
635
631
  free_write_bucket(bucket);
636
632
  client->bucket = NULL;
@@ -687,8 +683,6 @@ processs_write(client_t *client)
687
683
  iterator = client->response_iter;
688
684
 
689
685
  if(iterator != NULL){
690
- /* Check_Type(iterator, T_ARRAY); */
691
- /* assert(3 == RARRAY_LEN(iterator)); */
692
686
  if (TYPE(iterator) != T_ARRAY || RARRAY_LEN(iterator) != 3){
693
687
  return -1;
694
688
  }
@@ -698,29 +692,32 @@ processs_write(client_t *client)
698
692
  if (TYPE(arr) != T_ARRAY){
699
693
  return -1;
700
694
  }
701
-
702
- item = rb_ary_entry(arr, 0);
703
695
 
704
- if(TYPE(item) != T_STRING) {
705
- return -1;
706
- }
696
+ int hlen = RARRAY_LEN(arr);
707
697
 
708
- buf = StringValuePtr(item);
709
- buflen = RSTRING_LEN(item);
710
- //write
711
- bucket = new_write_bucket(client->fd, 1);
712
- set2bucket(bucket, buf, buflen);
713
- ret = writev_bucket(bucket);
714
- if(ret <= 0){
715
- return ret;
716
- }
717
- //mark
718
- client->write_bytes += buflen;
719
- //check write_bytes/content_length
720
- if(client->content_length_set){
721
- if(client->content_length <= client->write_bytes){
722
- // all done
723
- //break;
698
+ for(int i=0; i<hlen;i++){
699
+ item = rb_ary_entry(arr, i);
700
+ if(TYPE(item) != T_STRING) {
701
+ return -1;
702
+ }
703
+
704
+ buf = StringValuePtr(item);
705
+ buflen = RSTRING_LEN(item);
706
+ //write
707
+ bucket = new_write_bucket(client->fd, 1);
708
+ set2bucket(bucket, buf, buflen);
709
+ ret = writev_bucket(bucket);
710
+ if(ret <= 0){
711
+ return ret;
712
+ }
713
+ //mark
714
+ client->write_bytes += buflen;
715
+ //check write_bytes/content_length
716
+ if(client->content_length_set){
717
+ if(client->content_length <= client->write_bytes){
718
+ // all done
719
+ break;
720
+ }
724
721
  }
725
722
  }
726
723
  close_response(client);
@@ -755,7 +752,7 @@ static inline int
755
752
  start_response_write(client_t *client)
756
753
  {
757
754
  VALUE iterator;
758
- VALUE arr;
755
+ VALUE dict;
759
756
  VALUE item;
760
757
  char *buf;
761
758
  ssize_t buflen;
@@ -768,24 +765,16 @@ start_response_write(client_t *client)
768
765
  }
769
766
  assert(3 == RARRAY_LEN(iterator));
770
767
 
771
- arr = rb_ary_entry(iterator, 2);
768
+ dict = rb_ary_entry(iterator, 1);
772
769
 
773
- if (TYPE(arr) != T_ARRAY){
770
+ if (TYPE(dict) != T_HASH){
774
771
  return -1;
775
772
  }
776
773
 
777
- VALUE v_body = rb_ary_entry(arr, 0);
778
- Check_Type(v_body, T_STRING);
779
-
780
- if (v_body) {
781
- buf = StringValuePtr(v_body);
782
- buflen = RSTRING_LEN(v_body);
783
774
  #ifdef DEBUG
784
- printf("start_response_write buflen %d \n", buflen);
775
+ printf("start_response_write buflen %d \n", buflen);
785
776
  #endif
786
- return write_headers(client, buf, buflen);
787
- }
788
- return -1;
777
+ return write_headers(client);
789
778
  }
790
779
 
791
780
  inline int
@@ -794,7 +783,7 @@ response_start(client_t *client)
794
783
  int ret;
795
784
  enable_cork(client);
796
785
  if(client->status_code == 304){
797
- return write_headers(client, NULL, 0);
786
+ return write_headers(client);
798
787
  }
799
788
  ret = start_response_write(client);
800
789
  #ifdef DEBUG
@@ -1343,10 +1332,15 @@ init_parser(client_t *cli, const char *name, const short port)
1343
1332
  rb_hash_aset(cli->environ, server_name_key, server_name_val);
1344
1333
  rb_hash_aset(cli->environ, server_port_key, server_port_val);
1345
1334
 
1335
+ // query_string
1336
+ rb_hash_aset(cli->environ, query_string, empty_string);
1337
+
1346
1338
  object = rb_str_new2(cli->remote_addr);
1347
1339
  rb_hash_aset(cli->environ, rb_remote_addr, object);
1348
1340
 
1349
- object = INT2NUM(cli->remote_port);
1341
+ char r_port[6];
1342
+ sprintf(r_port, "%d", cli->remote_port);
1343
+ object = rb_str_new2(r_port);
1350
1344
  rb_hash_aset(cli->environ, rb_remote_port, object);
1351
1345
 
1352
1346
  http_parser_init(cli->http, HTTP_REQUEST);
@@ -1371,44 +1365,46 @@ inline void
1371
1365
  setup_static_env(char *name, int port)
1372
1366
  {
1373
1367
  version_val = rb_obj_freeze(rb_ary_new3(2, INT2FIX(1), INT2FIX(1)));
1374
- version_key = rb_str_new2("rack.version");
1368
+ version_key = rb_obj_freeze(rb_str_new2("rack.version"));
1375
1369
 
1376
- scheme_val = rb_str_new2("http");
1377
- scheme_key = rb_str_new2("rack.url_scheme");
1370
+ scheme_val = rb_obj_freeze(rb_str_new2("http"));
1371
+ scheme_key = rb_obj_freeze(rb_str_new2("rack.url_scheme"));
1378
1372
 
1379
1373
  errors_val = rb_stderr;
1380
- errors_key = rb_str_new2("rack.errors");
1374
+ errors_key = rb_obj_freeze(rb_str_new2("rack.errors"));
1381
1375
 
1382
1376
  multithread_val = Qfalse;
1383
- multithread_key = rb_str_new2("rack.multithread");
1377
+ multithread_key = rb_obj_freeze(rb_str_new2("rack.multithread"));
1384
1378
 
1385
1379
  multiprocess_val = Qfalse; /* or Qtrue? I have no clue.. */
1386
- multiprocess_key = rb_str_new2("rack.multiprocess");
1380
+ multiprocess_key = rb_obj_freeze(rb_str_new2("rack.multiprocess"));
1387
1381
 
1388
1382
  run_once_val = Qfalse;
1389
- run_once_key = rb_str_new2("rack.run_once");
1383
+ run_once_key = rb_obj_freeze(rb_str_new2("rack.run_once"));
1390
1384
 
1391
- script_val = rb_str_new2("");
1392
- script_key = rb_str_new2("SCRIPT_NAME");
1385
+ script_val = empty_string;
1386
+ script_key = rb_obj_freeze(rb_str_new2("SCRIPT_NAME"));
1393
1387
 
1394
- server_name_val = rb_str_new2(name);
1395
- server_name_key = rb_str_new2("SERVER_NAME");
1388
+ server_name_val = rb_obj_freeze(rb_str_new2(name));
1389
+ server_name_key = rb_obj_freeze(rb_str_new2("SERVER_NAME"));
1396
1390
 
1397
- server_port_val = INT2NUM(port);
1398
- server_port_key = rb_str_new2("SERVER_PORT");
1391
+ char vport[6];
1392
+ sprintf(vport, "%d", port);
1393
+ server_port_val = rb_obj_freeze(rb_str_new2(vport));
1394
+ server_port_key = rb_obj_freeze(rb_str_new2("SERVER_PORT"));
1399
1395
 
1400
- server_protocol = rb_str_new2("SERVER_PROTOCOL");
1401
- path_info = rb_str_new2("PATH_INFO");
1402
- request_uri = rb_str_new2("REQUEST_URI");
1403
- query_string = rb_str_new2("QUERY_STRING");
1404
- http_fragment = rb_str_new2("HTTP_FRAGMENT");
1405
- request_method = rb_str_new2("REQUEST_METHOD");
1406
- rb_remote_addr = rb_str_new2("REMOTE_ADDR");
1407
- rb_remote_port = rb_str_new2("REMOTE_PORT");
1408
- rack_input = rb_str_new2("rack.input");
1409
- http_connection = rb_str_new2("HTTP_CONNECTION");
1396
+ server_protocol = rb_obj_freeze(rb_str_new2("SERVER_PROTOCOL"));
1397
+ path_info = rb_obj_freeze(rb_str_new2("PATH_INFO"));
1398
+ request_uri = rb_obj_freeze(rb_str_new2("REQUEST_URI"));
1399
+ query_string = rb_obj_freeze(rb_str_new2("QUERY_STRING"));
1400
+ http_fragment = rb_obj_freeze(rb_str_new2("HTTP_FRAGMENT"));
1401
+ request_method = rb_obj_freeze(rb_str_new2("REQUEST_METHOD"));
1402
+ rb_remote_addr = rb_obj_freeze(rb_str_new2("REMOTE_ADDR"));
1403
+ rb_remote_port = rb_obj_freeze(rb_str_new2("REMOTE_PORT"));
1404
+ rack_input = rb_obj_freeze(rb_str_new2("rack.input"));
1405
+ http_connection = rb_obj_freeze(rb_str_new2("HTTP_CONNECTION"));
1410
1406
 
1411
- http_user_agent = rb_str_new2("HTTP_USER_AGENT");
1407
+ http_user_agent = rb_obj_freeze(rb_str_new2("HTTP_USER_AGENT"));
1412
1408
  }
1413
1409
 
1414
1410
  static inline int
@@ -1724,7 +1720,6 @@ r_callback(picoev_loop* loop, int fd, int events, void* cb_arg)
1724
1720
  }
1725
1721
  }
1726
1722
 
1727
- // TODO: use accept4 in linux
1728
1723
  static void
1729
1724
  accept_callback(picoev_loop* loop, int fd, int events, void* cb_arg)
1730
1725
  {
@@ -1737,8 +1732,7 @@ accept_callback(picoev_loop* loop, int fd, int events, void* cb_arg)
1737
1732
  return;
1738
1733
  }else if ((events & PICOEV_READ) != 0) {
1739
1734
  socklen_t client_len = sizeof(client_addr);
1740
- client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len);
1741
-
1735
+ client_fd = accept4(fd, (struct sockaddr *)&client_addr, &client_len, SOCK_NONBLOCK | SOCK_CLOEXEC);
1742
1736
  if (client_fd != -1) {
1743
1737
  #ifdef DEBUG
1744
1738
  printf("accept fd %d \n", client_fd);
@@ -1878,7 +1872,7 @@ bossan_access_log(VALUE self, VALUE args)
1878
1872
  return Qnil;
1879
1873
  }
1880
1874
 
1881
- // Bossan.run('127.0.0.1', 8000) do |env|
1875
+ // Bossan.run('127.0.0.1', 8000, proc do |env|
1882
1876
  // ...
1883
1877
  // end
1884
1878
  static VALUE
@@ -1987,15 +1981,20 @@ Init_bossan_ext(void)
1987
1981
 
1988
1982
  rb_gc_register_address(&http_user_agent);
1989
1983
 
1984
+ empty_string = rb_obj_freeze(rb_str_new2(""));
1985
+ rb_gc_register_address(&empty_string);
1986
+
1990
1987
  rb_gc_register_address(&i_keys);
1991
1988
  rb_gc_register_address(&i_call);
1992
1989
  rb_gc_register_address(&i_new);
1990
+ rb_gc_register_address(&i_key);
1993
1991
 
1994
1992
  rb_gc_register_address(&rack_app); //rack app
1995
1993
 
1996
1994
  i_new = rb_intern("new");
1997
1995
  i_call = rb_intern("call");
1998
1996
  i_keys = rb_intern("keys");
1997
+ i_key = rb_intern("key?");
1999
1998
 
2000
1999
  server = rb_define_module("Bossan");
2001
2000
  rb_gc_register_address(&server);
@@ -1,5 +1,7 @@
1
1
  require_relative "bossan/version"
2
2
  require_relative "bossan/bossan_ext"
3
+ require_relative "rack/handler/bossan"
3
4
 
4
- module Bossan
5
+ def run(host='127.0.0.1', port=8000, app)
6
+ Bossan.run(host, port, app)
5
7
  end
@@ -1,3 +1,3 @@
1
1
  module Bossan
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -0,0 +1,20 @@
1
+ require 'rack/handler'
2
+ require 'bossan'
3
+
4
+ module Rack
5
+ module Handler
6
+ module Bossan
7
+ DEFAULT_OPTIONS = {
8
+ "Host" => '0.0.0.0',
9
+ "Port" => 8080,
10
+ # :Verbose => false
11
+ }
12
+
13
+ def self.run(app, options = {})
14
+ options = DEFAULT_OPTIONS.merge(options)
15
+ ::Bossan.run(options["Host"], options["Port"], app)
16
+ end
17
+ end
18
+ register :bossan, Bossan
19
+ end
20
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bossan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-21 00:00:00.000000000 Z
12
+ date: 2012-11-30 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: high performance asynchronous rack web server
15
15
  email:
@@ -19,17 +19,27 @@ extensions:
19
19
  - ext/bossan/extconf.rb
20
20
  extra_rdoc_files: []
21
21
  files:
22
+ - .gitignore
23
+ - Gemfile
22
24
  - LICENSE.txt
23
- - lib/bossan/version.rb
24
- - lib/bossan.rb
25
+ - README.md
26
+ - Rakefile
27
+ - bossan.gemspec
28
+ - examples/hello.rb
29
+ - examples/sinatra_app.rb
30
+ - examples/views/index.haml
31
+ - examples/views_sample.rb
32
+ - ext/bossan/bossan_ext.c
33
+ - ext/bossan/extconf.rb
34
+ - ext/bossan/http_parser.c
25
35
  - ext/bossan/http_parser.h
36
+ - ext/bossan/picoev.h
26
37
  - ext/bossan/picoev_epoll.c
27
38
  - ext/bossan/time_cache.c
28
- - ext/bossan/picoev.h
29
39
  - ext/bossan/time_cache.h
30
- - ext/bossan/bossan_ext.c
31
- - ext/bossan/extconf.rb
32
- - ext/bossan/http_parser.c
40
+ - lib/bossan.rb
41
+ - lib/bossan/version.rb
42
+ - lib/rack/handler/bossan.rb
33
43
  homepage: https://github.com/kubo39/bossan
34
44
  licenses: []
35
45
  post_install_message: