Nikita Barsukov
Nikita Barsukov

Reputation: 2984

Ruby script does not send body of POST request to server

I am writing a mock server in Ruby to test our internal API. I'd like to include a body of POST request in server response. I use Net::HTTP::Server and Net::HTTP class. Right now mock server looks like this:

require 'net/http/server'
require 'pp'
require 'json'

Net::HTTP::Server.run(:port => 8080) do |request,stream|
  pp request
  [200, {'Content-Type' => 'text/html'}, [request[:body]]]
end

Script that sends POST request with body is this:

require 'net/http'
require 'uri'

url = URI.parse(http://localhost:8080/)
x = {"country" => "Ukraine", "city" => 'Kiev'}

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url.path)
request.set_form_data(x)
response = http.request(request)

puts "Request body: #{request.body}"
puts "Response Body: #{response.body}"

However server log indicates that POST request did not contain body:

#Output from server    
{:method=>"POST"@0,
 :uri=>{:path=>"/"},
 :version=>"1.1"@12,
 :headers=>
  {"Accept"@17=>"*/*"@25,
   "User-Agent"@30=>"Ruby"@42,
   "Content-Type"@48=>"application/x-www-form-urlencoded"@62,
   "Connection"@97=>"close"@109,
   "Host"@116=>"localhost:8080"@122,
   "Content-Length"@138=>"25"@154}}

#Output from script file:
Request body: country=Ukraine&city=Kiev
Response body:  

What did I do wrong?

Related: Parse body of POST reqest in self-made server in Ruby

Upvotes: 1

Views: 1826

Answers (3)

Alexis
Alexis

Reputation: 171

Post requests are not supported. They are not included in the spec. Calling stream.body will block if the request body content-length is not a multiple of 4096.

Because https://github.com/postmodern/net-http-server/blob/master/lib/net/http/server/stream.rb calls

IO.read(4096, '')

You can get around by reading the socket in a non-blocking way if you don't know the size in advance. For example following handler will try to read the Request body and then simply return it in the Response body. If Content-Length is provided in the header, it will block waiting for the stream to send enough data till specified amount of bytes.

class PostHandler
 BUF_SIZE = 4096

 def call(request, stream)
    case request[:method].to_s
    when "POST"
        body = nil
        if request[:headers]["Content-Type"] == "application/x-www-form-urlencoded"
            body = readBlock(stream.socket, request[:headers]["Content-Length"].to_i)
        else
            body = readNonblock(stream.socket)
        end
        unless body.nil?
            return [200, {}, [body] ]
        end
     end 

     [404, {}, [""]]
  end 

  private

  # blocking way to read the stream
  def readBlock(socket, length)
    socket.read(length)
  end

  # non-blocking alternative
  # stop reading as soon as it would block
  def readNonblock(socket)
    buffer = ""
    loop {
       begin
           buffer << socket.read_nonblock(BUF_SIZE)
       rescue Errno::EAGAIN
           # Resource temporarily unavailable - read would block
           break
       end
    }
    buffer
  end

end 

Upvotes: 2

Jakobinsky
Jakobinsky

Reputation: 1294

I'm working on a similar thing at the moment. For "quick and dirty" testing I would suggest curl. For example:

GET:

curl http://localhost:3000/locations"

POST:

curl http://localhost:3000/locations -d "location[country]=Ukraine&location[city]=Kiev"

PUT:

curl http://localhost:3000/locations/1 -X PUT -d "location[country]=Ukraine"

DELETE:

curl http://localhost:3000/locations/1 -X DELETE"

Having said that, for more robust testing, assuming you are using Rails/Rack, I'm using Cucumber for integration tests for the API. where you can simply use the rais/rack helpers, for example, in use case, for POST:

url = "http://localhost:3000/locations/"
hash = { "location" => { "country => "Ukraine", "city" => "Kiev" }
post url, hash

Upvotes: 1

emboss
emboss

Reputation: 39650

I assume it's a problem with net-http-server.

Try with WEBrick as your server:

require 'webrick'
include WEBrick   

def start_webrick(config = {})
  config.update(:Port => 8080)     
  server = HTTPServer.new(config)
  yield server if block_given?
  ['INT', 'TERM'].each {|signal| 
    trap(signal) {server.shutdown}
  }
  server.start
end

start_webrick {|server|
  server.mount_proc('/') {|req, resp| 
    resp.body = req.body
  }
}

This works for me, output is:

country=Ukraine&city=Kiev

Upvotes: 1

Related Questions