Skip to content

Commit 5c1fd5f

Browse files
committed
auto merge of #18462 : netvl/rust/to-socket-addr, r=alexcrichton
This is a follow-up to [RFC PR #173](rust-lang/rfcs#173). I was told there that changes like this don't need to go through the RFC process, so I'm submitting this directly. This PR introduces `ToSocketAddr` trait as defined in said RFC. This trait defines a conversion from different types like `&str`, `(&str, u16)` or even `SocketAddr` to `SocketAddr`. Then this trait is used in all constructor methods for `TcpStream`, `TcpListener` and `UdpSocket`. This unifies these constructor methods - previously they were using different types of input parameters (TCP ones used `(&str, u16)` pair while UDP ones used `SocketAddr`), which is not consistent by itself and sometimes inconvenient - for example, when the address initially is available as `SocketAddr`, you still need to convert it to string to pass it to e.g. `TcpStream`. This is very prominently demonstrated by the unit tests for TCP functionality. This PR makes working with network objects much like with `Path`, which also uses similar trait to be able to be constructed from `&[u8]`, `Vec<u8>` and other `Path`s. This is a breaking change. If constant literals were used before, like this: ```rust TcpStream::connect("localhost", 12345) ``` then the nicest fix is to change it to this: ```rust TcpStream::connect("localhost:12345") ``` If variables were used before, like this: ```rust TcpStream::connect(some_address, some_port) ``` then the arguments should be wrapped in another set of parentheses: ```rust TcpStream::connect((some_address, some_port)) ``` `UdpSocket` usages won't break because its constructor method accepted `SocketAddr` which implements `ToSocketAddr`, so `bind()` calls: ```rust UdpSocket::bind(some_socket_addr) ``` will continue working as before. I haven't changed `UdpStream` constructor because it is deprecated anyway.
2 parents 14cd5c5 + 0f610f3 commit 5c1fd5f

File tree

9 files changed

+411
-264
lines changed

9 files changed

+411
-264
lines changed

src/compiletest/runtest.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) {
444444
//waiting 1 second for gdbserver start
445445
timer::sleep(Duration::milliseconds(1000));
446446
let result = task::try(proc() {
447-
tcp::TcpStream::connect("127.0.0.1", 5039).unwrap();
447+
tcp::TcpStream::connect("127.0.0.1:5039").unwrap();
448448
});
449449
if result.is_err() {
450450
continue;

src/libstd/io/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Some examples of obvious things you might want to do
9191
# // locally, we still want to be type checking this code, so lets
9292
# // just stop it running (#11576)
9393
# if false {
94-
let mut socket = TcpStream::connect("127.0.0.1", 8080).unwrap();
94+
let mut socket = TcpStream::connect("127.0.0.1:8080").unwrap();
9595
socket.write(b"GET / HTTP/1.0\n\n");
9696
let response = socket.read_to_end();
9797
# }
@@ -106,7 +106,7 @@ Some examples of obvious things you might want to do
106106
use std::io::{TcpListener, TcpStream};
107107
use std::io::{Acceptor, Listener};
108108
109-
let listener = TcpListener::bind("127.0.0.1", 80);
109+
let listener = TcpListener::bind("127.0.0.1:80");
110110
111111
// bind the listener to the specified address
112112
let mut acceptor = listener.listen();

src/libstd/io/net/ip.rs

+231
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717

1818
use fmt;
1919
use from_str::FromStr;
20+
use io::{mod, IoResult, IoError};
21+
use io::net;
2022
use iter::Iterator;
2123
use option::{Option, None, Some};
24+
use result::{Ok, Err};
2225
use str::StrSlice;
2326
use slice::{MutableCloneableSlice, MutableSlice, ImmutableSlice};
27+
use vec::Vec;
2428

2529
pub type Port = u16;
2630

@@ -348,6 +352,189 @@ impl FromStr for SocketAddr {
348352
}
349353
}
350354

355+
/// A trait for objects which can be converted or resolved to one or more `SocketAddr` values.
356+
///
357+
/// Implementing types minimally have to implement either `to_socket_addr` or `to_socket_addr_all`
358+
/// method, and its trivial counterpart will be available automatically.
359+
///
360+
/// This trait is used for generic address resolution when constructing network objects.
361+
/// By default it is implemented for the following types:
362+
///
363+
/// * `SocketAddr` - `to_socket_addr` is identity function.
364+
///
365+
/// * `(IpAddr, u16)` - `to_socket_addr` constructs `SocketAddr` trivially.
366+
///
367+
/// * `(&str, u16)` - the string should be either a string representation of an IP address
368+
/// expected by `FromStr` implementation for `IpAddr` or a host name.
369+
///
370+
/// For the former, `to_socket_addr_all` returns a vector with a single element corresponding
371+
/// to that IP address joined with the given port.
372+
///
373+
/// For the latter, it tries to resolve the host name and returns a vector of all IP addresses
374+
/// for the host name, each joined with the given port.
375+
///
376+
/// * `&str` - the string should be either a string representation of a `SocketAddr` as
377+
/// expected by its `FromStr` implementation or a string like `<host_name>:<port>` pair
378+
/// where `<port>` is a `u16` value.
379+
///
380+
/// For the former, `to_socker_addr_all` returns a vector with a single element corresponding
381+
/// to that socker address.
382+
///
383+
/// For the latter, it tries to resolve the host name and returns a vector of all IP addresses
384+
/// for the host name, each joined with the port.
385+
///
386+
///
387+
/// This trait allows constructing network objects like `TcpStream` or `UdpSocket` easily with
388+
/// values of various types for the bind/connection address. It is needed because sometimes
389+
/// one type is more appropriate than the other: for simple uses a string like `"localhost:12345"`
390+
/// is much nicer than manual construction of the corresponding `SocketAddr`, but sometimes
391+
/// `SocketAddr` value is *the* main source of the address, and converting it to some other type
392+
/// (e.g. a string) just for it to be converted back to `SocketAddr` in constructor methods
393+
/// is pointless.
394+
///
395+
/// Some examples:
396+
///
397+
/// ```rust,no_run
398+
/// # #![allow(unused_must_use)]
399+
///
400+
/// use std::io::{TcpStream, TcpListener};
401+
/// use std::io::net::udp::UdpSocket;
402+
/// use std::io::net::ip::{Ipv4Addr, SocketAddr};
403+
///
404+
/// fn main() {
405+
/// // The following lines are equivalent modulo possible "localhost" name resolution
406+
/// // differences
407+
/// let tcp_s = TcpStream::connect(SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 12345 });
408+
/// let tcp_s = TcpStream::connect((Ipv4Addr(127, 0, 0, 1), 12345u16));
409+
/// let tcp_s = TcpStream::connect(("127.0.0.1", 12345u16));
410+
/// let tcp_s = TcpStream::connect(("localhost", 12345u16));
411+
/// let tcp_s = TcpStream::connect("127.0.0.1:12345");
412+
/// let tcp_s = TcpStream::connect("localhost:12345");
413+
///
414+
/// // TcpListener::bind(), UdpSocket::bind() and UdpSocket::send_to() behave similarly
415+
/// let tcp_l = TcpListener::bind("localhost:12345");
416+
///
417+
/// let mut udp_s = UdpSocket::bind(("127.0.0.1", 23451u16)).unwrap();
418+
/// udp_s.send_to([7u8, 7u8, 7u8].as_slice(), (Ipv4Addr(127, 0, 0, 1), 23451u16));
419+
/// }
420+
/// ```
421+
pub trait ToSocketAddr {
422+
/// Converts this object to single socket address value.
423+
///
424+
/// If more than one value is available, this method returns the first one. If no
425+
/// values are available, this method returns an `IoError`.
426+
///
427+
/// By default this method delegates to `to_socket_addr_all` method, taking the first
428+
/// item from its result.
429+
fn to_socket_addr(&self) -> IoResult<SocketAddr> {
430+
self.to_socket_addr_all()
431+
.and_then(|v| v.into_iter().next().ok_or_else(|| IoError {
432+
kind: io::InvalidInput,
433+
desc: "no address available",
434+
detail: None
435+
}))
436+
}
437+
438+
/// Converts this object to all available socket address values.
439+
///
440+
/// Some values like host name string naturally corrrespond to multiple IP addresses.
441+
/// This method tries to return all available addresses corresponding to this object.
442+
///
443+
/// By default this method delegates to `to_socket_addr` method, creating a singleton
444+
/// vector from its result.
445+
#[inline]
446+
fn to_socket_addr_all(&self) -> IoResult<Vec<SocketAddr>> {
447+
self.to_socket_addr().map(|a| vec![a])
448+
}
449+
}
450+
451+
impl ToSocketAddr for SocketAddr {
452+
#[inline]
453+
fn to_socket_addr(&self) -> IoResult<SocketAddr> { Ok(*self) }
454+
}
455+
456+
impl ToSocketAddr for (IpAddr, u16) {
457+
#[inline]
458+
fn to_socket_addr(&self) -> IoResult<SocketAddr> {
459+
let (ip, port) = *self;
460+
Ok(SocketAddr { ip: ip, port: port })
461+
}
462+
}
463+
464+
fn resolve_socket_addr(s: &str, p: u16) -> IoResult<Vec<SocketAddr>> {
465+
net::get_host_addresses(s)
466+
.map(|v| v.into_iter().map(|a| SocketAddr { ip: a, port: p }).collect())
467+
}
468+
469+
fn parse_and_resolve_socket_addr(s: &str) -> IoResult<Vec<SocketAddr>> {
470+
macro_rules! try_opt(
471+
($e:expr, $msg:expr) => (
472+
match $e {
473+
Some(r) => r,
474+
None => return Err(IoError {
475+
kind: io::InvalidInput,
476+
desc: $msg,
477+
detail: None
478+
})
479+
}
480+
)
481+
)
482+
483+
// split the string by ':' and convert the second part to u16
484+
let mut parts_iter = s.rsplitn(2, ':');
485+
let port_str = try_opt!(parts_iter.next(), "invalid socket address");
486+
let host = try_opt!(parts_iter.next(), "invalid socket address");
487+
let port: u16 = try_opt!(FromStr::from_str(port_str), "invalid port value");
488+
resolve_socket_addr(host, port)
489+
}
490+
491+
impl<'a> ToSocketAddr for (&'a str, u16) {
492+
fn to_socket_addr_all(&self) -> IoResult<Vec<SocketAddr>> {
493+
let (host, port) = *self;
494+
495+
// try to parse the host as a regular IpAddr first
496+
match FromStr::from_str(host) {
497+
Some(addr) => return Ok(vec![SocketAddr {
498+
ip: addr,
499+
port: port
500+
}]),
501+
None => {}
502+
}
503+
504+
resolve_socket_addr(host, port)
505+
}
506+
}
507+
508+
// accepts strings like 'localhost:12345'
509+
impl<'a> ToSocketAddr for &'a str {
510+
fn to_socket_addr(&self) -> IoResult<SocketAddr> {
511+
// try to parse as a regular SocketAddr first
512+
match FromStr::from_str(*self) {
513+
Some(addr) => return Ok(addr),
514+
None => {}
515+
}
516+
517+
parse_and_resolve_socket_addr(*self)
518+
.and_then(|v| v.into_iter().next()
519+
.ok_or_else(|| IoError {
520+
kind: io::InvalidInput,
521+
desc: "no address available",
522+
detail: None
523+
})
524+
)
525+
}
526+
527+
fn to_socket_addr_all(&self) -> IoResult<Vec<SocketAddr>> {
528+
// try to parse as a regular SocketAddr first
529+
match FromStr::from_str(*self) {
530+
Some(addr) => return Ok(vec![addr]),
531+
None => {}
532+
}
533+
534+
parse_and_resolve_socket_addr(*self)
535+
}
536+
}
537+
351538

