iodine 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.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +48 -0
 - data/README.md +18 -0
 - data/bin/hello_world +2 -2
 - data/lib/iodine/http.rb +12 -1
 - data/lib/iodine/http/http1.rb +36 -10
 - data/lib/iodine/http/http2.rb +13 -1
 - data/lib/iodine/http/rack_support.rb +2 -2
 - data/lib/iodine/http/request.rb +86 -57
 - data/lib/iodine/http/response.rb +5 -3
 - data/lib/iodine/http/websocket_client.rb +56 -10
 - data/lib/iodine/protocol.rb +3 -2
 - data/lib/iodine/version.rb +1 -1
 - metadata +3 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 08fe1f8f8ebb9d46760123f8ca97f9811e350190
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: bccdc5e606be4dcb98a27f81b39f115d6ee3c37e
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: b203fafc5965ae514600798dac5ac96ed61f9a54e5ef55b32bad1335c79844dbf1a967e0603530f68d48fadd919c4f4d81257917cdea0934c9d8ee9232573186
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: cb90e413302e828682192bc349c2cadcd1e817b9b3a5c3cb41139153deddcd08ea259e561d6b8c75a30742afb4536b9d74bf41e0ecf27f57b35c970ac0e9e7c7
         
     | 
    
        data/CHANGELOG.md
    ADDED
    
    | 
         @@ -0,0 +1,48 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Iodine
         
     | 
