use std::sync::{Arc, Mutex};
use std::time::Duration;
use go_http::{
client::Client,
cookie::{Cookie, MemoryCookieJar},
handler::ServeMux,
parse::transfer::Body,
server::Server,
status,
};
static PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(19200);
fn next_port() -> u16 {
PORT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
fn start_server_goroutine(addr: String, mux: Arc<ServeMux>) {
go_lib::go!(move || {
let mut srv = Server::new(addr);
srv.handler = Some(mux);
let _ = srv.listen_and_serve();
});
go_lib::sleep(Duration::from_millis(50));
}
#[test]
#[go_lib::main]
fn get_basic() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/hello", |w, _r| {
w.header().set("Content-Type", "text/plain");
let _ = w.write(b"Hello, world!");
});
start_server_goroutine(addr, mux);
let mut resp = Client::new()
.get(&format!("http://127.0.0.1:{port}/hello"))
.expect("GET failed");
assert_eq!(resp.status, status::OK);
assert_eq!(resp.body_string().unwrap(), "Hello, world!");
}
#[test]
#[go_lib::main]
fn get_not_found() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
start_server_goroutine(addr, Arc::new(ServeMux::new()));
let resp = Client::new()
.get(&format!("http://127.0.0.1:{port}/missing"))
.expect("GET failed");
assert_eq!(resp.status, status::NOT_FOUND);
}
#[test]
#[go_lib::main]
fn post_body_echo() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/echo", |w, r| {
let ct = r.header.get("Content-Type").unwrap_or("").to_owned();
w.header().set("X-Received-Content-Type", ct);
let _ = w.write(b"echoed");
});
start_server_goroutine(addr, mux);
let body = Body::Unbounded(Box::new(std::io::Cursor::new(b"hello post".to_vec())));
let resp = Client::new()
.post(&format!("http://127.0.0.1:{port}/echo"), "text/plain", body)
.expect("POST failed");
assert_eq!(resp.status, status::OK);
assert_eq!(resp.header.get("X-Received-Content-Type").unwrap_or(""), "text/plain");
}
#[test]
#[go_lib::main]
fn custom_response_headers() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/headers", |w, _| {
w.header().set("X-Custom", "go-http-test");
w.header().set("X-Another", "value2");
w.write_header(status::CREATED);
let _ = w.write(b"ok");
});
start_server_goroutine(addr, mux);
let resp = Client::new()
.get(&format!("http://127.0.0.1:{port}/headers"))
.expect("GET failed");
assert_eq!(resp.status, status::CREATED);
assert_eq!(resp.header.get("X-Custom").unwrap_or(""), "go-http-test");
assert_eq!(resp.header.get("X-Another").unwrap_or(""), "value2");
}
#[test]
#[go_lib::main]
fn request_headers_forwarded() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/mirror", |w, r| {
let token = r.header.get("X-Token").unwrap_or("missing").to_owned();
w.header().set("X-Token-Echo", token);
let _ = w.write(b"ok");
});
start_server_goroutine(addr, mux);
let mut req = go_http::request::Request::new(
"GET",
&format!("http://127.0.0.1:{port}/mirror"),
None,
)
.unwrap();
req.header.set("X-Token", "secret123");
let resp = Client::new().do_request(req).expect("GET failed");
assert_eq!(resp.header.get("X-Token-Echo").unwrap_or(""), "secret123");
}
#[test]
#[go_lib::main]
fn query_string_params() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/search", |w, r| {
let q = r.url.query_pairs()
.find(|(k, _)| k == "q")
.map(|(_, v)| v.into_owned())
.unwrap_or_default();
let _ = w.write(format!("query={q}").as_bytes());
});
start_server_goroutine(addr, mux);
let mut resp = Client::new()
.get(&format!("http://127.0.0.1:{port}/search?q=rustlang"))
.expect("GET failed");
assert_eq!(resp.body_string().unwrap(), "query=rustlang");
}
#[test]
#[go_lib::main]
fn mux_longest_prefix() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/api/", |w, _| { let _ = w.write(b"api-root"); });
mux.handle_func("/api/v2/", |w, _| { let _ = w.write(b"api-v2"); });
start_server_goroutine(addr, mux);
let mut resp = Client::new()
.get(&format!("http://127.0.0.1:{port}/api/v2/users"))
.expect("GET failed");
assert_eq!(resp.body_string().unwrap(), "api-v2");
}
#[test]
#[go_lib::main]
fn multiple_sequential_requests() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let counter = Arc::new(Mutex::new(0u32));
let mux = Arc::new(ServeMux::new());
let counter2 = Arc::clone(&counter);
mux.handle_func("/count", move |w, _| {
let mut c = counter2.lock().unwrap();
*c += 1;
let _ = w.write(format!("{}", *c).as_bytes());
});
start_server_goroutine(addr, mux);
let client = Client::new();
let mut bodies = Vec::new();
for _ in 0..5 {
let mut r = client
.get(&format!("http://127.0.0.1:{port}/count"))
.unwrap();
bodies.push(r.body_string().unwrap());
}
assert_eq!(bodies.len(), 5);
for (i, body) in bodies.iter().enumerate() {
let n: u32 = body.trim().parse().unwrap();
assert_eq!(n, (i + 1) as u32, "counter mismatch at request {i}");
}
}
#[test]
#[go_lib::main]
fn redirect_followed() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/old", |w, r| {
go_http::util::redirect(w, r, "/new", status::MOVED_PERMANENTLY);
});
mux.handle_func("/new", |w, _| {
let _ = w.write(b"new location");
});
start_server_goroutine(addr, mux);
let mut resp = Client::new()
.get(&format!("http://127.0.0.1:{port}/old"))
.expect("GET failed");
assert_eq!(resp.status, status::OK);
assert_eq!(resp.body_string().unwrap(), "new location");
}
#[test]
#[go_lib::main]
fn cookie_jar_round_trip() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/set-cookie", |w, _| {
go_http::util::set_cookie(w, &Cookie::new("session", "abc123"));
let _ = w.write(b"cookie set");
});
mux.handle_func("/get-cookie", |w, r| {
let val = r.cookie("session")
.map(|c| c.value.clone())
.unwrap_or_else(|| "none".to_owned());
let _ = w.write(val.as_bytes());
});
start_server_goroutine(addr, mux);
let jar = Arc::new(MemoryCookieJar::new());
let mut client = Client::new();
client.jar = Some(Arc::clone(&jar) as Arc<dyn go_http::cookie::CookieJar>);
let _ = client.get(&format!("http://127.0.0.1:{port}/set-cookie")).unwrap();
let mut r = client.get(&format!("http://127.0.0.1:{port}/get-cookie")).unwrap();
let body = r.body_string().unwrap();
assert!(!body.is_empty(), "unexpected empty body");
}
#[test]
#[go_lib::main]
fn large_body() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
const SIZE: usize = 64 * 1024;
let mux = Arc::new(ServeMux::new());
mux.handle_func("/big", move |w, _| {
let data: Vec<u8> = (0..SIZE).map(|i| (i % 251) as u8).collect();
let _ = w.write(&data);
});
start_server_goroutine(addr, mux);
let mut resp = Client::new()
.get(&format!("http://127.0.0.1:{port}/big"))
.unwrap();
let bytes = resp.body_bytes().unwrap();
assert_eq!(bytes.len(), SIZE);
for (i, &b) in bytes.iter().enumerate() {
assert_eq!(b, (i % 251) as u8);
}
}
#[test]
#[go_lib::main]
fn head_request_no_body() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/resource", |w, _| {
w.header().set("Content-Type", "text/plain");
let _ = w.write(b"body content!");
});
start_server_goroutine(addr, mux);
let mut resp = Client::new()
.head(&format!("http://127.0.0.1:{port}/resource"))
.expect("HEAD failed");
assert_eq!(resp.status, status::OK);
let body = resp.body_bytes().unwrap();
assert!(body.is_empty(), "HEAD response must have empty body");
}
#[test]
#[go_lib::main]
fn post_form_urlencoded() {
let port = next_port();
let addr = format!("127.0.0.1:{port}");
let mux = Arc::new(ServeMux::new());
mux.handle_func("/form", |w, r| {
let ct = r.header.get("Content-Type").unwrap_or("").to_owned();
w.header().set("X-Got-Content-Type", ct);
let _ = w.write(b"form received");
});
start_server_goroutine(addr, mux);
let resp = Client::new()
.post_form(
&format!("http://127.0.0.1:{port}/form"),
&[("name", "Alice"), ("age", "30")],
)
.expect("POST form failed");
assert_eq!(resp.status, status::OK);
assert!(
resp.header.get("X-Got-Content-Type").unwrap_or("").contains("urlencoded"),
"wrong Content-Type echoed"
);
}