352539
#[cfg(test)]
353540
mod test {
@@ -457,4 +644,48 @@ mod test {
457644
assert_eq!(Ipv6Addr(8, 9, 10, 11, 12, 13, 14, 15).to_string(),
458645
"8:9:a:b:c:d:e:f".to_string());
459646
}
647+
648+
#[test]
649+
fn to_socket_addr_socketaddr() {
650+
let a = SocketAddr { ip: Ipv4Addr(77, 88, 21, 11), port: 12345 };
651+
assert_eq!(Ok(a), a.to_socket_addr());
652+
assert_eq!(Ok(vec![a]), a.to_socket_addr_all());
653+
}
654+
655+
#[test]
656+
fn to_socket_addr_ipaddr_u16() {
657+
let a = Ipv4Addr(77, 88, 21, 11);
658+
let p = 12345u16;
659+
let e = SocketAddr { ip: a, port: p };
660+
assert_eq!(Ok(e), (a, p).to_socket_addr());
661+
assert_eq!(Ok(vec![e]), (a, p).to_socket_addr_all());
662+
}
663+
664+
#[test]
665+
fn to_socket_addr_str_u16() {
666+
let a = SocketAddr { ip: Ipv4Addr(77, 88, 21, 11), port: 24352 };
667+
assert_eq!(Ok(a), ("77.88.21.11", 24352u16).to_socket_addr());
668+
assert_eq!(Ok(vec![a]), ("77.88.21.11", 24352u16).to_socket_addr_all());
669+
670+
let a = SocketAddr { ip: Ipv6Addr(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), port: 53 };
671+
assert_eq!(Ok(a), ("2a02:6b8:0:1::1", 53).to_socket_addr());
672+
assert_eq!(Ok(vec![a]), ("2a02:6b8:0:1::1", 53).to_socket_addr_all());
673+
674+
let a = SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 23924 };
675+
assert!(("localhost", 23924u16).to_socket_addr_all().unwrap().contains(&a));
676+
}
677+
678+
#[test]
679+
fn to_socket_addr_str() {
680+
let a = SocketAddr { ip: Ipv4Addr(77, 88, 21, 11), port: 24352 };
681+
assert_eq!(Ok(a), "77.88.21.11:24352".to_socket_addr());
682+
assert_eq!(Ok(vec![a]), "77.88.21.11:24352".to_socket_addr_all());
683+
684+
let a = SocketAddr { ip: Ipv6Addr(0x2a02, 0x6b8, 0, 1, 0, 0, 0, 1), port: 53 };
685+
assert_eq!(Ok(a), "[2a02:6b8:0:1::1]:53".to_socket_addr());
686+
assert_eq!(Ok(vec![a]), "[2a02:6b8:0:1::1]:53".to_socket_addr_all());
687+
688+
let a = SocketAddr { ip: Ipv4Addr(127, 0, 0, 1), port: 23924 };
689+
assert!("localhost:23924".to_socket_addr_all().unwrap().contains(&a));
690+
}
460691
}

