Class: Iodine::Http::Http1

Inherits:
Protocol show all
Defined in:
lib/iodine/http/http1.rb

Instance Attribute Summary

Attributes inherited from Protocol

#io, #locker, #options

Instance Method Summary collapse

Methods inherited from Protocol

#call, #close, #closed?, each, #id, #initialize, #on_close, #on_shutdown, #ping, #read, #set_timeout, #ssl?, #timeout?, #write

Constructor Details

This class inherits a constructor from Iodine::Protocol

Instance Method Details

#go_away(error_code) ⇒ Object



154
155
156
157
# File 'lib/iodine/http/http1.rb', line 154

def go_away error_code
  return false if @io.closed?
  close
end

#on_message(data) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/iodine/http/http1.rb', line 9

def on_message data
    return if @refuse_requests
    @http2_pri_review ||= ( ::Iodine::Http.http2 && ::Iodine::Http::Http2.pre_handshake(self, data) && (return true) ) || true

    data = ::StringIO.new data
    until data.eof?
      request = (@request ||= ::Iodine::Http::Request.new(self))
      unless request[:method]
        l = data.gets.strip
        if l.bytesize > 16_384
          write "HTTP/1.0 414 Request-URI Too Long\r\ncontent-length: 20\r\n\r\nRequest URI too Long".freeze
          Iodine.warn "Http/1 URI too long, closing connection.".freeze
          return close
        end
        next if l.empty?
        request[:method], request[:query], request[:version] = l.split(/[\s]+/.freeze, 3)
        return (Iodine.warn('Http1 Protocol Error, closing connection.'.freeze, l, request) && close) unless request[:method] =~ HTTP_METHODS_REGEXP
        request[:version] = (request[:version] || '1.1'.freeze).match(/[\d\.]+/.freeze)[0]
        request[:time_recieved] = Iodine.time
      end
      until request[:headers_complete] || (l = data.gets).nil?
        # if l.bytesize > 16_384
        #   write "HTTP/1.0 413 Entity Too Large\r\ncontent-length: 16\r\n\r\nEntity Too Large".freeze
        #   Iodine.warn "Http/1 Header data too large, closing connection.".freeze
        #   return close
        # end
        if l.include? ':'.freeze
          # n = l.slice!(0, l.index(':')); l.slice! 0
          # n.strip! ; n.downcase!; n.freeze
          # request[n] ? (request[n].is_a?(Array) ? (request[n] << l) : request[n] = [request[n], l ]) : (request[n] = l)
          request[:headers_size] ||= 0
          request[:headers_size] += l.bytesize
          if request.length > 2096 || request[:headers_size] > 262_144
            write "HTTP/1.0 431 Request Header Fields Too Large\r\ncontent-length: 31\r\n\r\nRequest Header Fields Too Large".freeze
            return (Iodine.warn('Http1 header overloading, closing connection.'.freeze) && close)
          end
          l = l.strip.split(/:[\s]?/.freeze, 2)
          l[0].strip! ; l[0].downcase!;
          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])
        elsif l =~ /^[\r]?\n/.freeze
          request[:headers_complete] = true
        else
          #protocol error
          Iodine.warn 'Protocol Error, closing connection.'.freeze
          return close
        end
      end
      next unless request[:headers_complete]
      if request['transfer-coding'.freeze] == 'chunked'.freeze
        until request[:body_complete]
          # add mid chunk logic here
          if @parser[:length].to_i == 0
            chunk = data.gets
            return false unless chunk
            @parser[:length] = chunk.to_i(16)
            return (Iodine.warn('Protocol Error, closing connection.'.freeze) && close) unless @parser[:length]
            request[:body_complete] = true && break if @parser[:length] == 0
            @parser[:act_length] = 0
            request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze)
          end
          chunk = data.read(@parser[:length] - @parser[:act_length])
          return false unless chunk
          request[:body] << chunk
          @parser[:act_length] += chunk.bytesize
          (@parser[:act_length] = @parser[:length] = 0) && (data.gets) if @parser[:act_length] >= @parser[:length]
          return if bad_body_size?
        end
      elsif request['content-length'.freeze] && request['content-length'.freeze].to_i != 0
        until request[:body_complete]
          request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze)
          packet = data.read(request['content-length'.freeze].to_i - request[:body].size)
          return false unless packet
          request[:body] << packet
          return if bad_body_size?
          request[:body_complete] = true if request['content-length'.freeze].to_i - request[:body].size <= 0
        end
      elsif request['content-type'.freeze]
        until request[:body_complete]
          Iodine.warn 'Body type protocol error.'.freeze unless request[:body]
          line = data.gets
          return false unless line
          (request[:body] ||= Tempfile.new('iodine'.freeze, :encoding => 'binary'.freeze) ) << line
          return if bad_body_size?
          request[:body_complete] = true if line =~ EOHEADERS
        end
      else
        request[:body_complete] = true
      end
      (@request = ::Iodine::Http::Request.new(self)) && ( (::Iodine::Http.http2 && ::Iodine::Http::Http2.handshake(request, self, data)) || dispatch(request, data) ) if request.delete :body_complete
    end
end

#on_openObject



4
5
6
7
8
# File 'lib/iodine/http/http1.rb', line 4

def on_open
  set_timeout 1
  @refuse_requests = false
  @parser = {}
end

#send_response(response) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/iodine/http/http1.rb', line 101

def send_response response
  return false if response.headers.frozen?

  body = response.extract_body
  request = response.request
  headers = response.headers

  headers['content-length'.freeze] ||= body.size if body

  keep_alive = response.keep_alive
  if (request[:version].to_f > 1 && request['connection'.freeze].nil?) || request['connection'.freeze].to_s =~ /ke/i.freeze || (headers['connection'.freeze] && headers['connection'.freeze] =~ /^ke/i.freeze)
    keep_alive = true
    headers['connection'.freeze] ||= 'keep-alive'.freeze
    headers['keep-alive'.freeze] ||= "timeout=#{(@timeout ||= 3).to_s}".freeze
  else
    headers['connection'.freeze] ||= 'close'.freeze
  end
  send_headers response
  return log_finished(response) && (body && body.close) if request.head? || body.nil?
  
  until body.eof?
    written = write(body.read 65_536, Thread.current[:write_buffer])
    return Iodine.warn("Http/1 couldn't send response because connection was lost.".freeze) && body.close unless written
    response.bytes_written += written
  end
  body.close
  close unless keep_alive
  log_finished response
end

#stream_response(response, finish = false) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/iodine/http/http1.rb', line 130

def stream_response response, finish = false
  set_timeout 15
  unless response.headers.frozen?
    response['transfer-encoding'.freeze] = 'chunked'.freeze
    response.headers['connection'.freeze] = 'close'.freeze
    send_headers response
    @refuse_requests = true
  end
  return if response.request.head?
  body = response.extract_body
  until body.eof?
    written = stream_data(body.read 65_536, Thread.current[:write_buffer])
    return Iodine.warn("Http/1 couldn't send response because connection was lost.".freeze) && body.close unless written
    response.bytes_written += written
  end if body
  if finish
    response.bytes_written += stream_data(''.freeze)
    log_finished response
    close unless response.keep_alive
  end
  body.close if body
  true
end