Browse Source

Add an HTTP client example.

whitequark 7 years ago
parent
commit
a7d289eac1
3 changed files with 154 additions and 27 deletions
  1. 8 3
      Cargo.toml
  2. 45 24
      README.md
  3. 101 0
      examples/httpclient.rs

+ 8 - 3
Cargo.toml

@@ -26,6 +26,8 @@ features = ["map"]
 log = "0.3"
 env_logger = "0.4"
 getopts = "0.2"
+rand = "0.3"
+url = "1.0"
 
 [features]
 std = ["managed/std"]
@@ -38,10 +40,10 @@ verbose = []
 "socket-udp" = []
 "socket-tcp" = []
 "socket-icmp" = []
-default = ["std", "log",
+default = [
   "phy-raw_socket", "phy-tap_interface",
-  "socket-raw", "socket-udp", "socket-tcp",
-  "socket-icmp"]
+  "socket-raw", "socket-icmp", "socket-udp", "socket-tcp"
+]
 
 [[example]]
 name = "tcpdump"
@@ -52,6 +54,9 @@ name = "server"
 [[example]]
 name = "client"
 
+[[example]]
+name = "httpclient"
+
 [[example]]
 name = "ping"
 

+ 45 - 24
README.md

@@ -207,9 +207,52 @@ cargo build --example tcpdump
 sudo ./target/debug/tcpdump eth0
 ```
 
+### examples/httpclient.rs
+
+_examples/httpclient.rs_ emulates a network host that can initiate HTTP requests.
+
+The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`.
+
+Read its [source code](/examples/httpclient.rs), then run it as:
+
+```sh
+cargo run --example client -- tap0 ADDRESS URL
+```
+
+For example:
+
+```sh
+cargo run --example client -- tap0 93.184.216.34 http://example.org/
+```
+
+It connects to the given address (not a hostname) and URL, and prints any returned response data.
+The TCP socket buffers are limited to 1024 bytes to make packet traces more interesting.
+
+### examples/ping.rs
+
+_examples/ping.rs_ implements a minimal version of the `ping` utility using raw sockets.
+
+The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`.
+
+Read its [source code](/examples/ping.rs), then run it as:
+
+```sh
+cargo run --example ping -- tap0 ADDRESS
+```
+
+It sends a series of 4 ICMP ECHO\_REQUEST packets to the given address at one second intervals and
+prints out a status line on each valid ECHO\_RESPONSE received.
+
+The first ECHO\_REQUEST packet is expected to be lost since arp\_cache is empty after startup;
+the ECHO\_REQUEST packet is dropped and an ARP request is sent instead.
+
+Currently, netmasks are not implemented, and so the only address this example can reach
+is the other endpoint of the tap interface, `192.168.1.100`. It cannot reach itself because
+packets entering a tap interface do not loop back.
+
 ### examples/server.rs
 
-_examples/server.rs_ emulates a network host that can respond to requests.
+_examples/server.rs_ emulates a network host that can respond to basic requests.
 
 The host is assigned the hardware address `02-00-00-00-00-01` and IPv4 address `192.168.69.1`.
 
@@ -239,7 +282,7 @@ of testing resource exhaustion conditions.
 
 ### examples/client.rs
 
-_examples/client.rs_ emulates a network host that can initiate requests.
+_examples/client.rs_ emulates a network host that can initiate basic requests.
 
 The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.2`.
 
@@ -252,28 +295,6 @@ cargo run --example client -- tap0 ADDRESS PORT
 It connects to the given address (not a hostname) and port (e.g. `socat stdio tcp4-listen:1234`),
 and will respond with reversed chunks of the input indefinitely.
 
