@@ -123,8 +123,9 @@ use std::cmp;
123123use std:: error:: Error ;
124124use std:: fmt:: { self , Write } ;
125125use std:: hash;
126+ use std:: io;
126127use std:: mem;
127- use std:: net:: IpAddr ;
128+ use std:: net:: { IpAddr , SocketAddr , ToSocketAddrs } ;
128129use std:: ops:: { Range , RangeFrom , RangeTo } ;
129130use std:: path:: { Path , PathBuf } ;
130131use std:: str;
@@ -945,6 +946,61 @@ impl Url {
945946 self . port . or_else ( || parser:: default_port ( self . scheme ( ) ) )
946947 }
947948
949+ /// Resolve a URL’s host and port number to `SocketAddr`.
950+ ///
951+ /// If the URL has the default port number of a scheme that is unknown to this library,
952+ /// `default_port_number` provides an opportunity to provide the actual port number.
953+ /// In non-example code this should be implemented either simply as `|| None`,
954+ /// or by matching on the URL’s `.scheme()`.
955+ ///
956+ /// If the host is a domain, it is resolved using the standard library’s DNS support.
957+ ///
958+ /// # Examples
959+ ///
960+ /// ```no_run
961+ /// let url = url::Url::parse("https://example.net/").unwrap();
962+ /// let addrs = url.socket_addrs(|| None).unwrap();
963+ /// std::net::TcpStream::connect(&*addrs)
964+ /// # ;
965+ /// ```
966+ ///
967+ /// ```
968+ /// /// With application-specific known default port numbers
969+ /// fn socket_addrs(url: url::Url) -> std::io::Result<Vec<std::net::SocketAddr>> {
970+ /// url.socket_addrs(|| match url.scheme() {
971+ /// "socks5" | "socks5h" => Some(1080),
972+ /// _ => None,
973+ /// })
974+ /// }
975+ /// ```
976+ pub fn socket_addrs (
977+ & self ,
978+ default_port_number : impl Fn ( ) -> Option < u16 > ,
979+ ) -> io:: Result < Vec < SocketAddr > > {
980+ // Note: trying to avoid the Vec allocation by returning `impl AsRef<[SocketAddr]>`
981+ // causes borrowck issues because the return value borrows `default_port_number`:
982+ //
983+ // https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md#scoping-for-type-and-lifetime-parameters
984+ //
985+ // > This RFC proposes that *all* type parameters are considered in scope
986+ // > for `impl Trait` in return position
987+
988+ fn io_result < T > ( opt : Option < T > , message : & str ) -> io:: Result < T > {
989+ opt. ok_or_else ( || io:: Error :: new ( io:: ErrorKind :: InvalidData , message) )
990+ }
991+
992+ let host = io_result ( self . host ( ) , "No host name in the URL" ) ?;
993+ let port = io_result (
994+ self . port_or_known_default ( ) . or_else ( default_port_number) ,
995+ "No port number in the URL" ,
996+ ) ?;
997+ Ok ( match host {
998+ Host :: Domain ( domain) => ( domain, port) . to_socket_addrs ( ) ?. collect ( ) ,
999+ Host :: Ipv4 ( ip) => vec ! [ ( ip, port) . into( ) ] ,
1000+ Host :: Ipv6 ( ip) => vec ! [ ( ip, port) . into( ) ] ,
1001+ } )
1002+ }
1003+
9481004 /// Return the path for this URL, as a percent-encoded ASCII string.
9491005 /// For cannot-be-a-base URLs, this is an arbitrary string that doesn’t start with '/'.
9501006 /// For other URLs, this starts with a '/' slash
@@ -2296,9 +2352,8 @@ impl<'de> serde::Deserialize<'de> for Url {
22962352 where
22972353 E : Error ,
22982354 {
2299- Url :: parse ( s) . map_err ( |err| {
2300- Error :: invalid_value ( Unexpected :: Str ( s) , & err. description ( ) )
2301- } )
2355+ Url :: parse ( s)
2356+ . map_err ( |err| Error :: invalid_value ( Unexpected :: Str ( s) , & err. description ( ) ) )
23022357 }
23032358 }
23042359
0 commit comments