mirror of
https://github.com/cloudwu/skynet.git
synced 2026-07-22 11:03:12 +00:00
* 按照RFC 3986的规范处理ipv6 host的格式化问题,ipv6地址需要用 []括起来 参考: https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2 * 解决ipv6格式不对的问题 * 只有域名的情况下才 set_ext_host_name
217 lines
7.1 KiB
Lua
217 lines
7.1 KiB
Lua
local skynet = require "skynet"
|
|
local httpc = require "http.httpc"
|
|
local httpurl = require "http.url"
|
|
local dns = require "skynet.dns"
|
|
|
|
local function http_test(protocol)
|
|
--httpc.dns() -- set dns server
|
|
httpc.timeout = 100 -- set timeout 1 second
|
|
print("GET baidu.com")
|
|
protocol = protocol or "http"
|
|
local respheader = {}
|
|
local host = string.format("%s://baidu.com", protocol)
|
|
print("geting... ".. host)
|
|
local status, body = httpc.get(host, "/", respheader)
|
|
print("[header] =====>")
|
|
for k,v in pairs(respheader) do
|
|
print(k,v)
|
|
end
|
|
print("[body] =====>", status)
|
|
print(body)
|
|
|
|
local respheader = {}
|
|
local ip = dns.resolve "baidu.com"
|
|
print(string.format("GET %s (baidu.com)", ip))
|
|
local status, body = httpc.get(host, "/", respheader, { host = "baidu.com" })
|
|
print(status)
|
|
end
|
|
|
|
local function http_stream_test()
|
|
for resp, stream in httpc.request_stream("GET", "http://baidu.com", "/") do
|
|
print("STATUS", stream.status)
|
|
for k,v in pairs(stream.header) do
|
|
print("HEADER",k,v)
|
|
end
|
|
print("BODY", resp)
|
|
end
|
|
end
|
|
|
|
local function http_head_test()
|
|
httpc.timeout = 100
|
|
local respheader = {}
|
|
local status = httpc.head("http://baidu.com", "/", respheader)
|
|
for k,v in pairs(respheader) do
|
|
print("HEAD", k, v)
|
|
end
|
|
end
|
|
|
|
local function http_url_test()
|
|
local url = "http://baidu.com/get?k1=1&k2=2&k4=a%20space&k5=b%20space&k5=b%20space&k5=b%20space"
|
|
local path, query = httpurl.parse(url)
|
|
print("url", path, query)
|
|
local qret = httpurl.parse_query(query)
|
|
for k, v in pairs(qret) do
|
|
print(k, v)
|
|
end
|
|
assert(#qret["k5"] == 3)
|
|
assert(qret[1] == qret[2])
|
|
assert(qret[1] == qret[3])
|
|
end
|
|
|
|
-- Integration test: connect to real server with various URL formats
|
|
-- Verifies parse_host / parse_url via httpc public API
|
|
local function test_connect_formats()
|
|
print("[test_connect_formats] =====>")
|
|
httpc.timeout = 100
|
|
|
|
local passed, total = 0, 0
|
|
|
|
local function try_get(desc, host)
|
|
local ok, status, body = pcall(httpc.get, host, "/", {})
|
|
total = total + 1
|
|
if ok then
|
|
passed = passed + 1
|
|
print(string.format(" PASS: %-35s status=%s", desc, status))
|
|
else
|
|
print(string.format(" FAIL: %-35s error=%s", desc, status))
|
|
end
|
|
return ok
|
|
end
|
|
|
|
-- verify parsing is correct even when connection fails
|
|
-- "connect error" in the message means parsing succeeded
|
|
local function try_parse(desc, host)
|
|
local ok, err = pcall(httpc.get, host, "/", {})
|
|
total = total + 1
|
|
if ok then
|
|
passed = passed + 1
|
|
print(string.format(" PASS: %-35s connected", desc))
|
|
return true
|
|
end
|
|
err = tostring(err)
|
|
if err:find("connect error") or err:find("connect failed") or err:find("Socket Error") then
|
|
passed = passed + 1
|
|
print(string.format(" PASS: %-35s parsed ok (connect failed as expected)", desc))
|
|
return true
|
|
else
|
|
print(string.format(" FAIL: %-35s error=%s", desc, err))
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- verify that invalid input is properly rejected
|
|
local function try_error(desc, host, pattern)
|
|
local ok, err = pcall(httpc.get, host, "/", {})
|
|
total = total + 1
|
|
if ok then
|
|
print(string.format(" FAIL: %-35s expected error but succeeded", desc))
|
|
return false
|
|
end
|
|
err = tostring(err)
|
|
if err:find(pattern) then
|
|
passed = passed + 1
|
|
print(string.format(" PASS: %-35s rejected as expected", desc))
|
|
return true
|
|
else
|
|
print(string.format(" FAIL: %-35s wrong error=%s", desc, err))
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- hostname variants
|
|
try_get("bare hostname", "baidu.com")
|
|
try_get("hostname:port", "baidu.com:80")
|
|
try_get("http://hostname", "http://baidu.com")
|
|
try_get("http://hostname:port", "http://baidu.com:80")
|
|
try_get("HTTP:// uppercase", "HTTP://baidu.com")
|
|
|
|
-- ipv4 variants (resolve first to get a real IP)
|
|
local ip = dns.resolve("baidu.com")
|
|
if ip then
|
|
print(string.format(" resolved baidu.com -> %s", ip))
|
|
try_get("bare ipv4", ip)
|
|
try_get("ipv4:port", ip .. ":80")
|
|
try_get("http://ipv4", "http://" .. ip)
|
|
try_get("http://ipv4:port", "http://" .. ip .. ":80")
|
|
else
|
|
print(" SKIP: ipv4 tests (dns resolve failed)")
|
|
end
|
|
|
|
-- https variants (skip if no tls)
|
|
if pcall(require, "ltls.c") then
|
|
try_get("https://hostname", "https://baidu.com")
|
|
try_get("https://hostname:port", "https://baidu.com:443")
|
|
try_get("HTTPS:// uppercase", "HTTPS://baidu.com")
|
|
else
|
|
print(" SKIP: https tests (no ltls module)")
|
|
end
|
|
|
|
-- bare ipv6 should be rejected (RFC 3986: brackets required)
|
|
try_error("bare ipv6 ::1 (reject)", "::1", "bare IPv6")
|
|
try_error("bare full ipv6 (reject)", "2001:db8::1", "bare IPv6")
|
|
|
|
-- bracketed ipv6 (parsing verification, connection may fail without ipv6 network)
|
|
try_parse("[ipv6] no port", "[::1]")
|
|
try_parse("[ipv6]:port", "[::1]:8080")
|
|
try_parse("[full ipv6] no port", "[2001:db8::1]")
|
|
try_parse("[full ipv6]:port", "[2001:db8::1]:80")
|
|
try_parse("http://[ipv6]", "http://[::1]")
|
|
try_parse("http://[ipv6]:port", "http://[::1]:80")
|
|
if pcall(require, "ltls.c") then
|
|
try_parse("https://[ipv6]:port", "https://[::1]:443")
|
|
end
|
|
|
|
-- ipv6 real connection test via AAAA DNS resolution
|
|
local ok_aaaa, ipv6 = pcall(dns.resolve, "ipv6.google.com", true)
|
|
if ok_aaaa and ipv6 then
|
|
local ipv6_host = string.format("[%s]", ipv6)
|
|
print(string.format(" resolved ipv6.google.com AAAA -> %s", ipv6))
|
|
try_parse("[ipv6] from AAAA", ipv6_host)
|
|
try_parse("[ipv6]:80 from AAAA", ipv6_host .. ":80")
|
|
try_parse("http://[ipv6] from AAAA", "http://" .. ipv6_host)
|
|
try_parse("http://[ipv6]:80 from AAAA", "http://" .. ipv6_host .. ":80")
|
|
else
|
|
print(" SKIP: ipv6 AAAA tests (dns resolve failed for ipv6.google.com)")
|
|
end
|
|
|
|
-- k8s internal domain variants (parsing verification, connection will fail outside k8s)
|
|
-- domains ending with digits must not be mistaken for ipv4
|
|
try_parse("k8s svc short", "my-svc:8080")
|
|
try_parse("k8s svc.ns", "my-svc.default:8080")
|
|
try_parse("k8s svc.ns.svc", "my-svc.default.svc:8080")
|
|
try_parse("k8s svc fqdn", "my-svc.default.svc.cluster.local:8080")
|
|
try_parse("k8s svc fqdn no port", "my-svc.default.svc.cluster.local")
|
|
try_parse("k8s http:// svc fqdn", "http://my-svc.default.svc.cluster.local:8080")
|
|
try_parse("k8s headless pod", "pod-0.my-svc.default.svc.cluster.local:8080")
|
|
try_parse("k8s nodeport", "http://node1.cluster.local:30080")
|
|
try_parse("k8s statefulset pod-0", "web-0.nginx-svc.default:8080")
|
|
try_parse("k8s numeric ns", "my-svc.ns123:8080")
|
|
try_parse("k8s all-digit segments", "svc123.ns456:9090")
|
|
try_parse("k8s pod ip-style dns", "10-244-0-5.default.pod.cluster.local:80")
|
|
try_parse("k8s 5-segment digits", "10.0.0.1.nip.io:8080")
|
|
|
|
print(string.format("[test_connect_formats] done, %d/%d passed", passed, total))
|
|
end
|
|
|
|
local function main()
|
|
dns.server()
|
|
|
|
test_connect_formats()
|
|
|
|
http_stream_test()
|
|
http_head_test()
|
|
http_url_test()
|
|
|
|
http_test("http")
|
|
if not pcall(require,"ltls.c") then
|
|
print "No ltls module, https is not supported"
|
|
else
|
|
http_test("https")
|
|
end
|
|
end
|
|
|
|
skynet.start(function()
|
|
print(pcall(main))
|
|
skynet.exit()
|
|
end)
|