ma cılay
ma cılay

Reputation: 739

Can't read HTTP Request Header correctly with Ruby 1.9.3

I'm writing a small webserver. I want to read the HTTP Request. It works when there is no body involved. But when a body is sent then I can't read the content of the body in a satisfying manner.

I read the data coming from the client via TCPSocket. The TCPSocket::gets method reads until the data for the body is received. There is no delimiter or EOF send to signal for the end of the HTTP Request body. The HTTP/1.1 Specification - Section 4.4 lists five cases to get the message length. Point 1) works. Points 2) and 4) are not relevant for my application. Point 5) is not an option because I need to send an response.

I can read the value of the Content-Length field. But when I try to "persuade" the TCPSocket to read the last part of the HTTP Request via read(contentlength) or rcv(contentlength), I have no success. Reading line-by-line until the \r\n which separates Header and Body works, but after that I'm stuck - at least in the way I want to do it.

So my questions are:

Is there a possibility to do is like I intended in the code? Are there better ways to achieve my goal of reading the HTTP Request correctly (which I really hope for)?

Here is runnable code. The parts that I want to work is in comments.

#!/usr/bin/ruby
require 'socket'

server = TCPServer.new 2000
loop do
  Thread.start(server.accept) do |client|
    hascontent = false
    contentlength = 0
    content = ""
    request = ""
    #This seems to work, but I'm not really happy with it, too much is happening in
    #the loop
    while(buf = client.readpartial(4096))
       request = request + buf
       split = request.split("\r\n")
       puts request
       puts request.dump
       puts split.length
       puts split.inspect
       if(request.index("\r\n\r\n")>0)
         break
       end
    end
#This part is commented out because it doesn't work
=begin
    while(line = client.gets)
       puts ":" + line
       request = request + line
       if(line.start_with?("Content-Length"))
         hascontent = true
         split = line.split(' ')
         contentlength = split[1]
       end
       if(line == "\r\n" and !hascontent)
         break
       end
       if(line == "\r\n" and hascontent)
         puts "Trying to get content :P"
         puts contentlength
         puts content.length
         puts client.inspect
         #tried read, with and without parameter, rcv, also with and 
         #without param and their nonblocking couterparts
         #where does my thought process go in the wrong direction  
         while(readin = client.readpartial(contentlength))
           puts readin
           content = content + readin
         end
         break
       end
    end
=end
    puts request
    client.close
 end

Upvotes: 3

Views: 557

Answers (1)

Sancarn
Sancarn

Reputation: 2824

So... I have just had this issue for the past 2 hours also, and so I did some digging into the Socket API. Turns out Socket extends BasicSocket which has a method recvmsg. When I tried calling it I got the following:

["GET / HTTP/1.1\r\nHost: localhost:12357\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9\r\n\r\n", #<Addrinfo: empty-sockaddr SOCK_STREAM>, 0]

I.E. My the complete HTTP request, the sender's address information and any other ruby flags raised.

You can use recvmsg to read the entire HTTP request:

raw_request = client.recvmsg()
request = /(?<METHOD>\w+) \/(?<RESOURCE>[^ ]*) HTTP\/1.\d\r\n(?<HEADERS>(.+\r\n)*)(?:\r\n)?(?<BODY>(.|\s)*)/i.match(raw_request)
p request["BODY"]

I have no idea how to do it without recvmsg but I am glad the functionality exists.

Upvotes: 2

Related Questions