How To Pass Multiple Parameters In A URL To Plug

10,079

Solution 1

The URL structure you want to use is not really well-formed. And therefore your issue is framework-agnostic, because almost all frameworks/libraries will have a problem to parse the URL in your intended way.

/uid=ToddFlanders&pwd=MyTestPword is treated as a path without any query parameters.

PATH: /uid=ToddFlanders&pwd=MyTestPword
QUERY STRING: (empty/unset)
QUERY PARAMS: [ ]

I guess you really want to have something like /?uid=ToddFlanders&pwd=MyTestPword.

PATH: /
QUERY STRING: uid=ToddFlanders&pwd=MyTestPword
QUERY PARAMS: [ uid: "ToddFlanders", pwd: "MyTestPword" ]

Furthermore Plug.Conn.Utils.params/1 is intended to parse header parameters. Use Plug.Conn.Query.decode/1 for query parameters instead.

So, in your example I would suggest to adjust the application code as following:

defmodule Sci do
  import Plug.Conn
  use Plug.Router

  @userid   "uid"
  @password "pwd"

  plug :match
  plug :dispatch

  get "/" do
    conn = fetch_params(conn) # populates conn.params
    %{ @userid => usr, @password => pass } = conn.params
    send_resp(conn, 200, "Hello #{usr}. Your password is #{pass}")
  end

  match _ do
    send_resp(conn, 404, "oops")
  end

  def start do
    Plug.Adapters.Cowboy.http Sci, [], port: 4000
  end

  def stop do
    Plug.Adapters.Cowboy.shutdown Sci.HTTP
  end
end

And then you can successfully try http://localhost:4000/?uid=ToddFlanders&pwd=MyTestPword.