| 
      
 2 
     | 
    
         
            +
            [](https://badge.fury.io/rb/iodine)
         
     | 
| 
      
 3 
     | 
    
         
            +
            [](http://www.rubydoc.info/github/boazsegev/iodine/master/frames)
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Please notice that this change log contains changes for upcoming releases as well. Please refer to the current gem version to review the current release.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ## Changes:
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            ***
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            Change log v.0.1.1
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            **Fix**: Fixed an issue where slow processing of Http/1 requests could cause timeout disconnections to occur while the request is being processed.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            **Change/Security**: Uploads now use temporary files. Aceessing the data for file uploads should be done throught the `:file` property of the params hash (i.e. `params[:upload_field_name][:file]`). Using the `:data` property (old API) would cause the whole file to be dumped to the memory and the file's content will be returned as a String.
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            **Change/Security**: Http upload limits are now enforced. The current default limit is about ~0.5GB.
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            **Feature**: WebsocketClient now supports both an auto-connection-renewal and a polling machanism built in to the `WebsocketClient.connect` API. The polling feature is mostly a handy helper for testing, as it is assumed that connection renewal and pub/sub offer a better design than polling.
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            **Logging**: Better Http error logging and recognition.
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            ***
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            Change log v.0.1.0
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            **First actual release**:
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            We learn, we evolve, we change... but we remember our past and do our best to help with the transition and make it worth the toll it takes on our resources.
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            I took much of the code used for GRHttp and GReactor, changed it, morphed it and united it into the singular Iodine gem. This includes Major API changes, refactoring of code, bug fixes and changes to the core approach of how a task/io based application should behave or be constructed.
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            For example, Iodine kicks in automatically when the setup script is done, so that all code is run from within tasks and IO connections and no code is run in parallel to the Iodine engine.
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            Another example, Iodine now favors Object Oriented code, so that some actions - such as writing a network service - require classes of objects to be declared or inherited (i.e. the Protocol class).
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            This allows objects to manage their data as if they were in a single thread environment, unless the objects themselves are calling asynchronous code. For example, the Protocol class makes sure that the `on_open` and `on_message(data)` callbacks are excecuted within a Mutex (`on_close` is an exception to the rule since it is assumed that objects should be prepared to loose network connection at any moment).
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            Another example is that real-life deployemnt preferences were favored over adjustability or features. This means that some command-line arguments are automatically recognized (such as the `-p <port>` argument) and thet Iodine assumes a single web service per script/process (whereas GReactor and GRHttp allowed multiple listening sockets).
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            I tested this new gem during the 0.0.x version releases, and I feel that version 0.1.0 is stable enough to work with. For instance, I left the Iodine server running all night under stress (repeatedly benchmarking it)... millions of requests later, under heavey load, a restart wasn't required and memory consumption didn't show any increase after the warmup period.
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            ## License
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
            The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -127,6 +127,24 @@ end 
     | 
|
| 
       127 
127 
     | 
    
         
             
            Iodine::Http.on_websocket WSChatServer
         
     | 
| 
       128 
128 
     | 
    
         
             
            ```
         
     | 
| 
       129 
129 
     | 
    
         | 
| 
      
 130 
     | 
    
         
            +
            ### Security and limits
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
            Nobody wants their server to crash... Security measures are a fact of life as an internet entety. It is not only the theoretical malicious attacker from which a server must protect itself, but also from the unaware user or client.
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
            Mostly, it is assumed that Iodine will run behind a proxy (i.e. within a Heroku Dyno or viaduct.io process), as such it is assumed that the proxy will protect the Iodine Http server from undue stress.
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
            Having said that, Iodine is built with certain security measures in mind:
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
            - Iodine will not accept IO data (neither from new connections nor form existing ones) while still answering existing requests and performing tasks. This safeguards against task overloading and DoS attacks causing a global crash, allowing the server to resume normal operation once a DoS attack had run it's course (and potentially allowing legitimate requests to be answered while the attack is still underway).
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
            - Iodine will limit the query length (Http/1), header count and header data size as well as well as react to header overloading by immediate disconnections. Iodine's limits are hardcoded to be slightly more than double those of the common Proxies, so this counter-measure will only take effect should an attacker manage to bypass the Proxy.
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
            - Iodine limits every Http request body-size (file upload data, form data, etc') to ~0.5GB. This setting can be changed using `Iodine::Http.max_http_buffer`. This safeguard is meant to prevent Ruby from crashing due to insufficient memory (an error Iodine cannot, and should not, recover from).
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
               It is recommended that this number will be lowered substantially whenever possible, by using `Iodine::Http.max_http_buffer = new_value`
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
               Do be aware that, at the moment, file uploads are passed through the memory when parsed. The parser's memory consumption will hopefully decrese in future releases, however, it is always recomended that large data be avoided when possible or handled using download/upload management protocols and services.
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
       130 
148 
     | 
    
         
             
            ## Server Usage: Plug in your network protocol
         
     | 
| 
       131 
149 
     | 
    
         | 
| 
       132 
150 
     | 
    
         
             
            Iodine is designed to help write network services (Servers) where each script is intended to implement a single server.
         
     | 
    
        data/bin/hello_world
    CHANGED
    
    | 
         @@ -17,7 +17,7 @@ require "iodine/http" 
     | 
|
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
            # Iodine.processes = 4
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
            Iodine. 
     | 
| 
      
 20 
     | 
    
         
            +
            Iodine::Http.on_http { "Hello World!" }
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
            class WSChatServer < Iodine::Http::WebsocketHandler
         
     | 
| 
         @@ -46,7 +46,7 @@ Process.fork do 
     | 
|
| 
       46 
46 
     | 
    
         | 
| 
       47 
47 
     | 
    
         
             
              Iodine.ssl = true
         
     | 
| 
       48 
48 
     | 
    
         
             
            	Iodine.port = 3030
         
     | 
| 
       49 
     | 
    
         
            -
            	Iodine. 
     | 
| 
      
 49 
     | 
    
         
            +
            	Iodine::Http.on_http do |req, res|
         
     | 
| 
       50 
50 
     | 
    
         
             
            		res.session[:count] ||= 0
         
     | 
| 
       51 
51 
     | 
    
         
             
            		res.session[:count] += 1
         
     | 
| 
       52 
52 
     | 
    
         
             
                res['content-type'] = 'text/plain'
         
     | 
    
        data/lib/iodine/http.rb
    CHANGED
    
    | 
         @@ -7,6 +7,7 @@ require 'uri' 
     | 
|
| 
       7 
7 
     | 
    
         
             
            require 'tmpdir'
         
     | 
| 
       8 
8 
     | 
    
         
             
            require 'zlib'
         
     | 
| 
       9 
9 
     | 
    
         
             
            require 'securerandom'
         
     | 
| 
      
 10 
     | 
    
         
            +
            require 'tempfile.rb'
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
12 
     | 
    
         
             
            require 'iodine/http/request'
         
     | 
| 
       12 
13 
     | 
    
         
             
            require 'iodine/http/response'
         
     | 
| 
         @@ -94,11 +95,20 @@ module Iodine 
     | 
|
| 
       94 
95 
     | 
    
         
             
            		def session_token= token
         
     | 
| 
       95 
96 
     | 
    
         
             
            			@session_token = token
         
     | 
| 
       96 
97 
     | 
    
         
             
            		end
         
     | 
| 
       97 
     | 
    
         
            -
            		#  
     | 
| 
      
 98 
     | 
    
         
            +
            		# Gets the session token for the Http server (String). Defaults to the name of the script.
         
     | 
| 
       98 
99 
     | 
    
         
             
            		def session_token
         
     | 
| 
       99 
100 
     | 
    
         
             
            			@session_token
         
     | 
| 
       100 
101 
     | 
    
         
             
            		end
         
     | 
| 
       101 
102 
     | 
    
         | 
| 
      
 103 
     | 
    
         
            +
            		# Sets the maximum bytes allowed for an HTTP's body request (file upload data). Defaults to the command line `-limit` argument, or ~0.25GB.
         
     | 
| 
      
 104 
     | 
    
         
            +
            		def max_http_buffer= size
         
     | 
| 
      
 105 
     | 
    
         
            +
            			@max_http_buffer = size
         
     | 
| 
      
 106 
     | 
    
         
            +
            		end
         
     | 
| 
      
 107 
     | 
    
         
            +
            		# Gets the maximum bytes allowed for an HTTP's body request (file upload data). Defaults to the command line `-limit` argument, or ~0.25GB.
         
     | 
| 
      
 108 
     | 
    
         
            +
            		def max_http_buffer
         
     | 
| 
      
 109 
     | 
    
         
            +
            			@max_http_buffer
         
     | 
| 
      
 110 
     | 
    
         
            +
            		end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
       102 
112 
     | 
    
         
             
            		# Sets whether Iodine will allow connections to the experiemntal Http2 protocol. Defaults to false unless the `http2` command line flag is present.
         
     | 
| 
       103 
113 
     | 
    
         
             
            		def http2= allow
         
     | 
| 
       104 
114 
     | 
    
         
             
            			@http2 = allow && true
         
     | 
| 
         @@ -149,6 +159,7 @@ module Iodine 
     | 
|
| 
       149 
159 
     | 
    
         
             
            		protected
         
     | 
| 
       150 
160 
     | 
    
         | 
| 
       151 
161 
     | 
    
         
             
            		@http2 = (ARGV.index('http2') && true)
         
     | 
| 
      
 162 
     | 
    
         
            +
            		@max_http_buffer = ((ARGV.index('-limit') && ARGV[ARGV.index('-limit') + 1]) || 536_870_912).to_i
         
     | 
| 
       152 
163 
     | 
    
         | 
| 
       153 
164 
     | 
    
         
             
            		@websocket_app = @http_app = NOT_IMPLEMENTED = Proc.new { |i,o| false }
         
     | 
| 
       154 
165 
     | 
    
         
             
            		@session_token = "#{File.basename($0, '.*')}_uuid"
         
     | 
    
        data/lib/iodine/http/http1.rb
    CHANGED
    
    | 
         @@ -16,9 +16,14 @@ module Iodine 
     | 
|
| 
       16 
16 
     | 
    
         
             
            						request = (@request ||= ::Iodine::Http::Request.new(self))
         
     | 
| 
       17 
17 
     | 
    
         
             
            						unless request[:method]
         
     | 
| 
       18 
18 
     | 
    
         
             
            							l = data.gets.strip
         
     | 
| 
      
 19 
     | 
    
         
            +
            							if l.bytesize > 16_384
         
     | 
| 
      
 20 
     | 
    
         
            +
            								write "HTTP/1.0 414 Request-URI Too Long\r\ncontent-length: 20\r\n\r\nRequest URI too Long"
         
     | 
| 
      
 21 
     | 
    
         
            +
            								Iodine.warn "Http/1 URI too long, closing connection."
         
     | 
| 
      
 22 
     | 
    
         
            +
            								return close
         
     | 
| 
      
 23 
     | 
    
         
            +
            							end
         
     | 
| 
       19 
24 
     | 
    
         
             
            							next if l.empty?
         
     | 
| 
       20 
25 
     | 
    
         
             
            							request[:method], request[:query], request[:version] = l.split(/[\s]+/, 3)
         
     | 
| 
       21 
     | 
    
         
            -
            							return (Iodine.warn('Protocol Error, closing connection.') && close) unless request[:method] =~ HTTP_METHODS_REGEXP
         
     | 
| 
      
 26 
     | 
    
         
            +
            							return (Iodine.warn('Htt1 Protocol Error, closing connection.') && close) unless request[:method] =~ HTTP_METHODS_REGEXP
         
     | 
| 
       22 
27 
     | 
    
         
             
            							request[:version] = (request[:version] || '1.1'.freeze).match(/[\d\.]+/)[0]
         
     | 
| 
       23 
28 
     | 
    
         
             
            							request[:time_recieved] = Time.now
         
     | 
| 
       24 
29 
     | 
    
         
             
            						end
         
     | 
| 
         @@ -27,6 +32,8 @@ module Iodine 
     | 
|
| 
       27 
32 
     | 
    
         
             
            								# n = l.slice!(0, l.index(':')); l.slice! 0
         
     | 
| 
       28 
33 
     | 
    
         
             
            								# n.strip! ; n.downcase!; n.freeze
         
     | 
| 
       29 
34 
     | 
    
         
             
            								# request[n] ? (request[n].is_a?(Array) ? (request[n] << l) : request[n] = [request[n], l ]) : (request[n] = l)
         
     | 
| 
      
 35 
     | 
    
         
            +
            								request[:headers_size] ||= 0
         
     | 
| 
      
 36 
     | 
    
         
            +
            								request[:headers_size] += l.bytesize
         
     | 
| 
       30 
37 
     | 
    
         
             
            								l = l.strip.split(/:[\s]?/, 2)
         
     | 
| 
       31 
38 
     | 
    
         
             
            								l[0].strip! ; l[0].downcase!;
         
     | 
| 
       32 
39 
     | 
    
         
             
            								request[l[0]] ? (request[l[0]].is_a?(Array) ? (request[l[0]] << l[1]) : request[l[0]] = [request[l[0]], l[1] ]) : (request[l[0]] = l[1])
         
     | 
| 
         @@ -37,6 +44,10 @@ module Iodine 
     | 
|
| 
       37 
44 
     | 
    
         
             
            								Iodine.warn 'Protocol Error, closing connection.'
         
     | 
| 
       38 
45 
     | 
    
         
             
            								return close
         
     | 
| 
       39 
46 
     | 
    
         
             
            							end
         
     | 
| 
      
 47 
     | 
    
         
            +
            							if request.length > 2096 || request[:headers_size] > 262_144
         
     | 
| 
      
 48 
     | 
    
         
            +
            								write "HTTP/1.0 431 Request Header Fields Too Large\r\ncontent-length: 31\r\n\r\nRequest Header Fields Too Large"
         
     | 
| 
      
 49 
     | 
    
         
            +
            								return (Iodine.warn('Http1 header overloading, closing connection.') && close)
         
     | 
| 
      
 50 
     | 
    
         
            +
            							end
         
     | 
| 
       40 
51 
     | 
    
         
             
            						end
         
     | 
| 
       41 
52 
     | 
    
         
             
            						until request[:body_complete] && request[:headers_complete]
         
     | 
| 
       42 
53 
     | 
    
         
             
            							if request['transfer-coding'.freeze] == 'chunked'.freeze
         
     | 
| 
         @@ -48,7 +59,7 @@ module Iodine 
     | 
|
| 
       48 
59 
     | 
    
         
             
            									return (Iodine.warn('Protocol Error, closing connection.') && close) unless @parser[:length]
         
     | 
| 
       49 
60 
     | 
    
         
             
            									request[:body_complete] = true && break if @parser[:length] == 0
         
     | 
| 
       50 
61 
     | 
    
         
             
            									@parser[:act_length] = 0
         
     | 
| 
       51 
     | 
    
         
            -
            									request[:body] ||= ''
         
     | 
| 
      
 62 
     | 
    
         
            +
            									request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze)
         
     | 
| 
       52 
63 
     | 
    
         
             
            								end
         
     | 
| 
       53 
64 
     | 
    
         
             
            								chunk = data.read(@parser[:length] - @parser[:act_length])
         
     | 
| 
       54 
65 
     | 
    
         
             
            								return false unless chunk
         
     | 
| 
         @@ -56,21 +67,27 @@ module Iodine 
     | 
|
| 
       56 
67 
     | 
    
         
             
            								@parser[:act_length] += chunk.bytesize
         
     | 
| 
       57 
68 
     | 
    
         
             
            								(@parser[:act_length] = @parser[:length] = 0) && (data.gets) if @parser[:act_length] >= @parser[:length]
         
     | 
| 
       58 
69 
     | 
    
         
             
            							elsif request['content-length'.freeze] && request['content-length'.freeze].to_i != 0
         
     | 
| 
       59 
     | 
    
         
            -
            								request[:body] ||= ''
         
     | 
| 
       60 
     | 
    
         
            -
            								packet = data.read(request['content-length'.freeze].to_i - request[:body]. 
     | 
| 
      
 70 
     | 
    
         
            +
            								request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze)
         
     | 
| 
      
 71 
     | 
    
         
            +
            								packet = data.read(request['content-length'.freeze].to_i - request[:body].size)
         
     | 
| 
       61 
72 
     | 
    
         
             
            								return false unless packet
         
     | 
| 
       62 
73 
     | 
    
         
             
            								request[:body] << packet
         
     | 
| 
       63 
     | 
    
         
            -
            								request[:body_complete] = true if request['content-length'.freeze].to_i - request[:body]. 
     | 
| 
      
 74 
     | 
    
         
            +
            								request[:body_complete] = true if request['content-length'.freeze].to_i - request[:body].size <= 0
         
     | 
| 
       64 
75 
     | 
    
         
             
            							elsif request['content-type'.freeze]
         
     | 
| 
       65 
76 
     | 
    
         
             
            								Iodine.warn 'Body type protocol error.' unless request[:body]
         
     | 
| 
       66 
77 
     | 
    
         
             
            								line = data.gets
         
     | 
| 
       67 
78 
     | 
    
         
             
            								return false unless line
         
     | 
| 
       68 
     | 
    
         
            -
            								(request[:body] ||= '') << line
         
     | 
| 
      
 79 
     | 
    
         
            +
            								(request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze) ) << line
         
     | 
| 
       69 
80 
     | 
    
         
             
            								request[:body_complete] = true if line =~ EOHEADERS
         
     | 
| 
       70 
81 
     | 
    
         
             
            							else
         
     | 
| 
       71 
82 
     | 
    
         
             
            								request[:body_complete] = true
         
     | 
| 
       72 
83 
     | 
    
         
             
            							end
         
     | 
| 
       73 
84 
     | 
    
         
             
            						end
         
     | 
| 
      
 85 
     | 
    
         
            +
            						if request[:body] && request[:body].size > ::Iodine::Http.max_http_buffer
         
     | 
| 
      
 86 
     | 
    
         
            +
            							Iodine.warn("Http1 message body too big, closing connection (Iodine::Http.max_http_buffer == #{::Iodine::Http.max_http_buffer} bytes) - #{request[:body].size} bytes.")
         
     | 
| 
      
 87 
     | 
    
         
            +
            							request.delete(:body).tap {|f| f.close unless f.closed? } rescue false
         
     | 
| 
      
 88 
     | 
    
         
            +
            							write "HTTP/1.0 413 Payload Too Large\r\ncontent-length: 17\r\n\r\nPayload Too Large"
         
     | 
| 
      
 89 
     | 
    
         
            +
            							return close
         
     | 
| 
      
 90 
     | 
    
         
            +
            						end
         
     | 
| 
       74 
91 
     | 
    
         
             
            						(@request = ::Iodine::Http::Request.new(self)) && ( (::Iodine::Http.http2 && ::Iodine::Http::Http2.handshake(request, self, data)) || dispatch(request, data) ) if request.delete :body_complete
         
     | 
| 
       75 
92 
     | 
    
         
             
            					end
         
     | 
| 
       76 
93 
     | 
    
         
             
            			end
         
     | 
| 
         @@ -95,7 +112,12 @@ module Iodine 
     | 
|
| 
       95 
112 
     | 
    
         | 
| 
       96 
113 
     | 
    
         
             
            				send_headers response
         
     | 
| 
       97 
114 
     | 
    
         
             
            				return log_finished(response) if request.head?
         
     | 
| 
       98 
     | 
    
         
            -
            				 
     | 
| 
      
 115 
     | 
    
         
            +
            				if body
         
     | 
| 
      
 116 
     | 
    
         
            +
            					written = write(body)
         
     | 
| 
      
 117 
     | 
    
         
            +
            					return Iodine.warn "Http/1 couldn't send response because connection was lost." unless written
         
     | 
| 
      
 118 
     | 
    
         
            +
            					response.bytes_written += written
         
     | 
| 
      
 119 
     | 
    
         
            +
            					(body.frozen? || body.clear)
         
     | 
| 
      
 120 
     | 
    
         
            +
            				end
         
     | 
| 
       99 
121 
     | 
    
         
             
            				close unless keep_alive
         
     | 
| 
       100 
122 
     | 
    
         
             
            				log_finished response
         
     | 
| 
       101 
123 
     | 
    
         
             
            			end
         
     | 
| 
         @@ -108,9 +130,13 @@ module Iodine 
     | 
|
| 
       108 
130 
     | 
    
         
             
            				end
         
     | 
| 
       109 
131 
     | 
    
         
             
            				return if response.request.head?
         
     | 
| 
       110 
132 
     | 
    
         
             
            				body = response.extract_body
         
     | 
| 
       111 
     | 
    
         
            -
            				 
     | 
| 
      
 133 
     | 
    
         
            +
            				if body
         
     | 
| 
      
 134 
     | 
    
         
            +
            					written = stream_data(body)
         
     | 
| 
      
 135 
     | 
    
         
            +
            					return Iodine.warn "Http/1 couldn't send response because connection was lost." unless written
         
     | 
| 
      
 136 
     | 
    
         
            +
            					response.bytes_written += written
         
     | 
| 
      
 137 
     | 
    
         
            +
            				end
         
     | 
| 
       112 
138 
     | 
    
         
             
            				if finish
         
     | 
| 
       113 
     | 
    
         
            -
            					response.bytes_written += stream_data('') 
     | 
| 
      
 139 
     | 
    
         
            +
            					response.bytes_written += stream_data('')
         
     | 
| 
       114 
140 
     | 
    
         
             
            					log_finished response
         
     | 
| 
       115 
141 
     | 
    
         
             
            					close unless response.keep_alive
         
     | 
| 
       116 
142 
     | 
    
         
             
            				end
         
     | 
| 
         @@ -183,7 +209,7 @@ module Iodine 
     | 
|
| 
       183 
209 
     | 
    
         
             
            				response.raw_cookies.freeze
         
     | 
| 
       184 
210 
     | 
    
         
             
            			end
         
     | 
| 
       185 
211 
     | 
    
         
             
            			def stream_data data = nil
         
     | 
| 
       186 
     | 
    
         
            -
            				 write("#{data.to_s.bytesize.to_s(16)}\r\n#{data.to_s}\r\n") 
     | 
| 
      
 212 
     | 
    
         
            +
            				 write("#{data.to_s.bytesize.to_s(16)}\r\n#{data.to_s}\r\n")
         
     | 
| 
       187 
213 
     | 
    
         
             
            			end
         
     | 
| 
       188 
214 
     | 
    
         | 
| 
       189 
215 
     | 
    
         
             
            			def log_finished response
         
     | 
    
        data/lib/iodine/http/http2.rb
    CHANGED
    
    | 
         @@ -290,9 +290,15 @@ module Iodine 
     | 
|
| 
       290 
290 
     | 
    
         | 
| 
       291 
291 
     | 
    
         
             
            				@header_buffer << frame[:body]
         
     | 
| 
       292 
292 
     | 
    
         | 
| 
      
 293 
     | 
    
         
            +
            				frame[:stream][:headers_size] ||= 0
         
     | 
| 
      
 294 
     | 
    
         
            +
            				frame[:stream][:headers_size] += frame[:body].bytesize
         
     | 
| 
      
 295 
     | 
    
         
            +
             
     | 
| 
      
 296 
     | 
    
         
            +
            				return (Iodine.warn('Http2 header overloading, closing connection.') && connection_error( ENHANCE_YOUR_CALM ) ) if frame[:stream][:headers_size] >  262_144
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
       293 
298 
     | 
    
         
             
            				return unless frame[:flags][2] == 1 # fin
         
     | 
| 
       294 
299 
     | 
    
         | 
| 
       295 
300 
     | 
    
         
             
            				frame[:stream].update @hpack.decode(@header_buffer) # this is where HPACK comes in
         
     | 
| 
      
 301 
     | 
    
         
            +
            				return (Iodine.warn('Http2 header overloading, closing connection.') && connection_error( ENHANCE_YOUR_CALM ) ) if frame[:stream].length > 2096
         
     | 
| 
       296 
302 
     | 
    
         
             
            				frame[:stream][:time_recieved] ||= Time.now
         
     | 
| 
       297 
303 
     | 
    
         
             
            				frame[:stream][:version] ||= '2'.freeze
         
     | 
| 
       298 
304 
     | 
    
         | 
| 
         @@ -312,7 +318,13 @@ module Iodine 
     | 
|
| 
       312 
318 
     | 
    
         
             
            					frame[:body] = frame[:body][1...(0 - frame[:body][0].ord)]
         
     | 
| 
       313 
319 
     | 
    
         
             
            				end
         
     | 
| 
       314 
320 
     | 
    
         | 
| 
       315 
     | 
    
         
            -
            				frame[:stream][:body]  
     | 
| 
      
 321 
     | 
    
         
            +
            				(frame[:stream][:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze) ) << frame[:body]
         
     | 
| 
      
 322 
     | 
    
         
            +
             
     | 
| 
      
 323 
     | 
    
         
            +
            				# check request size
         
     | 
| 
      
 324 
     | 
    
         
            +
            				if frame[:stream][:body].size > ::Iodine::Http.max_http_buffer
         
     | 
| 
      
 325 
     | 
    
         
            +
            					Iodine.warn("Http2 payload (message size) too big (Iodine::Http.max_http_buffer == #{::Iodine::Http.max_http_buffer} bytes) - #{frame[:stream][:body].size} bytes.")
         
     | 
| 
      
 326 
     | 
    
         
            +
            					return connection_error( ENHANCE_YOUR_CALM )
         
     | 
| 
      
 327 
     | 
    
         
            +
            				end
         
     | 
| 
       316 
328 
     | 
    
         | 
| 
       317 
329 
     | 
    
         
             
            				process_request(@open_streams.delete frame[:sid]) if frame[:flags][0] == 1
         
     | 
| 
       318 
330 
     | 
    
         
             
            			end
         
     | 
| 
         @@ -41,7 +41,7 @@ module Iodine 
     | 
|
| 
       41 
41 
     | 
    
         
             
            				# env.each {|k, v| env[k] = @request[v] if v.is_a?(Symbol)}
         
     | 
| 
       42 
42 
     | 
    
         
             
            				RACK_ADDON.each {|k, v| env[k] = (request[v].is_a?(String) ? ( request[v].frozen? ? request[v].dup.force_encoding('ASCII-8BIT') : request[v].force_encoding('ASCII-8BIT') ): request[v])}
         
     | 
| 
       43 
43 
     | 
    
         
             
            				request.each {|k, v| env["HTTP_#{k.upcase.tr('-', '_')}"] = v if k.is_a?(String) }
         
     | 
| 
       44 
     | 
    
         
            -
            				env['rack.input'.freeze] ||= StringIO.new(''.force_encoding('ASCII-8BIT'.freeze))
         
     | 
| 
      
 44 
     | 
    
         
            +
            				env['rack.input'.freeze] ||= request[:body] || StringIO.new(''.force_encoding('ASCII-8BIT'.freeze))
         
     | 
| 
       45 
45 
     | 
    
         
             
            				env['CONTENT_LENGTH'.freeze] = env.delete 'HTTP_CONTENT_LENGTH'.freeze if env['HTTP_CONTENT_LENGTH'.freeze]
         
     | 
| 
       46 
46 
     | 
    
         
             
            				env['CONTENT_TYPE'.freeze] = env.delete 'HTTP_CONTENT_TYPE'.freeze if env['HTTP_CONTENT_TYPE'.freeze]
         
     | 
| 
       47 
47 
     | 
    
         
             
            				env['HTTP_VERSION'.freeze] = "HTTP/#{request[:version].to_s}"
         
     | 
| 
         @@ -64,7 +64,7 @@ module Iodine 
     | 
|
| 
       64 
64 
     | 
    
         
             
            				# 'gr.cookies'		=> :cookies,
         
     | 
| 
       65 
65 
     | 
    
         
             
            				'REQUEST_METHOD'	=> :method,
         
     | 
| 
       66 
66 
     | 
    
         
             
            				'rack.url_scheme'	=> :scheme,
         
     | 
| 
       67 
     | 
    
         
            -
            				'rack.input'		=> : 
     | 
| 
      
 67 
     | 
    
         
            +
            				'rack.input'		=> :body
         
     | 
| 
       68 
68 
     | 
    
         
             
            			}
         
     | 
| 
       69 
69 
     | 
    
         | 
| 
       70 
70 
     | 
    
         
             
            			RACK_DICTIONARY = {
         
     | 
    
        data/lib/iodine/http/request.rb
    CHANGED
    
    | 
         @@ -206,6 +206,9 @@ module Iodine 
     | 
|
| 
       206 
206 
     | 
    
         
             
            					# end
         
     | 
| 
       207 
207 
     | 
    
         
             
            				# end
         
     | 
| 
       208 
208 
     | 
    
         
             
            				return request if request[:client_ip]
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
            				request.delete :headers_size
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
       209 
212 
     | 
    
         
             
            				request[:client_ip] = request['x-forwarded-for'.freeze].to_s.split(/,[\s]?/)[0] || (request[:io].io.to_io.remote_address.ip_address) rescue 'unknown IP'.freeze
         
     | 
| 
       210 
213 
     | 
    
         
             
            				request[:version] ||= '1'
         
     | 
| 
       211 
214 
     | 
    
         | 
| 
         @@ -262,7 +265,7 @@ module Iodine 
     | 
|
| 
       262 
265 
     | 
    
         
             
            			end
         
     | 
| 
       263 
266 
     | 
    
         | 
| 
       264 
267 
     | 
    
         
             
            			# Adds paramaters to a Hash object, according to the Iodine's server conventions.
         
     | 
| 
       265 
     | 
    
         
            -
            			def self.add_param_to_hash name, value, target
         
     | 
| 
      
 268 
     | 
    
         
            +
            			def self.add_param_to_hash name, value, target, &block
         
     | 
| 
       266 
269 
     | 
    
         
             
            				begin
         
     | 
| 
       267 
270 
     | 
    
         
             
            					c = target
         
     | 
| 
       268 
271 
     | 
    
         
             
            					val = rubyfy! value
         
     | 
| 
         @@ -284,6 +287,7 @@ module Iodine 
     | 
|
| 
       284 
287 
     | 
    
         
             
            						else
         
     | 
| 
       285 
288 
     | 
    
         
             
            							c[n] = val
         
     | 
| 
       286 
289 
     | 
    
         
             
            						end
         
     | 
| 
      
 290 
     | 
    
         
            +
            						c.default_proc = block if block
         
     | 
| 
       287 
291 
     | 
    
         
             
            					else
         
     | 
| 
       288 
292 
     | 
    
         
             
            						if c[n]
         
     | 
| 
       289 
293 
     | 
    
         
             
            							c[n].is_a?(Array) ? (c[n] << val) : (c[n] = [c[n], val])
         
     | 
| 
         @@ -340,80 +344,105 @@ module Iodine 
     | 
|
| 
       340 
344 
     | 
    
         
             
            			# read the body's data and parse any incoming data.
         
     | 
| 
       341 
345 
     | 
    
         
             
            			def self.read_body request
         
     | 
| 
       342 
346 
     | 
    
         
             
            				# save body for Rack, if applicable
         
     | 
| 
       343 
     | 
    
         
            -
            				request[:rack_input] =  
     | 
| 
      
 347 
     | 
    
         
            +
            				# request[:rack_input] = request[:body]  if ::Iodine::Http.on_http == ::Iodine::Http::Rack
         
     | 
| 
       344 
348 
     | 
    
         
             
            				# parse content
         
     | 
| 
      
 349 
     | 
    
         
            +
            				request[:body].rewind
         
     | 
| 
       345 
350 
     | 
    
         
             
            				case request['content-type'.freeze].to_s
         
     | 
| 
       346 
351 
     | 
    
         
             
            				when /x-www-form-urlencoded/
         
     | 
| 
       347 
     | 
    
         
            -
            					extract_params request 
     | 
| 
      
 352 
     | 
    
         
            +
            					extract_params request[:body].read.split(/[&;]/), request[:params] #, :form # :uri
         
     | 
| 
       348 
353 
     | 
    
         
             
            				when /multipart\/form-data/
         
     | 
| 
       349 
     | 
    
         
            -
            					read_multipart request, request 
     | 
| 
      
 354 
     | 
    
         
            +
            					read_multipart request, request
         
     | 
| 
       350 
355 
     | 
    
         
             
            				when /text\/xml/
         
     | 
| 
       351 
356 
     | 
    
         
             
            					# to-do support xml?
         
     | 
| 
       352 
     | 
    
         
            -
            					make_utf8! request[:body]
         
     | 
| 
      
 357 
     | 
    
         
            +
            					# request[:xml] = make_utf8! request[:body].read
         
     | 
| 
       353 
358 
     | 
    
         
             
            					nil
         
     | 
| 
       354 
359 
     | 
    
         
             
            				when /application\/json/
         
     | 
| 
       355 
     | 
    
         
            -
            					JSON.parse(make_utf8! request[:body]).each {|k, v| add_param_to_hash k, v, request[:params]} rescue true
         
     | 
| 
      
 360 
     | 
    
         
            +
            					JSON.parse(make_utf8! request[:body].read).each {|k, v| add_param_to_hash k, v, request[:params]} rescue true
         
     | 
| 
       356 
361 
     | 
    
         
             
            				end
         
     | 
| 
      
 362 
     | 
    
         
            +
            				request[:body].rewind if request[:body]
         
     | 
| 
       357 
363 
     | 
    
         
             
            			end
         
     | 
| 
       358 
364 
     | 
    
         | 
| 
       359 
365 
     | 
    
         
             
            			# parse a mime/multipart body or part.
         
     | 
| 
       360 
     | 
    
         
            -
            			def self.read_multipart request, headers,  
     | 
| 
       361 
     | 
    
         
            -
            				 
     | 
| 
       362 
     | 
    
         
            -
             
     | 
| 
       363 
     | 
    
         
            -
             
     | 
| 
       364 
     | 
    
         
            -
             
     | 
| 
       365 
     | 
    
         
            -
             
     | 
| 
       366 
     | 
    
         
            -
             
     | 
| 
       367 
     | 
    
         
            -
             
     | 
| 
       368 
     | 
    
         
            -
            						 
     | 
| 
       369 
     | 
    
         
            -
             
     | 
| 
       370 
     | 
    
         
            -
            						 
     | 
| 
      
 366 
     | 
    
         
            +
            			def self.read_multipart request, headers = {}, boundary = [], name_prefix = ''
         
     | 
| 
      
 367 
     | 
    
         
            +
            				body = request[:body]
         
     | 
| 
      
 368 
     | 
    
         
            +
            				return unless headers['content-type'].to_s =~ /multipart/i
         
     | 
| 
      
 369 
     | 
    
         
            +
            				part_headers = {}
         
     | 
| 
      
 370 
     | 
    
         
            +
            				extract_header headers['content-type'].split(/[;,][\s]?/), part_headers
         
     | 
| 
      
 371 
     | 
    
         
            +
            				boundary << part_headers[:boundary]
         
     | 
| 
      
 372 
     | 
    
         
            +
            				if part_headers[:name]
         
     | 
| 
      
 373 
     | 
    
         
            +
            					if name_prefix.empty?
         
     | 
| 
      
 374 
     | 
    
         
            +
            						name_prefix << part_headers[:name]
         
     | 
| 
      
 375 
     | 
    
         
            +
            					else
         
     | 
| 
      
 376 
     | 
    
         
            +
            						name_prefix << "[#{part_headers[:name]}]"
         
     | 
| 
      
 377 
     | 
    
         
            +
            					end
         
     | 
| 
      
 378 
     | 
    
         
            +
            				end
         
     | 
| 
      
 379 
     | 
    
         
            +
            				part_headers.delete :name
         
     | 
| 
      
 380 
     | 
    
         
            +
            				part_headers.clear
         
     | 
| 
      
 381 
     | 
    
         
            +
            				line = nil
         
     | 
| 
      
 382 
     | 
    
         
            +
            				boundary_length = nil
         
     | 
| 
      
 383 
     | 
    
         
            +
            				true until ( (line = body.gets) ) && line =~ /\A--(#{boundary.join '|'})(--)?[\r]?\n/
         
     | 
| 
      
 384 
     | 
    
         
            +
            				until body.eof?
         
     | 
| 
      
 385 
     | 
    
         
            +
            					return if line =~ /--[\r]?\n/
         
     | 
| 
      
 386 
     | 
    
         
            +
            					return boundary.pop if boundary.count > 1 && line.match(/--(#{boundary.join '|'})/)[1] != boundary.last
         
     | 
| 
      
 387 
     | 
    
         
            +
            					boundary_length = line.bytesize
         
     | 
| 
      
 388 
     | 
    
         
            +
            					line = body.gets until line.nil? || line =~ /\:/
         
     | 
| 
      
 389 
     | 
    
         
            +
            					until line.nil? || line =~ /^[\r]?\n/
         
     | 
| 
      
 390 
     | 
    
         
            +
            						tmp = line.strip.split ':', 2
         
     | 
| 
      
 391 
     | 
    
         
            +
            						return Iodine.error "Http multipart parsing error (multipart header data malformed): #{line}" unless tmp && tmp.count == 2
         
     | 
| 
      
 392 
     | 
    
         
            +
            						tmp[0].strip!; tmp[0].downcase!; tmp[1].strip!; 
         
     | 
| 
      
 393 
     | 
    
         
            +
            						part_headers[tmp[0]] = tmp[1]
         
     | 
| 
      
 394 
     | 
    
         
            +
            						line = body.gets
         
     | 
| 
      
 395 
     | 
    
         
            +
            					end
         
     | 
| 
      
 396 
     | 
    
         
            +
            					return if line.nil?
         
     | 
| 
      
 397 
     | 
    
         
            +
            					if !part_headers['content-disposition'.freeze]
         
     | 
| 
      
 398 
     | 
    
         
            +
            						Iodine.error "Wrong multipart format with headers: #{part_headers}"
         
     | 
| 
      
 399 
     | 
    
         
            +
            						return
         
     | 
| 
      
 400 
     | 
    
         
            +
            					end
         
     | 
| 
      
 401 
     | 
    
         
            +
            					extract_header part_headers['content-disposition'.freeze].split(/[;,][\s]?/), part_headers
         
     | 
| 
      
 402 
     | 
    
         
            +
            					if name_prefix.empty?
         
     | 
| 
      
 403 
     | 
    
         
            +
            						name = part_headers[:name][1..-2]
         
     | 
| 
      
 404 
     | 
    
         
            +
            					else
         
     | 
| 
      
 405 
     | 
    
         
            +
            						name = "#{name_prefix}[part_headers[:name][1..-2]}]"
         
     | 
| 
       371 
406 
     | 
    
         
             
            					end
         
     | 
| 
       372 
     | 
    
         
            -
            					 
     | 
| 
       373 
     | 
    
         
            -
             
     | 
| 
       374 
     | 
    
         
            -
             
     | 
| 
       375 
     | 
    
         
            -
             
     | 
| 
       376 
     | 
    
         
            -
             
     | 
| 
       377 
     | 
    
         
            -
             
     | 
| 
       378 
     | 
    
         
            -
             
     | 
| 
       379 
     | 
    
         
            -
             
     | 
| 
       380 
     | 
    
         
            -
             
     | 
| 
       381 
     | 
    
         
            -
             
     | 
| 
      
 407 
     | 
    
         
            +
            					part_headers.delete :name
         
     | 
| 
      
 408 
     | 
    
         
            +
             
     | 
| 
      
 409 
     | 
    
         
            +
            					start_part_pos = body.pos
         
     | 
| 
      
 410 
     | 
    
         
            +
            					tmp = /\A--(#{boundary.join '|'})(--)?[\r]?\n/
         
     | 
| 
      
 411 
     | 
    
         
            +
            					line.clear until ( (line = body.gets) &&  line =~ tmp)
         
     | 
| 
      
 412 
     | 
    
         
            +
            					end_part_pos = (body.pos - line.bytesize) - 2
         
     | 
| 
      
 413 
     | 
    
         
            +
            					new_part_pos = body.pos 
         
     | 
| 
      
 414 
     | 
    
         
            +
            					body.pos = end_part_pos
         
     | 
| 
      
 415 
     | 
    
         
            +
            					end_part_pos += 1 unless body.getc == "\r"
         
     | 
| 
      
 416 
     | 
    
         
            +
             
     | 
| 
      
 417 
     | 
    
         
            +
            					if part_headers['content-type'.freeze]
         
     | 
| 
      
 418 
     | 
    
         
            +
            						if part_headers['content-type'.freeze] =~ /multipart/i
         
     | 
| 
      
 419 
     | 
    
         
            +
            							body.pos = start_part_pos
         
     | 
| 
      
 420 
     | 
    
         
            +
            							read_multipart request, part_headers, boundary, name_prefix
         
     | 
| 
      
 421 
     | 
    
         
            +
            						else
         
     | 
| 
      
 422 
     | 
    
         
            +
            							part_headers.delete 'content-disposition'.freeze
         
     | 
| 
      
 423 
     | 
    
         
            +
            							add_param_to_hash "#{name}[type]", make_utf8!(part_headers['content-type'.freeze]), request[:params]
         
     | 
| 
      
 424 
     | 
    
         
            +
            							part_headers.each {|k,v|  add_param_to_hash "#{name}[#{k.to_s}]", make_utf8!(v[0] == '"' ? v[1..-2].to_s : v), request[:params] if v}
         
     | 
| 
      
 425 
     | 
    
         
            +
             
     | 
| 
      
 426 
     | 
    
         
            +
            							tmp = Tempfile.new 'upload', encoding: 'binary'
         
     | 
| 
      
 427 
     | 
    
         
            +
            							body.pos = start_part_pos
         
     | 
| 
      
 428 
     | 
    
         
            +
            							((end_part_pos - start_part_pos)/65_536).to_i.times {tmp << body.read(65_536)} 
         
     | 
| 
      
 429 
     | 
    
         
            +
            							tmp << body.read(end_part_pos - body.pos)
         
     | 
| 
      
 430 
     | 
    
         
            +
            							add_param_to_hash "#{name}[size]", tmp.size, request[:params]
         
     | 
| 
      
 431 
     | 
    
         
            +
            							add_param_to_hash "#{name}[file]", tmp, request[:params] do |hash, key|
         
     | 
| 
      
 432 
     | 
    
         
            +
            								if key == :data || key == "data" && hash.has_key?(:file) && hash[:file].is_a?(::Tempfile)
         
     | 
| 
      
 433 
     | 
    
         
            +
            									hash[:file].rewind
         
     | 
| 
      
 434 
     | 
    
         
            +
            									(hash[:data] = hash[:file].read)
         
     | 
| 
      
 435 
     | 
    
         
            +
            								end
         
     | 
| 
       382 
436 
     | 
    
         
             
            							end
         
     | 
| 
       383 
     | 
    
         
            -
            							 
     | 
| 
       384 
     | 
    
         
            -
            							read_multipart request, h, p, name_prefix
         
     | 
| 
      
 437 
     | 
    
         
            +
            							tmp.rewind
         
     | 
| 
       385 
438 
     | 
    
         
             
            						end
         
     | 
| 
      
 439 
     | 
    
         
            +
            					else
         
     | 
| 
      
 440 
     | 
    
         
            +
            						body.pos = start_part_pos
         
     | 
| 
      
 441 
     | 
    
         
            +
            						add_param_to_hash name, uri_decode!( body.read(end_part_pos - start_part_pos) ), request[:params] 
         
     | 
| 
       386 
442 
     | 
    
         
             
            					end
         
     | 
| 
       387 
     | 
    
         
            -
            					 
     | 
| 
       388 
     | 
    
         
            -
            				end
         
     | 
| 
       389 
     | 
    
         
            -
             
     | 
| 
       390 
     | 
    
         
            -
            				# require a part body to exist (data exists) for parsing
         
     | 
| 
       391 
     | 
    
         
            -
            				return true if part.to_s.empty?
         
     | 
| 
       392 
     | 
    
         
            -
             
     | 
| 
       393 
     | 
    
         
            -
            				# convert part to `charset` if charset is defined?
         
     | 
| 
       394 
     | 
    
         
            -
             
     | 
| 
       395 
     | 
    
         
            -
            				if !headers['content-disposition'.freeze]
         
     | 
| 
       396 
     | 
    
         
            -
            					Iodine.error "Wrong multipart format with headers: #{headers} and body: #{part}"
         
     | 
| 
       397 
     | 
    
         
            -
            					return
         
     | 
| 
      
 443 
     | 
    
         
            +
            					body.pos = new_part_pos
         
     | 
| 
       398 
444 
     | 
    
         
             
            				end
         
     | 
| 
       399 
445 
     | 
    
         | 
| 
       400 
     | 
    
         
            -
            				cd = {}
         
     | 
| 
       401 
     | 
    
         
            -
             
     | 
| 
       402 
     | 
    
         
            -
            				extract_header headers['content-disposition'.freeze].split(/[;,][\s]?/), cd
         
     | 
| 
       403 
     | 
    
         
            -
             
     | 
| 
       404 
     | 
    
         
            -
            				if name_prefix.empty?
         
     | 
| 
       405 
     | 
    
         
            -
            					name = cd[:name][1..-2]
         
     | 
| 
       406 
     | 
    
         
            -
            				else
         
     | 
| 
       407 
     | 
    
         
            -
            					name = "#{name_prefix}[cd[:name][1..-2]}]"
         
     | 
| 
       408 
     | 
    
         
            -
            				end
         
     | 
| 
       409 
     | 
    
         
            -
            				if headers['content-type'.freeze]
         
     | 
| 
       410 
     | 
    
         
            -
            					add_param_to_hash "#{name}[data]", part, request[:params]
         
     | 
| 
       411 
     | 
    
         
            -
            					add_param_to_hash "#{name}[type]", make_utf8!(headers['content-type'.freeze]), request[:params]
         
     | 
| 
       412 
     | 
    
         
            -
            					cd.each {|k,v|  add_param_to_hash "#{name}[#{k.to_s}]", make_utf8!(v[1..-2].to_s), request[:params] unless k == :name || !v}
         
     | 
| 
       413 
     | 
    
         
            -
            				else
         
     | 
| 
       414 
     | 
    
         
            -
            					add_param_to_hash name, uri_decode!(part), request[:params]
         
     | 
| 
       415 
     | 
    
         
            -
            				end
         
     | 
| 
       416 
     | 
    
         
            -
            				true
         
     | 
| 
       417 
446 
     | 
    
         
             
            			end
         
     | 
| 
       418 
447 
     | 
    
         | 
| 
       419 
448 
     | 
    
         
             
            		end
         
     | 
    
        data/lib/iodine/http/response.rb
    CHANGED
    
    | 
         @@ -238,6 +238,7 @@ module Iodine 
     | 
|
| 
       238 
238 
     | 
    
         
             
            			# attempts to write a non-streaming response to the IO. This can be done only once and will quitely fail subsequently.
         
     | 
| 
       239 
239 
     | 
    
         
             
            			def finish
         
     | 
| 
       240 
240 
     | 
    
         
             
            				request[:io].send_response self
         
     | 
| 
      
 241 
     | 
    
         
            +
            				request.delete(:body).tap {|f| f.close unless f.respond_to?(:close) && f.closed? rescue false } if request[:body] && @http_sblocks_count.to_i == 0
         
     | 
| 
       241 
242 
     | 
    
         
             
            			end
         
     | 
| 
       242 
243 
     | 
    
         | 
| 
       243 
244 
     | 
    
         
             
            			# Returns the connection's UUID.
         
     | 
| 
         @@ -347,9 +348,9 @@ module Iodine 
     | 
|
| 
       347 
348 
     | 
    
         
             
            					# response.cookies.set_response nil
         
     | 
| 
       348 
349 
     | 
    
         
             
            					@flash.freeze
         
     | 
| 
       349 
350 
     | 
    
         
             
            				end
         
     | 
| 
       350 
     | 
    
         
            -
            				[] 
     | 
| 
       351 
     | 
    
         
            -
             
     | 
| 
       352 
     | 
    
         
            -
            				 
     | 
| 
      
 351 
     | 
    
         
            +
            				arr = []
         
     | 
| 
      
 352 
     | 
    
         
            +
            				@cookies.each {|k, v| arr << "#{k.to_s}=#{v.to_s}"}
         
     | 
| 
      
 353 
     | 
    
         
            +
            				arr
         
     | 
| 
       353 
354 
     | 
    
         
             
            			end
         
     | 
| 
       354 
355 
     | 
    
         | 
| 
       355 
356 
     | 
    
         
             
            			protected
         
     | 
| 
         @@ -373,6 +374,7 @@ module Iodine 
     | 
|
| 
       373 
374 
     | 
    
         
             
            			def finish_streaming
         
     | 
| 
       374 
375 
     | 
    
         
             
            				return unless @http_sblocks_count == 0
         
     | 
| 
       375 
376 
     | 
    
         
             
            				request[:io].stream_response self, true
         
     | 
| 
      
 377 
     | 
    
         
            +
            				request.delete(:body).tap {|f| f.close unless f.respond_to?(:close) && f.closed? rescue false } if request[:body]
         
     | 
| 
       376 
378 
     | 
    
         
             
            			end
         
     | 
| 
       377 
379 
     | 
    
         
             
            		end
         
     | 
| 
       378 
380 
     | 
    
         
             
            	end
         
     | 
| 
         @@ -8,16 +8,17 @@ module Iodine 
     | 
|
| 
       8 
8 
     | 
    
         
             
            		# Use {Iodine::Http::WebsocketClient.connect} to initialize a client with all the callbacks needed.
         
     | 
| 
       9 
9 
     | 
    
         
             
            		class WebsocketClient
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
            			attr_accessor :response, :request
         
     | 
| 
      
 11 
     | 
    
         
            +
            			attr_accessor :response, :request, :params
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
            			def initialize request
         
     | 
| 
       14 
14 
     | 
    
         
             
            				@response = nil
         
     | 
| 
       15 
15 
     | 
    
         
             
            				@request = request
         
     | 
| 
       16 
     | 
    
         
            -
            				params = request[:ws_client_params]
         
     | 
| 
       17 
     | 
    
         
            -
            				@on_message = params[:on_message]
         
     | 
| 
      
 16 
     | 
    
         
            +
            				@params = request[:ws_client_params]
         
     | 
| 
      
 17 
     | 
    
         
            +
            				@on_message = @params[:on_message]
         
     | 
| 
       18 
18 
     | 
    
         
             
            				raise "Websocket client must have an #on_message Proc or handler." unless @on_message && @on_message.respond_to?(:call)
         
     | 
| 
       19 
     | 
    
         
            -
            				@on_open = params[:on_open]
         
     | 
| 
       20 
     | 
    
         
            -
            				@on_close = params[:on_close]
         
     | 
| 
      
 19 
     | 
    
         
            +
            				@on_open = @params[:on_open]
         
     | 
| 
      
 20 
     | 
    
         
            +
            				@on_close = @params[:on_close]
         
     | 
| 
      
 21 
     | 
    
         
            +
            				@renew = @params[:renew].to_i
         
     | 
| 
       21 
22 
     | 
    
         
             
            			end
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
24 
     | 
    
         
             
            			def on event_name, &block
         
     | 
| 
         @@ -41,16 +42,49 @@ module Iodine 
     | 
|
| 
       41 
42 
     | 
    
         
             
            				instance_exec( data, &@on_message) 
         
     | 
| 
       42 
43 
     | 
    
         
             
            			end
         
     | 
| 
       43 
44 
     | 
    
         | 
| 
       44 
     | 
    
         
            -
            			def on_open 
     | 
| 
       45 
     | 
    
         
            -
            				raise 'The on_open even is invalid at this point.' if  
     | 
| 
      
 45 
     | 
    
         
            +
            			def on_open
         
     | 
| 
      
 46 
     | 
    
         
            +
            				raise 'The on_open even is invalid at this point.' if block_given?
         
     | 
| 
       46 
47 
     | 
    
         
             
            				@io = @request[:io]
         
     | 
| 
       47 
48 
     | 
    
         
             
            				Iodine::Http::Request.parse @request
         
     | 
| 
       48 
49 
     | 
    
         
             
            				instance_exec(&@on_open) if @on_open
         
     | 
| 
      
 50 
     | 
    
         
            +
            				if request[:ws_client_params][:every] && @params[:send]
         
     | 
| 
      
 51 
     | 
    
         
            +
            					raise TypeError, "Websocket Client `:send` should be either a String or a Proc object." unless @params[:send].is_a?(String) || @params[:send].is_a?(Proc)
         
     | 
| 
      
 52 
     | 
    
         
            +
            					Iodine.run_every @params[:every], self, @params do |ws, client_params, timer|
         
     | 
| 
      
 53 
     | 
    
         
            +
            						if ws.closed?
         
     | 
| 
      
 54 
     | 
    
         
            +
            							timer.stop!
         
     | 
| 
      
 55 
     | 
    
         
            +
            							next
         
     | 
| 
      
 56 
     | 
    
         
            +
            						end
         
     | 
| 
      
 57 
     | 
    
         
            +
            						if client_params[:send].is_a?(String)
         
     | 
| 
      
 58 
     | 
    
         
            +
            							ws.write client_params[:send]
         
     | 
| 
      
 59 
     | 
    
         
            +
            						elsif client_params[:send].is_a?(Proc)
         
     | 
| 
      
 60 
     | 
    
         
            +
            							ws.instance_exec(&client_params[:send])
         
     | 
| 
      
 61 
     | 
    
         
            +
            						end
         
     | 
| 
      
 62 
     | 
    
         
            +
            					end
         
     | 
| 
      
 63 
     | 
    
         
            +
            				end
         
     | 
| 
       49 
64 
     | 
    
         
             
            			end
         
     | 
| 
       50 
65 
     | 
    
         | 
| 
       51 
66 
     | 
    
         
             
            			def on_close(&block)
         
     | 
| 
       52 
     | 
    
         
            -
            				@on_close = block if block
         
     | 
| 
       53 
     | 
    
         
            -
            				 
     | 
| 
      
 67 
     | 
    
         
            +
            				return @on_close = block if block
         
     | 
| 
      
 68 
     | 
    
         
            +
            				if @renew > 0
         
     | 
| 
      
 69 
     | 
    
         
            +
            					renew_proc = Proc.new do
         
     | 
| 
      
 70 
     | 
    
         
            +
            						begin
         
     | 
| 
      
 71 
     | 
    
         
            +
            							Iodine::Http::WebsocketClient.connect(@params[:url], @params)
         
     | 
| 
      
 72 
     | 
    
         
            +
            						rescue
         
     | 
| 
      
 73 
     | 
    
         
            +
            							@renew -= 1
         
     | 
| 
      
 74 
     | 
    
         
            +
            							if @renew <= 0
         
     | 
| 
      
 75 
     | 
    
         
            +
            								Iodine.fatal "WebsocketClient renewal FAILED for #{@params[:url]}"
         
     | 
| 
      
 76 
     | 
    
         
            +
            								instance_exec(&@on_close) if @on_close
         
     | 
| 
      
 77 
     | 
    
         
            +
            							else
         
     | 
| 
      
 78 
     | 
    
         
            +
            								Iodine.run_after 2, &renew_proc
         
     | 
| 
      
 79 
     | 
    
         
            +
            								Iodine.warn "WebsocketClient renewal failed for #{@params[:url]}, #{@renew} attempts left"
         
     | 
| 
      
 80 
     | 
    
         
            +
            							end
         
     | 
| 
      
 81 
     | 
    
         
            +
            							false
         
     | 
| 
      
 82 
     | 
    
         
            +
            						end
         
     | 
| 
      
 83 
     | 
    
         
            +
            					end
         
     | 
| 
      
 84 
     | 
    
         
            +
            					renew_proc.call
         
     | 
| 
      
 85 
     | 
    
         
            +
            				else
         
     | 
| 
      
 86 
     | 
    
         
            +
            					instance_exec(&@on_close) if @on_close
         
     | 
| 
      
 87 
     | 
    
         
            +
            				end
         
     | 
| 
       54 
88 
     | 
    
         
             
            			end
         
     | 
| 
       55 
89 
     | 
    
         | 
| 
       56 
90 
     | 
    
         
             
            			# Sends data through the socket. a shortcut for ws_client.response <<
         
     | 
| 
         @@ -96,13 +130,23 @@ module Iodine 
     | 
|
| 
       96 
130 
     | 
    
         
             
            			# Acceptable options are:
         
     | 
| 
       97 
131 
     | 
    
         
             
            			# on_open:: the on_open callback. Must be an objects that answers `call(ws)`, usually a Proc.
         
     | 
| 
       98 
132 
     | 
    
         
             
            			# on_message:: the on_message callback. Must be an objects that answers `call(ws)`, usually a Proc.
         
     | 
| 
       99 
     | 
    
         
            -
            			# on_close:: the on_close callback. Must be an objects that answers `call(ws)`, usually a Proc.
         
     | 
| 
      
 133 
     | 
    
         
            +
            			# on_close:: the on_close callback - this will ONLY be called if the connection WASN'T renewed. Must be an objects that answers `call(ws)`, usually a Proc.
         
     | 
| 
       100 
134 
     | 
    
         
             
            			# headers:: a Hash of custom HTTP headers to be sent with the request. Header data, including cookie headers, should be correctly encoded.
         
     | 
| 
       101 
135 
     | 
    
         
             
            			# cookies:: a Hash of cookies to be sent with the request. cookie data will be encoded before being sent.
         
     | 
| 
       102 
136 
     | 
    
         
             
            			# timeout:: the number of seconds to wait before the connection is established. Defaults to 5 seconds.
         
     | 
| 
      
 137 
     | 
    
         
            +
            			# every:: this option, together with `:send` and `:renew`, implements a polling websocket. :every is the number of seconds between each polling event. without `:send`, this option will be ignored. defaults to nil.
         
     | 
| 
      
 138 
     | 
    
         
            +
            			# send:: a String to be sent or a Proc to be performed each polling interval. This option, together with `:every` and `:renew`, implements a polling websocket. without `:every`, this option will be ignored. defaults to nil. If `:send` is a Proc, it will be executed within the context of the websocket client object, with acess to the websocket client's instance variables and methods.
         
     | 
| 
      
 139 
     | 
    
         
            +
            			# renew:: the number of times to attempt to renew the connection if the connection is terminated by the remote server. Attempts are made in 2 seconds interval. The default for a polling websocket is 5 attempts to renew. For all other clients, the default is 0 (no renewal).
         
     | 
| 
       103 
140 
     | 
    
         
             
            			#
         
     | 
| 
       104 
141 
     | 
    
         
             
            			# The method will block until the connection is established or until 5 seconds have passed (the timeout). The method will either return a WebsocketClient instance object or raise an exception it the connection was unsuccessful.
         
     | 
| 
       105 
142 
     | 
    
         
             
            			#
         
     | 
| 
      
 143 
     | 
    
         
            +
            			# Use Iodine::Http.ws_connect for a non-blocking initialization.
         
     | 
| 
      
 144 
     | 
    
         
            +
            			#
         
     | 
| 
      
 145 
     | 
    
         
            +
            			# An #on_close callback will only be called if the connection isn't or cannot be renewed. If the connection is renewed,
         
     | 
| 
      
 146 
     | 
    
         
            +
            			# the #on_open callback will be called again for a new Websocket client instance - but the #on_close callback will NOT be called.
         
     | 
| 
      
 147 
     | 
    
         
            +
            			#
         
     | 
| 
      
 148 
     | 
    
         
            +
            			# Due to this design, the #on_open and #on_close methods should NOT be used for opening IO resources (i.e. file handles) nor for cleanup IF the `:renew` option is enabled.
         
     | 
| 
      
 149 
     | 
    
         
            +
            			#
         
     | 
| 
       106 
150 
     | 
    
         
             
            			# An on_message Proc must be defined, or the method will fail.
         
     | 
| 
       107 
151 
     | 
    
         
             
            			#
         
     | 
| 
       108 
152 
     | 
    
         
             
            			# The on_message Proc can be defined using the optional block:
         
     | 
| 
         @@ -136,6 +180,8 @@ module Iodine 
     | 
|
| 
       136 
180 
     | 
    
         
             
            				options[:on_message] ||= block
         
     | 
| 
       137 
181 
     | 
    
         
             
            				raise "No #on_message handler defined! please pass a block or define an #on_message handler!" unless options[:on_message]
         
     | 
| 
       138 
182 
     | 
    
         
             
            				url = URI.parse(url) unless url.is_a?(URI)
         
     | 
| 
      
 183 
     | 
    
         
            +
            				options[:url] = url
         
     | 
| 
      
 184 
     | 
    
         
            +
            				options[:renew] ||= 5 if options[:every] && options[:send]
         
     | 
| 
       139 
185 
     | 
    
         | 
| 
       140 
186 
     | 
    
         
             
            				ssl = url.scheme == "https" || url.scheme == "wss"
         
     | 
| 
       141 
187 
     | 
    
         | 
    
        data/lib/iodine/protocol.rb
    CHANGED
    
    | 
         @@ -49,9 +49,9 @@ module Iodine 
     | 
|
| 
       49 
49 
     | 
    
         
             
            		end
         
     | 
| 
       50 
50 
     | 
    
         | 
| 
       51 
51 
     | 
    
         
             
            		# This method is called whenever a timeout has occurred. Either implement a ping or close the connection.
         
     | 
| 
       52 
     | 
    
         
            -
            		# The default implementation closes the connection.
         
     | 
| 
      
 52 
     | 
    
         
            +
            		# The default implementation closes the connection unless the protocol is still processing information received before timeout occurred.
         
     | 
| 
       53 
53 
     | 
    
         
             
            		def ping
         
     | 
| 
       54 
     | 
    
         
            -
            			close
         
     | 
| 
      
 54 
     | 
    
         
            +
            			close unless @locker.locked?
         
     | 
| 
       55 
55 
     | 
    
         
             
            		end
         
     | 
| 
       56 
56 
     | 
    
         | 
| 
       57 
57 
     | 
    
         
             
            		#############
         
     | 
| 
         @@ -98,6 +98,7 @@ module Iodine 
     | 
|
| 
       98 
98 
     | 
    
         
             
            					r
         
     | 
| 
       99 
99 
     | 
    
         
             
            				end
         
     | 
| 
       100 
100 
     | 
    
         
             
            			rescue => e
         
     | 
| 
      
 101 
     | 
    
         
            +
            				# Iodine.info e.message
         
     | 
| 
       101 
102 
     | 
    
         
             
            				close
         
     | 
| 
       102 
103 
     | 
    
         
             
            			end
         
     | 
| 
       103 
104 
     | 
    
         
             
            		end
         
     | 
    
        data/lib/iodine/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: iodine
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.1. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.1.1
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Boaz Segev
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2015-10- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2015-10-24 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: bundler
         
     | 
| 
         @@ -61,6 +61,7 @@ extra_rdoc_files: [] 
     | 
|
| 
       61 
61 
     | 
    
         
             
            files:
         
     | 
| 
       62 
62 
     | 
    
         
             
            - ".gitignore"
         
     | 
| 
       63 
63 
     | 
    
         
             
            - ".travis.yml"
         
     | 
| 
      
 64 
     | 
    
         
            +
            - CHANGELOG.md
         
     | 
| 
       64 
65 
     | 
    
         
             
            - Gemfile
         
     | 
| 
       65 
66 
     | 
    
         
             
            - LICENSE.txt
         
     | 
| 
       66 
67 
     | 
    
         
             
            - README.md
         
     |