Class: Puma::Client

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Const
Defined in:
lib/puma/client.rb

Overview

An instance of this class represents a unique request from a client. For example, this could be a web request from a browser or from CURL.

An instance of `Puma::Client` can be used as if it were an IO object by the reactor. The reactor is expected to call `#to_io` on any non-IO objects it polls. For example, nio4r internally calls `IO::try_convert` (which may call `#to_io`) when a new socket is registered.

Instances of this class are responsible for knowing if the header and body are fully buffered via the `try_to_finish` method. They can be used to “time out” a response via the `timeout_at` reader.

Constant Summary collapse

EmptyBody =

The object used for a request with no body. All requests with no body share this one object since it has no state.

NullIO.new

Constants included from Const

Puma::Const::CGI_VER, Puma::Const::CHUNKED, Puma::Const::CHUNK_SIZE, Puma::Const::CLOSE, Puma::Const::CLOSE_CHUNKED, Puma::Const::CODE_NAME, Puma::Const::COLON, Puma::Const::CONNECTION_CLOSE, Puma::Const::CONNECTION_KEEP_ALIVE, Puma::Const::CONTENT_LENGTH, Puma::Const::CONTENT_LENGTH2, Puma::Const::CONTENT_LENGTH_S, Puma::Const::CONTINUE, Puma::Const::EARLY_HINTS, Puma::Const::ERROR_RESPONSE, Puma::Const::FAST_TRACK_KA_TIMEOUT, Puma::Const::FIRST_DATA_TIMEOUT, Puma::Const::GATEWAY_INTERFACE, Puma::Const::HALT_COMMAND, Puma::Const::HEAD, Puma::Const::HIJACK, Puma::Const::HIJACK_IO, Puma::Const::HIJACK_P, Puma::Const::HTTP, Puma::Const::HTTPS, Puma::Const::HTTPS_KEY, Puma::Const::HTTP_10_200, Puma::Const::HTTP_11, Puma::Const::HTTP_11_100, Puma::Const::HTTP_11_200, Puma::Const::HTTP_CONNECTION, Puma::Const::HTTP_EXPECT, Puma::Const::HTTP_HOST, Puma::Const::HTTP_VERSION, Puma::Const::HTTP_X_FORWARDED_FOR, Puma::Const::HTTP_X_FORWARDED_PROTO, Puma::Const::HTTP_X_FORWARDED_SCHEME, Puma::Const::HTTP_X_FORWARDED_SSL, Puma::Const::KEEP_ALIVE, Puma::Const::LINE_END, Puma::Const::LOCALHOST, Puma::Const::LOCALHOST_ADDR, Puma::Const::LOCALHOST_IP, Puma::Const::MAX_BODY, Puma::Const::MAX_HEADER, Puma::Const::NEWLINE, Puma::Const::PATH_INFO, Puma::Const::PERSISTENT_TIMEOUT, Puma::Const::PORT_443, Puma::Const::PORT_80, Puma::Const::PUMA_CONFIG, Puma::Const::PUMA_PEERCERT, Puma::Const::PUMA_SERVER_STRING, Puma::Const::PUMA_SOCKET, Puma::Const::PUMA_TMP_BASE, Puma::Const::PUMA_VERSION, Puma::Const::QUERY_STRING, Puma::Const::RACK_AFTER_REPLY, Puma::Const::RACK_INPUT, Puma::Const::RACK_URL_SCHEME, Puma::Const::REMOTE_ADDR, Puma::Const::REQUEST_METHOD, Puma::Const::REQUEST_PATH, Puma::Const::REQUEST_URI, Puma::Const::RESTART_COMMAND, Puma::Const::SERVER_NAME, Puma::Const::SERVER_PORT, Puma::Const::SERVER_PROTOCOL, Puma::Const::SERVER_SOFTWARE, Puma::Const::STOP_COMMAND, Puma::Const::TRANSFER_ENCODING, Puma::Const::TRANSFER_ENCODING2, Puma::Const::TRANSFER_ENCODING_CHUNKED, Puma::Const::WORKER_CHECK_INTERVAL, Puma::Const::WRITE_TIMEOUT

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io, env = nil) ⇒ Client

Returns a new instance of Client.


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
# File 'lib/puma/client.rb', line 46

def initialize(io, env=nil)
  @io = io
  @to_io = io.to_io
  @proto_env = env
  if !env
    @env = nil
  else
    @env = env.dup
  end

  @parser = HttpParser.new
  @parsed_bytes = 0
  @read_header = true
  @ready = false

  @body = nil
  @body_read_start = nil
  @buffer = nil
  @tempfile = nil

  @timeout_at = nil

  @requests_served = 0
  @hijacked = false

  @peerip = nil
  @remote_addr_header = nil

  @body_remain = 0

  @in_last_chunk = false
end

Instance Attribute Details

#bodyObject (readonly)

Returns the value of attribute body


79
80
81
# File 'lib/puma/client.rb', line 79

def body
  @body
end

#envObject (readonly)

Returns the value of attribute env


79
80
81
# File 'lib/puma/client.rb', line 79

def env
  @env
end

#hijackedObject (readonly)

Returns the value of attribute hijacked


79
80
81
# File 'lib/puma/client.rb', line 79

