bossan 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: