1 2 3 4 5 6 7 8 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 100 101 102 103 104 105 106
|
# frozen_string_literal: true
require "http/headers"
module HTTP
class Request
class Writer
# CRLF is the universal HTTP delimiter
CRLF = "\r\n"
# Chunked data termintaor.
ZERO = "0"
# Chunked transfer encoding
CHUNKED = "chunked"
# End of a chunked transfer
CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}"
def initialize(socket, body, headers, headline)
@body = body
@socket = socket
@headers = headers
@request_header = [headline]
end
# Adds headers to the request header from the headers array
def add_headers
@headers.each do |field, value|
@request_header << "#{field}: #{value}"
end
end
# Stream the request to a socket
def stream
add_headers
add_body_type_headers
send_request
end
# Send headers needed to connect through proxy
def connect_through_proxy
add_headers
write(join_headers)
end
# Adds the headers to the header array for the given request body we are working
# with
def add_body_type_headers
return if @headers[Headers::CONTENT_LENGTH] || chunked?
@request_header << "#{Headers::CONTENT_LENGTH}: #{@body.size}"
end
# Joins the headers specified in the request into a correctly formatted
# http request header string
def join_headers
# join the headers array with crlfs, stick two on the end because
# that ends the request header
@request_header.join(CRLF) + CRLF * 2
end
def send_request
# It's important to send the request in a single write call when
# possible in order to play nicely with Nagle's algorithm. Making
# two writes in a row triggers a pathological case where Nagle is
# expecting a third write that never happens.
data = join_headers
@body.each do |chunk|
data << encode_chunk(chunk)
write(data)
data.clear
end
write(data) unless data.empty?
write(CHUNKED_END) if chunked?
end
# Returns the chunk encoded for to the specified "Transfer-Encoding" header.
def encode_chunk(chunk)
if chunked?
chunk.bytesize.to_s(16) << CRLF << chunk << CRLF
else
chunk
end
end
# Returns true if the request should be sent in chunked encoding.
def chunked?
@headers[Headers::TRANSFER_ENCODING] == CHUNKED
end
private
def write(data)
until data.empty?
length = @socket.write(data)
break unless data.bytesize > length
data = data.byteslice(length..-1)
end
end
end
end
end
|