Http/server How to get the request body?

http/server… How to get the request body?
The POST body from the client is JSON in the format below:

{
"service": "serviceName",
"method": "methodName",
"params": {}
}

Server side I tried to retrieve that like this:

postbody = context.request.body.to_s

but that gives me something like: #<HTTP::FixedLengthContent:0x7f30dcf19850>
How do I get that request body as a string?

HTTP::FixedLengthContent is an IO stream. You can consume it partially (bytewise), or entirely with #gets_to_end:

postbody = context.request.body.gets_to_end

I can’t even run with that, I get the following error:
Error: undefined method 'gets_to_end' for Nil (compile-time type is (IO | Nil))

It’s possible a request does not have a body, so it could be nil. If you are 100% sure there will be one, could do something like context.request.body.not_nil!.gets_to_end. Otherwise might be better to check for one and return a proper HTTP error back.

Thank you.
If the body is missing can I be sure this will just resolve to an empty string?

No, if the body is missing and you’re using .not_nil! it would likely result in an exception which would likely return a 500 error to the user. If this code is going to be used for real, I’d consider explicitly handling it by returning a 400 or something if there is no body and you’re expecting one.

Sorry, I’m new to Crystal. I love the syntax and I seemed to be doing OK until I hit this bump in the road :confused:

How can I test for nil in my code when the compiler won’t even let me run when that possibility exists?
Error: undefined method 'gets_to_end' for Nil (compile-time type is (IO | Nil))

Something like this should do it, assuming you’re using the default HTTP server and not a framework:

unless body = context.request.body
  break context.response.respond_with_status(400, "Request is missing a body")
end

puts body.gets_to_end

Alternative:

context.request.body.try(&.gets_to_end)

This evaluates to either a string containing the body content, or nil if there’s no body.

Thank you both for your help.

This had me going for a while but I’ve found even if there is no body in the POST request context.request.body is still not nil…

    unless body = context.request.body
        puts "It's nil"
        return
    end
    
    puts typeof(body) # IO
    postbody = body.gets_to_end
    puts typeof(postbody) # String
    if postbody == ""
        puts "It's an empty string!" # is displayed
    end

What does the POST request look like?

I just commented out the body:

const response = await fetch("/", {
    method: "POST",
    //body: JSON.stringify({"service": "foo", "method": "bar", "params": ["baz"]})
});
console.log(response);

And now I currently have this in the server’s handler:

begin
    js = JSON.parse(context.request.body.not_nil!.gets_to_end)
rescue
    do_error(context, HTTP::Status::BAD_REQUEST, "Malformed request", "text/plain")
    return    
end

It’s possible the body ain’t nil but empty? Like the server is returning content-length zero or a chunked response with an empty body?

BTW: the nilable body sounds a bit harsh. Couldn’t it always return an empty IO object?

1 Like

That JS request sends the header Content-Length: 0. That implies an empty body (not a nil body), just as you observed.

I was thinking the same.
It’s quite rare that the body is actually nil.

Oh, yes, please!

I had so many fights with code readability due that little detail.

Ended always with this noop pattern:

:crying_cat:

Quite a neat bit of lateral thinking there imo :grin:

3 Likes