(Also it should not hit the match(_) function, because / should always match if you do the request to this path properly.

Solution 2

I'm not versatile with Plug, but for a demo code, I used fetch_params/1 and then conn.params["foo"], conn.params["bar"] and so on, and this definitely worked.

Update: since Plug 0.12.0, fetch_query_params/2 should be used

Solution 3

You should use fetch_query_params now. https://hexdocs.pm/plug/Plug.Conn.html#fetch_query_params/2

Share:
10,079
Onorio Catenacci
Author by

Onorio Catenacci

I've been writing software for 25+ years now. I pursue ornithology as an avocation. If you want to contact me, contact me via my blog. http://onor.io av00M52Kg330gF5G

Updated on June 04, 2022

Comments

  • Onorio Catenacci
    Onorio Catenacci almost 2 years

    So I'm trying to pass multiple parameters in a URL to plug. This is the URL I'm using for testing:

    http://localhost:4000/uid=ToddFlanders&pwd=MyTestPword
    

    And this is the code:

    defmodule Sci do
        @userid "uid"
        @password "pwd"
        import Plug.Conn
        import Plug.Conn.Utils
        use Plug.Router
    
    
        plug :match
        plug :dispatch
    
        get "/:args" do
            %{@userid => usr} = params(args)
            %{@password => pass} = params(args)
    
            send_resp(conn, 200, "Hello #{usr}. Your password is #{pass}")
        end
    
        match _ do
            send_resp(conn, 404, "oops")
      end
    
      def start do
        Plug.Adapters.Cowboy.http Sci, [], port: 4000
      end
    
      def stop do
        Plug.Adapters.Cowboy.shutdown Sci.HTTP
      end
    end
    

    If I pass just one argument (either uid or pwd) it matches fine. If I remove the ? at the front of the query string it fails--throws an exception.

    I also tried this:

    get "/:args" do
      %{@userid => usr, @password => pass} = params(args)
    

    Also didn't work. So two questions:

    1.) Is that the right way to pass multiple parameters in a URL?

    2.) Is there some trick to get plug to see both parameters?

    Any thoughts or insights would be welcome--even a suggestion of a better way to do this.

    Elixir v0.15.1 and Plug v0.5.1

    EDIT:

    Adding a stacktrace per Jose's comment:

    iex(2)> 07:46:56.499 [error] Error in process <0.232.0> with exit value: {[{reason,{badmatch,#{}}},{mfa,{'Elixir.Plug.Adapters.Cowboy.Handler',init,3}},{stacktrace,[{'Elixir.Sci','-do_match/2-fun-0-',2,[{file,"lib/sci.ex"},{line,18}]},{'Eli
    xir.Sci',call,2,[{file,"lib/sci.ex"},{line,1}]},{'Elixir.Plug.Adapters.Cowboy.Handler'...                                                                                                                                                       
    
    
    iex(2)> 07:46:56.510 [error] Ranch listener Sci.HTTP had connection process started with :cowboy_protocol:start_link/4 at #PID<0.232.0> exit with reason: {[reason: {:badmatch, %{}}, mfa: {Plug.Adapters.Cowboy.Handler, :init, 3}, stacktrace:
     [{Sci, :"-do_match/2-fun-0-", 2, [file: 'lib/sci.ex', line: 18]}, {Sci, :call, 2, [file: 'lib/sci.ex', line: 1]}, {Plug.Adapters.Cowboy.Handler, :init, 3, [file: 'lib/plug/adapters/cowboy/handler.ex', line: 7]}, {:cowboy_handler, :handler_
    init, 4, [file: 'src/cowboy_handler.erl', line: 64]}, {:cowboy_protocol, :execute, 4, [file: 'src/cowboy_protocol.erl', line: 435]}], req: [socket: #Port<0.5168>, transport: :ranch_tcp, connection: :keepalive, pid: #PID<0.232.0>, method: "G
    ET", version: :"HTTP/1.1", peer: {{127, 0, 0, 1}, 63880}, host: "localhost", host_info: :undefined, port: 4000, path: "/uid=ToddFlanders&pwd=MyTestPassword", path_info: :undefined, qs: "", qs_vals: :undefined, bindings: [], headers: [{"host
    ", "localhost:4000"}, {"connection", "keep-alive"}, {"cache-control", "max-age=0"}, {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"}, {"user-agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/53
    7.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"}, {"accept-encoding", "gzip,deflate,sdch"}, {"accept-language", "en-US,en;q=0.8"}], p_headers: [{"connection", ["keep-alive"]}], cookies: :undefined, meta: [], body_state: :waitin
    g, buffer: "", multipart: :undefined, resp_compress: false, resp_state: :waiting, resp_headers: [], resp_body: "", onresponse: :undefined], opts: {Sci, []}], [{:cowboy_protocol, :execute, 4, [file: 'src/cowboy_protocol.erl', line: 435]}]}  
    
    iex(2)> 07:46:56.969 [error] Error in process <0.233.0> with exit value: {[{reason,{badmatch,#{}}},{mfa,{'Elixir.Plug.Adapters.Cowboy.Handler',init,3}},{stacktrace,[{'Elixir.Sci','-do_match/2-fun-0-',2,[{file,"lib/sci.ex"},{line,18}]},{'Eli
    xir.Sci',call,2,[{file,"lib/sci.ex"},{line,1}]},{'Elixir.Plug.Adapters.Cowboy.Handler'...                                                                                                                                                       
    
    
    iex(2)> 07:46:56.971 [error] Ranch listener Sci.HTTP had connection process started with :cowboy_protocol:start_link/4 at #PID<0.233.0> exit with reason: {[reason: {:badmatch, %{}}, mfa: {Plug.Adapters.Cowboy.Handler, :init, 3}, stacktrace:
     [{Sci, :"-do_match/2-fun-0-", 2, [file: 'lib/sci.ex', line: 18]}, {Sci, :call, 2, [file: 'lib/sci.ex', line: 1]}, {Plug.Adapters.Cowboy.Handler, :init, 3, [file: 'lib/plug/adapters/cowboy/handler.ex', line: 7]}, {:cowboy_handler, :handler_
    init, 4, [file: 'src/cowboy_handler.erl', line: 64]}, {:cowboy_protocol, :execute, 4, [file: 'src/cowboy_protocol.erl', line: 435]}], req: [socket: #Port<0.5174>, transport: :ranch_tcp, connection: :keepalive, pid: #PID<0.233.0>, method: "G
    ET", version: :"HTTP/1.1", peer: {{127, 0, 0, 1}, 63881}, host: "localhost", host_info: :undefined, port: 4000, path: "/favicon.ico", path_info: :undefined, qs: "", qs_vals: :undefined, bindings: [], headers: [{"host", "localhost:4000"}, {"
    connection", "keep-alive"}, {"accept", "*/*"}, {"user-agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"}, {"accept-encoding", "gzip,deflate,sdch"}, {"accept-language", "e
    n-US,en;q=0.8"}], p_headers: [{"connection", ["keep-alive"]}], cookies: :undefined, meta: [], body_state: :waiting, buffer: "", multipart: :undefined, resp_compress: false, resp_state: :waiting, resp_headers: [], resp_body: "", onresponse: 
    :undefined], opts: {Sci, []}], [{:cowboy_protocol, :execute, 4, [file: 'src/cowboy_protocol.erl', line: 435]}]}                                                                                                                                 
    

    EDIT 2: I had jotted down the wrong URL above; that's now corrected. Also to add to the question, if I make the URL this: http://localhost:4000/?uid=ToddFlanders&pwd=MyTestPword, the code jumps down to the match _ do clause

  • Onorio Catenacci
    Onorio Catenacci over 9 years
    It's a good suggestion but it still doesn't seem to be working. Could you point me to your demo code?
  • sasajuric
    sasajuric over 9 years
    I don't have it online, but here's a sketch: gist.github.com/sasa1977/99cc0526f5b4dd903e21
  • Onorio Catenacci
    Onorio Catenacci over 9 years
    That doesn't seem to do it either Sasa. I'd paste the stacktrace up here but I get the feeling that I'm missing something very obvious.
  • sasajuric
    sasajuric over 9 years
    Here's the working demo: gist.github.com/sasa1977/1917609ecd6127f736e6. I've just tried with Elixir 0.15.0 and Plug 0.6. Include it in your mix project, make sure you call Web.start_server from somewhere, and try it out. I can't see your code, so a blind guess - are you threading connection while handling a request? This is crucial, because every operation (such as fetch_params) returns a modified conn struct. If you run fetch_params, but then look into the previous version of conn struct, you won't find those params there. You have to use the result of fetch_params (new conn).
  • Onorio Catenacci
    Onorio Catenacci over 9 years
    I'll take a look at the working demo when I get a chance; thanks for your assistance Sasa!
  • Onorio Catenacci
    Onorio Catenacci over 9 years
    Ok, I have to admit the difference between header parameters and query parameters wasn't clear to me but after having read your response (among those others) I see that I was mixing up the two ideas.
  • CoderDennis
    CoderDennis about 8 years
    The docs link in this answer now leads to a 404 and I'm getting (UndefinedFunctionError) undefined function Plug.Conn.fetch_params/1. Was this function replaced by something else? (P.S. I'm enjoying your book!)
  • CoderDennis
    CoderDennis about 8 years
    fetch_query_params looks like the actual function name.
  • sasajuric
    sasajuric about 8 years
    @CoderDennis Yes, this happened at 0.12.0, see github.com/elixir-lang/plug/blob/…. I'll update the answer, thanks for pointing this out. (P.S. glad you like the book :-) )