This repository has been archived by the owner on Jun 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
twisted-web
287 lines (225 loc) · 8.76 KB
/
twisted-web
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# For more information and general comments on editing this file
# please refer to README.Debian
#
import os
from twisted.python.url import URL
from twisted.internet import reactor
from twisted.web import script, static, server, distrib, twcgi, vhost, rewrite
from twisted.web import util as twutil, proxy, http, resource
from twisted.application import service
from twisted.application.internet import StreamServerEndpointService
from twisted.internet.endpoints import serverFromString
from twisted.python import filepath
from josepy.jwa import RS256
from txacme.challenges import HTTP01Responder
from txacme.client import Client
from txacme.endpoint import load_or_create_client_key
from txacme.service import AcmeIssuingService
from txacme.store import DirectoryStore
def movedTo(request, url):
"""
Permanently redirect C{request} to C{url}.
@param request: The L{twisted.web.server.Reqeuest} to redirect.
@param url: The new URL to which to redirect the request.
@type url: L{bytes}
@return: The redirect HTML page.
@rtype: L{bytes}
"""
request.setResponseCode(http.MOVED_PERMANENTLY)
request.setHeader(b"location", url)
return ("""
<html>
<head>
<meta http-equiv=\"refresh\" content=\"0;URL=%(url)s\">
</head>
<body bgcolor=\"#FFFFFF\" text=\"#000000\">
<a href=\"%(url)s\">click here</a>
</body>
</html>
""" % {'url': url}).encode('ascii')
class RespondToHTTP01AndRedirectToHTTPS(resource.Resource):
"""
Allow an L{HTTP01Responder} to handle requests for
C{.well_known/acme-challenges} only. Redirect any other requests
to their HTTPS equivalent.
"""
def __init__(self, responderResource):
resource.Resource.__init__(self)
wellKnown = resource.Resource()
wellKnown.putChild(b'acme-challenge', responderResource)
self.putChild(b'.well-known', wellKnown)
self.putChild(b'check', static.Data(b'OK', b'text/plain'))
def render(self, request):
# request.args can include URL encoded bodies, so extract the
# query from request.uri
_, _, query = request.uri.partition(b'?')
# Assume HTTPS is served over 443
httpsURL = URL(
scheme=u'https',
# I'm sure ASCII will be fine.
host=request.getRequestHostname().decode('ascii'),
path=tuple(segment.decode('ascii')
for segment in request.prepath + request.postpath),
)
httpsLocation = httpsURL.asText().encode('ascii')
if query:
httpsLocation += (b'?' + query)
return movedTo(request, httpsLocation)
def getChild(self, path, request):
return self
class EnsureHTTPS(resource.Resource):
"""
Wrap a resource so that all requests that are not over HTTPS are
redirected to HTTPS.
"""
def __init__(self, wrappedResource, responderResource):
"""
Initialize L{EnsureHTTPS}.
@param wrappedResource: A resource representing the root of a web site.
@type wrappedResource: L{twisted.web.resource.Resource}
"""
self._wrappedResource = wrappedResource
self._httpResource = RespondToHTTP01AndRedirectToHTTPS(
responderResource)
def getChildWithDefault(self, path, request):
if request.isSecure():
return self._wrappedResource.getChildWithDefault(path, request)
else:
return self._httpResource.getChildWithDefault(path, request)
# Add useful extra extension -> content-type mapping entries
static.File.contentTypes['.tac'] = 'text/plain'
static.File.contentTypes['.mbox'] = 'application/octet-stream'
root = vhost.NameVirtualHost()
websiteRoot = filepath.FilePath(__file__).parent().parent()
vhostDir = websiteRoot.child('vhosts')
# The default processors in Twisted, by file extension.
processors = {
'.epy': script.PythonScript,
'.rpy': script.ResourceScript,
}
indexNames = ['index', 'index.html', 'index.xhtml', 'index.rpy','index.cgi']
for file in vhostDir.children():
r = static.File(file.path)
r.processors = processors
r.indexNames = indexNames
r.ignoreExt('.rpy')
root.addHost(file.basename(), r)
root.addHost('www.' + file.basename(), r)
# Subdomains under which www.twistedmatrix.com has historically been
# available
SUBDOMAINS = [
'dornkirk',
'ftp',
'irc',
'mail',
'ns1',
'ns2',
'projects',
'reality',
'saph',
'smtp',
]
for sub in SUBDOMAINS:
root.addHost(sub + '.twistedmatrix.com',
twutil.ChildRedirector('https://www.twistedmatrix.com'))
tm = root.hosts['twistedmatrix.com']
tm.putChild('', twutil.Redirect('trac/'))
# These are children no matter what domain you use
root.putChild('mailman', twcgi.CGIDirectory('/usr/lib/cgi-bin'))
root.putChild('pipermail', static.File('/var/lib/mailman/archives/public/'))
users = distrib.UserDirectory()
root.putChild('users', users)
root.putChild('cgi-bin', twcgi.CGIDirectory('/usr/lib/cgi-bin'))
###
### Codespeed Speedcenter
###
root.addHost('speed.twistedmatrix.com', proxy.ReverseProxyResource('localhost', 8123, '/http/speed.twistedmatrix.com'))
###
### Other stuff
###
trac = proxy.ReverseProxyResource('127.0.0.1', 9881, '/trac')
trac.putChild('chrome', static.File(websiteRoot.child('trac-files').path))
tm.putChild('trac', trac)
# Change this if you want default mime-type to be different:
# default.defaultType = 'text/html'
# Ignore all extensions -- Allow /foo/bar.ext to be served for /foo/bar
# Uncomment to enable
# default.ignoreExt('*')
# Ignore a partial list of extensions
# default.ignoreExt('html') # Allow /foo.html to be served for /foo
# default.ignoreExt('jpg')
# Useful to make ResourceScripts which look like directories.
# default.ignoreExt('rpy')
# set logfile name.
# This is the *Apache compatible* log file, not the twisted-style logfile.
# Leaving this as None will have no Apache compatible log file. Apache
# compatible logfiles are useful because there are quite a few programs
# which analyse them and display statistics.
# This absolute path is extremely obnoxious. Why can't I just say
# "httpd.log"? The working directory when it is interpreted seems to be
# wrong. -exarkun
logPath = os.path.expanduser('~/log/httpd.log')
# Add a rule to rewrite /~foo/bar.html to /usrs/foo/bar.html
# so that the classical user URLs will work.
top = rewrite.RewriterResource(root, rewrite.tildeToUsers, rewrite.alias("highscores", "users/highscore.twistd"))
# Generate the Site factory. You will not normally
# want to modify this line.
responder = HTTP01Responder()
# site = server.Site(EnsureHTTPS(top, responder.resource), logPath=logPath)
site = server.Site(EnsureHTTPS(top, static.File("/srv/t-web/certbot-webroot/.well-known/acme-challenge")), logPath=logPath)
# Generate the Application. You will not normally
# want to modify this line.
application = service.Application("web")
# Limit the number of logfiles which will be kept when rotation is done.
from twisted.python.log import ILogObserver, FileLogObserver
from twisted.python.logfile import LogFile
application.setComponent(
ILogObserver,
FileLogObserver(LogFile("twistd.log", os.path.expanduser("~/log"), maxRotatedFiles=10)).emit)
certificates = filepath.FilePath('/srv/t-web/ssl')
httpService = StreamServerEndpointService(
serverFromString(reactor, 'tcp:80'),
site,
)
httpService.setServiceParent(application)
httpsService = StreamServerEndpointService(
serverFromString(
reactor,
'txsni:{}:tcp:443'.format(certificates.path),
),
site,
)
httpsService.setServiceParent(application)
if websiteRoot.child('production').exists():
d = Client.from_url(
reactor=reactor,
url=URL.fromText(
u'https://acme-v02.api.letsencrypt.org/directory'),
key=load_or_create_client_key(certificates),
alg=RS256,
)
def cb_got_client(client):
issuingService = AcmeIssuingService(
cert_store=DirectoryStore(certificates),
client=client,
clock=reactor,
responders=[responder],
)
issuingService.start()
issuingService.setServiceParent(application)
d.addCallback(cb_got_client)
# If you do not want to modify this file directly, most modifications
# can be done via putting a Python file in /etc/twisted-web/local.d
# This file will have access to root, default, site and application and
# can call any method or change any attribute on them.
#
# If you want to change the root itself, "root=something" will not
# have the desired effect. Instead, use site.resource=something
#
default = None
dictionary = {'root': root, 'site': site, 'application': application,
'default': default}
files = filepath.FilePath(__file__).sibling('local.d').globChildren('*.py')
files.sort()
for file in files:
execfile(file.path, dictionary)