aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/web/distrib.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/web/distrib.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/web/distrib.py')
-rw-r--r--contrib/python/Twisted/py3/twisted/web/distrib.py390
1 files changed, 390 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/web/distrib.py b/contrib/python/Twisted/py3/twisted/web/distrib.py
new file mode 100644
index 0000000000..4f25c03ee8
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/web/distrib.py
@@ -0,0 +1,390 @@
+# -*- test-case-name: twisted.web.test.test_distrib -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Distributed web servers.
+
+This is going to have to be refactored so that argument parsing is done
+by each subprocess and not by the main web server (i.e. GET, POST etc.).
+"""
+
+import copy
+import os
+import sys
+
+try:
+ import pwd
+except ImportError:
+ pwd = None # type: ignore[assignment]
+from io import BytesIO
+from xml.dom.minidom import getDOMImplementation
+
+from twisted.internet import address, reactor
+from twisted.logger import Logger
+from twisted.persisted import styles
+from twisted.spread import pb
+from twisted.spread.banana import SIZE_LIMIT
+from twisted.web import http, resource, server, static, util
+from twisted.web.http_headers import Headers
+
+
+class _ReferenceableProducerWrapper(pb.Referenceable):
+ def __init__(self, producer):
+ self.producer = producer
+
+ def remote_resumeProducing(self):
+ self.producer.resumeProducing()
+
+ def remote_pauseProducing(self):
+ self.producer.pauseProducing()
+
+ def remote_stopProducing(self):
+ self.producer.stopProducing()
+
+
+class Request(pb.RemoteCopy, server.Request):
+ """
+ A request which was received by a L{ResourceSubscription} and sent via
+ PB to a distributed node.
+ """
+
+ def setCopyableState(self, state):
+ """
+ Initialize this L{twisted.web.distrib.Request} based on the copied
+ state so that it closely resembles a L{twisted.web.server.Request}.
+ """
+ for k in "host", "client":
+ tup = state[k]
+ addrdesc = {"INET": "TCP", "UNIX": "UNIX"}[tup[0]]
+ addr = {
+ "TCP": lambda: address.IPv4Address(addrdesc, tup[1], tup[2]),
+ "UNIX": lambda: address.UNIXAddress(tup[1]),
+ }[addrdesc]()
+ state[k] = addr
+ state["requestHeaders"] = Headers(dict(state["requestHeaders"]))
+ pb.RemoteCopy.setCopyableState(self, state)
+ # Emulate the local request interface --
+ self.content = BytesIO(self.content_data)
+ self.finish = self.remote.remoteMethod("finish")
+ self.setHeader = self.remote.remoteMethod("setHeader")
+ self.addCookie = self.remote.remoteMethod("addCookie")
+ self.setETag = self.remote.remoteMethod("setETag")
+ self.setResponseCode = self.remote.remoteMethod("setResponseCode")
+ self.setLastModified = self.remote.remoteMethod("setLastModified")
+
+ # To avoid failing if a resource tries to write a very long string
+ # all at once, this one will be handled slightly differently.
+ self._write = self.remote.remoteMethod("write")
+
+ def write(self, bytes):
+ """
+ Write the given bytes to the response body.
+
+ @param bytes: The bytes to write. If this is longer than 640k, it
+ will be split up into smaller pieces.
+ """
+ start = 0
+ end = SIZE_LIMIT
+ while True:
+ self._write(bytes[start:end])
+ start += SIZE_LIMIT
+ end += SIZE_LIMIT
+ if start >= len(bytes):
+ break
+
+ def registerProducer(self, producer, streaming):
+ self.remote.callRemote(
+ "registerProducer", _ReferenceableProducerWrapper(producer), streaming
+ ).addErrback(self.fail)
+
+ def unregisterProducer(self):
+ self.remote.callRemote("unregisterProducer").addErrback(self.fail)
+
+ def fail(self, failure):
+ self._log.failure("", failure=failure)
+
+
+pb.setUnjellyableForClass(server.Request, Request)
+
+
+class Issue:
+ _log = Logger()
+
+ def __init__(self, request):
+ self.request = request
+
+ def finished(self, result):
+ if result is not server.NOT_DONE_YET:
+ assert isinstance(result, str), "return value not a string"
+ self.request.write(result)
+ self.request.finish()
+
+ def failed(self, failure):
+ # XXX: Argh. FIXME.
+ failure = str(failure)
+ self.request.write(
+ resource._UnsafeErrorPage(
+ http.INTERNAL_SERVER_ERROR,
+ "Server Connection Lost",
+ # GHSA-vg46-2rrj-3647 note: _PRE does HTML-escape the input.
+ "Connection to distributed server lost:" + util._PRE(failure),
+ ).render(self.request)
+ )
+ self.request.finish()
+ self._log.info(failure)
+
+
+class ResourceSubscription(resource.Resource):
+ isLeaf = 1
+ waiting = 0
+ _log = Logger()
+
+ def __init__(self, host, port):
+ resource.Resource.__init__(self)
+ self.host = host
+ self.port = port
+ self.pending = []
+ self.publisher = None
+
+ def __getstate__(self):
+ """Get persistent state for this ResourceSubscription."""
+ # When I unserialize,
+ state = copy.copy(self.__dict__)
+ # Publisher won't be connected...
+ state["publisher"] = None
+ # I won't be making a connection
+ state["waiting"] = 0
+ # There will be no pending requests.
+ state["pending"] = []
+ return state
+
+ def connected(self, publisher):
+ """I've connected to a publisher; I'll now send all my requests."""
+ self._log.info("connected to publisher")
+ publisher.broker.notifyOnDisconnect(self.booted)
+ self.publisher = publisher
+ self.waiting = 0
+ for request in self.pending:
+ self.render(request)
+ self.pending = []
+
+ def notConnected(self, msg):
+ """I can't connect to a publisher; I'll now reply to all pending
+ requests.
+ """
+ self._log.info("could not connect to distributed web service: {msg}", msg=msg)
+ self.waiting = 0
+ self.publisher = None
+ for request in self.pending:
+ request.write("Unable to connect to distributed server.")
+ request.finish()
+ self.pending = []
+
+ def booted(self):
+ self.notConnected("connection dropped")
+
+ def render(self, request):
+ """Render this request, from my server.
+
+ This will always be asynchronous, and therefore return NOT_DONE_YET.
+ It spins off a request to the pb client, and either adds it to the list
+ of pending issues or requests it immediately, depending on if the
+ client is already connected.
+ """
+ if not self.publisher:
+ self.pending.append(request)
+ if not self.waiting:
+ self.waiting = 1
+ bf = pb.PBClientFactory()
+ timeout = 10
+ if self.host == "unix":
+ reactor.connectUNIX(self.port, bf, timeout)
+ else:
+ reactor.connectTCP(self.host, self.port, bf, timeout)
+ d = bf.getRootObject()
+ d.addCallbacks(self.connected, self.notConnected)
+
+ else:
+ i = Issue(request)
+ self.publisher.callRemote("request", request).addCallbacks(
+ i.finished, i.failed
+ )
+ return server.NOT_DONE_YET
+
+
+class ResourcePublisher(pb.Root, styles.Versioned):
+ """
+ L{ResourcePublisher} exposes a remote API which can be used to respond
+ to request.
+
+ @ivar site: The site which will be used for resource lookup.
+ @type site: L{twisted.web.server.Site}
+ """
+
+ _log = Logger()
+
+ def __init__(self, site):
+ self.site = site
+
+ persistenceVersion = 2
+
+ def upgradeToVersion2(self):
+ self.application.authorizer.removeIdentity("web")
+ del self.application.services[self.serviceName]
+ del self.serviceName
+ del self.application
+ del self.perspectiveName
+
+ def getPerspectiveNamed(self, name):
+ return self
+
+ def remote_request(self, request):
+ """
+ Look up the resource for the given request and render it.
+ """
+ res = self.site.getResourceFor(request)
+ self._log.info(request)
+ result = res.render(request)
+ if result is not server.NOT_DONE_YET:
+ request.write(result)
+ request.finish()
+ return server.NOT_DONE_YET
+
+
+class UserDirectory(resource.Resource):
+ """
+ A resource which lists available user resources and serves them as
+ children.
+
+ @ivar _pwd: An object like L{pwd} which is used to enumerate users and
+ their home directories.
+ """
+
+ userDirName = "public_html"
+ userSocketName = ".twistd-web-pb"
+
+ template = """
+<html>
+ <head>
+ <title>twisted.web.distrib.UserDirectory</title>
+ <style>
+
+ a
+ {
+ font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
+ color: #369;
+ text-decoration: none;
+ }
+
+ th
+ {
+ font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
+ font-weight: bold;
+ text-decoration: none;
+ text-align: left;
+ }
+
+ pre, code
+ {
+ font-family: "Courier New", Courier, monospace;
+ }
+
+ p, body, td, ol, ul, menu, blockquote, div
+ {
+ font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
+ color: #000;
+ }
+ </style>
+ </head>
+
+ <body>
+ <h1>twisted.web.distrib.UserDirectory</h1>
+
+ %(users)s
+</body>
+</html>
+"""
+
+ def __init__(self, userDatabase=None):
+ resource.Resource.__init__(self)
+ if userDatabase is None:
+ userDatabase = pwd
+ self._pwd = userDatabase
+
+ def _users(self):
+ """
+ Return a list of two-tuples giving links to user resources and text to
+ associate with those links.
+ """
+ users = []
+ for user in self._pwd.getpwall():
+ name, passwd, uid, gid, gecos, dir, shell = user
+ realname = gecos.split(",")[0]
+ if not realname:
+ realname = name
+ if os.path.exists(os.path.join(dir, self.userDirName)):
+ users.append((name, realname + " (file)"))
+ twistdsock = os.path.join(dir, self.userSocketName)
+ if os.path.exists(twistdsock):
+ linkName = name + ".twistd"
+ users.append((linkName, realname + " (twistd)"))
+ return users
+
+ def render_GET(self, request):
+ """
+ Render as HTML a listing of all known users with links to their
+ personal resources.
+ """
+
+ domImpl = getDOMImplementation()
+ newDoc = domImpl.createDocument(None, "ul", None)
+ listing = newDoc.documentElement
+ for link, text in self._users():
+ linkElement = newDoc.createElement("a")
+ linkElement.setAttribute("href", link + "/")
+ textNode = newDoc.createTextNode(text)
+ linkElement.appendChild(textNode)
+ item = newDoc.createElement("li")
+ item.appendChild(linkElement)
+ listing.appendChild(item)
+
+ htmlDoc = self.template % ({"users": listing.toxml()})
+ return htmlDoc.encode("utf-8")
+
+ def getChild(self, name, request):
+ if name == b"":
+ return self
+
+ td = b".twistd"
+
+ if name.endswith(td):
+ username = name[: -len(td)]
+ sub = 1
+ else:
+ username = name
+ sub = 0
+ try:
+ # Decode using the filesystem encoding to reverse a transformation
+ # done in the pwd module.
+ (
+ pw_name,
+ pw_passwd,
+ pw_uid,
+ pw_gid,
+ pw_gecos,
+ pw_dir,
+ pw_shell,
+ ) = self._pwd.getpwnam(username.decode(sys.getfilesystemencoding()))
+ except KeyError:
+ return resource._UnsafeNoResource()
+ if sub:
+ twistdsock = os.path.join(pw_dir, self.userSocketName)
+ rs = ResourceSubscription("unix", twistdsock)
+ self.putChild(name, rs)
+ return rs
+ else:
+ path = os.path.join(pw_dir, self.userDirName)
+ if not os.path.exists(path):
+ return resource._UnsafeNoResource()
+ return static.File(path)