From c7bd63af273c6285965632cea8cf187516a7c223 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Tue, 2 Jan 2024 13:21:37 +0100 Subject: [PATCH 01/10] Bump Rack maximum version --- thin.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thin.gemspec b/thin.gemspec index d5faf34e..74bfbf2b 100644 --- a/thin.gemspec +++ b/thin.gemspec @@ -22,7 +22,7 @@ Thin::GemSpec ||= Gem::Specification.new do |s| s.required_ruby_version = '>= 1.8.5' - s.add_dependency 'rack', '>= 1', '< 3' + s.add_dependency 'rack', '>= 1', '< 4' s.add_dependency 'eventmachine', '~> 1.0', '>= 1.0.4' s.add_dependency 'daemons', '~> 1.0', '>= 1.0.9' unless Thin.win? From 638821924d6b39212135301790b709f9fd7b9890 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Tue, 2 Jan 2024 13:22:09 +0100 Subject: [PATCH 02/10] Use lowercase header in `validate_with_lint` helper Casue: Response header keys can no longer include uppercase characters. --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3819f04a..d92ae73a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -66,7 +66,7 @@ def failure_message_when_negated class ValidateWithLint def matches?(request) @request = request - Rack::Lint.new(proc{[200, {'Content-Type' => 'text/html', 'Content-Length' => '0'}, []]}).call(@request.env) + Rack::Lint.new(proc{[200, {'content-type' => 'text/html', 'content-length' => '0'}, []]}).call(@request.env) true rescue Rack::Lint::LintError => e @message = e.message From 1c655dd2be3c04ee7d211666f9326ba386d00e4d Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Tue, 2 Jan 2024 14:04:24 +0100 Subject: [PATCH 03/10] Remove HTTP_VERSION This spec failed: Thin::Request parser should not fuck up on stupid fucked IE6 headers with: should validate with Rack Lint: env[HTTP_VERSION] does not equal env[SERVER_PROTOCOL] where: "HTTP_VERSION"=>"HTTP/1.0", "SERVER_PROTOCOL"=>"HTTP/1.1", Indeed: > Rack::HTTP_VERSION has been removed and the HTTP_VERSION env setting is no longer set in the CGI and Webrick handlers See: - https://github.com/rack/rack/issues/970 - https://github.com/rack/rack/pull/969 - https://github.com/rack/rack/pull/1691 The version is necessary to negotiate `persistent?`, so here we punt on a further refactoring to find a good way to get the request HTTP version from the first line, instead shoving it in a Thin-specific `thin.request_http_header`. --- ext/thin_parser/common.rl | 2 +- ext/thin_parser/parser.c | 4 ++-- ext/thin_parser/parser.h | 2 +- ext/thin_parser/parser.rl | 6 +++--- ext/thin_parser/thin.c | 10 +++++----- lib/thin/request.rb | 4 ++-- spec/request/parser_spec.rb | 5 ++--- spec/request/persistent_spec.rb | 12 ++++++------ 8 files changed, 22 insertions(+), 23 deletions(-) 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..98a9fdfa 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,11 @@ 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); } /** Finalizes the request header to have a bunch of stuff that's @@ -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/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/spec/request/parser_spec.rb b/spec/request/parser_spec.rb index 2e11e2a0..7be5cac6 100644 --- a/spec/request/parser_spec.rb +++ b/spec/request/parser_spec.rb @@ -12,7 +12,7 @@ request = R("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n") expect(request.env['SERVER_PROTOCOL']).to eq('HTTP/1.1') expect(request.env['REQUEST_PATH']).to eq('/') - expect(request.env['HTTP_VERSION']).to eq('HTTP/1.1') + expect(request.env['thin.request_http_version']).to eq('HTTP/1.1') expect(request.env['REQUEST_URI']).to eq('/') expect(request.env['GATEWAY_INTERFACE']).to eq('CGI/1.2') expect(request.env['REQUEST_METHOD']).to eq('GET') @@ -195,7 +195,6 @@ expect(request.env['PATH_INFO']).to eq('/hi') expect(request.env['REQUEST_PATH']).to eq('/hi') expect(request.env['REQUEST_URI']).to eq('/hi?qs') - expect(request.env['HTTP_VERSION']).to eq('HTTP/1.1') expect(request.env['REQUEST_METHOD']).to eq('GET') expect(request.env["rack.url_scheme"]).to eq('http') expect(request.env['FRAGMENT'].to_s).to eq("f") @@ -276,4 +275,4 @@ expect(request.env['REQUEST_URI']).to eq("/H%uFFFDhnchenbrustfilet") end -end \ No newline at end of file +end diff --git a/spec/request/persistent_spec.rb b/spec/request/persistent_spec.rb index 2419c082..f86a46d4 100644 --- a/spec/request/persistent_spec.rb +++ b/spec/request/persistent_spec.rb @@ -6,30 +6,30 @@ end it "should not assume that a persistent connection is maintained for HTTP version 1.0" do - @request.env['HTTP_VERSION'] = 'HTTP/1.0' + @request.env['thin.request_http_version'] = 'HTTP/1.0' expect(@request).not_to be_persistent end it "should assume that a persistent connection is maintained for HTTP version 1.0 when specified" do - @request.env['HTTP_VERSION'] = 'HTTP/1.0' + @request.env['thin.request_http_version'] = 'HTTP/1.0' @request.env['HTTP_CONNECTION'] = 'Keep-Alive' expect(@request).to be_persistent end it "should maintain a persistent connection for HTTP/1.1 client" do - @request.env['HTTP_VERSION'] = 'HTTP/1.1' + @request.env['thin.request_http_version'] = 'HTTP/1.1' @request.env['HTTP_CONNECTION'] = 'Keep-Alive' expect(@request).to be_persistent end it "should maintain a persistent connection for HTTP/1.1 client by default" do - @request.env['HTTP_VERSION'] = 'HTTP/1.1' + @request.env['thin.request_http_version'] = 'HTTP/1.1' expect(@request).to be_persistent end it "should not maintain a persistent connection for HTTP/1.1 client when Connection header include close" do - @request.env['HTTP_VERSION'] = 'HTTP/1.1' + @request.env['thin.request_http_version'] = 'HTTP/1.1' @request.env['HTTP_CONNECTION'] = 'close' expect(@request).not_to be_persistent end -end \ No newline at end of file +end From 142594d2cb3df6038059dc12c770f29746f49267 Mon Sep 17 00:00:00 2001 From: Loic Nageleisen Date: Tue, 2 Jan 2024 16:38:58 +0100 Subject: [PATCH 04/10] Support streaming response via #call Follows the Rack 3 spec: - https://github.com/rack/rack/blob/64ad26e3381da2ce1853638a2c4ea241c2ad3729/SPEC.rdoc#label-The+Body - https://github.com/rack/rack/blob/64ad26e3381da2ce1853638a2c4ea241c2ad3729/SPEC.rdoc#label-Streaming+Body Implemented using a wrapper object that pretends to be IO and makes it behave like it used to for enumerable. Does not support reading, hence no WebSockets. This needs some bidirectional access to receiving data and it's a bit complex to address with EventMachine + Thin's Connection#receive_data. --- lib/thin/response.rb | 52 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/lib/thin/response.rb b/lib/thin/response.rb index 7a5ce93c..4fde1c3a 100644 --- a/lib/thin/response.rb +++ b/lib/thin/response.rb @@ -1,6 +1,52 @@ module Thin # A response sent to the client. class Response + 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 @@ -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 From 259493af4fc92b627cafc6712c25989a4b8e7464 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 5 Dec 2022 20:39:19 +1300 Subject: [PATCH 05/10] Fix usage of `Rack::File`. --- example/adapter.rb | 4 ++-- lib/rack/adapter/loader.rb | 2 +- lib/rack/adapter/rails.rb | 2 +- spec/rack/loader_spec.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/adapter.rb b/example/adapter.rb index 77375fdf..206c150c 100644 --- a/example/adapter.rb +++ b/example/adapter.rb @@ -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/lib/rack/adapter/loader.rb b/lib/rack/adapter/loader.rb index 4574f284..60572c50 100644 --- a/lib/rack/adapter/loader.rb +++ b/lib/rack/adapter/loader.rb @@ -64,7 +64,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/spec/rack/loader_spec.rb b/spec/rack/loader_spec.rb index e1e6efbe..e35b691b 100644 --- a/spec/rack/loader_spec.rb +++ b/spec/rack/loader_spec.rb @@ -32,7 +32,7 @@ end it "should load File adapter" do - expect(Rack::File).to receive(:new) + expect(Rack::Files).to receive(:new) Rack::Adapter.for(:file) end From 60647021c808bd3c50800f217dd1fe16126354a2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 29 Jan 2025 22:51:00 +1300 Subject: [PATCH 06/10] More compatibility. --- .github/workflows/test.yml | 21 +++++++++++++-------- benchmark/benchmarker.rb | 2 +- example/adapter.rb | 2 +- example/async_app.ru | 2 +- example/async_chat.ru | 4 ++-- example/async_tailer.ru | 2 +- example/config.ru | 2 +- ext/thin_parser/thin.c | 3 ++- gems/rack-v2.rb | 2 +- gems/rack-v3.rb | 16 ++++++++++++++++ lib/rack/adapter/loader.rb | 7 +++++++ lib/thin/response.rb | 14 +++++++------- lib/thin/stats.rb | 2 +- script/profile | 2 +- site/rdoc.rb | 4 ++-- site/thin.rb | 4 ++-- spec/daemonizing_spec.rb | 1 + spec/perf/server_perf_spec.rb | 4 ++-- spec/response_spec.rb | 20 ++++++++++---------- spec/server/robustness_spec.rb | 2 +- spec/server/stopping_spec.rb | 2 +- spec/server/swiftiply_spec.rb | 2 +- spec/server/tcp_spec.rb | 6 +++--- spec/server/threaded_spec.rb | 2 +- spec/server/unix_socket_spec.rb | 2 +- 25 files changed, 80 insertions(+), 50 deletions(-) create mode 100644 gems/rack-v3.rb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1b5e6a8..7cc8818b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,13 +35,15 @@ jobs: - 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 +55,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/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 206c150c..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 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 ["

