The Crystal Programming Language Forum

Fetching post params

Hello Everyone!

I swear I had this thing working before but now I’m stumped! I can’t seem to get to the POST params, any idea what I’m doing wrong?

params = HTTP::Params.parse((context.request.query || "") + "&" + (context.request.body.try &.gets_to_end || ""))
pp params # no posted param shows up! HTTP::Params(@raw_params={})

The form has an enctype set

<form method="post" enctype="application/x-www-form-urlencoded">

I think it would be super nice if the Request class had a params that took care of this! ;)

Thanks in advance!

Well, it looks like it certainly works in a simple setting like so:

require "http"

server = HTTP::Server.new do |context|
  params = HTTP::Params.parse((context.request.query || "") + "&" + (context.request.body.try &.gets_to_end || ""))
  pp params
  context.response.content_type = "text/plain"
  context.response.print "Hello world!"
end

address = server.bind_tcp 9090
puts "Listening on http://#{address}"
server.listen

same when using a handler BUT NOT when chaining a bunch of handlers, so this fails

module Foo
  class CustomHandler
    include HTTP::Handler

    def call(context)
      params = HTTP::Params.parse((context.request.query || "") + "&" + (context.request.body.try &.gets_to_end || ""))
      pp params
      context.response.content_type = "text/plain"
      context.response.print "Hello world!"

      call_next(context)
    end
  end

  class CustomHandler2
    include HTTP::Handler

    def call(context)
      params = HTTP::Params.parse((context.request.query || "") + "&" + (context.request.body.try &.gets_to_end || ""))
      pp params
      context.response.content_type = "text/plain"
      context.response.print "Hello world!"

      call_next(context)
    end
  end
end

server = HTTP::Server.new([
  HTTP::ErrorHandler.new,
  HTTP::LogHandler.new,
  HTTP::CompressHandler.new,

  Foo::CustomHandler.new,
  Foo::CustomHandler2.new,
  HTTP::StaticFileHandler.new("public"),
])

server.bind_tcp "127.0.0.1", 9090
server.listen

testing it like so:

curl -v --data 'id=2&email=foo' 'localhost:9090/'

outputs (pp)

HTTP::Params(@raw_params={"id" => ["2"], "email" => ["foo"]})
HTTP::Params(@raw_params={})

ie, one of the custom handlers doesn’t get any params because the body has been gets_to_end in the other. Now onto my question, is this by design or a bug because I’d expect every handler get a clean context/request environment to work with. If this is indeed by design, then is chaining more than one Handler not recommended? And if this is so, what happens when one in the chain (Error or Log or Compress for ex) consumes an IO that the others can’t get their hands on anymore?

Overall, I think, the model is slightly confusing and or broken, does anyone agree or am I completely mistaken here on how this thing works?

As a workaround, I decided to open and add a params to Request like so:

class HTTP::Request
  def params
    @params ||= HTTP::Params.parse((query || "") + "&" + (body.try &.gets_to_end || ""))
  end
end

module Foo
  class CustomHandler
    include HTTP::Handler

    def call(context)
    #   params = HTTP::Params.parse((context.request.query || "") + "&" + (context.request.body.try &.gets_to_end || ""))
      pp context.request.params
      context.response.content_type = "text/plain"
      context.response.print "Hello world!"

      call_next(context)
    end
  end

  class CustomHandler2
    include HTTP::Handler

    def call(context)
    #   params = HTTP::Params.parse((context.request.query || "") + "&" + (context.request.body.try &.gets_to_end || ""))
      pp context.request.params
      context.response.content_type = "text/plain"
      context.response.print "Hello world!"

      call_next(context)
    end
  end
end

and now testing with curl produces this:

HTTP::Params(@raw_params={"id" => ["2"], "email" => ["foo"]})
HTTP::Params(@raw_params={"id" => ["2"], "email" => ["foo"]})

Not sure if this approach is recommended but it certainly eliminates the problem I’ve been having. Can someone with more knowledge on this care to comment if this would end up causing trouble? Much thanks!

At the moment this is expected due to the fact the request body is an IO which gets consumed using .gets_to_end. I created an issue about this a little while ago, of which I did essentially the same thing as you, but I’m not sold on if it’s the “right” long term fix, or more of a workaround.

1 Like

Perfect! Thanks @Blacksmoke16