Module: TestPuma::PumaSocket
| Relationships & Source Files | |
| Defined in: | test/helpers/test_puma/puma_socket.rb |
Overview
This module is included in CI test files, and provides methods to create
client sockets. Normally, the socket parameters are defined by the code
creating the ::Puma server (in-process or spawned), so they do not need to be
specified. Regardless, many of the less frequently used parameters still
have keyword arguments and they can be set to whatever is required.
This module closes all sockets and performs all reads non-blocking and all writes using syswrite. These are helpful for reliable tests. Please do not use native Ruby sockets except if absolutely necessary.
Methods that return a socket or sockets:
- #new_socket - Opens a socket
- #send_http - Opens a socket and sends a request, which defaults to GET_11
- #send_http_array - Creates an array of sockets. It opens each and sends a request on each
All methods that create a socket have the following optional keyword parameters:
host:- tcp/ssl host (String)port:- tcp/ssl port (Integer,String)path:- unix socket, full path (String)ctx:- ssl context (OpenSSL::SSL::SSLContext)session:- ssl session (OpenSSL::SSL::Session)
Methods that process the response:
- #send_http_read_response - sends a request and returns the whole response
- #send_http_read_body/
send_http_read_resp_body- sends a request and returns the response body - #send_http_read_headers/
send_http_read_resp_headers- sends a request and returns the response with the body removed as an array of lines
All methods that process the response have the following optional keyword parameters:
timeout:- total socket read timeout, defaults to RESP_READ_TIMEOUT (Float)len:- theread_nonblockmaxlen, defaults to RESP_READ_LEN (Integer)
Methods available on socket instances:
read_response- reads the response and returns itread_body- reads the response and returns the bodyread_all- reads all available data on the socket- #send_http/
<</req_write- writes to the socket withsyswrite, returns the socket
Constant Summary
-
EMPTY_200 =
# File 'test/helpers/test_puma/puma_socket.rb', line 72[200, {}, ['']]
-
GET_10 =
# File 'test/helpers/test_puma/puma_socket.rb', line 63"GET / HTTP/1.0\r\n\r\n" -
GET_11 =
# File 'test/helpers/test_puma/puma_socket.rb', line 64"GET / HTTP/1.1\r\n\r\n" -
HAS_APPEND_AS_BYTES =
# File 'test/helpers/test_puma/puma_socket.rb', line 74::String.new.respond_to? :append_as_bytes
-
HELLO_11 =
# File 'test/helpers/test_puma/puma_socket.rb', line 66"HTTP/1.1 200 OK\r\ncontent-type: text/plain\r\n" \ "Content-Length: 11\r\n\r\nHello World"
-
NO_ENTITY_BODY =
# File 'test/helpers/test_puma/puma_socket.rb', line 71PumaSocketInclude::NO_ENTITY_BODY
-
RESP_READ_LEN =
# File 'test/helpers/test_puma/puma_socket.rb', line 69PumaSocketInclude::RESP_READ_LEN
-
RESP_READ_TIMEOUT =
# File 'test/helpers/test_puma/puma_socket.rb', line 70PumaSocketInclude::RESP_READ_TIMEOUT
-
SET_TCP_NODELAY =
# File 'test/helpers/test_puma/puma_socket.rb', line 78Socket.const_defined?(:IPPROTO_TCP) && ::Socket.const_defined?(:TCP_NODELAY)
-
UTF8 =
# File 'test/helpers/test_puma/puma_socket.rb', line 76::Encoding::UTF_8
Instance Method Summary
-
#after_teardown
Closes all io’s in
@ios_to_close, also deletes them if they are files. - #before_setup
- #close_ios
-
#new_ctx {|OpenSSL::SSL::SSLContext| ... } ⇒ OpenSSL::SSL::SSLContext
Helper for creating an
OpenSSL::SSL::SSLContext. -
#new_socket(host: nil, port: nil, path: nil, ctx: nil, session: nil) ⇒ PumaSSLSocket, ...
Creates a new client socket.
-
#read_response_array(skts, resp_count: nil, body_only: nil) ⇒ Array<String, Class>
Reads an array of sockets that have already had requests sent.
-
#send_http(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil) ⇒ PumaSSLSocket, ...
Sends a request and returns the socket.
-
#send_http_array(req = GET_11, len, dly: 0.000_1, max_retries: 5) ⇒ Array<PumaSSLSocket, PumaTCPSocket, PumaUNIXSocket>
Creates an array of sockets, sending a request on each.
-
#send_http_read_all(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: RESP_READ_LEN, timeout: 15) ⇒ String
Sends a request and returns whatever can be read.
-
#send_http_read_body(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil) ⇒ Response
(also: #send_http_read_resp_body)
Sends a request and returns the HTTP response body.
-
#send_http_read_headers(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil) ⇒ Array<String>
(also: #send_http_read_resp_headers)
Sends a request and returns the response header lines as an array of strings.
-
#send_http_read_resp_body(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil)
Alias for #send_http_read_body.
-
#send_http_read_resp_headers(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil)
Alias for #send_http_read_headers.
-
#send_http_read_response(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil) ⇒ Response
Sends a request and returns the HTTP response.
-
#skt_closed_by_server(socket) ⇒ Boolean
Determines whether the socket has been closed by the server.
Instance Method Details
#after_teardown
Closes all io’s in @ios_to_close, also deletes them if they are files
# File 'test/helpers/test_puma/puma_socket.rb', line 91
def after_teardown return if skipped? super close_ios if @ios_to_close return unless @ssl_socket_contexts @ssl_socket_contexts.pop until @ssl_socket_contexts.empty? @ssl_socket_contexts.close @ssl_socket_contexts = nil end
#before_setup
[ GitHub ]# File 'test/helpers/test_puma/puma_socket.rb', line 80
def before_setup @bind_port = nil @bind_path = nil @control_port = nil @control_path = nil @ssl_socket_contexts = Queue.new @ios_to_close ||= Queue.new super end
#close_ios
[ GitHub ]# File 'test/helpers/test_puma/puma_socket.rb', line 103
def close_ios until @ios_to_close.empty? io = @ios_to_close.pop begin if io.respond_to? :sysclose io.sync_close = true io.sysclose unless io.closed? else io.close if io.respond_to?(:close) && !io.closed? if io.is_a?(File) && (path = io&.path) && File.exist?(path) File.unlink path end end rescue Errno::EBADF, Errno::ENOENT, IOError ensure io = nil end end @ios_to_close = nil end
#new_ctx {|OpenSSL::SSL::SSLContext| ... } ⇒ OpenSSL::SSL::SSLContext
Helper for creating an OpenSSL::SSL::SSLContext.
# File 'test/helpers/test_puma/puma_socket.rb', line 211
def new_ctx(&blk) ctx = OpenSSL::SSL::SSLContext.new if blk yield ctx else ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE end ctx end
#new_socket(host: nil, port: nil, path: nil, ctx: nil, session: nil) ⇒ PumaSSLSocket, ...
Creates a new client socket. TCP, SSL, and UNIX are supported
# File 'test/helpers/test_puma/puma_socket.rb', line 225
def new_socket(host: nil, port: nil, path: nil, ctx: nil, session: nil) port ||= @bind_port path ||= @bind_path ip ||= (host || HOST.ip).gsub RE_HOST_TO_IP, '' # in case a URI style IPv6 is passed skt = if path && !port && !ctx PumaUNIXSocket.new path.sub(/\A@/, "\0") # sub is for abstract elsif port # && !path tcp = PumaTCPSocket.new ip, port.to_i tcp.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if SET_TCP_NODELAY if ctx @ssl_socket_contexts << ctx if @ssl_socket_contexts PumaSSLSocket.new tcp, ctx else tcp end else raise 'port or path must be set!' end @ios_to_close << skt if ctx skt.session = session if session skt.sync_close = true skt.connect end skt end
#read_response_array(skts, resp_count: nil, body_only: nil) ⇒ Array<String, Class>
Reads an array of sockets that have already had requests sent.
# File 'test/helpers/test_puma/puma_socket.rb', line 284
def read_response_array(skts, resp_count: nil, body_only: nil) results = Array.new skts.length Thread.new do until skts.compact.empty? skts.each_with_index do |skt, idx| next if skt.nil? begin next unless skt.wait_readable 0.000_5 if resp_count resp = skt.read_response.dup cntr = 0 until resp.split(RESP_SPLIT).length == resp_count + 1 || cntr > 20 cntr += 1 Thread.pass if skt.wait_readable 0.001 begin resp << skt.read_response rescue EOFError break end end end results[idx] = resp else results[idx] = body_only ? skt.read_body : skt.read_response end rescue StandardError => e results[idx] = e.class end begin skt.close unless skt.closed? # skt.close may return Errno::EBADF rescue StandardError => e results[idx] ||= e.class end skts[idx] = nil end end end.join 15 results end
#send_http(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil) ⇒ PumaSSLSocket, ...
Sends a request and returns the socket
# File 'test/helpers/test_puma/puma_socket.rb', line 181
def send_http(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil) new_socket(host: host, port: port, path: path, ctx: ctx, session: session) .send_http req end
#send_http_array(req = GET_11, len, dly: 0.000_1, max_retries: 5) ⇒ Array<PumaSSLSocket, PumaTCPSocket, PumaUNIXSocket>
Creates an array of sockets, sending a request on each
# File 'test/helpers/test_puma/puma_socket.rb', line 260
def send_http_array(req = GET_11, len, dly: 0.000_1, max_retries: 5) Array.new(len) { retries = 0 begin skt = send_http req sleep dly if dly skt rescue Errno::ECONNREFUSED retries += 1 if retries < max_retries retry else flunk 'Generate requests failed from Errno::ECONNREFUSED' end end } end
#send_http_read_all(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: RESP_READ_LEN, timeout: 15) ⇒ String
Sends a request and returns whatever can be read. Use when multiple responses are sent by the server
# File 'test/helpers/test_puma/puma_socket.rb', line 159
def send_http_read_all(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: RESP_READ_LEN, timeout: 15) send_http(req, host: host, port: port, path: path, ctx: ctx, session: session) .read_all(timeout: timeout, len: len) end
#send_http_read_body(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil) ⇒ Response Also known as: #send_http_read_resp_body
Sends a request and returns the HTTP response body.
#send_http_read_headers(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil) ⇒ Array<String>
Also known as: #send_http_read_resp_headers
Sends a request and returns the response header lines as an array of strings. Includes the status line.
# File 'test/helpers/test_puma/puma_socket.rb', line 132
def send_http_read_headers(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil) send_http(req, host: host, port: port, path: path, ctx: ctx, session: session) .read_response(timeout: timeout, len: len) .split(RESP_SPLIT, 2).first.split "\r\n" end
#send_http_read_resp_body(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil)
Alias for #send_http_read_body.
# File 'test/helpers/test_puma/puma_socket.rb', line 151
alias :send_http_read_resp_body :send_http_read_body
#send_http_read_resp_headers(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil)
Alias for #send_http_read_headers.
# File 'test/helpers/test_puma/puma_socket.rb', line 150
alias :send_http_read_resp_headers :send_http_read_headers
#send_http_read_response(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil) ⇒ Response
Sends a request and returns the HTTP response. Assumes one response is sent.
# File 'test/helpers/test_puma/puma_socket.rb', line 170
def send_http_read_response(req = GET_11, host: nil, port: nil, path: nil, ctx: nil, session: nil, len: nil, timeout: nil) send_http(req, host: host, port: port, path: path, ctx: ctx, session: session) .read_response(timeout: timeout, len: len) end
#skt_closed_by_server(socket) ⇒ Boolean
Determines whether the socket has been closed by the server. Only works when Socket::TCP_INFO is defined, linux/Ubuntu
# File 'test/helpers/test_puma/puma_socket.rb', line 192
def skt_closed_by_server(socket) skt = socket.to_io return false unless skt.kind_of?(TCPSocket) begin tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO) rescue IOError, SystemCallError false else state = tcp_info.unpack('C')[0] # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11 (state >= 6 && state <= 9) || state == 11 end end