如何安全地加入相对url段?

我试图找到一种将部分url路径段连接在一起的可靠方法。 有快速的方法吗?

我尝试了以下方法:

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

我预计:

 resource/edit/12?option=test 

但我得到错误:

 `merge': both URI are relative (URI::BadURIError) 

我过去曾使用过File.join() ,但是对于使用url的文件库来说似乎并不正确。

URI的api并不是很好。

URI :: join只有在第一个以协议开头的绝对uri时才会起作用,而后者则以正确的方式相对…除了我尝试这样做,甚至无法使其工作。

这至少没有错误,但为什么它会跳过中间组件?

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

我认为URI可能很糟糕。 它缺少对实例的重要API,例如实例#join或相对于基本uri的方法,您期望它。 这有点蹩脚。

我想你将不得不自己写。 或者只是使用File.join和其他文件路径方法,在测试了所有可以想到的边缘情况后,确保它能达到您想要/期望的效果。

编辑 2016年12月9日我发现可寻址的gem非常好。

 base = Addressable::URI.parse("http://example.com") base + "foo.html" # => # base = Addressable::URI.parse("http://example.com/path/to/file.html") base + "relative_file.xml" # => # base = Addressable::URI.parse("https://example.com/path") base + "//newhost/somewhere.jpg" # => # base = Addressable::URI.parse("http://example.com/path/subpath/file.html") base + "../up-one-level.html" => # 

问题是resource/是相对于当前目录,但/edit是由于前导斜杠而引用顶级目录。 在不知道edit包含resource的情况下加入这两个目录是不可能的。

如果您正在寻找纯粹的字符串操作,只需从所有部分中删除前导或尾部斜杠,然后使用/作为粘合剂将它们连接起来。

使用URI.join的方法是:

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

注意尾随斜杠。 您可以在此处找到完整的文档:

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

uri作为URI::Generic或其子类

 uri.path += '/123' 

请享用!

2016年6月25日对持怀疑态度的人进行更新

 require 'uri' uri = URI('http://ioffe.net/boris') uri.path += '/123' p uri 

输出

   

跑我

使用File.join并不健壮,因为它将使用操作系统文件系统分隔符,在Windows中它是\而不是/ ,失去了可移植性。

正如您所注意到的, URI::join不会将路径与重复斜杠组合在一起,因此它不适合该部件。

事实certificate它不需要很多Ruby代码来实现这一点:

 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.starts_with?(separator) && current != 0 path = path[1..-1] end unless path.ends_with?(separator) || current == last path = [path, separator] end path end end 

该算法处理连续斜杠,保留开始和结束斜杠,并忽略nil和空字符串。

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

输出

 resource/edit/12?option=test 

使用此代码:

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

空字符串的示例:

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

或者如果可能的话使用它来使用像resource/edit/12?option=test ,其中http:只是一个占位符来获取有效的URI。 这对我有用。

 URI. join('http:', 'resource/', 'edit/', '12?option=test'). path. sub(/^\//, '') # => "resource/edit/12" 

我改进了@Maximo Mussini的脚本,使其优雅地工作:

 SmartURI.join('http://example.com/subpath', 'hello', query: { token: secret }) => "http://example.com/subpath/hello?token=secret" 

https://gist.github.com/zernel/0f10c71f5a9e044653c1a65c6c5ad697

 require 'uri' module SmartURI SEPARATOR = '/' def self.join(*paths, query: nil) paths = paths.compact.reject(&:empty?) last = paths.length - 1 url = paths.each_with_index.map { |path, index| _expand(path, index, last) }.join if query.nil? return url elsif query.is_a? Hash return url + "?#{URI.encode_www_form(query.to_a)}" else raise "Unexpected input type for query: #{query}, it should be a hash." end end def self._expand(path, current, last) if path.starts_with?(SEPARATOR) && current != 0 path = path[1..-1] end unless path.ends_with?(SEPARATOR) || current == last path = [path, SEPARATOR] end path end end 

你可以用这个:

 URI.join('http://exemple.com', '/a/', 'b/', 'c/', 'd') => # URI.join('http://exemple.com', '/a/', 'b/', 'c/', 'd').to_s => "http://exemple.com/a/b/c/d" 

见: http : //ruby-doc.org/stdlib-2.4.1/libdoc/uri/rdoc/URI.html#method-c-join-label-Synopsis

你可以使用File.join('resource/', '/edit', '12?option=test')