web-dev-qa-db-fra.com

Comment joindre en toute sécurité des segments d'URL relatifs?

J'essaie de trouver une méthode robuste pour joindre des segments de chemin d'URL partiels. Y a-t-il un moyen rapide de faire cela?

J'ai essayé ce qui suit:

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

Je m'attends à:

resource/edit/12?option=test

Mais je reçois l'erreur:

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

J'ai utilisé File.join() dans le passé pour cela, mais quelque chose ne semble pas correct concernant l'utilisation de la bibliothèque de fichiers pour les URL.

26
Tim Santeford

L'API d'URI n'est pas vraiment génial. 

URI :: join ne fonctionnera que si le premier commence comme un uri absolu avec un protocole, et que les derniers sont relatifs de la bonne manière ... sauf que j'essaie de le faire et que je ne peux même pas le faire fonctionner. 

Ceci au moins n’est pas une erreur, mais pourquoi saute-t-il le composant du milieu?

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

Je pense que peut-être que URI est un peu nul. Il manque des instances d'api on significatives, telles qu'une instance #join ou une méthode à évaluer par rapport à une base uri, que vous êtes en droit d'attendre. C'est juste un peu merdique. 

Je pense que vous allez devoir l'écrire vous-même. Ou utilisez simplement File.join et d'autres méthodes de chemin de fichier, après avoir testé tous les cas Edge auxquels vous pouvez penser pour vous assurer qu'il répond à vos attentes. 

edit 9 déc 2016 J'ai pensé que le adressable gem le fait très bien. 

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>
21
jrochkind

Le problème est que resource/ est relatif au répertoire en cours, mais /edit fait référence au répertoire de niveau supérieur en raison de la barre oblique. Il est impossible de joindre les deux répertoires sans déjà être certain que edit contient resource

Si vous recherchez uniquement des opérations sur des chaînes, supprimez simplement les barres obliques de fin ou de fin, puis associez-les avec / comme colle. 

8
Tim

La façon de le faire en utilisant URI.join est:

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

Faites attention aux barres obliques. Vous pouvez trouver la documentation complète ici:

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

6
amit_saxena

Avoir uri comme URI::Generic ou une sous-classe de celui-ci 

uri.path += '/123' 

Prendre plaisir!

25/06/2016 UPDATE pour les sceptiques

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

Les sorties

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

Courez-moi

4
bioffe

L'utilisation de File.join n'est pas robuste car elle utilisera le séparateur de système de fichiers du système d'exploitation, qui sous Windows est \ au lieu de /, perd la portabilité.

Comme vous l'avez remarqué, URI::join ne combinera pas les chemins avec des barres obliques répétées, il ne correspond donc pas à la pièce.

Il s'avère que cela ne nécessite pas beaucoup de code Ruby pour y parvenir:

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

L'algorithme prend en charge les barres consécutives, conserve les barres de début et de fin, et ignore les chaînes nil et les chaînes vides.

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

les sorties

resource/edit/12?option=test
3
Maximo Mussini

Utilisez ce code:

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

exemple avec des chaînes vides:

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

Ou utilisez ceci si possible pour utiliser des segments tels que resource/, edit/, 12?option=test et où http: est uniquement un espace réservé pour obtenir un URI valide. Cela fonctionne pour moi.

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

J'ai amélioré le script de @ Maximo Mussini pour qu'il fonctionne correctement:

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
0
Zernel

Vous pouvez utiliser ceci:

URI.join('http://exemple.com', '/a/', 'b/', 'c/', 'd')
=> #<URI::HTTP 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"

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

0
gndm