diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1b5e6a8..87af470e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,20 +28,21 @@ jobs: - ubuntu ruby: - - 2.5 - 2.6 - 2.7 - 3.0 - 3.1 - 3.2 - 3.3 + - 3.4 - gemfile: ["Gemfile"] + gemfile: + - gems/rack-v2.rb include: - experimental: false os: macos - ruby: 3.3 + ruby: 3.4 gemfile: gems/rack-v2.rb - experimental: true os: ubuntu @@ -53,13 +54,16 @@ jobs: gemfile: gems/rack-v1.rb - experimental: true os: ubuntu - ruby: 3.2 + ruby: 3.4 gemfile: gems/rack-v2.rb - # enable when rack v3 is supported - # - experimental: true - # os: ubuntu - # ruby: 3.2 - # env: BUNDLE_GEMFILE=gems/rack-head.rb + - experimental: true + os: ubuntu + ruby: 3.4 + gemfile: gems/rack-v3.rb + - experimental: true + os: ubuntu + ruby: 3.4 + gemfile: gems/rack-head.rb steps: - uses: actions/checkout@v3 diff --git a/Gemfile b/Gemfile index 7d8e9e86..0711ed08 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,8 @@ gemspec group :development do gem "rake-compiler" + gem "rdoc" + gem "benchmark" end group :test do diff --git a/benchmark/benchmarker.rb b/benchmark/benchmarker.rb index 17378245..7929ba42 100644 --- a/benchmark/benchmarker.rb +++ b/benchmark/benchmarker.rb @@ -37,7 +37,7 @@ def start_server(handler_name) end app = proc do |env| - [200, {'Content-Type' => 'text/html', 'Content-Length' => '11'}, ['hello world']] + [200, {'content-type' => 'text/html', 'content-length' => '11'}, ['hello world']] end handler = Rack::Handler.const_get(handler_name) diff --git a/example/adapter.rb b/example/adapter.rb index 77375fdf..d583159e 100644 --- a/example/adapter.rb +++ b/example/adapter.rb @@ -8,7 +8,7 @@ def call(env) body = ["hello!"] [ 200, - { 'Content-Type' => 'text/plain' }, + { 'content-type' => 'text/plain' }, body ] end @@ -20,13 +20,13 @@ def call(env) run SimpleAdapter.new end map '/files' do - run Rack::File.new('.') + run Rack::Files.new('.') end end # You could also start the server like this: # # app = Rack::URLMap.new('/test' => SimpleAdapter.new, -# '/files' => Rack::File.new('.')) +# '/files' => Rack::Files.new('.')) # Thin::Server.start('0.0.0.0', 3000, app) # diff --git a/example/async_app.ru b/example/async_app.ru index 4e9be17b..8c5a2d8b 100755 --- a/example/async_app.ru +++ b/example/async_app.ru @@ -87,7 +87,7 @@ class AsyncApp body = DeferrableBody.new # Get the headers out there asap, let the client know we're alive... - EventMachine::next_tick { env['async.callback'].call [200, {'Content-Type' => 'text/plain'}, body] } + EventMachine::next_tick { env['async.callback'].call [200, {'content-type' => 'text/plain'}, body] } # Semi-emulate a long db request, instead of a timer, in reality we'd be # waiting for the response data. Whilst this happens, other connections diff --git a/example/async_chat.ru b/example/async_chat.ru index e9e3ddf8..55d12040 100755 --- a/example/async_chat.ru +++ b/example/async_chat.ru @@ -118,7 +118,7 @@ class Chat send_message = function(message_box) { xhr = XHR(); xhr.open("POST", "/", true); - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("X_REQUESTED_WITH", "XMLHttpRequest"); xhr.send("message="+escape(message_box.value)); scroll(); @@ -161,7 +161,7 @@ class Chat body.callback { delete_user user_id } EventMachine::next_tick do - renderer.call [200, {'Content-Type' => 'text/html'}, body] + renderer.call [200, {'content-type' => 'text/html'}, body] end end diff --git a/example/async_tailer.ru b/example/async_tailer.ru index 467f0df6..eda6be9d 100755 --- a/example/async_tailer.ru +++ b/example/async_tailer.ru @@ -70,7 +70,7 @@ class AsyncTailer EventMachine::next_tick do - env['async.callback'].call [200, {'Content-Type' => 'text/html'}, body] + env['async.callback'].call [200, {'content-type' => 'text/html'}, body] body.call ["
"]
diff --git a/example/config.ru b/example/config.ru
index bc655f85..8a530fd2 100644
--- a/example/config.ru
+++ b/example/config.ru
@@ -14,7 +14,7 @@ app = proc do |env|
[
200, # Status code
- { 'Content-Type' => 'text/html' }, # Reponse headers
+ { 'content-type' => 'text/html' }, # Reponse headers
body # Body of the response
]
end
diff --git a/ext/thin_parser/common.rl b/ext/thin_parser/common.rl
index 08877036..ad9ba529 100644
--- a/ext/thin_parser/common.rl
+++ b/ext/thin_parser/common.rl
@@ -43,7 +43,7 @@
Method = ( upper | digit | safe ){1,20} >mark %request_method;
http_number = ( digit+ "." digit+ ) ;
- HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ;
+ HTTP_Version = ( "HTTP/" http_number ) >mark %request_http_version ;
Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ;
field_name = ( token -- ":" )+ >start_field %write_field;
diff --git a/ext/thin_parser/parser.c b/ext/thin_parser/parser.c
index 42832de4..7005d083 100644
--- a/ext/thin_parser/parser.c
+++ b/ext/thin_parser/parser.c
@@ -295,8 +295,8 @@ case 13:
tr17:
#line 59 "parser.rl"
{
- if (parser->http_version != NULL) {
- parser->http_version(parser->data, PTR_TO(mark), LEN(mark, p));
+ if (parser->request_http_version != NULL) {
+ parser->request_http_version(parser->data, PTR_TO(mark), LEN(mark, p));
}
}
goto st14;
diff --git a/ext/thin_parser/parser.h b/ext/thin_parser/parser.h
index f80d40f6..6a587e3a 100644
--- a/ext/thin_parser/parser.h
+++ b/ext/thin_parser/parser.h
@@ -33,7 +33,7 @@ typedef struct http_parser {
element_cb fragment;
element_cb request_path;
element_cb query_string;
- element_cb http_version;
+ element_cb request_http_version;
element_cb header_done;
} http_parser;
diff --git a/ext/thin_parser/parser.rl b/ext/thin_parser/parser.rl
index 05c8eb62..eb0db63c 100644
--- a/ext/thin_parser/parser.rl
+++ b/ext/thin_parser/parser.rl
@@ -56,9 +56,9 @@
}
}
- action http_version {
- if (parser->http_version != NULL) {
- parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
+ action request_http_version {
+ if (parser->request_http_version != NULL) {
+ parser->request_http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
}
}
diff --git a/ext/thin_parser/thin.c b/ext/thin_parser/thin.c
index b3e777cc..082d7bd5 100644
--- a/ext/thin_parser/thin.c
+++ b/ext/thin_parser/thin.c
@@ -21,7 +21,7 @@ static VALUE global_request_method;
static VALUE global_request_uri;
static VALUE global_fragment;
static VALUE global_query_string;
-static VALUE global_http_version;
+static VALUE global_request_http_version;
static VALUE global_content_length;
static VALUE global_http_content_length;
static VALUE global_request_path;
@@ -150,11 +150,12 @@ static void query_string(void *data, const char *at, size_t length)
rb_hash_aset(req, global_query_string, val);
}
-static void http_version(void *data, const char *at, size_t length)
+static void request_http_version(void *data, const char *at, size_t length)
{
VALUE req = (VALUE)data;
VALUE val = rb_str_new(at, length);
- rb_hash_aset(req, global_http_version, val);
+ rb_hash_aset(req, global_request_http_version, val);
+ rb_hash_aset(req, global_server_protocol, val);
}
/** Finalizes the request header to have a bunch of stuff that's
@@ -211,7 +212,6 @@ static void header_done(void *data, const char *at, size_t length)
}
/* set some constants */
- rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
rb_hash_aset(req, global_url_scheme, global_url_scheme_value);
rb_hash_aset(req, global_script_name, global_empty);
}
@@ -237,7 +237,7 @@ VALUE Thin_HttpParser_alloc(VALUE klass)
hp->fragment = fragment;
hp->request_path = request_path;
hp->query_string = query_string;
- hp->http_version = http_version;
+ hp->request_http_version = request_http_version;
hp->header_done = header_done;
thin_http_parser_init(hp);
@@ -401,7 +401,7 @@ void Init_thin_parser()
DEF_GLOBAL(request_uri, "REQUEST_URI");
DEF_GLOBAL(fragment, "FRAGMENT");
DEF_GLOBAL(query_string, "QUERY_STRING");
- DEF_GLOBAL(http_version, "HTTP_VERSION");
+ DEF_GLOBAL(request_http_version, "thin.request_http_version");
DEF_GLOBAL(request_path, "REQUEST_PATH");
DEF_GLOBAL(content_length, "CONTENT_LENGTH");
DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
diff --git a/gems/rack-head.rb b/gems/rack-head.rb
index e79a8585..d3001cbe 100644
--- a/gems/rack-head.rb
+++ b/gems/rack-head.rb
@@ -1,16 +1,5 @@
# frozen_string_literal: true
-source 'https://rubygems.org'
-
-gemspec path: "../"
+eval_gemfile "../Gemfile"
gem 'rack', github: 'rack/rack'
-
-group :development do
- gem "rake-compiler"
-end
-
-group :test do
- gem "rake", ">= 12.3.3"
- gem "rspec", "~> 3.5"
-end
diff --git a/gems/rack-v1.rb b/gems/rack-v1.rb
index a77be550..e5be601e 100644
--- a/gems/rack-v1.rb
+++ b/gems/rack-v1.rb
@@ -1,17 +1,5 @@
# frozen_string_literal: true
-source 'https://rubygems.org'
-
-gemspec path: "../"
+eval_gemfile "../Gemfile"
gem 'rack', '~> 1.0'
-
-group :development do
- gem "rake-compiler"
-end
-
-group :test do
- gem "rake", ">= 12.3.3"
- gem "rspec", "~> 3.5"
-end
-
diff --git a/gems/rack-v2.rb b/gems/rack-v2.rb
index 498d17c4..8dfecd29 100644
--- a/gems/rack-v2.rb
+++ b/gems/rack-v2.rb
@@ -1,16 +1,5 @@
# frozen_string_literal: true
-source 'https://rubygems.org'
+eval_gemfile "../Gemfile"
-gemspec path: "../"
-
-gem 'rack', '~> 2.0'
-
-group :development do
- gem "rake-compiler"
-end
-
-group :test do
- gem "rake", ">= 12.3.3"
- gem "rspec", "~> 3.5"
-end
+gem 'rack', "~> 2.0"
diff --git a/gems/rack-v3.rb b/gems/rack-v3.rb
new file mode 100644
index 00000000..01923819
--- /dev/null
+++ b/gems/rack-v3.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+eval_gemfile "../Gemfile"
+
+gem 'rack', "~> 3.0"
diff --git a/lib/rack/adapter/loader.rb b/lib/rack/adapter/loader.rb
index 4574f284..c05968ed 100644
--- a/lib/rack/adapter/loader.rb
+++ b/lib/rack/adapter/loader.rb
@@ -15,6 +15,13 @@ class AdapterNotFound < RuntimeError; end
[:file, nil]
]
+ # Rack v1 compatibility...
+ unless const_defined?(:Files)
+ require 'rack/file'
+
+ Files = File
+ end
+
module Adapter
# Guess which adapter to use based on the directory structure
# or file content.
@@ -64,7 +71,7 @@ def self.for(name, options={})
return Merb::Rack::Application.new
when :file
- return Rack::File.new(options[:chdir])
+ return Rack::Files.new(options[:chdir])
else
raise AdapterNotFound, "Adapter not found: #{name}"
diff --git a/lib/rack/adapter/rails.rb b/lib/rack/adapter/rails.rb
index 0da1b98b..31113e20 100644
--- a/lib/rack/adapter/rails.rb
+++ b/lib/rack/adapter/rails.rb
@@ -23,7 +23,7 @@ def initialize(options = {})
load_application
@rails_app = self.class.rack_based? ? ActionController::Dispatcher.new : CgiApp.new
- @file_app = Rack::File.new(::File.join(RAILS_ROOT, "public"))
+ @file_app = Rack::Files.new(::File.join(RAILS_ROOT, "public"))
end
def load_application
diff --git a/lib/thin/request.rb b/lib/thin/request.rb
index b5efe24b..4b3fe4df 100644
--- a/lib/thin/request.rb
+++ b/lib/thin/request.rb
@@ -22,7 +22,7 @@ class Request
SERVER_NAME = 'SERVER_NAME'.freeze
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
LOCALHOST = 'localhost'.freeze
- HTTP_VERSION = 'HTTP_VERSION'.freeze
+ REQUEST_HTTP_VERSION = 'thin.request_http_version'.freeze
HTTP_1_0 = 'HTTP/1.0'.freeze
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
@@ -114,7 +114,7 @@ def persistent?
# Clients and servers SHOULD NOT assume that a persistent connection
# is maintained for HTTP versions less than 1.1 unless it is explicitly
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
- if @env[HTTP_VERSION] == HTTP_1_0
+ if @env[REQUEST_HTTP_VERSION] == HTTP_1_0
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP
# HTTP/1.1 client intends to maintain a persistent connection unless
diff --git a/lib/thin/response.rb b/lib/thin/response.rb
index 7a5ce93c..f5ff806e 100644
--- a/lib/thin/response.rb
+++ b/lib/thin/response.rb
@@ -1,18 +1,64 @@
module Thin
# A response sent to the client.
class Response
- CONNECTION = 'Connection'.freeze
+ class Stream
+ def initialize(writer)
+ @read_closed = true
+ @write_closed = false
+ @writer = writer
+ end
+
+ def read(length = nil, outbuf = nil)
+ raise ::IOError, 'not opened for reading' if @read_closed
+ end
+
+ def write(chunk)
+ raise ::IOError, 'not opened for writing' if @write_closed
+
+ @writer.call(chunk)
+ end
+
+ alias :<< :write
+
+ def close
+ @read_closed = @write_closed = true
+
+ nil
+ end
+
+ def closed?
+ @read_closed && @write_closed
+ end
+
+ def close_read
+ @read_closed = true
+
+ nil
+ end
+
+ def close_write
+ @write_closed = true
+
+ nil
+ end
+
+ def flush
+ self
+ end
+ end
+
+ CONNECTION = 'connection'.freeze
CLOSE = 'close'.freeze
KEEP_ALIVE = 'keep-alive'.freeze
- SERVER = 'Server'.freeze
- CONTENT_LENGTH = 'Content-Length'.freeze
+ SERVER = 'server'.freeze
+ CONTENT_LENGTH = 'content-length'.freeze
PERSISTENT_STATUSES = [100, 101].freeze
#Error Responses
- ERROR = [500, {'Content-Type' => 'text/plain'}, ['Internal server error']].freeze
- PERSISTENT_ERROR = [500, {'Content-Type' => 'text/plain', 'Connection' => 'keep-alive', 'Content-Length' => "21"}, ['Internal server error']].freeze
- BAD_REQUEST = [400, {'Content-Type' => 'text/plain'}, ['Bad Request']].freeze
+ ERROR = [500, {'content-type' => 'text/plain'}, ['Internal server error']].freeze
+ PERSISTENT_ERROR = [500, {'content-type' => 'text/plain', 'connection' => 'keep-alive', 'content-length' => "21"}, ['Internal server error']].freeze
+ BAD_REQUEST = [400, {'content-type' => 'text/plain'}, ['Bad Request']].freeze
# Status code
attr_accessor :status
@@ -86,14 +132,16 @@ def close
# Yields each chunk of the response.
# To control the size of each chunk
# define your own +each+ method on +body+.
- def each
+ def each(&block)
yield head
unless @skip_body
if @body.is_a?(String)
yield @body
- else
+ elsif @body.respond_to?(:each)
@body.each { |chunk| yield chunk }
+ else
+ @body.call(Stream.new(block))
end
end
end
@@ -104,7 +152,7 @@ def persistent!
end
# Persistent connection must be requested as keep-alive
- # from the server and have a Content-Length, or the response
+ # from the server and have a content-length, or the response
# status must require that the connection remain open.
def persistent?
(@persistent && @headers.has_key?(CONTENT_LENGTH)) || PERSISTENT_STATUSES.include?(@status)
diff --git a/lib/thin/stats.rb b/lib/thin/stats.rb
index 57252363..385b0887 100644
--- a/lib/thin/stats.rb
+++ b/lib/thin/stats.rb
@@ -43,7 +43,7 @@ def serve(env)
[
200,
- { 'Content-Type' => 'text/html' },
+ { 'content-type' => 'text/html' },
[body]
]
end
diff --git a/script/profile b/script/profile
index 87045277..6bc81fdb 100755
--- a/script/profile
+++ b/script/profile
@@ -9,7 +9,7 @@ require 'thin'
class Adapter
def call(env)
- [200, {'Content-Type' => 'text/html', 'Content-Length' => '11'}, ['hello world']]
+ [200, {'content-type' => 'text/html', 'content-length' => '11'}, ['hello world']]
end
end
diff --git a/site/rdoc.rb b/site/rdoc.rb
index aca0e339..9652805f 100644
--- a/site/rdoc.rb
+++ b/site/rdoc.rb
@@ -169,7 +169,7 @@ module Page
%realtitle%
ENDIF:title
-
+