@@ -123,8 +123,9 @@ use std::cmp;
123
123
use std:: error:: Error ;
124
124
use std:: fmt:: { self , Write } ;
125
125
use std:: hash;
126
+ use std:: io;
126
127
use std:: mem;
127
- use std:: net:: IpAddr ;
128
+ use std:: net:: { IpAddr , SocketAddr , ToSocketAddrs } ;
128
129
use std:: ops:: { Range , RangeFrom , RangeTo } ;
129
130
use std:: path:: { Path , PathBuf } ;
130
131
use std:: str;
@@ -945,6 +946,61 @@ impl Url {
945
946
self . port . or_else ( || parser:: default_port ( self . scheme ( ) ) )
946
947
}
947
948
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
+
948
1004
/// Return the path for this URL, as a percent-encoded ASCII string.
949
1005
/// For cannot-be-a-base URLs, this is an arbitrary string that doesn’t start with '/'.
950
1006
/// For other URLs, this starts with a '/' slash
@@ -2296,9 +2352,8 @@ impl<'de> serde::Deserialize<'de> for Url {
2296
2352
where
2297
2353
E : Error ,
2298
2354
{
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 ( ) ) )
2302
2357
}
2303
2358
}
2304
2359
0 commit comments