rust-mc-status 3.0.0

High-performance asynchronous Rust library for querying Minecraft server status (Java & Bedrock)
Documentation
//! SOCKS5 and HTTP CONNECT proxy usage example for the rust-mc-status library.
//!
//! Demonstrates all proxy configuration scenarios:
//! - Anonymous SOCKS5 proxy (Java/TCP only)
//! - Authenticated SOCKS5 proxy
//! - HTTP CONNECT proxy (Squid, Nginx, corporate firewalls)
//! - SOCKS5 with UDP support (Java + Bedrock)
//! - Expected error when Bedrock used with a TCP-only proxy
//! - Proxy + response cache combined
//! - Multiple clients with different proxies
//!
//! Requires the `proxy` feature:
//!   cargo run --example proxy_usage --features proxy
//!
//! You need a running SOCKS5 or HTTP proxy to see successful pings.
//! Quick local SOCKS5: `ssh -D 1080 user@host`
//! Quick local HTTP:   `docker run -p 3128:3128 sameersbn/squid`

use rust_mc_status::{McClient, McError, ProxyConfig, StatusExt};
use rust_mc_status::error::ProxyError;
use std::time::{Duration, Instant};

fn sep() {
    println!("\n{}\n", "".repeat(50));
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Proxy Usage ===\n");

    // ─── 1. Anonymous SOCKS5 — Java Edition (TCP) ─────────────────────────────
    //
    // All TCP connections (Java ping) are routed through the proxy.
    // Replace "127.0.0.1:1080" with your actual proxy address.

    println!("1. Anonymous SOCKS5 — Java Edition");
    let client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(ProxyConfig::socks5("127.0.0.1:1080"))
        .build();

    match client.java("mc.hypixel.net").await {
        Ok(s)  => println!("{} | {} ms | {}", s.display_players(), s.latency_ms() as u32, s.motd_clean()),
        Err(e) => println!("{e}  (is your proxy running on 127.0.0.1:1080?)"),
    }

    sep();

    // ─── 2. Authenticated SOCKS5 ──────────────────────────────────────────────
    //
    // Credentials use RFC 1929 username/password sub-negotiation.
    // Passwords are never included in error messages or Debug output.

    println!("2. Authenticated SOCKS5");
    let client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(
            ProxyConfig::socks5("proxy.example.com:1080")
                .with_auth("username", "password")
        )
        .build();

    match client.java("mc.hypixel.net").await {
        Ok(s)  => println!("{} | {} ms", s.display_players(), s.latency_ms() as u32),
        Err(e) => println!("{e}"),
    }

    sep();

    // ─── 3. HTTP CONNECT proxy ────────────────────────────────────────────────
    //
    // Supported by Squid, Nginx, HAProxy, and most corporate firewalls.
    // The library sends a plain `CONNECT host:port HTTP/1.1` request —
    // no extra dependencies required.
    //
    // Common ports: 3128 (Squid), 8080 (generic), 8888 (Charles/Fiddler).

    println!("3. HTTP CONNECT proxy — anonymous");
    let client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(ProxyConfig::http("squid.example.com:3128"))
        .build();

    match client.java("mc.hypixel.net").await {
        Ok(s)  => println!("{} | {} ms | {}", s.display_players(), s.latency_ms() as u32, s.motd_clean()),
        Err(e) => println!("{e}  (is your HTTP proxy running?)"),
    }

    // HTTP CONNECT with Basic authentication
    println!("\n3b. HTTP CONNECT proxy — authenticated");
    let client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(
            ProxyConfig::http("squid.corp.example.com:3128")
                .with_auth("proxyuser", "proxypass")
        )
        .build();

    match client.java("mc.hypixel.net").await {
        Ok(s)  => println!("{} | {} ms", s.display_players(), s.latency_ms() as u32),
        Err(e) => println!("{e}"),
    }

    sep();

    // ─── 4. Bedrock via TCP-only proxy — expected error ───────────────────────
    //
    // Bedrock Edition uses UDP (RakNet). Most SOCKS5 proxies and ALL HTTP
    // CONNECT proxies do not support UDP ASSOCIATE.
    //
    // Attempting a Bedrock ping through such a proxy returns
    // McError::Proxy(ProxyError::UdpUnsupported) immediately — no network
    // connection is made. This is intentional: a silent timeout would give
    // no useful diagnostic.

    println!("4. Bedrock via TCP-only SOCKS5 — expected UdpUnsupported error");
    let client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(ProxyConfig::socks5("127.0.0.1:1080")) // no UDP support
        .build();

    match client.bedrock("geo.hivebedrock.network").await {
        Ok(s) => println!("   ✅ Unexpected: {}", s.display_players()),
        Err(McError::Proxy(ProxyError::UdpUnsupported(addr))) => {
            println!("   ❌ ProxyUdpUnsupported({addr}) ← expected, no network attempt made");
            println!("      Use ProxyConfig::socks5_with_udp() if your proxy supports UDP ASSOCIATE.");
        }
        Err(e) => println!("{e}"),
    }

    println!("\n4b. Bedrock via HTTP proxy — same expected error");
    let client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(ProxyConfig::http("squid.example.com:3128"))
        .build();

    match client.bedrock("geo.hivebedrock.network").await {
        Ok(s) => println!("   ✅ Unexpected: {}", s.display_players()),
        Err(McError::Proxy(ProxyError::UdpUnsupported(addr))) => {
            println!("   ❌ ProxyUdpUnsupported({addr}) ← expected");
        }
        Err(e) => println!("{e}"),
    }

    sep();

    // ─── 5. SOCKS5 with UDP support — Java + Bedrock ──────────────────────────
    //
    // Use socks5_with_udp() only when your proxy genuinely supports UDP
    // ASSOCIATE (e.g. dante with udp_relay yes, shadowsocks-libev, tun2socks).
    // With this flag, Bedrock pings are attempted — if UDP is not actually
    // supported the ping will time out rather than fail immediately.

    println!("5. SOCKS5 with UDP ASSOCIATE — Java + Bedrock");
    let client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(ProxyConfig::socks5_with_udp("127.0.0.1:1080"))
        .build();

    match client.java("mc.hypixel.net").await {
        Ok(s)  => println!("   ✅ Java    {} | {} ms", s.display_players(), s.latency_ms() as u32),
        Err(e) => println!("   ❌ Java    {e}"),
    }
    match client.bedrock("geo.hivebedrock.network").await {
        Ok(s)  => println!("   ✅ Bedrock {} | {} ms | {}", s.display_players(), s.latency_ms() as u32, s.edition()),
        Err(e) => println!("   ❌ Bedrock {e}"),
    }

    sep();

    // ─── 6. Proxy + response cache ────────────────────────────────────────────
    //
    // Proxy and response cache are independent. The first (proxied) ping is
    // cached — subsequent calls return instantly without going through the proxy.

    println!("6. Proxy + response cache");
    let client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(ProxyConfig::socks5("127.0.0.1:1080"))
        .response_cache(Duration::from_secs(30), 64)
        .build();

    // First call — goes through the proxy
    let t1 = Instant::now();
    match client.java("mc.hypixel.net").await {
        Ok(s)  => println!("   live  {} ms  cached: {}  (via proxy)", t1.elapsed().as_millis(), s.is_cached()),
        Err(e) => println!("{e}"),
    }

    // Second call — served from memory, proxy not contacted
    let t2 = Instant::now();
    match client.java("mc.hypixel.net").await {
        Ok(s)  => println!("   cache {} ms  cached: {}  (no proxy contact)", t2.elapsed().as_millis(), s.is_cached()),
        Err(e) => println!("{e}"),
    }

    sep();

    // ─── 7. Multiple clients with different proxies ───────────────────────────
    //
    // Clients are cheaply clonable and share only their own caches.
    // Use separate clients when you need different proxy routes for different
    // server regions.

    println!("7. Multiple clients — different proxies per region");

    let eu_client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(ProxyConfig::socks5("eu-proxy.example.com:1080"))
        .build();

    let us_client = McClient::builder()
        .timeout(Duration::from_secs(10))
        .proxy(ProxyConfig::socks5("us-proxy.example.com:1080"))
        .build();

    for (label, client, server) in [
        ("EU", &eu_client, "eu.example-mc.com"),
        ("US", &us_client, "mc.hypixel.net"),
    ] {
        match client.java(server).await {
            Ok(s)  => println!("   [{label}] ✅ {server}{} | {} ms", s.display_players(), s.latency_ms() as u32),
            Err(e) => println!("   [{label}] ❌ {server}{e}"),
        }
    }

    sep();

    // ─── Summary ──────────────────────────────────────────────────────────────

    println!("Summary\n");
    println!("  Constructor                    Protocol     UDP (Bedrock)  Auth");
    println!("  ─────────────────────────────  ───────────  ─────────────  ────────");
    println!("  ProxyConfig::socks5(addr)      SOCKS5       ❌             optional");
    println!("  ProxyConfig::socks5_with_udp   SOCKS5+UDP   ✅             optional");
    println!("  ProxyConfig::http(addr)        HTTP CONNECT ❌             optional");
    println!();
    println!("  .with_auth(\"user\", \"pass\")  — works for all three");
    println!("  Bedrock without UDP support    — fails immediately, no network attempt");

    Ok(())
}