Sinatra/Rack `ERROR URI::InvalidURIError: bad URI(

2019-07-27 13:54发布

问题:

I've just started my first Sinatra project, a simple TV-show management web app, and wanted to have beautiful URLs. So when a user types in the search box and submits, I don't want to have a /?search=foobar-style URL and want do redirect them to /search/foobar. This also enables me to separate the get '/search/:name route from the main get '/' route. I've implemented the redirect using a before filter and properly escaped the params[] variable:

before do
    if params.has_key? 'search'
        redirect to("/search/#{URI.escape(params['search'])}")
    end
end

and later I continue with

get '/search/:query' do
    result = search_api params[:query]
    if result == 'null'
        # no results
    else
        result = JSON.parse(result)
        if result.key? 'shows'
            # display search results
        else
            # redirect to one single show
            # (result.keys).first is the real name of the show provided
            # by the api. It may contain special characters
            #
            # (result.keys).first #=> "Breaking Bad"
            # result.keys #=> "Breaking Bad"
            # result.key? "Breaking Bad" #=> true
            redirect to('/show/#{URI.escape((result.keys).first)}')
        end
    end
end

unfortunately, the redirect to the /show page only works, if there are no URI special characters in the name apart from %. This also means no spaces. When I search for something with a space or an umlaut or anything, e.g. a GET for /?search=Breaking%20Bad, I get the following error from Sinatra/Rack:

[2013-02-02 00:30:29] ERROR URI::InvalidURIError: bad URI(is not URI?): http://localhost:9393/show/Breaking Bad
    /Users/Ps0ke/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/uri/generic.rb:1202:in `rescue in merge'
    /Users/Ps0ke/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/uri/generic.rb:1199:in `merge'
    /Users/Ps0ke/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/httpresponse.rb:220:in `setup_header'
    /Users/Ps0ke/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/httpresponse.rb:150:in `send_response'
    /Users/Ps0ke/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/httpserver.rb:110:in `run'
    /Users/Ps0ke/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/1.9.1/webrick/server.rb:191:in `block in start_thread'

The browser shows me, that it got redirected to /search/Breaking%20Bad, so the first redirect worked. This "bug" only occurs, when the search produces an exact hit, so the problem with the redirect is to search in the get '/search/:query' route. I remember that it worked once, but can't find the right commit in my git history.

I am running

% gem list sinatra
sinatra (1.3.4, 1.3.3)

% gem list rack
rack (1.4.4, 1.4.1)
rack-cache (1.2)
rack-flash3 (1.0.3)
rack-protection (1.3.2, 1.2.0)
rack-ssl (1.3.2)
rack-test (0.6.2, 0.6.1)

Maybe someone of you can tell me:

  1. whether this is good or bad practices/there is another way in sinatra to prettifiy URLs
  2. how to fix this, since I think I have already escaped everything carefully

Thank you very much in advance :)

回答1:

In the line

redirect to('/show/#{URI.escape((result.keys).first)}')

you are using single quotes. This means that string interpolation isn’t performed, so the literal string /show/#{URI.escape((result.keys).first)} is being used as the url and that is why it is failing.

In order for interpolation to work you need to use double quotes like this:

redirect to("/show/#{URI.escape((result.keys).first)}")

This will cause #{URI.escape((result.keys).first)} to be replaced with the escaped movie name, which should be a valid url.

Note that in your first redirect you do use double quotes, so it works as expected:

redirect to("/search/#{URI.escape(params['search'])}")