From 8fd21b3d7afd24ae5b8180b02feacb75a8286d8e Mon Sep 17 00:00:00 2001 From: alor Date: Mon, 31 Jan 2011 09:49:44 +0100 Subject: [PATCH 01/10] http status string can be specified --- .gitignore | 3 ++- eventmachine_httpserver.gemspec | 2 +- eventmachine_httpserver.gemspec.tmpl | 2 +- lib/evma_httpserver/response.rb | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 823d4d9..5017be2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ ext/*.log ext/*.o ext/*.dll ext/*.bundle -ext/*.so \ No newline at end of file +ext/*.so +.idea \ No newline at end of file diff --git a/eventmachine_httpserver.gemspec b/eventmachine_httpserver.gemspec index 3ed8dc4..c3ec27f 100644 --- a/eventmachine_httpserver.gemspec +++ b/eventmachine_httpserver.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = %q{eventmachine_httpserver} - s.version = "0.2.1" + s.version = "0.2.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Francis Cianfrocca"] diff --git a/eventmachine_httpserver.gemspec.tmpl b/eventmachine_httpserver.gemspec.tmpl index 179f7e0..98919af 100644 --- a/eventmachine_httpserver.gemspec.tmpl +++ b/eventmachine_httpserver.gemspec.tmpl @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = %q{eventmachine_httpserver} - s.version = "0.2.1" + s.version = "0.2.2" s.specification_version = 1 if s.respond_to? :specification_version= diff --git a/lib/evma_httpserver/response.rb b/lib/evma_httpserver/response.rb index 61f7965..fcb5ebd 100644 --- a/lib/evma_httpserver/response.rb +++ b/lib/evma_httpserver/response.rb @@ -49,7 +49,7 @@ module EventMachine # EventMachine::Connection. # class HttpResponse - attr_accessor :status, :content, :headers, :chunks, :multiparts + attr_accessor :status, :status_string, :content, :headers, :chunks, :multiparts def initialize @headers = {} @@ -111,7 +111,7 @@ def send_headers fixup_headers ary = [] - ary << "HTTP/1.1 #{@status || 200} ...\r\n" + ary << "HTTP/1.1 #{@status || 200} #{@status_string || '...'}\r\n" ary += generate_header_lines(@headers) ary << "\r\n" From ed639711869ad6d544bcd54dea22d81047858740 Mon Sep 17 00:00:00 2001 From: alor Date: Mon, 31 Jan 2011 09:56:31 +0100 Subject: [PATCH 02/10] trivial --- lib/evma_httpserver/response.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/evma_httpserver/response.rb b/lib/evma_httpserver/response.rb index fcb5ebd..59e16fb 100644 --- a/lib/evma_httpserver/response.rb +++ b/lib/evma_httpserver/response.rb @@ -286,6 +286,7 @@ def self.concoct_multipart_boundary def send_redirect location @status = 302 # TODO, make 301 available by parameter + @status_string = "Moved Temporarily" @headers["Location"] = location send_response end From 89bca678d29ac5d560a51c38284b686457a27491 Mon Sep 17 00:00:00 2001 From: alor Date: Mon, 31 Jan 2011 10:00:32 +0100 Subject: [PATCH 03/10] tests for the status string --- test/test_response.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/test_response.rb b/test/test_response.rb index 5e54c4d..a81d509 100644 --- a/test/test_response.rb +++ b/test/test_response.rb @@ -70,6 +70,19 @@ def test_send_response assert_equal( true, a.closed_after_writing ) end + def test_send_response_with_status + a = EventMachine::HttpResponse.new + a.status = 200 + a.status_string = "OK-TEST" + a.send_response + assert_equal([ + "HTTP/1.1 200 OK-TEST\r\n", + "Content-length: 0\r\n", + "\r\n" + ].join, a.output_data) + assert_equal( true, a.closed_after_writing ) + end + def test_send_response_1 a = EventMachine::HttpResponse.new a.status = 200 From 381dbd936a570c29bbc99447e0bb4ec1faef2c0e Mon Sep 17 00:00:00 2001 From: alor Date: Mon, 31 Jan 2011 14:37:40 +0100 Subject: [PATCH 04/10] max content length for POST requests can be modified at runtime --- README | 7 ++++++- Rakefile | 3 ++- ext/http.cpp | 50 +++++++++++++++++++++++++++++++++++------------- ext/http.h | 4 ++++ ext/rubyhttp.cpp | 29 ++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/README b/README index 86bd426..3cc2b07 100644 --- a/README +++ b/README @@ -19,7 +19,12 @@ def post_init super - no_environment_strings + + # faster if you don't need evironment variables (CGI methods) + self.no_environment_strings + + # max length of the POST content + self.max_content_length = 10_000_000 end def process_http_request diff --git a/Rakefile b/Rakefile index 08be62c..89532c8 100644 --- a/Rakefile +++ b/Rakefile @@ -20,7 +20,8 @@ namespace :build do end CLEAN.include('ext/Makefile') CLEAN.include('ext/*.log') - + CLEAN.include('ext/*.o') + libfile = "ext/eventmachine_httpserver.#{Config::CONFIG['DLEXT']}" file libfile => ['ext/Makefile', *sources] do Dir.chdir 'ext' do diff --git a/ext/http.cpp b/ext/http.cpp index 241974b..e95b670 100644 --- a/ext/http.cpp +++ b/ext/http.cpp @@ -75,6 +75,10 @@ HttpConnection_t::HttpConnection_t() // instead of buffering it here. To get the latter behavior, user code must call // dont_accumulate_post. bAccumulatePost = true; + + // By default this limit is initialized to 20 MiB, it could be changed at runtime + // by the user if needed + ContentLengthLimit = MaxContentLength; } @@ -140,6 +144,20 @@ void HttpConnection_t::ReceivePostData (const char *data, int len) cerr << "UNIMPLEMENTED ReceivePostData" << endl; } +/********************************* +HttpConnection_t::Get/SetMaxContentLength +*********************************/ + +int HttpConnection_t::GetMaxContentLength () +{ + return ContentLengthLimit; +} + +void HttpConnection_t::SetMaxContentLength (int len) +{ + ContentLengthLimit = len; +} + /***************************** HttpConnection_t::ConsumeData *****************************/ @@ -254,7 +272,7 @@ void HttpConnection_t::ConsumeData (const char *data, int length) } else { const char *nl = strpbrk (data, "\r\n"); - int len = nl ? (nl - data) : length; + int len = nl ? (int)(nl - data) : length; if ((size_t)(HeaderLinePos + len) >= sizeof(HeaderLine)) { // TODO, log this goto fail_connection; @@ -358,7 +376,7 @@ bool HttpConnection_t::_InterpretHeaderLine (const char *header) if (bContentLengthSeen) { // TODO, log this. There are some attacks that depend // on sending more than one content-length header. - _SendError (406); + _SendError (400, "Bad Request"); return false; } bContentLengthSeen = true; @@ -366,9 +384,9 @@ bool HttpConnection_t::_InterpretHeaderLine (const char *header) while (*s && ((*s==' ') || (*s=='\t'))) s++; ContentLength = atoi (s); - if (ContentLength > MaxContentLength) { + if (ContentLength > ContentLengthLimit) { // TODO, log this. - _SendError (406); + _SendError (413, "Request Entity Too Large"); return false; } } @@ -400,7 +418,7 @@ bool HttpConnection_t::_InterpretHeaderLine (const char *header) // Copy the incoming header into a block if ((HeaderBlockPos + strlen(header) + 1) < HeaderBlockSize) { - int len = strlen(header); + int len = (int)strlen(header); memcpy (HeaderBlock+HeaderBlockPos, header, len); HeaderBlockPos += len; HeaderBlock [HeaderBlockPos++] = 0; @@ -439,26 +457,27 @@ bool HttpConnection_t::_InterpretRequest (const char *header) const char *blank = strchr (header, ' '); if (!blank) { - _SendError (406); + _SendError (400, "Bad Request"); return false; } - if (!_DetectVerbAndSetEnvString (header, blank - header)) + if (!_DetectVerbAndSetEnvString (header, (int)(blank - header))) return false; blank++; if (*blank != '/') { - _SendError (406); + _SendError (400, "Bad Request"); return false; } const char *blank2 = strchr (blank, ' '); if (!blank2) { - _SendError (406); + _SendError (400, "Bad Request"); return false; } + if (strcasecmp (blank2 + 1, "HTTP/1.0") && strcasecmp (blank2 + 1, "HTTP/1.1")) { - _SendError (505); + _SendError (505, "HTTP Version Not Supported"); return false; } @@ -573,13 +592,18 @@ HttpConnection_t::_SendError ****************************/ void HttpConnection_t::_SendError (int code) +{ + _SendError(code, "..."); +} + +void HttpConnection_t::_SendError (int code, const char *desc) { stringstream ss; - ss << "HTTP/1.1 " << code << " ...\r\n"; + ss << "HTTP/1.1 " << code << " " << desc << "\r\n"; ss << "Connection: close\r\n"; ss << "Content-type: text/plain\r\n"; ss << "\r\n"; ss << "Detected error: HTTP code " << code; - SendData (ss.str().c_str(), ss.str().length()); -} + SendData (ss.str().c_str(), (int)ss.str().length()); +} \ No newline at end of file diff --git a/ext/http.h b/ext/http.h index 48f48df..75f193d 100644 --- a/ext/http.h +++ b/ext/http.h @@ -58,6 +58,8 @@ class HttpConnection_t virtual void ReceivePostData(const char *data, int len); virtual void SetNoEnvironmentStrings() {bSetEnvironmentStrings = false;} virtual void SetDontAccumulatePost() {bAccumulatePost = false;} + virtual int GetMaxContentLength(); + virtual void SetMaxContentLength(int len); private: @@ -85,6 +87,7 @@ class HttpConnection_t int HeaderBlockPos; int ContentLength; + int ContentLengthLimit; int ContentPos; char *_Content; @@ -107,6 +110,7 @@ class HttpConnection_t bool _InterpretRequest (const char*); bool _DetectVerbAndSetEnvString (const char*, int); void _SendError (int); + void _SendError (int, const char*); }; #endif // __HttpPersonality__H_ diff --git a/ext/rubyhttp.cpp b/ext/rubyhttp.cpp index e35ee4d..bd97d42 100644 --- a/ext/rubyhttp.cpp +++ b/ext/rubyhttp.cpp @@ -258,6 +258,33 @@ static VALUE t_dont_accumulate_post (VALUE self) return Qnil; } +/********************** +t_get_max_content_length +**********************/ + +static VALUE t_get_max_content_length (VALUE self) +{ + RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn))); + if (hc) + return INT2FIX (hc->GetMaxContentLength()); + + return Qnil; +} + +/********************** +t_set_max_content_length +**********************/ + +static VALUE t_set_max_content_length (VALUE self, VALUE data) +{ + RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn))); + if (hc) { + hc->SetMaxContentLength(FIX2INT(data)); + return INT2FIX (hc->GetMaxContentLength()); + } + + return Qnil; +} /**************************** Init_eventmachine_httpserver @@ -276,4 +303,6 @@ extern "C" void Init_eventmachine_httpserver() rb_define_method (HttpServer, "process_http_request", (VALUE(*)(...))t_process_http_request, 0); rb_define_method (HttpServer, "no_environment_strings", (VALUE(*)(...))t_no_environment_strings, 0); rb_define_method (HttpServer, "dont_accumulate_post", (VALUE(*)(...))t_dont_accumulate_post, 0); + rb_define_method (HttpServer, "max_content_length", (VALUE(*)(...))t_get_max_content_length, 0); + rb_define_method (HttpServer, "max_content_length=", (VALUE(*)(...))t_set_max_content_length, 1); } From 59e052d349a34559e17bee88438c795cbe0ff16e Mon Sep 17 00:00:00 2001 From: Greg Brockman Date: Wed, 16 Mar 2011 18:33:26 -0700 Subject: [PATCH 05/10] Register HttpServer's RubyHttpConnection object with the garbage collector Currently, if a class overrides the 'unbind' method, the RubyHttpConnection object in each connection object will be leaked. Similarly, if no one ever calls 'unbind' on the HttpServer, the RubyHttpConnection will be leaked on garbage collection. For example, the following program has a memory leak: """ require 'rubygems'; require 'eventmachine'; require 'evma_httpserver' class MyServer < EM::Connection include EM::HttpServer def unbind; end end EM.run do EM.start_server '127.0.0.1', 9000, MyServer end """ As does the following: """ require 'rubygems'; require 'eventmachine'; require 'evma_httpserver' class MyServer < EM::Connection include EM::HttpServer end 10000.times { MyServer.new 1 } """ This patch moves destruction of the RubyHttpConnection object from the unbind method to the garbage collector. One may wonder about the performance implications of this patch. As a rough estimate, on my quad-core/16 GB RAM desktop, I ran time for i in $(seq 1 10000); do curl localhost:9000; done with and without this patch against the following server program: """ require 'rubygems'; require 'eventmachine'; require 'evma_httpserver' class MyServer < EM::Connection; include EM::HttpServer; end EM.run { EM.start_server '0.0.0.0', 9000, MyServer } """ Results are shown below. [Without this patch (8e20269d574bc19222e0db2dc38cbf2a6d88969f)] $ time ( for i in $(seq 1 10000); do curl 127.0.0.1:9000; done >/dev/null 2>/dev/null ) real 0m57.654s user 0m2.770s sys 0m3.230s [With this patch] $ time ( for i in $(seq 1 10000); do curl 127.0.0.1:9000; done >/dev/null 2>/dev/null ) real 0m55.123s user 0m2.550s sys 0m3.240s --- ext/rubyhttp.cpp | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/ext/rubyhttp.cpp b/ext/rubyhttp.cpp index e35ee4d..99bdaa9 100644 --- a/ext/rubyhttp.cpp +++ b/ext/rubyhttp.cpp @@ -172,6 +172,30 @@ Statics VALUE Intern_http_conn; +/******************** +t_get_http_connection +********************/ + +static RubyHttpConnection_t *t_get_http_connection(VALUE self) +{ + RubyHttpConnection_t *hc; + VALUE ivar = rb_ivar_get (self, Intern_http_conn); + if (ivar != Qnil) + Data_Get_Struct (ivar, RubyHttpConnection_t, hc); + else + hc = NULL; + return hc; +} + +/******************** +t_delete_http_connection +********************/ + +void t_delete_http_connection(RubyHttpConnection_t *hc) +{ + delete hc; +} + /*********** t_post_init ***********/ @@ -182,7 +206,12 @@ static VALUE t_post_init (VALUE self) if (!hc) throw std::runtime_error ("no http-connection object"); - rb_ivar_set (self, Intern_http_conn, LONG2NUM ((long)hc)); + /* + HACK: http_connection should be given an actual type. No one should + be touching it from inside ruby, but still + */ + VALUE http_connection = Data_Wrap_Struct(CLASS_OF(self), 0, t_delete_http_connection, hc); + rb_ivar_set (self, Intern_http_conn, http_connection); return Qnil; } @@ -194,7 +223,7 @@ t_receive_data static VALUE t_receive_data (VALUE self, VALUE data) { int length = NUM2INT (rb_funcall (data, rb_intern ("length"), 0)); - RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn))); + RubyHttpConnection_t *hc = t_get_http_connection (self); if (hc) hc->ConsumeData (StringValuePtr (data), length); return Qnil; @@ -216,9 +245,6 @@ t_unbind static VALUE t_unbind (VALUE self) { - RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn))); - if (hc) - delete hc; return Qnil; } @@ -240,7 +266,7 @@ t_no_environment_strings static VALUE t_no_environment_strings (VALUE self) { - RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn))); + RubyHttpConnection_t *hc = t_get_http_connection (self); if (hc) hc->SetNoEnvironmentStrings(); return Qnil; @@ -252,7 +278,7 @@ t_dont_accumulate_post static VALUE t_dont_accumulate_post (VALUE self) { - RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn))); + RubyHttpConnection_t *hc = t_get_http_connection (self); if (hc) hc->SetDontAccumulatePost(); return Qnil; From b96beb4639d3f0787b02869f172bd7b22f6b26aa Mon Sep 17 00:00:00 2001 From: Tom Lea Date: Fri, 25 Mar 2011 17:56:34 +0000 Subject: [PATCH 06/10] Keep alive for all status codes but 5xx A 404/201 response does not indicate that the connection is unviable. --- lib/evma_httpserver/response.rb | 2 +- test/test_response.rb | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/evma_httpserver/response.rb b/lib/evma_httpserver/response.rb index 61f7965..09fe032 100644 --- a/lib/evma_httpserver/response.rb +++ b/lib/evma_httpserver/response.rb @@ -98,7 +98,7 @@ def send_response send_headers send_body send_trailer - close_connection_after_writing unless (@keep_connection_open and (@status || 200) == 200) + close_connection_after_writing unless (@keep_connection_open and (@status || 200) < 500) end # Send the headers out in alpha-sorted order. This will degrade performance to some diff --git a/test/test_response.rb b/test/test_response.rb index 5e54c4d..e385479 100644 --- a/test/test_response.rb +++ b/test/test_response.rb @@ -103,6 +103,57 @@ def test_send_response_no_close assert( ! a.closed_after_writing ) end + def test_send_response_no_close_with_a_404_response + a = EventMachine::HttpResponse.new + a.status = 404 + a.content_type "text/plain" + a.content = "ABC" + a.keep_connection_open + a.send_response + assert_equal([ + "HTTP/1.1 404 ...\r\n", + "Content-length: 3\r\n", + "Content-type: text/plain\r\n", + "\r\n", + "ABC" + ].join, a.output_data) + assert( ! a.closed_after_writing ) + end + + def test_send_response_no_close_with_a_201_response + a = EventMachine::HttpResponse.new + a.status = 201 + a.content_type "text/plain" + a.content = "ABC" + a.keep_connection_open + a.send_response + assert_equal([ + "HTTP/1.1 201 ...\r\n", + "Content-length: 3\r\n", + "Content-type: text/plain\r\n", + "\r\n", + "ABC" + ].join, a.output_data) + assert( ! a.closed_after_writing ) + end + + def test_send_response_no_close_with_a_500_response + a = EventMachine::HttpResponse.new + a.status = 500 + a.content_type "text/plain" + a.content = "ABC" + a.keep_connection_open + a.send_response + assert_equal([ + "HTTP/1.1 500 ...\r\n", + "Content-length: 3\r\n", + "Content-type: text/plain\r\n", + "\r\n", + "ABC" + ].join, a.output_data) + assert( a.closed_after_writing ) + end + def test_send_response_multiple_times a = EventMachine::HttpResponse.new a.status = 200 From 10a993ad96954b867070402ffabc0b86e4ecebef Mon Sep 17 00:00:00 2001 From: alor Date: Fri, 15 Apr 2011 13:36:21 +0200 Subject: [PATCH 07/10] fix to comply to the new http_connection type --- eventmachine_httpserver.gemspec | 3 +-- ext/rubyhttp.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/eventmachine_httpserver.gemspec b/eventmachine_httpserver.gemspec index c3ec27f..17ff345 100644 --- a/eventmachine_httpserver.gemspec +++ b/eventmachine_httpserver.gemspec @@ -17,11 +17,10 @@ Gem::Specification.new do |s| s.rdoc_options = ["--title", "EventMachine_HttpServer", "--main", "docs/README", "--line-numbers"] s.require_paths = ["lib"] s.required_ruby_version = Gem::Requirement.new("> 0.0.0") - s.rubygems_version = %q{1.3.7} + s.rubygems_version = %q{1.6.2} s.summary = %q{EventMachine HTTP Server} if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION s.specification_version = 1 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then diff --git a/ext/rubyhttp.cpp b/ext/rubyhttp.cpp index ea3a45b..e12628b 100644 --- a/ext/rubyhttp.cpp +++ b/ext/rubyhttp.cpp @@ -290,7 +290,7 @@ t_get_max_content_length static VALUE t_get_max_content_length (VALUE self) { - RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn))); + RubyHttpConnection_t *hc = t_get_http_connection (self); if (hc) return INT2FIX (hc->GetMaxContentLength()); @@ -303,7 +303,7 @@ t_set_max_content_length static VALUE t_set_max_content_length (VALUE self, VALUE data) { - RubyHttpConnection_t *hc = (RubyHttpConnection_t*)(NUM2LONG (rb_ivar_get (self, Intern_http_conn))); + RubyHttpConnection_t *hc = t_get_http_connection (self); if (hc) { hc->SetMaxContentLength(FIX2INT(data)); return INT2FIX (hc->GetMaxContentLength()); From bd84414393fae2d653f2394ea75324e2af0d990c Mon Sep 17 00:00:00 2001 From: Martin Skinner Date: Mon, 30 May 2011 11:53:31 +0200 Subject: [PATCH 08/10] added support for absolute URIs in request line --- ext/http.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/http.cpp b/ext/http.cpp index e95b670..d4a9e4f 100644 --- a/ext/http.cpp +++ b/ext/http.cpp @@ -465,6 +465,16 @@ bool HttpConnection_t::_InterpretRequest (const char *header) return false; blank++; + // according to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1 + // HTTP 1.1 complient servers MUST also accept absolute URIs in the request line + // e.g. GET http://foo/bar + if (strncasecmp (blank, "http://", 7) == 0) { + blank += 7; // skip over scheme part + while (*blank && *blank != '/') { // skip to next slash + blank++; + } + } + if (*blank != '/') { _SendError (400, "Bad Request"); return false; From e2defc63a0dd39a4cce48c47d2bc7cb8cb53c236 Mon Sep 17 00:00:00 2001 From: daniele Date: Wed, 1 Jun 2011 11:18:49 +0200 Subject: [PATCH 09/10] Fixed cert_chain in gemspec. --- eventmachine_httpserver.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eventmachine_httpserver.gemspec b/eventmachine_httpserver.gemspec index 17ff345..ffe6e6f 100644 --- a/eventmachine_httpserver.gemspec +++ b/eventmachine_httpserver.gemspec @@ -6,7 +6,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Francis Cianfrocca"] - s.cert_chain = nil + s.cert_chain = [] s.date = %q{2007-03-16} s.description = %q{} s.email = %q{garbagecat10@gmail.com} From b8388e3134296f769fe4ac5fb4b37872b152b308 Mon Sep 17 00:00:00 2001 From: Martin Skinner Date: Tue, 4 Oct 2011 17:04:41 +0200 Subject: [PATCH 10/10] added OPTIONS to list of supported verbs. --- ext/http.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/http.cpp b/ext/http.cpp index d4a9e4f..009789b 100644 --- a/ext/http.cpp +++ b/ext/http.cpp @@ -572,7 +572,8 @@ bool HttpConnection_t::_DetectVerbAndSetEnvString (const char *request, int verb "POST", "PUT", "DELETE", - "HEAD" + "HEAD", + "OPTIONS" }; int n_verbs = sizeof(verbs) / sizeof(const char*);