Skip to content

Commit 2f199ff

Browse files
committed
Add Url::socket_addrs
1 parent bb84560 commit 2f199ff

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
name = "url"
44
# When updating version, also modify html_root_url in the lib.rs
5-
version = "2.0.0"
5+
version = "2.1.0"
66
authors = ["The rust-url developers"]
77

88
description = "URL library for Rust, based on the WHATWG URL Standard"

src/lib.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,9 @@ use std::cmp;
123123
use std::error::Error;
124124
use std::fmt::{self, Write};
125125
use std::hash;
126+
use std::io;
126127
use std::mem;
127-
use std::net::IpAddr;
128+
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
128129
use std::ops::{Range, RangeFrom, RangeTo};
129130
use std::path::{Path, PathBuf};
130131
use 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

0 commit comments

Comments
 (0)