mirror of
https://github.com/cloudwu/skynet.git
synced 2026-07-24 20:23:06 +00:00
use async dns resolve in httpc, and dns resolve cache by ttl. see about issue #253
This commit is contained in:
@@ -71,6 +71,7 @@ local MAX_DOMAIN_LEN = 1024
|
|||||||
local MAX_LABEL_LEN = 63
|
local MAX_LABEL_LEN = 63
|
||||||
local MAX_PACKET_LEN = 2048
|
local MAX_PACKET_LEN = 2048
|
||||||
local DNS_HEADER_LEN = 12
|
local DNS_HEADER_LEN = 12
|
||||||
|
local TIMEOUT = 30 * 100 -- 30 seconds
|
||||||
|
|
||||||
local QTYPE = {
|
local QTYPE = {
|
||||||
A = 1,
|
A = 1,
|
||||||
@@ -82,8 +83,18 @@ local QCLASS = {
|
|||||||
IN = 1,
|
IN = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local weak = {__mode = "kv"}
|
||||||
|
local CACHE = {}
|
||||||
|
|
||||||
local dns = {}
|
local dns = {}
|
||||||
|
|
||||||
|
function dns.flush()
|
||||||
|
CACHE[QTYPE.A] = setmetatable({},weak)
|
||||||
|
CACHE[QTYPE.AAAA] = setmetatable({},weak)
|
||||||
|
end
|
||||||
|
|
||||||
|
dns.flush()
|
||||||
|
|
||||||
local function verify_domain_name(name)
|
local function verify_domain_name(name)
|
||||||
if #name > MAX_DOMAIN_LEN then
|
if #name > MAX_DOMAIN_LEN then
|
||||||
return false
|
return false
|
||||||
@@ -206,33 +217,51 @@ local function resolve(content)
|
|||||||
-- verify answer
|
-- verify answer
|
||||||
assert(answer_header.qdcount == 1, "malformed packet")
|
assert(answer_header.qdcount == 1, "malformed packet")
|
||||||
|
|
||||||
local resp = request_pool[answer_header.tid]
|
|
||||||
if not resp then
|
|
||||||
skynet.error("Recv an invalid tid when dns query")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local question,left = unpack_question(content, left)
|
local question,left = unpack_question(content, left)
|
||||||
if question.name ~= resp.name then
|
|
||||||
skynet.error("Recv an invalid name when dns query")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local ttl
|
local ttl
|
||||||
local answer
|
local answer
|
||||||
local answers = {}
|
local answers_ipv4
|
||||||
|
local answers_ipv6
|
||||||
|
|
||||||
for i=1, answer_header.ancount do
|
for i=1, answer_header.ancount do
|
||||||
answer, left = unpack_answer(content, left)
|
answer, left = unpack_answer(content, left)
|
||||||
-- only extract qtype address
|
local answers
|
||||||
if answer.atype == resp.qtype then
|
if answer.atype == QTYPE.A then
|
||||||
local ip = unpack_rdata(resp.qtype, answer.rdata)
|
answers_ipv4 = answers_ipv4 or {}
|
||||||
|
answers = answers_ipv4
|
||||||
|
elseif answer.atype == QTYPE.AAAA then
|
||||||
|
answers_ipv6 = answers_ipv6 or {}
|
||||||
|
answers = answers_ipv6
|
||||||
|
end
|
||||||
|
if answers then
|
||||||
|
local ip = unpack_rdata(answer.atype, answer.rdata)
|
||||||
ttl = ttl and math.min(ttl, answer.ttl) or answer.ttl
|
ttl = ttl and math.min(ttl, answer.ttl) or answer.ttl
|
||||||
answers[#answers+1] = ip
|
answers[#answers+1] = ip
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #answers > 0 then
|
if answers_ipv4 then
|
||||||
resp.answers = answers
|
CACHE[QTYPE.A][question.name] = { answers = answers_ipv4, ttl = skynet.now() + ttl * 100 }
|
||||||
|
end
|
||||||
|
|
||||||
|
if answers_ipv6 then
|
||||||
|
CACHE[QTYPE.AAAA][question.name] = { answers = answers_ipv6, ttl = skynet.now() + ttl * 100 }
|
||||||
|
end
|
||||||
|
|
||||||
|
local resp = request_pool[answer_header.tid]
|
||||||
|
if not resp then
|
||||||
|
-- the resp may be timeout
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if question.name ~= resp.name then
|
||||||
|
skynet.error("Recv an invalid name when dns query")
|
||||||
|
end
|
||||||
|
|
||||||
|
local r = CACHE[resp.qtype][resp.name]
|
||||||
|
if r then
|
||||||
|
resp.answers = r.answers
|
||||||
end
|
end
|
||||||
|
|
||||||
skynet.wakeup(resp.co)
|
skynet.wakeup(resp.co)
|
||||||
@@ -256,26 +285,54 @@ function dns.server(server, port)
|
|||||||
return server
|
return server
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function lookup_cache(name, qtype, ignorettl)
|
||||||
|
local result = CACHE[qtype][name]
|
||||||
|
if result then
|
||||||
|
if ignorettl or (result.ttl > skynet.now()) then
|
||||||
|
return result.answers
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function suspend(tid, name, qtype)
|
local function suspend(tid, name, qtype)
|
||||||
local req = {
|
local req = {
|
||||||
name = name,
|
name = name,
|
||||||
tid = tid,
|
tid = tid,
|
||||||
qtype = qtype,
|
qtype = qtype,
|
||||||
time = skynet.now(), -- for timeout
|
|
||||||
co = coroutine.running(),
|
co = coroutine.running(),
|
||||||
}
|
}
|
||||||
request_pool[tid] = req
|
request_pool[tid] = req
|
||||||
|
skynet.fork(function()
|
||||||
|
skynet.sleep(TIMEOUT)
|
||||||
|
local req = request_pool[tid]
|
||||||
|
if req then
|
||||||
|
-- cancel tid
|
||||||
|
skynet.error(string.format("DNS query %s timeout", name))
|
||||||
|
request_pool[tid] = nil
|
||||||
|
skynet.wakeup(req.co)
|
||||||
|
end
|
||||||
|
end)
|
||||||
skynet.wait(req.co)
|
skynet.wait(req.co)
|
||||||
local answers = request_pool[tid].answers
|
local answers = req.answers
|
||||||
request_pool[tid] = nil
|
request_pool[tid] = nil
|
||||||
assert(answers, "no ip")
|
if not req.answers then
|
||||||
return answers[1], answers
|
local answers = lookup_cache(name, qtype, true)
|
||||||
|
if answers then
|
||||||
|
return answers[1], answers
|
||||||
|
end
|
||||||
|
error "timeout or no answer"
|
||||||
|
end
|
||||||
|
return req.answers[1], req.answers
|
||||||
end
|
end
|
||||||
|
|
||||||
function dns.resolve(name, ipv6)
|
function dns.resolve(name, ipv6)
|
||||||
local qtype = ipv6 and QTYPE.AAAA or QTYPE.A
|
local qtype = ipv6 and QTYPE.AAAA or QTYPE.A
|
||||||
local name = name:lower()
|
local name = name:lower()
|
||||||
assert(verify_domain_name(name) , "illegal name")
|
assert(verify_domain_name(name) , "illegal name")
|
||||||
|
local answers = lookup_cache(name, qtype)
|
||||||
|
if answers then
|
||||||
|
return answers[1], answers
|
||||||
|
end
|
||||||
local question_header = {
|
local question_header = {
|
||||||
tid = gen_tid(),
|
tid = gen_tid(),
|
||||||
flags = 0x100, -- flags: 00000001 00000000, set RD
|
flags = 0x100, -- flags: 00000001 00000000, set RD
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local socket = require "http.sockethelper"
|
local socket = require "http.sockethelper"
|
||||||
local url = require "http.url"
|
local url = require "http.url"
|
||||||
local internal = require "http.internal"
|
local internal = require "http.internal"
|
||||||
|
local dns = require "dns"
|
||||||
local string = string
|
local string = string
|
||||||
local table = table
|
local table = table
|
||||||
|
|
||||||
@@ -78,6 +79,13 @@ local function request(fd, method, host, url, recvheader, header, content)
|
|||||||
return code, body
|
return code, body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local async_dns
|
||||||
|
|
||||||
|
function httpc.dns(server,port)
|
||||||
|
async_dns = true
|
||||||
|
dns.server(server,port)
|
||||||
|
end
|
||||||
|
|
||||||
function httpc.request(method, host, url, recvheader, header, content)
|
function httpc.request(method, host, url, recvheader, header, content)
|
||||||
local hostname, port = host:match"([^:]+):?(%d*)$"
|
local hostname, port = host:match"([^:]+):?(%d*)$"
|
||||||
if port == "" then
|
if port == "" then
|
||||||
@@ -85,6 +93,9 @@ function httpc.request(method, host, url, recvheader, header, content)
|
|||||||
else
|
else
|
||||||
port = tonumber(port)
|
port = tonumber(port)
|
||||||
end
|
end
|
||||||
|
if async_dns and not hostname:match(".*%d+$") then
|
||||||
|
hostname = dns.resolve(hostname)
|
||||||
|
end
|
||||||
local fd = socket.connect(hostname, port)
|
local fd = socket.connect(hostname, port)
|
||||||
local ok , statuscode, body = pcall(request, fd,method, host, url, recvheader, header, content)
|
local ok , statuscode, body = pcall(request, fd,method, host, url, recvheader, header, content)
|
||||||
socket.close(fd)
|
socket.close(fd)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ local httpc = require "http.httpc"
|
|||||||
local dns = require "dns"
|
local dns = require "dns"
|
||||||
|
|
||||||
skynet.start(function()
|
skynet.start(function()
|
||||||
|
httpc.dns() -- set dns server
|
||||||
print("GET baidu.com")
|
print("GET baidu.com")
|
||||||
local respheader = {}
|
local respheader = {}
|
||||||
local status, body = httpc.get("baidu.com", "/", respheader)
|
local status, body = httpc.get("baidu.com", "/", respheader)
|
||||||
|
|||||||
Reference in New Issue
Block a user