Подтвердить что ты не робот

Как безопасно присоединяться к сегментам относительного 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-адресов.

4b9b3361

Ответ 1

URI api не всегда велик.

URI:: join будет работать только в том случае, если первый запускается как абсолютный uri с протоколом, а более поздние - относительно правильным образом... за исключением того, что я пытаюсь это сделать и даже не могу получить это Работа.

Это по крайней мере не ошибка, но почему она пропускает средний компонент?

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

Я думаю, что URI просто отстой. Он не имеет значительных api для экземпляров, таких как экземпляр #join или метод для оценки относительно базового uri, который вы ожидаете. Это просто любопытное дерьмо.

Думаю, вам придется писать это самостоятельно. Или просто используйте File.join и другие методы пути к файлу, после тестирования всех крайних случаев, о которых вы можете думать, чтобы убедиться, что он делает то, что вы хотите/ожидаете.

edit 9 декабря 2016 г. Я понял, что addressable gem делает это очень красиво.

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>

Ответ 2

Проблема заключается в том, что resource/ относится к текущему каталогу, но /edit относится к каталогу верхнего уровня из-за ведущей косой черты. Невозможно присоединиться к двум каталогам, не зная наверняка, что edit содержит resource.

Если вы ищете чисто строковые операции, просто удалите ведущие или конечные косые черты из всех частей, затем присоедините их к / как клей.

Ответ 4

Использование File.join не является устойчивым, так как он будет использовать ОСА файловой системы разделитель, который в окнах \ вместо /, теряя портативность.

Как вы заметили, URI::join не будет объединять пути с повторяющимися косыми чертами, поэтому он не подходит для части.

Оказывается, для этого не нужно много кода на 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.start_with?(separator) && current != 0
      path = path[1..-1]
    end

    unless path.end_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

Ответ 5

Имеет uri как URI::Generic или его подкласс

uri.path += '/123' 

Наслаждайтесь!

25/06/2016 UPDATE для скептически настроенных людей

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

Выходы

 <URI::HTTP:0x2341a58 URL:http://ioffe.net/boris/123>

Запустите меня

Ответ 6

Используйте этот код:

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"

Ответ 7

Я улучшил @Maximo Mussini script, чтобы он работал изящно:

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

Ответ 9

Мое понимание URI::join заключается в том, что он мыслит так же, как веб-браузер.

Чтобы оценить его, наведите указатель на свой интеллектуальный веб-браузер на первый параметр и продолжайте нажимать ссылки, пока не перейдете к последнему параметру.

Например, URI::join('http://example.com/resource/', '/edit', '12?option=test'), вы можете просмотреть это так:

Если первая ссылка была /edit/ (с косой чертой) или /edit/foo, то следующая ссылка будет относиться к /edit/, а не к /.

Возможно, эта страница объясняет это лучше, чем я: Почему URI.join так нелогичен?

Ответ 10

Вы можете использовать File.join('resource/', '/edit', '12?option=test')