Async Tailer

"]
       
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/thin.c b/ext/thin_parser/thin.c
index 98a9fdfa..d38cf95a 100644
--- a/ext/thin_parser/thin.c
+++ b/ext/thin_parser/thin.c
@@ -155,6 +155,8 @@ 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_request_http_version, val);
+  rb_hash_aset(req, global_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 +213,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);
 }
diff --git a/gems/rack-v2.rb b/gems/rack-v2.rb
index 498d17c4..58d4df42 100644
--- a/gems/rack-v2.rb
+++ b/gems/rack-v2.rb
@@ -4,7 +4,7 @@
 
 gemspec path: "../"
 
-gem 'rack', '~> 2.0'
+gem 'rack', "~> 2.0"
 
 group :development do
   gem "rake-compiler"
diff --git a/gems/rack-v3.rb b/gems/rack-v3.rb
new file mode 100644
index 00000000..d6c48bb4
--- /dev/null
+++ b/gems/rack-v3.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+source 'https://rubygems.org'
+
+gemspec path: "../"
+
+gem 'rack', "~> 3.0"
+
+group :development do
+  gem "rake-compiler"
+end
+
+group :test do
+  gem "rake", ">= 12.3.3"
+  gem "rspec", "~> 3.5"
+end
diff --git a/lib/rack/adapter/loader.rb b/lib/rack/adapter/loader.rb
index 60572c50..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.
diff --git a/lib/thin/response.rb b/lib/thin/response.rb
index 4fde1c3a..f5ff806e 100644
--- a/lib/thin/response.rb
+++ b/lib/thin/response.rb
@@ -47,18 +47,18 @@ def flush
       end
     end
 
-    CONNECTION     = 'Connection'.freeze
+    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
@@ -152,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
   
-  
+