src/libstd/io/net/mod.rs

+45-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010

1111
//! Networking I/O
1212
13+
use io::{IoError, IoResult, InvalidInput};
14+
use option::None;
15+
use result::{Result, Ok, Err};
1316
use rt::rtio;
14-
use self::ip::{Ipv4Addr, Ipv6Addr, IpAddr};
17+
use self::ip::{Ipv4Addr, Ipv6Addr, IpAddr, SocketAddr, ToSocketAddr};
1518

1619
pub use self::addrinfo::get_host_addresses;
1720

@@ -38,3 +41,44 @@ fn from_rtio(ip: rtio::IpAddr) -> IpAddr {
3841
}
3942
}
4043
}
44+
45+
fn with_addresses_io<A: ToSocketAddr, T>(
46+
addr: A,
47+
action: |&mut rtio::IoFactory, rtio::SocketAddr| -> Result<T, rtio::IoError>
48+
) -> Result<T, IoError> {
49+
const DEFAULT_ERROR: IoError = IoError {
50+
kind: InvalidInput,
51+
desc: "no addresses found for hostname",
52+
detail: None
53+
};
54+
55+
let addresses = try!(addr.to_socket_addr_all());
56+
let mut err = DEFAULT_ERROR;
57+
for addr in addresses.into_iter() {
58+
let addr = rtio::SocketAddr { ip: to_rtio(addr.ip), port: addr.port };
59+
match rtio::LocalIo::maybe_raise(|io| action(io, addr)) {
60+
Ok(r) => return Ok(r),
61+
Err(e) => err = IoError::from_rtio_error(e)
62+
}
63+
}
64+
Err(err)
65+
}
66+
67+
fn with_addresses<A: ToSocketAddr, T>(addr: A, action: |SocketAddr| -> IoResult<T>)
68+
-> IoResult<T> {
69+
const DEFAULT_ERROR: IoError = IoError {
70+
kind: InvalidInput,
71+
desc: "no addresses found for hostname",
72+
detail: None
73+
};
74+
75+
let addresses = try!(addr.to_socket_addr_all());
76+
let mut err = DEFAULT_ERROR;
77+
for addr in addresses.into_iter() {
78+
match action(addr) {
79+
Ok(r) => return Ok(r),
80+
Err(e) => err = e
81+
}
82+
}
83+
Err(err)
84+
}

0 commit comments

Comments
 (0)