-### examples/ping.rs
-
-_examples/ping.rs_ implements a minimal version of the `ping` utility using raw sockets.
-
-The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`.
-
-Read its [source code](/examples/ping.rs), then run it as:
-
-```sh
-cargo run --example ping -- tap0 ADDRESS
-```
-
-It sends a series of 4 ICMP ECHO\_REQUEST packets to the given address at one second intervals and
-prints out a status line on each valid ECHO\_RESPONSE received.
-
-The first ECHO\_REQUEST packet is expected to be lost since arp\_cache is empty after startup;
-the ECHO\_REQUEST packet is dropped and an ARP request is sent instead.
-
-Currently, netmasks are not implemented, and so the only address this example can reach
-is the other endpoint of the tap interface, `192.168.1.100`. It cannot reach itself because
-packets entering a tap interface do not loop back.
-
 ## Bare-metal usage examples
 
 Examples that use no services from the host OS are necessarily less illustrative than examples

+ 101 - 0
examples/httpclient.rs

@@ -0,0 +1,101 @@
+#[macro_use]
+extern crate log;
+extern crate env_logger;
+extern crate getopts;
+extern crate rand;
+extern crate url;
+extern crate smoltcp;
+
+mod utils;
+
+use std::str::{self, FromStr};
+use std::collections::BTreeMap;
+use std::time::Instant;
+use std::os::unix::io::AsRawFd;
+use url::Url;
+use smoltcp::phy::wait as phy_wait;
+use smoltcp::wire::{EthernetAddress, Ipv4Address, IpAddress, IpCidr};
+use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder};
+use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer};
+
+fn main() {
+    utils::setup_logging("");
+
+    let (mut opts, mut free) = utils::create_options();
+    utils::add_tap_options(&mut opts, &mut free);
+    utils::add_middleware_options(&mut opts, &mut free);
+    free.push("ADDRESS");
+    free.push("URL");
+
+    let mut matches = utils::parse_options(&opts, free);
+    let device = utils::parse_tap_options(&mut matches);
+    let fd = device.as_raw_fd();
+    let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false);
+    let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format");
+    let url = Url::parse(&matches.free[1]).expect("invalid url format");
+
+    let startup_time = Instant::now();
+
+    let neighbor_cache = NeighborCache::new(BTreeMap::new());
+
+    let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
+    let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
+    let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
+
+    let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]);
+    let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)];
+    let default_v4_gw = Ipv4Address::new(192, 168, 69, 100);
+    let mut iface = EthernetInterfaceBuilder::new(device)
+            .ethernet_addr(ethernet_addr)
+            .neighbor_cache(neighbor_cache)
+            .ip_addrs(ip_addrs)
+            .ipv4_gateway(default_v4_gw)
+            .finalize();
+
+    let mut sockets = SocketSet::new(vec![]);
+    let tcp_handle = sockets.add(tcp_socket);
+
+    enum State { Connect, Request, Response };
+    let mut state = State::Connect;
+
+    loop {
+        {
+            let mut socket = sockets.get::<TcpSocket>(tcp_handle);
+
+            state = match state {
+                State::Connect if !socket.is_active() => {
+                    debug!("connecting");
+                    let local_port = 49152 + rand::random::<u16>() % 16384;
+                    socket.connect((address, url.port().unwrap_or(80)), local_port).unwrap();
+                    State::Request
+                }
+                State::Request if socket.may_send() => {
+                    debug!("sending request");
+                    let http_get = "GET ".to_owned() + url.path() + " HTTP/1.1\r\n";
+                    socket.send_slice(http_get.as_ref()).expect("cannot send");
+                    let http_host = "Host: ".to_owned() + url.host_str().unwrap() + "\r\n";
+                    socket.send_slice(http_host.as_ref()).expect("cannot send");
+                    socket.send_slice(b"Connection: close\r\n").expect("cannot send");
+                    socket.send_slice(b"\r\n").expect("cannot send");
+                    State::Response
+                }
+                State::Response if socket.can_recv() => {
+                    socket.recv(|data| {
+                        println!("{}", str::from_utf8(data).unwrap_or("(invalid utf8)"));
+                        (data.len(), ())
+                    }).unwrap();
+                    State::Response
+                }
+                State::Response if !socket.may_recv() => {
+                    debug!("received complete response");
+                    break
+                }
+                _ => state
+            }
+        }
+
+        let timestamp = utils::millis_since(startup_time);
+        let poll_at = iface.poll(&mut sockets, timestamp).expect("poll error");
+        phy_wait(fd, poll_at.map(|at| at.saturating_sub(timestamp))).expect("wait error");
+    }
+}