mirror of
https://github.com/servo/servo.git
synced 2025-09-02 11:08:22 +01:00
Update web-platform-tests to revision b9d4748d6a7f9b21bd420486955b44349aa005ea
This commit is contained in:
parent
0a9a222356
commit
28ed236c3a
76 changed files with 2158 additions and 581 deletions
|
@ -153,7 +153,7 @@ class MtimeCache(CacheFile):
|
|||
|
||||
def __init__(self, cache_root, tests_root, manifest_path, rebuild=False):
|
||||
self.manifest_path = manifest_path
|
||||
super(MtimeCache, self).__init__(cache_root, tests_root, rebuild=False)
|
||||
super(MtimeCache, self).__init__(cache_root, tests_root, rebuild)
|
||||
|
||||
def updated(self, rel_path, stat):
|
||||
"""Return a boolean indicating whether the file changed since the cache was last updated.
|
||||
|
|
|
@ -179,7 +179,11 @@ class Firefox(Browser):
|
|||
binary = find_executable("firefox", os.path.join(path, "firefox"))
|
||||
elif self.platform == "win":
|
||||
import mozinstall
|
||||
binary = mozinstall.get_binary(path, "firefox")
|
||||
try:
|
||||
binary = mozinstall.get_binary(path, "firefox")
|
||||
except mozinstall.InvalidBinary:
|
||||
# ignore the case where we fail to get a binary
|
||||
pass
|
||||
elif self.platform == "macos":
|
||||
binary = find_executable("firefox", os.path.join(path, self.application_name.get(channel, "Firefox Nightly.app"),
|
||||
"Contents", "MacOS"))
|
||||
|
|
|
@ -125,6 +125,11 @@ def check_environ(product):
|
|||
else:
|
||||
hosts_path = "/etc/hosts"
|
||||
|
||||
if os.path.abspath(os.curdir) == wpt_root:
|
||||
wpt_path = "wpt"
|
||||
else:
|
||||
wpt_path = os.path.join(wpt_root, "wpt")
|
||||
|
||||
with open(hosts_path, "r") as f:
|
||||
for line in f:
|
||||
line = line.split("#", 1)[0].strip()
|
||||
|
@ -136,13 +141,14 @@ def check_environ(product):
|
|||
if is_windows:
|
||||
message = """Missing hosts file configuration. Run
|
||||
|
||||
python wpt make-hosts-file | Out-File %s -Encoding ascii -Append
|
||||
python %s make-hosts-file | Out-File %s -Encoding ascii -Append
|
||||
|
||||
in PowerShell with Administrator privileges.""" % hosts_path
|
||||
in PowerShell with Administrator privileges.""" % (wpt_path, hosts_path)
|
||||
else:
|
||||
message = """Missing hosts file configuration. Run
|
||||
|
||||
./wpt make-hosts-file | sudo tee -a %s""" % hosts_path
|
||||
%s make-hosts-file | sudo tee -a %s""" % ("./wpt" if wpt_path == "wpt" else wpt_path,
|
||||
hosts_path)
|
||||
raise WptrunError(message)
|
||||
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ class Virtualenv(object):
|
|||
def install(self, *requirements):
|
||||
try:
|
||||
self.working_set.require(*requirements)
|
||||
except pkg_resources.ResolutionError:
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
@ -98,7 +98,7 @@ class Virtualenv(object):
|
|||
with open(requirements_path) as f:
|
||||
try:
|
||||
self.working_set.require(f.read())
|
||||
except pkg_resources.ResolutionError:
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
|
|
@ -3,7 +3,7 @@ from six.moves.urllib.parse import urljoin
|
|||
from collections import deque
|
||||
|
||||
from wptmanifest.backends import static
|
||||
from wptmanifest.backends.static import ManifestItem
|
||||
from wptmanifest.backends.base import ManifestItem
|
||||
|
||||
import expected
|
||||
|
||||
|
@ -198,7 +198,7 @@ def fuzzy_prop(node):
|
|||
|
||||
|
||||
class ExpectedManifest(ManifestItem):
|
||||
def __init__(self, name, test_path, url_base):
|
||||
def __init__(self, node, test_path, url_base):
|
||||
"""Object representing all the tests in a particular manifest
|
||||
|
||||
:param name: Name of the AST Node associated with this object.
|
||||
|
@ -207,13 +207,14 @@ class ExpectedManifest(ManifestItem):
|
|||
:param test_path: Path of the test file associated with this manifest.
|
||||
:param url_base: Base url for serving the tests in this manifest
|
||||
"""
|
||||
name = node.data
|
||||
if name is not None:
|
||||
raise ValueError("ExpectedManifest should represent the root node")
|
||||
if test_path is None:
|
||||
raise ValueError("ExpectedManifest requires a test path")
|
||||
if url_base is None:
|
||||
raise ValueError("ExpectedManifest requires a base url")
|
||||
ManifestItem.__init__(self, name)
|
||||
ManifestItem.__init__(self, node)
|
||||
self.child_map = {}
|
||||
self.test_path = test_path
|
||||
self.url_base = url_base
|
||||
|
@ -339,12 +340,12 @@ class DirectoryManifest(ManifestItem):
|
|||
|
||||
|
||||
class TestNode(ManifestItem):
|
||||
def __init__(self, name):
|
||||
def __init__(self, node, **kwargs):
|
||||
"""Tree node associated with a particular test in a manifest
|
||||
|
||||
:param name: name of the test"""
|
||||
assert name is not None
|
||||
ManifestItem.__init__(self, name)
|
||||
assert node.data is not None
|
||||
ManifestItem.__init__(self, node, **kwargs)
|
||||
self.updated_expected = []
|
||||
self.new_expected = []
|
||||
self.subtests = {}
|
||||
|
@ -431,12 +432,6 @@ class TestNode(ManifestItem):
|
|||
|
||||
|
||||
class SubtestNode(TestNode):
|
||||
def __init__(self, name):
|
||||
"""Tree node associated with a particular subtest in a manifest
|
||||
|
||||
:param name: name of the subtest"""
|
||||
TestNode.__init__(self, name)
|
||||
|
||||
@property
|
||||
def is_empty(self):
|
||||
if self._data:
|
||||
|
|
|
@ -7,9 +7,7 @@ from six.moves.queue import Empty
|
|||
from collections import namedtuple
|
||||
from multiprocessing import Process, current_process, Queue
|
||||
|
||||
from mozlog import structuredlog
|
||||
|
||||
import wptlogging
|
||||
from mozlog import structuredlog, capture
|
||||
|
||||
# Special value used as a sentinal in various commands
|
||||
Stop = object()
|
||||
|
@ -142,7 +140,7 @@ def start_runner(runner_command_queue, runner_result_queue,
|
|||
|
||||
logger = MessageLogger(send_message)
|
||||
|
||||
with wptlogging.CaptureIO(logger, capture_stdio):
|
||||
with capture.CaptureIO(logger, capture_stdio):
|
||||
try:
|
||||
browser = executor_browser_cls(**executor_browser_kwargs)
|
||||
executor = executor_cls(browser, **executor_kwargs)
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import logging
|
||||
import sys
|
||||
import threading
|
||||
from six import StringIO
|
||||
from multiprocessing import Queue
|
||||
|
||||
from mozlog import commandline, stdadapter, set_default_logger
|
||||
from mozlog.structuredlog import StructuredLogger
|
||||
|
||||
|
||||
def setup(args, defaults):
|
||||
logger = args.pop('log', None)
|
||||
if logger:
|
||||
|
@ -49,86 +46,3 @@ class LogLevelRewriter(object):
|
|||
data = data.copy()
|
||||
data["level"] = self.to_level
|
||||
return self.inner(data)
|
||||
|
||||
|
||||
class LogThread(threading.Thread):
|
||||
def __init__(self, queue, logger, level):
|
||||
self.queue = queue
|
||||
self.log_func = getattr(logger, level)
|
||||
threading.Thread.__init__(self, name="Thread-Log")
|
||||
self.daemon = True
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
try:
|
||||
msg = self.queue.get()
|
||||
except (EOFError, IOError):
|
||||
break
|
||||
if msg is None:
|
||||
break
|
||||
else:
|
||||
self.log_func(msg)
|
||||
|
||||
|
||||
class LoggingWrapper(StringIO):
|
||||
"""Wrapper for file like objects to redirect output to logger
|
||||
instead"""
|
||||
|
||||
def __init__(self, queue, prefix=None):
|
||||
StringIO.__init__(self)
|
||||
self.queue = queue
|
||||
self.prefix = prefix
|
||||
|
||||
def write(self, data):
|
||||
if isinstance(data, str):
|
||||
try:
|
||||
data = data.decode("utf8")
|
||||
except UnicodeDecodeError:
|
||||
data = data.encode("string_escape").decode("ascii")
|
||||
|
||||
if data.endswith("\n"):
|
||||
data = data[:-1]
|
||||
if data.endswith("\r"):
|
||||
data = data[:-1]
|
||||
if not data:
|
||||
return
|
||||
if self.prefix is not None:
|
||||
data = "%s: %s" % (self.prefix, data)
|
||||
self.queue.put(data)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
|
||||
class CaptureIO(object):
|
||||
def __init__(self, logger, do_capture):
|
||||
self.logger = logger
|
||||
self.do_capture = do_capture
|
||||
self.logging_queue = None
|
||||
self.logging_thread = None
|
||||
self.original_stdio = None
|
||||
|
||||
def __enter__(self):
|
||||
if self.do_capture:
|
||||
self.original_stdio = (sys.stdout, sys.stderr)
|
||||
self.logging_queue = Queue()
|
||||
self.logging_thread = LogThread(self.logging_queue, self.logger, "info")
|
||||
sys.stdout = LoggingWrapper(self.logging_queue, prefix="STDOUT")
|
||||
sys.stderr = LoggingWrapper(self.logging_queue, prefix="STDERR")
|
||||
self.logging_thread.start()
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
if self.do_capture:
|
||||
sys.stdout, sys.stderr = self.original_stdio
|
||||
if self.logging_queue is not None:
|
||||
self.logger.info("Closing logging queue")
|
||||
self.logging_queue.put(None)
|
||||
if self.logging_thread is not None:
|
||||
self.logging_thread.join(10)
|
||||
while not self.logging_queue.empty():
|
||||
try:
|
||||
self.logger.warning("Dropping log message: %r", self.logging_queue.get())
|
||||
except Exception:
|
||||
pass
|
||||
self.logging_queue.close()
|
||||
self.logger.info("queue closed")
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
import abc
|
||||
|
||||
from ..node import NodeVisitor
|
||||
from ..parser import parse
|
||||
|
||||
|
||||
class Compiler(NodeVisitor):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def compile(self, tree, data_cls_getter=None, **kwargs):
|
||||
self._kwargs = kwargs
|
||||
return self._compile(tree, data_cls_getter, **kwargs)
|
||||
|
||||
def _compile(self, tree, data_cls_getter=None, **kwargs):
|
||||
"""Compile a raw AST into a form where conditional expressions
|
||||
are represented by ConditionalValue objects that can be evaluated
|
||||
at runtime.
|
||||
|
||||
tree - The root node of the wptmanifest AST to compile
|
||||
|
||||
data_cls_getter - A function taking two parameters; the previous
|
||||
output node and the current ast node and returning
|
||||
the class of the output node to use for the current
|
||||
ast node
|
||||
"""
|
||||
if data_cls_getter is None:
|
||||
self.data_cls_getter = lambda x, y: ManifestItem
|
||||
else:
|
||||
self.data_cls_getter = data_cls_getter
|
||||
|
||||
self.tree = tree
|
||||
self.output_node = self._initial_output_node(tree, **kwargs)
|
||||
self.visit(tree)
|
||||
if hasattr(self.output_node, "set_defaults"):
|
||||
self.output_node.set_defaults()
|
||||
assert self.output_node is not None
|
||||
return self.output_node
|
||||
|
||||
def _initial_output_node(self, node, **kwargs):
|
||||
return self.data_cls_getter(None, None)(node, **kwargs)
|
||||
|
||||
def visit_DataNode(self, node):
|
||||
if node != self.tree:
|
||||
output_parent = self.output_node
|
||||
self.output_node = self.data_cls_getter(self.output_node, node)(node, **self._kwargs)
|
||||
else:
|
||||
output_parent = None
|
||||
|
||||
assert self.output_node is not None
|
||||
|
||||
for child in node.children:
|
||||
self.visit(child)
|
||||
|
||||
if output_parent is not None:
|
||||
# Append to the parent *after* processing all the node data
|
||||
output_parent.append(self.output_node)
|
||||
self.output_node = self.output_node.parent
|
||||
|
||||
assert self.output_node is not None
|
||||
|
||||
@abc.abstractmethod
|
||||
def visit_KeyValueNode(self, node):
|
||||
pass
|
||||
|
||||
def visit_ListNode(self, node):
|
||||
return [self.visit(child) for child in node.children]
|
||||
|
||||
def visit_ValueNode(self, node):
|
||||
return node.data
|
||||
|
||||
def visit_AtomNode(self, node):
|
||||
return node.data
|
||||
|
||||
@abc.abstractmethod
|
||||
def visit_ConditionalNode(self, node):
|
||||
pass
|
||||
|
||||
def visit_StringNode(self, node):
|
||||
indexes = [self.visit(child) for child in node.children]
|
||||
|
||||
def value(x):
|
||||
rv = node.data
|
||||
for index in indexes:
|
||||
rv = rv[index(x)]
|
||||
return rv
|
||||
return value
|
||||
|
||||
def visit_NumberNode(self, node):
|
||||
if "." in node.data:
|
||||
return float(node.data)
|
||||
else:
|
||||
return int(node.data)
|
||||
|
||||
def visit_VariableNode(self, node):
|
||||
indexes = [self.visit(child) for child in node.children]
|
||||
|
||||
def value(x):
|
||||
data = x[node.data]
|
||||
for index in indexes:
|
||||
data = data[index(x)]
|
||||
return data
|
||||
return value
|
||||
|
||||
def visit_IndexNode(self, node):
|
||||
assert len(node.children) == 1
|
||||
return self.visit(node.children[0])
|
||||
|
||||
@abc.abstractmethod
|
||||
def visit_UnaryExpressionNode(self, node):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def visit_BinaryExpressionNode(self, node):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def visit_UnaryOperatorNode(self, node):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def visit_BinaryOperatorNode(self, node):
|
||||
pass
|
||||
|
||||
|
||||
class ManifestItem(object):
|
||||
def __init__(self, node, **kwargs):
|
||||
self.parent = None
|
||||
self.node = node
|
||||
self.children = []
|
||||
self._data = {}
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s>" % (self.__class__, self.node.data)
|
||||
|
||||
def __str__(self):
|
||||
rv = [repr(self)]
|
||||
for item in self.children:
|
||||
rv.extend(" %s" % line for line in str(item).split("\n"))
|
||||
return "\n".join(rv)
|
||||
|
||||
def set_defaults(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def is_empty(self):
|
||||
if self._data:
|
||||
return False
|
||||
return all(child.is_empty for child in self.children)
|
||||
|
||||
@property
|
||||
def root(self):
|
||||
node = self
|
||||
while node.parent is not None:
|
||||
node = node.parent
|
||||
return node
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.node.data
|
||||
|
||||
def get(self, key):
|
||||
for node in [self, self.root]:
|
||||
if key in node._data:
|
||||
return node._data[key]
|
||||
raise KeyError
|
||||
|
||||
def set(self, name, value):
|
||||
self._data[name] = value
|
||||
|
||||
def remove(self):
|
||||
if self.parent:
|
||||
self.parent.children.remove(self)
|
||||
self.parent = None
|
||||
|
||||
def iterchildren(self, name=None):
|
||||
for item in self.children:
|
||||
if item.name == name or name is None:
|
||||
yield item
|
||||
|
||||
def has_key(self, key):
|
||||
for node in [self, self.root]:
|
||||
if key in node._data:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _flatten(self):
|
||||
rv = {}
|
||||
for node in [self, self.root]:
|
||||
for name, value in node._data.iteritems():
|
||||
if name not in rv:
|
||||
rv[name] = value
|
||||
return rv
|
||||
|
||||
def iteritems(self):
|
||||
for item in self._flatten().iteritems():
|
||||
yield item
|
||||
|
||||
def iterkeys(self):
|
||||
for item in self._flatten().iterkeys():
|
||||
yield item
|
||||
|
||||
def itervalues(self):
|
||||
for item in self._flatten().itervalues():
|
||||
yield item
|
||||
|
||||
def append(self, child):
|
||||
child.parent = self
|
||||
self.children.append(child)
|
||||
return child
|
||||
|
||||
|
||||
def compile_ast(compiler, ast, data_cls_getter=None, **kwargs):
|
||||
return compiler().compile(ast,
|
||||
data_cls_getter=data_cls_getter,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def compile(compiler, stream, data_cls_getter=None, **kwargs):
|
||||
return compile_ast(compiler,
|
||||
parse(stream),
|
||||
data_cls_getter=data_cls_getter,
|
||||
**kwargs)
|
|
@ -1,10 +1,10 @@
|
|||
import operator
|
||||
|
||||
from ..node import NodeVisitor
|
||||
from . import base
|
||||
from ..parser import parse
|
||||
|
||||
|
||||
class Compiler(NodeVisitor):
|
||||
class Compiler(base.Compiler):
|
||||
"""Compiler backend that evaluates conditional expressions
|
||||
to give static output"""
|
||||
|
||||
|
@ -26,29 +26,7 @@ class Compiler(NodeVisitor):
|
|||
self._kwargs = kwargs
|
||||
self.expr_data = expr_data
|
||||
|
||||
if data_cls_getter is None:
|
||||
self.data_cls_getter = lambda x, y: ManifestItem
|
||||
else:
|
||||
self.data_cls_getter = data_cls_getter
|
||||
|
||||
self.output_node = None
|
||||
self.visit(tree)
|
||||
return self.output_node
|
||||
|
||||
def visit_DataNode(self, node):
|
||||
output_parent = self.output_node
|
||||
if self.output_node is None:
|
||||
assert node.parent is None
|
||||
self.output_node = self.data_cls_getter(None, None)(None, **self._kwargs)
|
||||
else:
|
||||
self.output_node = self.data_cls_getter(self.output_node, node)(node.data)
|
||||
|
||||
for child in node.children:
|
||||
self.visit(child)
|
||||
|
||||
if output_parent is not None:
|
||||
output_parent.append(self.output_node)
|
||||
self.output_node = self.output_node.parent
|
||||
return self._compile(tree, data_cls_getter, **kwargs)
|
||||
|
||||
def visit_KeyValueNode(self, node):
|
||||
key_name = node.data
|
||||
|
@ -61,15 +39,6 @@ class Compiler(NodeVisitor):
|
|||
if key_value is not None:
|
||||
self.output_node.set(key_name, key_value)
|
||||
|
||||
def visit_ValueNode(self, node):
|
||||
return node.data
|
||||
|
||||
def visit_AtomNode(self, node):
|
||||
return node.data
|
||||
|
||||
def visit_ListNode(self, node):
|
||||
return [self.visit(child) for child in node.children]
|
||||
|
||||
def visit_ConditionalNode(self, node):
|
||||
assert len(node.children) == 2
|
||||
if self.visit(node.children[0]):
|
||||
|
@ -81,12 +50,6 @@ class Compiler(NodeVisitor):
|
|||
value = self.visit(child)(value)
|
||||
return value
|
||||
|
||||
def visit_NumberNode(self, node):
|
||||
if "." in node.data:
|
||||
return float(node.data)
|
||||
else:
|
||||
return int(node.data)
|
||||
|
||||
def visit_VariableNode(self, node):
|
||||
value = self.expr_data[node.data]
|
||||
for child in node.children:
|
||||
|
@ -123,92 +86,6 @@ class Compiler(NodeVisitor):
|
|||
"!=": operator.ne}[node.data]
|
||||
|
||||
|
||||
class ManifestItem(object):
|
||||
def __init__(self, name, **kwargs):
|
||||
self.parent = None
|
||||
self.name = name
|
||||
self.children = []
|
||||
self._data = {}
|
||||
|
||||
def __repr__(self):
|
||||
return "<static.ManifestItem %s>" % (self.name)
|
||||
|
||||
def __str__(self):
|
||||
rv = [repr(self)]
|
||||
for item in self.children:
|
||||
rv.extend(" %s" % line for line in str(item).split("\n"))
|
||||
return "\n".join(rv)
|
||||
|
||||
def set_defaults(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def is_empty(self):
|
||||
if self._data:
|
||||
return False
|
||||
return all(child.is_empty for child in self.children)
|
||||
|
||||
@property
|
||||
def root(self):
|
||||
node = self
|
||||
while node.parent is not None:
|
||||
node = node.parent
|
||||
return node
|
||||
|
||||
def has_key(self, key):
|
||||
for node in [self, self.root]:
|
||||
if key in node._data:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get(self, key):
|
||||
for node in [self, self.root]:
|
||||
if key in node._data:
|
||||
return node._data[key]
|
||||
raise KeyError
|
||||
|
||||
def set(self, name, value):
|
||||
self._data[name] = value
|
||||
|
||||
def remove(self):
|
||||
if self.parent:
|
||||
self.parent._remove_child(self)
|
||||
|
||||
def _remove_child(self, child):
|
||||
self.children.remove(child)
|
||||
child.parent = None
|
||||
|
||||
def iterchildren(self, name=None):
|
||||
for item in self.children:
|
||||
if item.name == name or name is None:
|
||||
yield item
|
||||
|
||||
def _flatten(self):
|
||||
rv = {}
|
||||
for node in [self, self.root]:
|
||||
for name, value in node._data.iteritems():
|
||||
if name not in rv:
|
||||
rv[name] = value
|
||||
return rv
|
||||
|
||||
def iteritems(self):
|
||||
for item in self._flatten().iteritems():
|
||||
yield item
|
||||
|
||||
def iterkeys(self):
|
||||
for item in self._flatten().iterkeys():
|
||||
yield item
|
||||
|
||||
def itervalues(self):
|
||||
for item in self._flatten().itervalues():
|
||||
yield item
|
||||
|
||||
def append(self, child):
|
||||
child.parent = self
|
||||
self.children.append(child)
|
||||
return child
|
||||
|
||||
|
||||
def compile_ast(ast, expr_data, data_cls_getter=None, **kwargs):
|
||||
return Compiler().compile(ast,
|
||||
expr_data,
|
||||
|
|
|
@ -12,6 +12,7 @@ import testloader
|
|||
import wptcommandline
|
||||
import wptlogging
|
||||
import wpttest
|
||||
from mozlog import capture
|
||||
from font import FontInstaller
|
||||
from testrunner import ManagerGroup
|
||||
from browsers.base import NullBrowser
|
||||
|
@ -132,7 +133,7 @@ def get_pause_after_test(test_loader, **kwargs):
|
|||
|
||||
|
||||
def run_tests(config, test_paths, product, **kwargs):
|
||||
with wptlogging.CaptureIO(logger, not kwargs["no_capture_stdio"]):
|
||||
with capture.CaptureIO(logger, not kwargs["no_capture_stdio"]):
|
||||
env.do_delayed_imports(logger, test_paths)
|
||||
|
||||
product = products.load_product(config, product, load_cls=True)
|
||||
|
|
|
@ -71,16 +71,13 @@ decorator::
|
|||
Python File Handlers
|
||||
--------------------
|
||||
|
||||
Python file handlers are designed to provide a vaguely PHP-like interface
|
||||
where each resource corresponds to a particular python file on the
|
||||
filesystem. Typically this is hooked up to a route like ``("*",
|
||||
"*.py", python_file_handler)``, meaning that any .py file will be
|
||||
treated as a handler file (note that this makes python files unsafe in
|
||||
much the same way that .php files are when using PHP).
|
||||
Python file handlers are Python files which the server executes in response to
|
||||
requests made to the corresponding URL. This is hooked up to a route like
|
||||
``("*", "*.py", python_file_handler)``, meaning that any .py file will be
|
||||
treated as a handler file (note that this makes it easy to write unsafe
|
||||
handlers, particularly when running the server in a web-exposed setting).
|
||||
|
||||
Unlike PHP, the python files don't work by outputting text to stdout
|
||||
from the global scope. Instead they must define a single function
|
||||
`main` with the signature::
|
||||
The Python files must define a single function `main` with the signature::
|
||||
|
||||
main(request, response)
|
||||
|
||||
|
|
|
@ -1,163 +1,6 @@
|
|||
Pipes
|
||||
======
|
||||
|
||||
Pipes are functions that may be used when serving files to alter parts
|
||||
of the response. These are invoked by adding a pipe= query parameter
|
||||
taking a | separated list of pipe functions and parameters. The pipe
|
||||
functions are applied to the response from left to right. For example::
|
||||
|
||||
GET /sample.txt?pipe=slice(1,200)|status(404).
|
||||
|
||||
This would serve bytes 1 to 199, inclusive, of foo.txt with the HTTP status
|
||||
code 404.
|
||||
|
||||
.. note::
|
||||
If you write directly to the response socket using ResponseWriter,
|
||||
or when using the asis handler, only the trickle pipe will affect the response.
|
||||
|
||||
There are several built-in pipe functions, and it is possible to add
|
||||
more using the `@pipe` decorator on a function, if required.
|
||||
|
||||
.. note::
|
||||
Because of the way pipes compose, using some pipe functions prevents the
|
||||
content-length of the response from being known in advance. In these cases
|
||||
the server will close the connection to indicate the end of the response,
|
||||
preventing the use of HTTP 1.1 keepalive.
|
||||
|
||||
Built-In Pipes
|
||||
--------------
|
||||
|
||||
sub
|
||||
~~~
|
||||
|
||||
Used to substitute variables from the server environment, or from the
|
||||
request into the response.
|
||||
|
||||
Substitutions are marked in a file using a block delimited by `{{`
|
||||
and `}}`. Inside the block the following variables are available:
|
||||
|
||||
`{{host}}`
|
||||
The host name of the server excluding any subdomain part.
|
||||
|
||||
`{{domains[]}}`
|
||||
The domain name of a particular subdomain
|
||||
e.g. `{{domains[www]}}` for the `www` subdomain.
|
||||
|
||||
`{{ports[][]}}`
|
||||
The port number of servers, by protocol
|
||||
e.g. `{{ports[http][0]}}` for the first (and, depending on setup,
|
||||
possibly only) http server
|
||||
|
||||
`{{headers[]}}`
|
||||
The HTTP headers in the request
|
||||
e.g. `{{headers[X-Test]}}` for a hypothetical `X-Test` header.
|
||||
|
||||
`{{header_or_default(header, default)}}`
|
||||
The value of an HTTP header, or a default value if it is absent.
|
||||
e.g. `{{header_or_default(X-Test, test-header-absent)}}`
|
||||
|
||||
`{{GET[]}}`
|
||||
The query parameters for the request
|
||||
e.g. `{{GET[id]}}` for an id parameter sent with the request.
|
||||
|
||||
So, for example, to write a JavaScript file called `xhr.js` that
|
||||
depends on the host name of the server, without hardcoding, one might
|
||||
write::
|
||||
|
||||
var server_url = http://{{host}}:{{ports[http][0]}}/path/to/resource;
|
||||
//Create the actual XHR and so on
|
||||
|
||||
The file would then be included as:
|
||||
|
||||
<script src="xhr.js?pipe=sub"></script>
|
||||
|
||||
This pipe can also be enabled by using a filename `*.sub.ext`, e.g. the file above could be called `xhr.sub.js`.
|
||||
|
||||
status
|
||||
~~~~~~
|
||||
|
||||
Used to set the HTTP status of the response, for example::
|
||||
|
||||
example.js?pipe=status(410)
|
||||
|
||||
headers
|
||||
~~~~~~~
|
||||
|
||||
Used to add or replace http headers in the response. Takes two or
|
||||
three arguments; the header name, the header value and whether to
|
||||
append the header rather than replace an existing header (default:
|
||||
False). So, for example, a request for::
|
||||
|
||||
example.html?pipe=header(Content-Type,text/plain)
|
||||
|
||||
causes example.html to be returned with a text/plain content type
|
||||
whereas::
|
||||
|
||||
example.html?pipe=header(Content-Type,text/plain,True)
|
||||
|
||||
Will cause example.html to be returned with both text/html and
|
||||
text/plain content-type headers.
|
||||
|
||||
slice
|
||||
~~~~~
|
||||
|
||||
Used to send only part of a response body. Takes the start and,
|
||||
optionally, end bytes as arguments, although either can be null to
|
||||
indicate the start or end of the file, respectively. So for example::
|
||||
|
||||
example.txt?pipe=slice(10,20)
|
||||
|
||||
Would result in a response with a body containing 10 bytes of
|
||||
example.txt including byte 10 but excluding byte 20.
|
||||
|
||||
::
|
||||
|
||||
example.txt?pipe=slice(10)
|
||||
|
||||
Would cause all bytes from byte 10 of example.txt to be sent, but::
|
||||
|
||||
example.txt?pipe=slice(null,20)
|
||||
|
||||
Would send the first 20 bytes of example.txt.
|
||||
|
||||
trickle
|
||||
~~~~~~~
|
||||
|
||||
.. note::
|
||||
Using this function will force a connection close.
|
||||
|
||||
Used to send the body of a response in chunks with delays. Takes a
|
||||
single argument that is a microsyntax consisting of colon-separated
|
||||
commands. There are three types of commands:
|
||||
|
||||
* Bare numbers represent a number of bytes to send
|
||||
|
||||
* Numbers prefixed `d` indicate a delay in seconds
|
||||
|
||||
* Numbers prefixed `r` must only appear at the end of the command, and
|
||||
indicate that the preceding N items must be repeated until there is
|
||||
no more content to send. The number of items to repeat must be even.
|
||||
|
||||
In the absence of a repetition command, the entire remainder of the content is
|
||||
sent at once when the command list is exhausted. So for example::
|
||||
|
||||
example.txt?pipe=trickle(d1)
|
||||
|
||||
causes a 1s delay before sending the entirety of example.txt.
|
||||
|
||||
::
|
||||
|
||||
example.txt?pipe=trickle(100:d1)
|
||||
|
||||
causes 100 bytes of example.txt to be sent, followed by a 1s delay,
|
||||
and then the remainder of the file to be sent. On the other hand::
|
||||
|
||||
example.txt?pipe=trickle(100:d1:r2)
|
||||
|
||||
Will cause the file to be sent in 100 byte chunks separated by a 1s
|
||||
delay until the whole content has been sent.
|
||||
|
||||
|
||||
:mod:`Interface <pipes>`
|
||||
------------------------
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue