fix #37 and add support for openmetrics format

master
parent e2fef64a18
commit 4857fbf564
  1. 131
      DNSLG/Formatter.py
  2. 27
      DNSLG/Resolver.py
  3. 47
      DNSLG/__init__.py

@ -5,7 +5,7 @@ import dns.dnssec
import dns.version as dnspythonversion
import base64
import platform
import pkg_resources
import pkg_resources
import time
import sys
import struct
@ -37,7 +37,7 @@ def keylength(alg, key):
# Unknown, best guess. Read the RFCs 6605 or 5933 to find out
# the format of ECDSA or GOST keys.
return len(key)*8
class Formatter(object):
""" This ia the base class for the various Formatters. A formatter
takes a "DNS answer" object and format it for a given output
@ -49,7 +49,7 @@ class Formatter(object):
except pkg_resources.DistributionNotFound:
self.myversion = "VERSION UNKNOWN"
self.domain = domain
def format(self, answer, qtype, qclass, flags, querier):
""" Parameter "answer" must be of type
Answer.ExtendedAnswer. "qtype" and "qclass" are strings, flags an integer
@ -152,7 +152,7 @@ class TextFormatter(Formatter):
keylength(rdata.algorithm, rdata.key),
rdata.flags, flags_text)
elif rdata.rdtype == dns.rdatatype.NSEC3PARAM:
self.output += "NSEC3PARAM: algorithm %i, iterations %i, salt %s\n" % (rdata.algorithm, rdata.iterations, to_hexstring(rdata.salt))
self.output += "NSEC3PARAM: algorithm %i, iterations %i, salt %s\n" % (rdata.algorithm, rdata.iterations, to_hexstring(rdata.salt))
elif rdata.rdtype == dns.rdatatype.SSHFP:
self.output += "SSH fingerprint: algorithm %i, digest type %i, fingerprint %s\n" % \
(rdata.algorithm, rdata.fp_type, to_hexstring(rdata.fingerprint))
@ -178,7 +178,7 @@ class TextFormatter(Formatter):
(self.myversion,
dnspythonversion.version, platform.python_implementation(),
platform.python_version(), platform.system())
def result(self, querier):
return self.output.encode()
@ -282,7 +282,7 @@ class ZoneFormatter(Formatter):
(self.myversion, dnspythonversion.version,
platform.python_implementation(),
platform.python_version(), platform.system())
def result(self, querier):
return self.output.encode()
@ -327,7 +327,7 @@ class JsonFormatter(Formatter):
self.object['AnswerSection'].append({'Type': 'CNAME',
'Target': str(rdata.target)})
elif rdata.rdtype == dns.rdatatype.MX:
self.object['AnswerSection'].append({'Type': 'MX',
self.object['AnswerSection'].append({'Type': 'MX',
'MailExchanger': str(rdata.exchange),
'Preference': rdata.preference})
elif rdata.rdtype == dns.rdatatype.TXT:
@ -358,8 +358,8 @@ class JsonFormatter(Formatter):
# always available on 2012-05-17
pass
self.object['AnswerSection'].append(returned_object)
elif rdata.rdtype == dns.rdatatype.NSEC3PARAM:
self.object['AnswerSection'].append({'Type': 'NSEC3PARAM', 'Algorithm': rdata.algorithm, 'Iterations': rdata.iterations, 'Salt': to_hexstring(rdata.salt), 'Flags': rdata.flags})
elif rdata.rdtype == dns.rdatatype.NSEC3PARAM:
self.object['AnswerSection'].append({'Type': 'NSEC3PARAM', 'Algorithm': rdata.algorithm, 'Iterations': rdata.iterations, 'Salt': to_hexstring(rdata.salt), 'Flags': rdata.flags})
elif rdata.rdtype == dns.rdatatype.DS:
self.object['AnswerSection'].append({'Type': 'DS', 'DelegationKey': rdata.key_tag,
'DigestType': rdata.digest_type})
@ -387,7 +387,7 @@ class JsonFormatter(Formatter):
'Priority': rdata.priority,
'Weight': rdata.weight})
else:
self.object['AnswerSection'].append({'Type': "unknown %i" % rdata.rdtype})
self.object['AnswerSection'].append({'Type': "unknown %i" % rdata.rdtype})
if rdata.rdtype != dns.rdatatype.RRSIG:
self.object['AnswerSection'][-1]['TTL'] = rrset.ttl
self.object['AnswerSection'][-1]['Name'] = str(rrset.name)
@ -408,11 +408,56 @@ class JsonFormatter(Formatter):
platform.python_implementation(),
platform.python_version(), platform.system())
def result(self, querier):
return (json.dumps(self.object, indent=True) + "\n").encode()
from prometheus_client.core import InfoMetricFamily, GaugeMetricFamily
from prometheus_client import CollectorRegistry, generate_latest
class OpenMetricsFormatter(Formatter):
class DnsLgCollector(object):
def __init__(self, json_data, version):
self.json_data = json_data
self.version = version
def collect(self):
for record in self.json_data['AnswerSection']:
yield InfoMetricFamily('answer_section_rr', 'Record values', value={
k: str(v) for k, v in record.items()
})
yield InfoMetricFamily('dns_lg', 'Build information', value={
'dns_lg_version': self.version,
'dnspython_version': dnspythonversion.version,
'python_implementation': platform.python_implementation(),
'python_version': platform.python_version(),
'system': platform.system(),
})
query_labels = dict(self.json_data['QuestionSection'], Server=self.json_data['Query']['Server'])
duration = GaugeMetricFamily('query_duration_seconds', 'Query duration in seconds', labels=query_labels.keys())
duration.add_metric(query_labels.values(), self.json_data['Query']['Duration'])
yield duration
rrs_count = GaugeMetricFamily('answer_rrs_count', 'Number of records in the answer section', labels=query_labels.keys())
rrs_count.add_metric(query_labels.values(), len(self.json_data['AnswerSection']))
yield rrs_count
def format(self, answer, qtype, qclass, flags, querier):
json_formatter = JsonFormatter(self.domain)
json_formatter.format(answer, qtype, qclass, flags, querier)
json_data = json_formatter.object
self.registry = CollectorRegistry()
self.registry.register(self.DnsLgCollector(json_data, self.myversion))
def result(self, querier):
return generate_latest(self.registry)
# XML
# http://www.owlfish.com/software/simpleTAL/
from simpletal import simpleTAL, simpleTALES, simpleTALUtils
@ -572,11 +617,11 @@ class XmlFormatter(Formatter):
icontext.addGlobal ("address", rdata.address)
if rdata.rdtype == dns.rdatatype.A:
self.a_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
else:
self.aaaa_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.SRV:
icontext.addGlobal ("priority", rdata.priority)
@ -584,20 +629,20 @@ class XmlFormatter(Formatter):
icontext.addGlobal ("port", rdata.port)
icontext.addGlobal ("name", rdata.target)
self.srv_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.MX:
icontext.addGlobal ("preference", rdata.preference)
icontext.addGlobal ("exchange", rdata.exchange)
self.mx_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.NSEC3PARAM:
icontext.addGlobal ("algorithm", rdata.algorithm)
icontext.addGlobal ("flags", rdata.flags)
icontext.addGlobal ("iterations", rdata.iterations)
self.nsec3param_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.DS:
icontext.addGlobal ("keytag", rdata.key_tag)
@ -605,15 +650,15 @@ class XmlFormatter(Formatter):
icontext.addGlobal ("algorithm", rdata.algorithm)
icontext.addGlobal ("digest", to_hexstring(rdata.digest))
self.ds_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.DLV:
icontext.addGlobal ("keytag", rdata.key_tag)
icontext.addGlobal ("digesttype", rdata.digest_type)
icontext.addGlobal ("algorithm", rdata.algorithm)
icontext.addGlobal ("digest", to_hexstring(rdata.digest))
icontext.addGlobal ("digest", to_hexstring(rdata.digest))
self.dlv_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.DNSKEY:
try:
@ -622,21 +667,21 @@ class XmlFormatter(Formatter):
except AttributeError:
# key_id appeared only in dnspython 1.9. Not
# always available on 2012-05-17
pass
pass
icontext.addGlobal ("protocol", rdata.protocol)
icontext.addGlobal ("flags", rdata.flags)
icontext.addGlobal ("algorithm", rdata.algorithm)
icontext.addGlobal ("length", keylength(rdata.algorithm, rdata.key))
icontext.addGlobal ("key", to_hexstring(rdata.key))
self.dnskey_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.SSHFP:
icontext.addGlobal ("algorithm", rdata.algorithm)
icontext.addGlobal ("fptype", rdata.fp_type)
icontext.addGlobal ("fingerprint", to_hexstring(rdata.fingerprint))
self.sshfp_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.NAPTR:
icontext.addGlobal ("flags", rdata.flags)
@ -651,7 +696,7 @@ class XmlFormatter(Formatter):
# always be UTF-8
icontext.addGlobal ("replacement", rdata.replacement)
self.naptr_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.TXT:
# Yes, some people add Unicode in TXT records,
@ -660,36 +705,36 @@ class XmlFormatter(Formatter):
text = str((b" ".join(rdata.strings)).decode())
icontext.addGlobal ("text", text)
self.txt_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.SPF:
icontext.addGlobal ("text", " ".join(rdata.strings))
self.spf_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.PTR:
icontext.addGlobal ("name", rdata.target)
self.ptr_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.CNAME:
icontext.addGlobal ("target", rdata.target)
self.cname_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.LOC:
icontext.addGlobal ("latitude", rdata.float_latitude)
icontext.addGlobal ("longitude", rdata.float_longitude)
icontext.addGlobal ("altitude", float(rdata.altitude)/100)
self.loc_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.URI:
icontext.addGlobal ("target", rdata.target)
icontext.addGlobal ("weight", rdata.weight)
icontext.addGlobal ("priority", rdata.priority)
self.uri_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.NS:
icontext.addGlobal ("name", rdata.target)
@ -705,16 +750,16 @@ class XmlFormatter(Formatter):
icontext.addGlobal ("expire", rdata.expire)
icontext.addGlobal ("minimum", rdata.minimum)
self.soa_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
else:
icontext.addGlobal ("rtype", rdata.rdtype)
icontext.addGlobal ("rdlength", 0) # TODO: useless, anyway (and
# no easy way to compute it in dnspython)
# TODO: rdata
self.unknown_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
records.append(iresult.getvalue())
else:
pass # TODO what to send back when no data for this QTYPE?
@ -723,7 +768,7 @@ class XmlFormatter(Formatter):
self.acontext.addGlobal ("ttl", rrset.ttl)
iresult = io.StringIO()
self.set_template.expand (self.acontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
self.rrsets.append(iresult.getvalue())
else:
@ -732,7 +777,7 @@ class XmlFormatter(Formatter):
def result(self, querier):
result = io.StringIO()
self.context.addGlobal("rrsets", self.rrsets)
self.xml_template.expand (self.context, result,
self.xml_template.expand (self.context, result,
outputEncoding=querier.encoding)
return result.getvalue().encode()
@ -747,7 +792,7 @@ html_template = """<?xml version="1.0" encoding="utf-8"?>
<link rel="author" href="http://www.bortzmeyer.org/static/moi.html"/>
<link rel="service-doc" href="https://www.bortzmeyer.org/dns-lg-usage.html"/>
<link tal:condition="opensearch" rel="search"
type="application/opensearchdescription+xml"
type="application/opensearchdescription+xml"
tal:attributes="href opensearch"
title="DNS Looking Glass" />
<meta http-equiv="Content-Type" tal:attributes="content contenttype"/>
@ -904,7 +949,7 @@ class HtmlFormatter(Formatter):
plural = ""
result += "%i second%s" % (seconds, plural)
return result
def format(self, answer, qtype, qclass, flags, querier):
self.template = simpleTAL.compileXMLTemplate (html_template)
self.address_template = simpleTAL.compileXMLTemplate (address_html_template)
@ -934,7 +979,7 @@ class HtmlFormatter(Formatter):
self.context.addGlobal ("resolver", answer.nameserver)
self.context.addGlobal ("email", querier.email_admin)
self.context.addGlobal ("doc", querier.url_doc)
self.context.addGlobal("contenttype",
self.context.addGlobal("contenttype",
"text/html; charset=%s" % querier.encoding)
self.context.addGlobal ("css", querier.url_css)
self.context.addGlobal ("opensearch", querier.url_opensearch)
@ -947,12 +992,12 @@ class HtmlFormatter(Formatter):
self.context.addGlobal("description", querier.description)
iresult = io.StringIO()
icontext = simpleTALES.Context(allowPythonPath=False)
icontext.addGlobal("pyversion", platform.python_implementation() + " " +
icontext.addGlobal("pyversion", platform.python_implementation() + " " +
platform.python_version() + " on " + platform.system())
icontext.addGlobal("dnsversion", dnspythonversion.version)
icontext.addGlobal("myversion", self.myversion)
self.version_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
self.context.addGlobal("versions", iresult.getvalue())
str_flags = ""
@ -977,7 +1022,7 @@ class HtmlFormatter(Formatter):
querier,
reverse=True))
self.address_template.expand (icontext, iresult,
suppressXMLDeclaration=True,
suppressXMLDeclaration=True,
outputEncoding=querier.encoding)
elif rdata.rdtype == dns.rdatatype.SOA:
icontext.addGlobal ("master", rdata.mname)
@ -1139,9 +1184,9 @@ class HtmlFormatter(Formatter):
self.rrsets = None
def result(self, querier):
result = io.StringIO()
result = io.StringIO()
self.context.addGlobal("rrsets", self.rrsets)
self.template.expand (self.context, result,
self.template.expand (self.context, result,
outputEncoding=querier.encoding,
docType='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">')
return ((result.getvalue() + "\n").encode())

@ -4,6 +4,7 @@ import copy
import dns.message
import dns.resolver
import dns.inet
from . import Answer
DEFAULT_EDNS_SIZE=2048
@ -39,7 +40,7 @@ class UnknownError(Exception):
return repr(self.value)
class Resolver(object):
def __init__(self, nameservers=None, maximum=3, timeout=1.0,
edns_version=0, edns_payload=DEFAULT_EDNS_SIZE, do_dnssec=False):
# TODO: ednsflags such as NSID
@ -66,7 +67,7 @@ class Resolver(object):
for i in range(0, len(self.original_nameservers)):
self.nameservers.append(self.original_nameservers[i])
self.do = self.original_do
def query(self, name, type, klass='IN', tcp=False, cd=False):
""" The returned value is a DNSLG.Answer """
if len(self.nameservers) == 0:
@ -79,7 +80,7 @@ class Resolver(object):
except TypeError: # Old DNS Python... Code here just as long as it lingers in some places
try:
message = dns.message.make_query(name, type, rdclass=klass,
use_edns=self.edns,
use_edns=self.edns,
want_dnssec=self.do)
except dns.rdatatype.UnknownRdatatype:
raise UnknownRRtype()
@ -136,8 +137,24 @@ class Resolver(object):
self.do = dnssec
def set_nameservers(self, nameservers):
self.nameservers = nameservers
self.nameservers = []
for ns in nameservers:
try:
dns.inet.af_for_address(ns)
except ValueError:
try:
self.nameservers.extend(
res.to_text() for res in dns.resolver.query(ns, 'aaaa')
)
self.nameservers.extend(
res.to_text() for res in dns.resolver.query(ns, 'a')
)
except:
# TODO: Add error handling here
pass
else:
self.nameservers.append(ns)
def reset(self):
self.edns = self.original_edns
self.payload = self.original_payload

@ -3,7 +3,7 @@
# Standard library
from builtins import str
from builtins import object
from cgi import escape
from html import escape
from urllib.parse import parse_qs
import encodings.idna
import os
@ -31,7 +31,7 @@ from . import Resolver
# If you need to change these values, it is better to do it when
# calling the Querier() constructor.
default_base_url = ""
default_base_url = ""
default_edns_size = 4096
# TODO: allow to use prefixes in the whitelist
default_whitelist=[netaddr.IPAddress("127.0.0.1"), netaddr.IPAddress("::1")]
@ -57,7 +57,7 @@ class Querier(object):
bucket_size=default_bucket_size,
whitelist=default_whitelist, edns_size=default_edns_size,
handle_wk_files=default_handle_wk_files,
google_code=None, description=None, description_html=None,
google_code=None, description=None, description_html=None,
forbidden_suffixes=[]):
self.resolver = Resolver.Resolver(edns_payload=edns_size)
self.logger = logging.getLogger('DNSLG')
@ -67,7 +67,7 @@ class Querier(object):
ft.converter = time.gmtime
fh = logging.handlers.SysLogHandler(address="/dev/log", facility="local0")
fh.setFormatter(ft)
self.logger.addHandler(fh)
self.logger.addHandler(fh)
self.buckets = {}
self.base_url = base_url
self.whitelist = whitelist
@ -92,7 +92,7 @@ class Querier(object):
suffix += '.'
self.forbidden_suffixes.append(suffix)
self.resolver.reset()
def default(self, start_response, path):
output = """
I'm the default handler, \"%s\" was called.
@ -101,7 +101,7 @@ Are you sure of the URL?\n""" % path
return [output.encode()]
def emptyfile(self, start_response):
output = ""
output = ""
send_response(start_response, '200 OK' , output, 'text/plain')
return [output.encode()]
@ -111,12 +111,12 @@ Are you sure of the URL?\n""" % path
output = """
User-agent: *
Disallow: /
"""
"""
send_response(start_response, '200 OK' , output, 'text/plain')
return [output.encode()]
def notfound(self, start_response):
output = "Not found\r\n"
output = "Not found\r\n"
send_response(start_response, '404 Not Found' , output, 'text/plain')
return [output.encode()]
@ -131,7 +131,7 @@ Disallow: /
if not format:
mformat = req.accept.best_match(['text/html', 'application/xml',
'application/json', 'text/dns',
'text/plain'])
'text/plain', 'application/openmetrics-text'])
if mformat == "text/html":
format = "HTML"
elif mformat == "application/xml":
@ -141,9 +141,11 @@ Disallow: /
elif mformat == "text/dns":
format = "ZONE"
elif mformat == "text/plain":
format = "TEXT"
format = "TEXT"
elif mformat == 'application/openmetrics-text':
format = 'OPENMETRICS'
if not mformat:
output = "No suitable output format found\n"
output = "No suitable output format found\n"
send_response(start_response, '400 Bad request', output, plaintype)
return [output.encode()]
mtype = '%s; charset=%s' % (mformat, self.encoding)
@ -160,6 +162,8 @@ Disallow: /
# TODO: application/dns, "detached" DNS (binary), see issue #20
elif format == "XML":
mtype = 'application/xml'
elif format == 'OPENMETRICS':
mtype = 'application/openmetrics-text'
else:
output = "Unsupported format \"%s\"\n" % format
send_response(start_response, '400 Bad request', output, plaintype)
@ -224,7 +228,7 @@ Disallow: /
else:
qtype = requested_qtype
if reverse and qtype != 'PTR':
output = "You cannot ask for a query type other than PTR with reverse queries\n"
output = "You cannot ask for a query type other than PTR with reverse queries\n"
send_response(start_response, '400 Bad qtype with reverse',
output, plaintype)
return [output.encode()]
@ -260,6 +264,8 @@ Disallow: /
formatter = Formatter.ZoneFormatter(domain)
elif format == "XML":
formatter = Formatter.XmlFormatter(domain)
elif format == 'OPENMETRICS':
formatter = Formatter.OpenMetricsFormatter(domain)
self.resolver.reset()
if edns_size is None:
self.resolver.set_edns(version=-1)
@ -276,15 +282,15 @@ Disallow: /
else:
try:
answer = self.resolver.query(qdomain, "A", tcp=tcp, cd=cd)
except dns.resolver.NoAnswer:
except dns.resolver.NoAnswer:
answer = None
try:
answer_bis = self.resolver.query(qdomain, "AAAA", tcp=tcp, cd=cd)
if answer_bis is not None:
for rrset in answer_bis.answer:
answer.answer.append(rrset)
except dns.resolver.NoAnswer:
pass
except dns.resolver.NoAnswer:
pass
# TODO: what if flags are different with A and AAAA? (Should not happen)
if answer is None:
query_end = datetime.now()
@ -301,12 +307,12 @@ Disallow: /
except Resolver.UnknownRRtype:
output = "Record type %s does not exist\n" % qtype
output = output.encode(self.encoding)
send_response(start_response, '400 Unknown record type', output,
send_response(start_response, '400 Unknown record type', output,
plaintype)
except Resolver.UnknownClass:
output = "Class %s does not exist\n" % qclass
output = output.encode(self.encoding)
send_response(start_response, '400 Unknown class', output,
send_response(start_response, '400 Unknown class', output,
plaintype)
except Resolver.NoSuchDomainName:
output = "Domain %s/%s does not exist\n" % (domain, qdomain)
@ -321,13 +327,13 @@ Disallow: /
output = "Server failure for all name servers for %s (may be a DNSSEC validation error)\n" % domain
output = output.encode(self.encoding)
send_response(start_response, '504 Servfail', output, plaintype)
except Resolver.Timeout:
except Resolver.Timeout:
output = "No server replies for domain %s\n" % domain
output = output.encode(self.encoding)
# TODO issue #11. In that case, do not serialize output.
send_response(start_response, '504 Timeout', output,
"text/plain")
except Resolver.NoPositiveAnswer:
except Resolver.NoPositiveAnswer:
output = "No server replies for domain %s\n" % domain
output = output.encode(self.encoding)
# TODO issue #11
@ -339,7 +345,7 @@ Disallow: /
# TODO issue #11
send_response(start_response, '500 Unknown server error', output, plaintype)
return [output]
def application(self, environ, start_response):
plaintype = 'text/plain; charset=%s' % self.encoding
# TODO see issue #1 about HEAD support
@ -412,4 +418,3 @@ Disallow: /
do_dnssec, tcp, cd, edns_size, reverse)
else:
return self.default(start_response, path)

Loading…
Cancel
Save