server.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. # -*- test-case-name: twisted.web.test.test_web -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. This is a web-server which integrates with the twisted.internet
  6. infrastructure.
  7. """
  8. from __future__ import division, absolute_import
  9. import copy
  10. import os
  11. try:
  12. from urllib import quote
  13. except ImportError:
  14. from urllib.parse import quote as _quote
  15. def quote(string, *args, **kwargs):
  16. return _quote(
  17. string.decode('charmap'), *args, **kwargs).encode('charmap')
  18. import zlib
  19. from binascii import hexlify
  20. from zope.interface import implementer
  21. from twisted.python.compat import networkString, nativeString, intToBytes
  22. from twisted.spread.pb import Copyable, ViewPoint
  23. from twisted.internet import address, interfaces
  24. from twisted.web import iweb, http, util
  25. from twisted.web.http import unquote
  26. from twisted.python import log, reflect, failure, components
  27. from twisted import copyright
  28. from twisted.web import resource
  29. from twisted.web.error import UnsupportedMethod
  30. from incremental import Version
  31. from twisted.python.deprecate import deprecatedModuleAttribute
  32. from twisted.python.compat import escape
  33. NOT_DONE_YET = 1
  34. __all__ = [
  35. 'supportedMethods',
  36. 'Request',
  37. 'Session',
  38. 'Site',
  39. 'version',
  40. 'NOT_DONE_YET',
  41. 'GzipEncoderFactory'
  42. ]
  43. # backwards compatibility
  44. deprecatedModuleAttribute(
  45. Version("Twisted", 12, 1, 0),
  46. "Please use twisted.web.http.datetimeToString instead",
  47. "twisted.web.server",
  48. "date_time_string")
  49. deprecatedModuleAttribute(
  50. Version("Twisted", 12, 1, 0),
  51. "Please use twisted.web.http.stringToDatetime instead",
  52. "twisted.web.server",
  53. "string_date_time")
  54. date_time_string = http.datetimeToString
  55. string_date_time = http.stringToDatetime
  56. # Support for other methods may be implemented on a per-resource basis.
  57. supportedMethods = (b'GET', b'HEAD', b'POST')
  58. def _addressToTuple(addr):
  59. if isinstance(addr, address.IPv4Address):
  60. return ('INET', addr.host, addr.port)
  61. elif isinstance(addr, address.UNIXAddress):
  62. return ('UNIX', addr.name)
  63. else:
  64. return tuple(addr)
  65. @implementer(iweb.IRequest)
  66. class Request(Copyable, http.Request, components.Componentized):
  67. """
  68. An HTTP request.
  69. @ivar defaultContentType: A C{bytes} giving the default I{Content-Type}
  70. value to send in responses if no other value is set. L{None} disables
  71. the default.
  72. @ivar _insecureSession: The L{Session} object representing state that will
  73. be transmitted over plain-text HTTP.
  74. @ivar _secureSession: The L{Session} object representing the state that
  75. will be transmitted only over HTTPS.
  76. """
  77. defaultContentType = b"text/html"
  78. site = None
  79. appRootURL = None
  80. __pychecker__ = 'unusednames=issuer'
  81. _inFakeHead = False
  82. _encoder = None
  83. def __init__(self, *args, **kw):
  84. http.Request.__init__(self, *args, **kw)
  85. components.Componentized.__init__(self)
  86. def getStateToCopyFor(self, issuer):
  87. x = self.__dict__.copy()
  88. del x['transport']
  89. # XXX refactor this attribute out; it's from protocol
  90. # del x['server']
  91. del x['channel']
  92. del x['content']
  93. del x['site']
  94. self.content.seek(0, 0)
  95. x['content_data'] = self.content.read()
  96. x['remote'] = ViewPoint(issuer, self)
  97. # Address objects aren't jellyable
  98. x['host'] = _addressToTuple(x['host'])
  99. x['client'] = _addressToTuple(x['client'])
  100. # Header objects also aren't jellyable.
  101. x['requestHeaders'] = list(x['requestHeaders'].getAllRawHeaders())
  102. return x
  103. # HTML generation helpers
  104. def sibLink(self, name):
  105. """
  106. Return the text that links to a sibling of the requested resource.
  107. """
  108. if self.postpath:
  109. return (len(self.postpath)*b"../") + name
  110. else:
  111. return name
  112. def childLink(self, name):
  113. """
  114. Return the text that links to a child of the requested resource.
  115. """
  116. lpp = len(self.postpath)
  117. if lpp > 1:
  118. return ((lpp-1)*b"../") + name
  119. elif lpp == 1:
  120. return name
  121. else: # lpp == 0
  122. if len(self.prepath) and self.prepath[-1]:
  123. return self.prepath[-1] + b'/' + name
  124. else:
  125. return name
  126. def process(self):
  127. """
  128. Process a request.
  129. """
  130. # get site from channel
  131. self.site = self.channel.site
  132. # set various default headers
  133. self.setHeader(b'server', version)
  134. self.setHeader(b'date', http.datetimeToString())
  135. # Resource Identification
  136. self.prepath = []
  137. self.postpath = list(map(unquote, self.path[1:].split(b'/')))
  138. try:
  139. resrc = self.site.getResourceFor(self)
  140. if resource._IEncodingResource.providedBy(resrc):
  141. encoder = resrc.getEncoder(self)
  142. if encoder is not None:
  143. self._encoder = encoder
  144. self.render(resrc)
  145. except:
  146. self.processingFailed(failure.Failure())
  147. def write(self, data):
  148. """
  149. Write data to the transport (if not responding to a HEAD request).
  150. @param data: A string to write to the response.
  151. """
  152. if not self.startedWriting:
  153. # Before doing the first write, check to see if a default
  154. # Content-Type header should be supplied.
  155. modified = self.code != http.NOT_MODIFIED
  156. contentType = self.responseHeaders.getRawHeaders(b'content-type')
  157. if (modified and contentType is None and
  158. self.defaultContentType is not None
  159. ):
  160. self.responseHeaders.setRawHeaders(
  161. b'content-type', [self.defaultContentType])
  162. # Only let the write happen if we're not generating a HEAD response by
  163. # faking out the request method. Note, if we are doing that,
  164. # startedWriting will never be true, and the above logic may run
  165. # multiple times. It will only actually change the responseHeaders
  166. # once though, so it's still okay.
  167. if not self._inFakeHead:
  168. if self._encoder:
  169. data = self._encoder.encode(data)
  170. http.Request.write(self, data)
  171. def finish(self):
  172. """
  173. Override C{http.Request.finish} for possible encoding.
  174. """
  175. if self._encoder:
  176. data = self._encoder.finish()
  177. if data:
  178. http.Request.write(self, data)
  179. return http.Request.finish(self)
  180. def render(self, resrc):
  181. """
  182. Ask a resource to render itself.
  183. @param resrc: a L{twisted.web.resource.IResource}.
  184. """
  185. try:
  186. body = resrc.render(self)
  187. except UnsupportedMethod as e:
  188. allowedMethods = e.allowedMethods
  189. if (self.method == b"HEAD") and (b"GET" in allowedMethods):
  190. # We must support HEAD (RFC 2616, 5.1.1). If the
  191. # resource doesn't, fake it by giving the resource
  192. # a 'GET' request and then return only the headers,
  193. # not the body.
  194. log.msg("Using GET to fake a HEAD request for %s" %
  195. (resrc,))
  196. self.method = b"GET"
  197. self._inFakeHead = True
  198. body = resrc.render(self)
  199. if body is NOT_DONE_YET:
  200. log.msg("Tried to fake a HEAD request for %s, but "
  201. "it got away from me." % resrc)
  202. # Oh well, I guess we won't include the content length.
  203. else:
  204. self.setHeader(b'content-length', intToBytes(len(body)))
  205. self._inFakeHead = False
  206. self.method = b"HEAD"
  207. self.write(b'')
  208. self.finish()
  209. return
  210. if self.method in (supportedMethods):
  211. # We MUST include an Allow header
  212. # (RFC 2616, 10.4.6 and 14.7)
  213. self.setHeader(b'Allow', b', '.join(allowedMethods))
  214. s = ('''Your browser approached me (at %(URI)s) with'''
  215. ''' the method "%(method)s". I only allow'''
  216. ''' the method%(plural)s %(allowed)s here.''' % {
  217. 'URI': escape(nativeString(self.uri)),
  218. 'method': nativeString(self.method),
  219. 'plural': ((len(allowedMethods) > 1) and 's') or '',
  220. 'allowed': ', '.join(
  221. [nativeString(x) for x in allowedMethods])
  222. })
  223. epage = resource.ErrorPage(http.NOT_ALLOWED,
  224. "Method Not Allowed", s)
  225. body = epage.render(self)
  226. else:
  227. epage = resource.ErrorPage(
  228. http.NOT_IMPLEMENTED, "Huh?",
  229. "I don't know how to treat a %s request." %
  230. (escape(self.method.decode("charmap")),))
  231. body = epage.render(self)
  232. # end except UnsupportedMethod
  233. if body == NOT_DONE_YET:
  234. return
  235. if not isinstance(body, bytes):
  236. body = resource.ErrorPage(
  237. http.INTERNAL_SERVER_ERROR,
  238. "Request did not return bytes",
  239. "Request: " + util._PRE(reflect.safe_repr(self)) + "<br />" +
  240. "Resource: " + util._PRE(reflect.safe_repr(resrc)) + "<br />" +
  241. "Value: " + util._PRE(reflect.safe_repr(body))).render(self)
  242. if self.method == b"HEAD":
  243. if len(body) > 0:
  244. # This is a Bad Thing (RFC 2616, 9.4)
  245. log.msg("Warning: HEAD request %s for resource %s is"
  246. " returning a message body."
  247. " I think I'll eat it."
  248. % (self, resrc))
  249. self.setHeader(b'content-length',
  250. intToBytes(len(body)))
  251. self.write(b'')
  252. else:
  253. self.setHeader(b'content-length',
  254. intToBytes(len(body)))
  255. self.write(body)
  256. self.finish()
  257. def processingFailed(self, reason):
  258. log.err(reason)
  259. if self.site.displayTracebacks:
  260. body = (b"<html><head><title>web.Server Traceback"
  261. b" (most recent call last)</title></head>"
  262. b"<body><b>web.Server Traceback"
  263. b" (most recent call last):</b>\n\n" +
  264. util.formatFailure(reason) +
  265. b"\n\n</body></html>\n")
  266. else:
  267. body = (b"<html><head><title>Processing Failed"
  268. b"</title></head><body>"
  269. b"<b>Processing Failed</b></body></html>")
  270. self.setResponseCode(http.INTERNAL_SERVER_ERROR)
  271. self.setHeader(b'content-type', b"text/html")
  272. self.setHeader(b'content-length', intToBytes(len(body)))
  273. self.write(body)
  274. self.finish()
  275. return reason
  276. def view_write(self, issuer, data):
  277. """Remote version of write; same interface.
  278. """
  279. self.write(data)
  280. def view_finish(self, issuer):
  281. """Remote version of finish; same interface.
  282. """
  283. self.finish()
  284. def view_addCookie(self, issuer, k, v, **kwargs):
  285. """Remote version of addCookie; same interface.
  286. """
  287. self.addCookie(k, v, **kwargs)
  288. def view_setHeader(self, issuer, k, v):
  289. """Remote version of setHeader; same interface.
  290. """
  291. self.setHeader(k, v)
  292. def view_setLastModified(self, issuer, when):
  293. """Remote version of setLastModified; same interface.
  294. """
  295. self.setLastModified(when)
  296. def view_setETag(self, issuer, tag):
  297. """Remote version of setETag; same interface.
  298. """
  299. self.setETag(tag)
  300. def view_setResponseCode(self, issuer, code, message=None):
  301. """
  302. Remote version of setResponseCode; same interface.
  303. """
  304. self.setResponseCode(code, message)
  305. def view_registerProducer(self, issuer, producer, streaming):
  306. """Remote version of registerProducer; same interface.
  307. (requires a remote producer.)
  308. """
  309. self.registerProducer(_RemoteProducerWrapper(producer), streaming)
  310. def view_unregisterProducer(self, issuer):
  311. self.unregisterProducer()
  312. ### these calls remain local
  313. _secureSession = None
  314. _insecureSession = None
  315. @property
  316. def session(self):
  317. """
  318. If a session has already been created or looked up with
  319. L{Request.getSession}, this will return that object. (This will always
  320. be the session that matches the security of the request; so if
  321. C{forceNotSecure} is used on a secure request, this will not return
  322. that session.)
  323. @return: the session attribute
  324. @rtype: L{Session} or L{None}
  325. """
  326. if self.isSecure():
  327. return self._secureSession
  328. else:
  329. return self._insecureSession
  330. def getSession(self, sessionInterface=None, forceNotSecure=False):
  331. """
  332. Check if there is a session cookie, and if not, create it.
  333. By default, the cookie with be secure for HTTPS requests and not secure
  334. for HTTP requests. If for some reason you need access to the insecure
  335. cookie from a secure request you can set C{forceNotSecure = True}.
  336. @param forceNotSecure: Should we retrieve a session that will be
  337. transmitted over HTTP, even if this L{Request} was delivered over
  338. HTTPS?
  339. @type forceNotSecure: L{bool}
  340. """
  341. # Make sure we aren't creating a secure session on a non-secure page
  342. secure = self.isSecure() and not forceNotSecure
  343. if not secure:
  344. cookieString = b"TWISTED_SESSION"
  345. sessionAttribute = "_insecureSession"
  346. else:
  347. cookieString = b"TWISTED_SECURE_SESSION"
  348. sessionAttribute = "_secureSession"
  349. session = getattr(self, sessionAttribute)
  350. # Session management
  351. if not session:
  352. cookiename = b"_".join([cookieString] + self.sitepath)
  353. sessionCookie = self.getCookie(cookiename)
  354. if sessionCookie:
  355. try:
  356. session = self.site.getSession(sessionCookie)
  357. except KeyError:
  358. pass
  359. # if it still hasn't been set, fix it up.
  360. if not session:
  361. session = self.site.makeSession()
  362. self.addCookie(cookiename, session.uid, path=b"/",
  363. secure=secure)
  364. session.touch()
  365. setattr(self, sessionAttribute, session)
  366. if sessionInterface:
  367. return session.getComponent(sessionInterface)
  368. return session
  369. def _prePathURL(self, prepath):
  370. port = self.getHost().port
  371. if self.isSecure():
  372. default = 443
  373. else:
  374. default = 80
  375. if port == default:
  376. hostport = ''
  377. else:
  378. hostport = ':%d' % port
  379. prefix = networkString('http%s://%s%s/' % (
  380. self.isSecure() and 's' or '',
  381. nativeString(self.getRequestHostname()),
  382. hostport))
  383. path = b'/'.join([quote(segment, safe=b'') for segment in prepath])
  384. return prefix + path
  385. def prePathURL(self):
  386. return self._prePathURL(self.prepath)
  387. def URLPath(self):
  388. from twisted.python import urlpath
  389. return urlpath.URLPath.fromRequest(self)
  390. def rememberRootURL(self):
  391. """
  392. Remember the currently-processed part of the URL for later
  393. recalling.
  394. """
  395. url = self._prePathURL(self.prepath[:-1])
  396. self.appRootURL = url
  397. def getRootURL(self):
  398. """
  399. Get a previously-remembered URL.
  400. """
  401. return self.appRootURL
  402. @implementer(iweb._IRequestEncoderFactory)
  403. class GzipEncoderFactory(object):
  404. """
  405. @cvar compressLevel: The compression level used by the compressor, default
  406. to 9 (highest).
  407. @since: 12.3
  408. """
  409. compressLevel = 9
  410. def encoderForRequest(self, request):
  411. """
  412. Check the headers if the client accepts gzip encoding, and encodes the
  413. request if so.
  414. """
  415. acceptHeaders = request.requestHeaders.getRawHeaders(
  416. 'accept-encoding', [])
  417. supported = ','.join(acceptHeaders).split(',')
  418. if 'gzip' in supported:
  419. encoding = request.responseHeaders.getRawHeaders(
  420. 'content-encoding')
  421. if encoding:
  422. encoding = '%s,gzip' % ','.join(encoding)
  423. else:
  424. encoding = 'gzip'
  425. request.responseHeaders.setRawHeaders('content-encoding',
  426. [encoding])
  427. return _GzipEncoder(self.compressLevel, request)
  428. @implementer(iweb._IRequestEncoder)
  429. class _GzipEncoder(object):
  430. """
  431. An encoder which supports gzip.
  432. @ivar _zlibCompressor: The zlib compressor instance used to compress the
  433. stream.
  434. @ivar _request: A reference to the originating request.
  435. @since: 12.3
  436. """
  437. _zlibCompressor = None
  438. def __init__(self, compressLevel, request):
  439. self._zlibCompressor = zlib.compressobj(
  440. compressLevel, zlib.DEFLATED, 16 + zlib.MAX_WBITS)
  441. self._request = request
  442. def encode(self, data):
  443. """
  444. Write to the request, automatically compressing data on the fly.
  445. """
  446. if not self._request.startedWriting:
  447. # Remove the content-length header, we can't honor it
  448. # because we compress on the fly.
  449. self._request.responseHeaders.removeHeader(b'content-length')
  450. return self._zlibCompressor.compress(data)
  451. def finish(self):
  452. """
  453. Finish handling the request request, flushing any data from the zlib
  454. buffer.
  455. """
  456. remain = self._zlibCompressor.flush()
  457. self._zlibCompressor = None
  458. return remain
  459. class _RemoteProducerWrapper:
  460. def __init__(self, remote):
  461. self.resumeProducing = remote.remoteMethod("resumeProducing")
  462. self.pauseProducing = remote.remoteMethod("pauseProducing")
  463. self.stopProducing = remote.remoteMethod("stopProducing")
  464. class Session(components.Componentized):
  465. """
  466. A user's session with a system.
  467. This utility class contains no functionality, but is used to
  468. represent a session.
  469. @ivar uid: A unique identifier for the session.
  470. @type uid: L{bytes}
  471. @ivar _reactor: An object providing L{IReactorTime} to use for scheduling
  472. expiration.
  473. @ivar sessionTimeout: timeout of a session, in seconds.
  474. """
  475. sessionTimeout = 900
  476. _expireCall = None
  477. def __init__(self, site, uid, reactor=None):
  478. """
  479. Initialize a session with a unique ID for that session.
  480. """
  481. components.Componentized.__init__(self)
  482. if reactor is None:
  483. from twisted.internet import reactor
  484. self._reactor = reactor
  485. self.site = site
  486. self.uid = uid
  487. self.expireCallbacks = []
  488. self.touch()
  489. self.sessionNamespaces = {}
  490. def startCheckingExpiration(self):
  491. """
  492. Start expiration tracking.
  493. @return: L{None}
  494. """
  495. self._expireCall = self._reactor.callLater(
  496. self.sessionTimeout, self.expire)
  497. def notifyOnExpire(self, callback):
  498. """
  499. Call this callback when the session expires or logs out.
  500. """
  501. self.expireCallbacks.append(callback)
  502. def expire(self):
  503. """
  504. Expire/logout of the session.
  505. """
  506. del self.site.sessions[self.uid]
  507. for c in self.expireCallbacks:
  508. c()
  509. self.expireCallbacks = []
  510. if self._expireCall and self._expireCall.active():
  511. self._expireCall.cancel()
  512. # Break reference cycle.
  513. self._expireCall = None
  514. def touch(self):
  515. """
  516. Notify session modification.
  517. """
  518. self.lastModified = self._reactor.seconds()
  519. if self._expireCall is not None:
  520. self._expireCall.reset(self.sessionTimeout)
  521. version = networkString("TwistedWeb/%s" % (copyright.version,))
  522. @implementer(interfaces.IProtocolNegotiationFactory)
  523. class Site(http.HTTPFactory):
  524. """
  525. A web site: manage log, sessions, and resources.
  526. @ivar counter: increment value used for generating unique sessions ID.
  527. @ivar requestFactory: A factory which is called with (channel)
  528. and creates L{Request} instances. Default to L{Request}.
  529. @ivar displayTracebacks: if set, Twisted internal errors are displayed on
  530. rendered pages. Default to C{True}.
  531. @ivar sessionFactory: factory for sessions objects. Default to L{Session}.
  532. @ivar sessionCheckTime: Deprecated. See L{Session.sessionTimeout} instead.
  533. """
  534. counter = 0
  535. requestFactory = Request
  536. displayTracebacks = True
  537. sessionFactory = Session
  538. sessionCheckTime = 1800
  539. _entropy = os.urandom
  540. def __init__(self, resource, requestFactory=None, *args, **kwargs):
  541. """
  542. @param resource: The root of the resource hierarchy. All request
  543. traversal for requests received by this factory will begin at this
  544. resource.
  545. @type resource: L{IResource} provider
  546. @param requestFactory: Overwrite for default requestFactory.
  547. @type requestFactory: C{callable} or C{class}.
  548. @see: L{twisted.web.http.HTTPFactory.__init__}
  549. """
  550. http.HTTPFactory.__init__(self, *args, **kwargs)
  551. self.sessions = {}
  552. self.resource = resource
  553. if requestFactory is not None:
  554. self.requestFactory = requestFactory
  555. def _openLogFile(self, path):
  556. from twisted.python import logfile
  557. return logfile.LogFile(os.path.basename(path), os.path.dirname(path))
  558. def __getstate__(self):
  559. d = self.__dict__.copy()
  560. d['sessions'] = {}
  561. return d
  562. def _mkuid(self):
  563. """
  564. (internal) Generate an opaque, unique ID for a user's session.
  565. """
  566. self.counter = self.counter + 1
  567. return hexlify(self._entropy(32))
  568. def makeSession(self):
  569. """
  570. Generate a new Session instance, and store it for future reference.
  571. """
  572. uid = self._mkuid()
  573. session = self.sessions[uid] = self.sessionFactory(self, uid)
  574. session.startCheckingExpiration()
  575. return session
  576. def getSession(self, uid):
  577. """
  578. Get a previously generated session.
  579. @param uid: Unique ID of the session.
  580. @type uid: L{bytes}.
  581. @raise: L{KeyError} if the session is not found.
  582. """
  583. return self.sessions[uid]
  584. def buildProtocol(self, addr):
  585. """
  586. Generate a channel attached to this site.
  587. """
  588. channel = http.HTTPFactory.buildProtocol(self, addr)
  589. channel.requestFactory = self.requestFactory
  590. channel.site = self
  591. return channel
  592. isLeaf = 0
  593. def render(self, request):
  594. """
  595. Redirect because a Site is always a directory.
  596. """
  597. request.redirect(request.prePathURL() + b'/')
  598. request.finish()
  599. def getChildWithDefault(self, pathEl, request):
  600. """
  601. Emulate a resource's getChild method.
  602. """
  603. request.site = self
  604. return self.resource.getChildWithDefault(pathEl, request)
  605. def getResourceFor(self, request):
  606. """
  607. Get a resource for a request.
  608. This iterates through the resource hierarchy, calling
  609. getChildWithDefault on each resource it finds for a path element,
  610. stopping when it hits an element where isLeaf is true.
  611. """
  612. request.site = self
  613. # Sitepath is used to determine cookie names between distributed
  614. # servers and disconnected sites.
  615. request.sitepath = copy.copy(request.prepath)
  616. return resource.getChildForRequest(self.resource, request)
  617. # IProtocolNegotiationFactory
  618. def acceptableProtocols(self):
  619. """
  620. Protocols this server can speak.
  621. """
  622. baseProtocols = [b'http/1.1']
  623. if http.H2_ENABLED:
  624. baseProtocols.insert(0, b'h2')
  625. return baseProtocols