I'm currently writing a pure ruby webserver, and one of the things that I have to do is parse a HTTP request. The method I've pasted below takes a HTTP request, and puts it in a map keyed by the header field names.
The biggest issue I faced while doing this was dealing with the lack of an EOF
from the TCPSocket
on requests with bodies (basically every POST
request). This meant that I couldn't just keep doing file.gets
until I reached the end of the file, because there was no end. What I ended up doing instead, was to create a while true
loop that breaks when it finds '\r\n'
, which are the last characters before the Body
, then read the Body
separately using the number I get from Content-length
.
Is there a more elegant way in ruby
to do this? I feel like it's unnecessary to use a infinite loop, but I can't think of anything else that would work.
# Takes a HTTP request and parses it into a map that's keyed
# by the title of the heading and the heading itself.
# Request should always be a TCPSocket object.
def self.parse_http_request(request)
headers = {}
#get the first heading (first line)
headers['Heading'] = request.gets.gsub /^"|"$/, ''.chomp
method = headers['Heading'].split(' ')[0]
#parse the header
while true
#do inspect to get the escape characters as literals
#also remove quotes
line = request.gets.inspect.gsub /^"|"$/, ''
#if the line only contains a newline, then the body is about to start
break if line.eql? '\r\n'
label = line[0..line.index(':')-1]
#get rid of the escape characters
val = line[line.index(':')+1..line.length].tap{|val|val.slice!('\r\n')}.strip
headers[label] = val
end
#If it's a POST, then we need to get the body
if method.eql?('POST')
headers['Body'] = request.read(headers['Content-Length'].to_i)
end
return headers
end