def hijacked
  @hijacked
end

#ioObject (readonly)

Returns the value of attribute io


79
80
81
# File 'lib/puma/client.rb', line 79

def io
  @io
end

#peeripObject


258
259
260
261
262
263
264
265
266
267
268
# File 'lib/puma/client.rb', line 258

def peerip
  return @peerip if @peerip

  if @remote_addr_header
    hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
    @peerip = hdr
    return hdr
  end

  @peerip ||= @io.peeraddr.last
end

#readyObject (readonly)

Returns the value of attribute ready


79
80
81
# File 'lib/puma/client.rb', line 79

def ready
  @ready
end

#remote_addr_headerObject

Returns the value of attribute remote_addr_header


84
85
86
# File 'lib/puma/client.rb', line 84

def remote_addr_header
  @remote_addr_header
end

#tempfileObject (readonly)

Returns the value of attribute tempfile


79
80
81
# File 'lib/puma/client.rb', line 79

def tempfile
  @tempfile
end

#timeout_atObject (readonly)

Returns the value of attribute timeout_at


79
80
81
# File 'lib/puma/client.rb', line 79

def timeout_at
  @timeout_at
end

#to_ioObject (readonly)

Returns the value of attribute to_io


79
80
81
# File 'lib/puma/client.rb', line 79

def to_io
  @to_io
end

Instance Method Details

#callObject

For the hijack protocol (allows us to just put the Client object into the env)


94
95
96
97
# File 'lib/puma/client.rb', line 94

def call
  @hijacked = true
  env[HIJACK_IO] ||= @io
end

#closeObject


143
144
145
146
147
148
149
# File 'lib/puma/client.rb', line 143

def close
  begin
    @io.close
  rescue IOError
    Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
  end
end

#eagerly_finishObject


223
224
225
226
227
228
229
230
231
232
# File 'lib/puma/client.rb', line 223

def eagerly_finish
  return true if @ready

  if @io.kind_of? OpenSSL::SSL::SSLSocket
    return true if jruby_start_try_to_finish
  end

  return false unless IO.select([@to_io], nil, nil, 0)
  try_to_finish
end

#finishObject

IS_JRUBY


243
244
245
246
247
248
249
# File 'lib/puma/client.rb', line 243

def finish
  return true if @ready
  until try_to_finish
    IO.select([@to_io], nil, nil)
  end
  true
end

#in_data_phaseObject


99
100
101
# File 'lib/puma/client.rb', line 99

def in_data_phase
  !@read_header
end

#inspectObject


88
89
90
# File 'lib/puma/client.rb', line 88

def inspect
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
end

#jruby_start_try_to_finishObject


188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/puma/client.rb', line 188

def jruby_start_try_to_finish
  return read_body unless @read_header

  begin
    data = @io.sysread_nonblock(CHUNK_SIZE)
  rescue OpenSSL::SSL::SSLError => e
    return false if e.kind_of? IO::WaitReadable
    raise e
  end

  # No data means a closed socket
  unless data
    @buffer = nil
    set_ready
    raise EOFError
  end

  if @buffer
    @buffer << data
  else
    @buffer = data
  end

  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)

  if @parser.finished?
    return setup_body
  elsif @parsed_bytes >= MAX_HEADER
    raise HttpParserError,
      "HEADER is longer than allowed, aborting client early."
  end

  false
end

#reset(fast_check = true) ⇒ Object


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/puma/client.rb', line 107

def reset(fast_check=true)
  @parser.reset
  @read_header = true
  @env = @proto_env.dup
  @body = nil
  @tempfile = nil
  @parsed_bytes = 0
  @ready = false
  @body_remain = 0
  @peerip = nil
  @in_last_chunk = false

  if @buffer
    @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)

    if @parser.finished?
      return setup_body
    elsif @parsed_bytes >= MAX_HEADER
      raise HttpParserError,
        "HEADER is longer than allowed, aborting client early."
    end

    return false
  else
    begin
      if fast_check &&
          IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
        return try_to_finish
      end
    rescue IOError
      # swallow it
    end

  end
end

#set_timeout(val) ⇒ Object


103
104
105
# File 'lib/puma/client.rb', line 103

def set_timeout(val)
  @timeout_at = Time.now + val
end

#try_to_finishObject


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/puma/client.rb', line 151

def try_to_finish
  return read_body unless @read_header

  begin
    data = @io.read_nonblock(CHUNK_SIZE)
  rescue Errno::EAGAIN
    return false
  rescue SystemCallError, IOError, EOFError
    raise ConnectionError, "Connection error detected during read"
  end

  # No data means a closed socket
  unless data
    @buffer = nil
    set_ready
    raise EOFError
  end

  if @buffer
    @buffer << data
  else
    @buffer = data
  end

  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)

  if @parser.finished?
    return setup_body
  elsif @parsed_bytes >= MAX_HEADER
    raise HttpParserError,
      "HEADER is longer than allowed, aborting client early."
  end

  false
end

#write_error(status_code) ⇒ Object


251
252
253
254
255
256
# File 'lib/puma/client.rb', line 251

def write_error(status_code)
  begin
    @io << ERROR_RESPONSE[status_code]
  rescue StandardError
  end
end