How do I safely join relative url segments?

前端 未结 11 1837
南方客
南方客 2020-12-30 19:27

I\'m trying to find a robust method of joining partial url path segments together. Is there a quick way to do this?

I tried the following:

puts URI::         


        
相关标签:
11条回答
  • 2020-12-30 20:08

    The way to do it using URI.join is:

    URI.join('http://example.com', '/foo/', 'bar')

    Pay attention to the trailing slashes. You can find the complete documentation here:

    http://www.ruby-doc.org/stdlib-1.9.3/libdoc/uri/rdoc/URI.html#method-c-join

    0 讨论(0)
  • 2020-12-30 20:10

    A not optimized solution. Note that it doesn't take query params into account. It only handles paths.

    class URL
      def self.join(*str)
        str.map { |path|
          new_path = path
          # Check the first character
          if path[0] == "/"
            new_path = new_path[1..-1]
          end
    
          # Check the last character
          if path[-1] != "/"
            new_path += "/"
          end
    
          new_path
        }.join
      end
    end
    
    0 讨论(0)
  • 2020-12-30 20:11

    Using File.join is not robust since it will use the OS filesystem separator, which in Windows is \ instead of /, losing portability.

    As you noticed, URI::join won't combine paths with repeated slashes, so it doesn't fit the part.

    Turns out it doesn't require a lot of Ruby code to achieve this:

    module GluePath
    
      def self.join(*paths, separator: '/')
        paths = paths.compact.reject(&:empty?)
        last = paths.length - 1
        paths.each_with_index.map { |path, index|
          _expand(path, index, last, separator)
        }.join
      end
    
      def self._expand(path, current, last, separator)
        if path.start_with?(separator) && current != 0
          path = path[1..-1]
        end
    
        unless path.end_with?(separator) || current == last
          path = [path, separator]
        end
    
        path
      end
    end
    

    The algorithm takes care of consecutive slashes, preserves start and end slashes, and ignores nil and empty strings.

    puts GluePath::join('resource/', '/edit', '12?option=test')
    

    outputs

    resource/edit/12?option=test
    
    0 讨论(0)
  • 2020-12-30 20:13

    Use this code:

    File.join('resource/', '/edit', '12?option=test').
         gsub(File::SEPARATOR, '/').
         sub(/^\//, '')
    # => resource/edit/12?option=test
    

    example with empty strings:

    File.join('', '/edit', '12?option=test').
         gsub(File::SEPARATOR, '/').
         sub(/^\//, '')
    # => edit/12?option=test
    

    Or use this if possible to use segments like resource/, edit/, 12?option=test and where http: is only a placeholder to get a valid URI. This works for me.

    URI.
      join('http:', 'resource/', 'edit/', '12?option=test').
      path.
      sub(/^\//, '')
    # => "resource/edit/12"
    
    0 讨论(0)
  • 2020-12-30 20:19

    URI's api is not neccearily great.

    URI::join will work only if the first one starts out as an absolute uri with protocol, and the later ones are relative in the right ways... except I try to do that and can't even get that to work.

    This at least doesn't error, but why is it skipping the middle component?

     URI::join('http://somewhere.com/resource', './edit', '12?option=test') 
    

    I think maybe URI just kind of sucks. It lacks significant api on instances, such as an instance #join or method to evaluate relative to a base uri, that you'd expect. It's just kinda crappy.

    I think you're going to have to write it yourself. Or just use File.join and other File path methods, after testing all the edge cases you can think of to make sure it does what you want/expect.

    edit 9 Dec 2016 I figured out the addressable gem does it very nicely.

    base = Addressable::URI.parse("http://example.com")
    base + "foo.html"
    # => #<Addressable::URI:0x3ff9964aabe4 URI:http://example.com/foo.html>
    
    base = Addressable::URI.parse("http://example.com/path/to/file.html")
    base + "relative_file.xml"
    # => #<Addressable::URI:0x3ff99648bc80 URI:http://example.com/path/to/relative_file.xml>
    
    base = Addressable::URI.parse("https://example.com/path")
    base + "//newhost/somewhere.jpg"
    # => #<Addressable::URI:0x3ff9960c9ebc URI:https://newhost/somewhere.jpg>
    
    base = Addressable::URI.parse("http://example.com/path/subpath/file.html")
    base + "../up-one-level.html"
    => #<Addressable::URI:0x3fe13ec5e928 URI:http://example.com/path/up-one-level.html>
    
    0 讨论(0)
  • 2020-12-30 20:21

    The problem is that resource/ is relative to the current directory, but /edit refers to the top level directory due to the leading slash. It's impossible to join the two directories without already knowing for certain that edit contains resource.

    If you're looking for purely string operations, simply remove the leading or trailing slashes from all parts, then join them with / as the glue.

    0 讨论(0)
提交回复
热议问题