Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Stop using Raindrops for check_client_connection #123

Merged
merged 1 commit into from
May 28, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 11 additions & 66 deletions lib/pitchfork/http_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class HttpParser
EMPTY_ARRAY = [].freeze
@@input_class = Pitchfork::TeeInput
@@check_client_connection = false
@@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)

def self.input_class
@@input_class
Expand Down Expand Up @@ -108,80 +107,26 @@ def hijacked?
env.include?('rack.hijack_io')
end

if Raindrops.const_defined?(:TCP_Info)
TCPI = Raindrops::TCP_Info.allocate

if Socket.const_defined?(:TCP_INFO) # Linux
def check_client_connection(socket) # :nodoc:
if TCPSocket === socket
# Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
raise Errno::EPIPE, "client closed connection",
EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
else
write_http_header(socket)
end
end

if Raindrops.const_defined?(:TCP)
# raindrops 0.18.0+ supports FreeBSD + Linux using the same names
# Evaluate these hash lookups at load time so we can
# generate an opt_case_dispatch instruction
eval <<-EOS
def closed_state?(state) # :nodoc:
case state
when #{Raindrops::TCP[:ESTABLISHED]}
false
when #{Raindrops::TCP.values_at(
:CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
true
else
false
end
end
EOS
else
# raindrops before 0.18 only supported TCP_INFO under Linux
def closed_state?(state) # :nodoc:
case state
when 1 # ESTABLISHED
false
when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
true
else
false
begin
tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
rescue IOError, SystemCallError
return write_http_header(socket)
end
end
end
else

# Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
# Not that efficient, but probably still better than doing unnecessary
# work after a client gives up.
def check_client_connection(socket) # :nodoc:
if TCPSocket === socket && @@tcpi_inspect_ok
opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
if opt =~ /\bstate=(\S+)/
raise Errno::EPIPE, "client closed connection",
EMPTY_ARRAY if closed_state_str?($1)
else
@@tcpi_inspect_ok = false
write_http_header(socket)
case tcp_info.data.unpack1("C")
when 6, 7, 8, 9, 11 # TIME_WAIT, CLOSE, CLOSE_WAIT, LAST_ACK, CLOSING
raise Errno::EPIPE, "client closed connection", EMPTY_ARRAY
end
opt.clear
else
write_http_header(socket)
end
end

def closed_state_str?(state)
case state
when 'ESTABLISHED'
false
# not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
true
else
false
end
else
def check_client_connection(socket) # :nodoc:
write_http_header(socket)
end
end

Expand Down