16 from twisted.internet.defer import maybeDeferred |
16 from twisted.internet.defer import maybeDeferred |
17 from twisted.web2 import channel, http, server, iweb |
17 from twisted.web2 import channel, http, server, iweb |
18 from twisted.web2 import static, resource, responsecode |
18 from twisted.web2 import static, resource, responsecode |
19 |
19 |
20 from cubicweb import ObjectNotFound |
20 from cubicweb import ObjectNotFound |
21 from cubicweb.web import (AuthenticationError, NotFound, Redirect, |
21 from cubicweb.web import (AuthenticationError, NotFound, Redirect, |
22 RemoteCallFailed, DirectResponse, StatusResponse, |
22 RemoteCallFailed, DirectResponse, StatusResponse, |
23 ExplicitLogin) |
23 ExplicitLogin) |
24 from cubicweb.web.application import CubicWebPublisher |
24 from cubicweb.web.application import CubicWebPublisher |
25 |
25 |
26 from cubicweb.etwist.request import CubicWebTwistedRequestAdapter |
26 from cubicweb.etwist.request import CubicWebTwistedRequestAdapter |
40 except: |
40 except: |
41 repo.exception('error in looping task') |
41 repo.exception('error in looping task') |
42 start_task(interval, catch_error_func) |
42 start_task(interval, catch_error_func) |
43 # ensure no tasks will be further added |
43 # ensure no tasks will be further added |
44 repo._looping_tasks = () |
44 repo._looping_tasks = () |
45 |
45 |
46 |
46 |
47 class LongTimeExpiringFile(static.File): |
47 class LongTimeExpiringFile(static.File): |
48 """overrides static.File and sets a far futre ``Expires`` date |
48 """overrides static.File and sets a far futre ``Expires`` date |
49 on the resouce. |
49 on the resouce. |
50 |
50 |
69 return d.addCallback(setExpireHeader) |
69 return d.addCallback(setExpireHeader) |
70 |
70 |
71 |
71 |
72 class CubicWebRootResource(resource.PostableResource): |
72 class CubicWebRootResource(resource.PostableResource): |
73 addSlash = False |
73 addSlash = False |
74 |
74 |
75 def __init__(self, config, debug=None): |
75 def __init__(self, config, debug=None): |
76 self.appli = CubicWebPublisher(config, debug=debug) |
76 self.appli = CubicWebPublisher(config, debug=debug) |
77 self.debugmode = debug |
77 self.debugmode = debug |
78 self.config = config |
78 self.config = config |
79 self.base_url = config['base-url'] or config.default_base_url() |
79 self.base_url = config['base-url'] or config.default_base_url() |
101 except ObjectNotFound: |
101 except ObjectNotFound: |
102 self.url_rewriter = None |
102 self.url_rewriter = None |
103 interval = min(config['cleanup-session-time'] or 120, |
103 interval = min(config['cleanup-session-time'] or 120, |
104 config['cleanup-anonymous-session-time'] or 720) / 2. |
104 config['cleanup-anonymous-session-time'] or 720) / 2. |
105 start_task(interval, self.appli.session_handler.clean_sessions) |
105 start_task(interval, self.appli.session_handler.clean_sessions) |
106 |
106 |
107 def shutdown_event(self): |
107 def shutdown_event(self): |
108 """callback fired when the server is shutting down to properly |
108 """callback fired when the server is shutting down to properly |
109 clean opened sessions |
109 clean opened sessions |
110 """ |
110 """ |
111 self.appli.repo.shutdown() |
111 self.appli.repo.shutdown() |
114 """listen for pyro events""" |
114 """listen for pyro events""" |
115 try: |
115 try: |
116 self.pyro_daemon.handleRequests(self.pyro_listen_timeout) |
116 self.pyro_daemon.handleRequests(self.pyro_listen_timeout) |
117 except select.error: |
117 except select.error: |
118 return |
118 return |
119 |
119 |
120 def locateChild(self, request, segments): |
120 def locateChild(self, request, segments): |
121 """Indicate which resource to use to process down the URL's path""" |
121 """Indicate which resource to use to process down the URL's path""" |
122 if segments: |
122 if segments: |
123 if segments[0] == 'https': |
123 if segments[0] == 'https': |
124 segments = segments[1:] |
124 segments = segments[1:] |
141 elif segments[0] == 'fckeditor': |
141 elif segments[0] == 'fckeditor': |
142 fckeditordir = self.config.ext_resources['FCKEDITOR_PATH'] |
142 fckeditordir = self.config.ext_resources['FCKEDITOR_PATH'] |
143 return static.File(fckeditordir), segments[1:] |
143 return static.File(fckeditordir), segments[1:] |
144 # Otherwise we use this single resource |
144 # Otherwise we use this single resource |
145 return self, () |
145 return self, () |
146 |
146 |
147 def render(self, request): |
147 def render(self, request): |
148 """Render a page from the root resource""" |
148 """Render a page from the root resource""" |
149 # reload modified files (only in development or debug mode) |
149 # reload modified files (only in development or debug mode) |
150 if self.config.mode == 'dev' or self.debugmode: |
150 if self.config.mode == 'dev' or self.debugmode: |
151 self.appli.vreg.register_objects(self.config.vregistry_path()) |
151 self.appli.vreg.register_objects(self.config.vregistry_path()) |
152 if self.config['profile']: # default profiler don't trace threads |
152 if self.config['profile']: # default profiler don't trace threads |
153 return self.render_request(request) |
153 return self.render_request(request) |
154 else: |
154 else: |
155 return threads.deferToThread(self.render_request, request) |
155 return threads.deferToThread(self.render_request, request) |
156 |
156 |
157 def render_request(self, request): |
157 def render_request(self, request): |
158 origpath = request.path |
158 origpath = request.path |
159 host = request.host |
159 host = request.host |
160 # dual http/https access handling: expect a rewrite rule to prepend |
160 # dual http/https access handling: expect a rewrite rule to prepend |
161 # 'https' to the path to detect https access |
161 # 'https' to the path to detect https access |
162 if origpath.split('/', 2)[1] == 'https': |
162 if origpath.split('/', 2)[1] == 'https': |
163 origpath = origpath[6:] |
163 origpath = origpath[6:] |
164 request.uri = request.uri[6:] |
164 request.uri = request.uri[6:] |
165 https = True |
165 https = True |
166 baseurl = self.https_url or self.base_url |
166 baseurl = self.https_url or self.base_url |
167 else: |
167 else: |
168 https = False |
168 https = False |
169 baseurl = self.base_url |
169 baseurl = self.base_url |
170 req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https, baseurl) |
170 req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https, baseurl) |
171 if req.authmode == 'http': |
171 if req.authmode == 'http': |
178 return self.request_auth(req) |
178 return self.request_auth(req) |
179 except Redirect, ex: |
179 except Redirect, ex: |
180 return self.redirect(req, ex.location) |
180 return self.redirect(req, ex.location) |
181 if https and req.cnx.anonymous_connection: |
181 if https and req.cnx.anonymous_connection: |
182 # don't allow anonymous on https connection |
182 # don't allow anonymous on https connection |
183 return self.request_auth(req) |
183 return self.request_auth(req) |
184 if self.url_rewriter is not None: |
184 if self.url_rewriter is not None: |
185 # XXX should occur before authentication? |
185 # XXX should occur before authentication? |
186 try: |
186 try: |
187 path = self.url_rewriter.rewrite(host, origpath) |
187 path = self.url_rewriter.rewrite(host, origpath) |
188 except Redirect, ex: |
188 except Redirect, ex: |
224 # in http we have to request auth to flush current http auth |
224 # in http we have to request auth to flush current http auth |
225 # information |
225 # information |
226 return self.request_auth(req, loggedout=True) |
226 return self.request_auth(req, loggedout=True) |
227 except Redirect, ex: |
227 except Redirect, ex: |
228 return self.redirect(req, ex.location) |
228 return self.redirect(req, ex.location) |
229 if not result: |
|
230 # no result, something went wrong... |
|
231 self.error('no data (%s)', req) |
|
232 # 500 Internal server error |
|
233 return self.redirect(req, req.build_url('error')) |
|
234 # request may be referenced by "onetime callback", so clear its entity |
229 # request may be referenced by "onetime callback", so clear its entity |
235 # cache to avoid memory usage |
230 # cache to avoid memory usage |
236 req.drop_entity_cache() |
231 req.drop_entity_cache() |
237 return http.Response(stream=result, code=responsecode.OK, |
232 return http.Response(stream=result, code=responsecode.OK, |
238 headers=req.headers_out or None) |
233 headers=req.headers_out or None) |
240 def redirect(self, req, location): |
235 def redirect(self, req, location): |
241 req.headers_out.setHeader('location', str(location)) |
236 req.headers_out.setHeader('location', str(location)) |
242 self.debug('redirecting to %s', location) |
237 self.debug('redirecting to %s', location) |
243 # 303 See other |
238 # 303 See other |
244 return http.Response(code=303, headers=req.headers_out) |
239 return http.Response(code=303, headers=req.headers_out) |
245 |
240 |
246 def request_auth(self, req, loggedout=False): |
241 def request_auth(self, req, loggedout=False): |
247 if self.https_url and req.base_url() != self.https_url: |
242 if self.https_url and req.base_url() != self.https_url: |
248 req.headers_out.setHeader('location', self.https_url + 'login') |
243 req.headers_out.setHeader('location', self.https_url + 'login') |
249 return http.Response(code=303, headers=req.headers_out) |
244 return http.Response(code=303, headers=req.headers_out) |
250 if self.config['auth-mode'] == 'http': |
245 if self.config['auth-mode'] == 'http': |
251 code = responsecode.UNAUTHORIZED |
246 code = responsecode.UNAUTHORIZED |
252 else: |
247 else: |
253 code = responsecode.FORBIDDEN |
248 code = responsecode.FORBIDDEN |
254 if loggedout: |
249 if loggedout: |
258 content = self.appli.loggedout_content(req) |
253 content = self.appli.loggedout_content(req) |
259 else: |
254 else: |
260 content = self.appli.need_login_content(req) |
255 content = self.appli.need_login_content(req) |
261 return http.Response(code, req.headers_out, content) |
256 return http.Response(code, req.headers_out, content) |
262 |
257 |
263 |
258 |
264 # This part gets run when you run this file via: "twistd -noy demo.py" |
259 # This part gets run when you run this file via: "twistd -noy demo.py" |
265 def main(appid, cfgname): |
260 def main(appid, cfgname): |
266 """Starts an cubicweb twisted server for an application |
261 """Starts an cubicweb twisted server for an application |
267 |
262 |
268 appid: application's identifier |
263 appid: application's identifier |
293 from twisted.internet import defer |
288 from twisted.internet import defer |
294 from twisted.web2 import fileupload |
289 from twisted.web2 import fileupload |
295 |
290 |
296 # XXX set max file size to 100Mo: put max upload size in the configuration |
291 # XXX set max file size to 100Mo: put max upload size in the configuration |
297 # line below for twisted >= 8.0, default param value for earlier version |
292 # line below for twisted >= 8.0, default param value for earlier version |
298 resource.PostableResource.maxSize = 100*1024*1024 |
293 resource.PostableResource.maxSize = 100*1024*1024 |
299 def parsePOSTData(request, maxMem=100*1024, maxFields=1024, |
294 def parsePOSTData(request, maxMem=100*1024, maxFields=1024, |
300 maxSize=100*1024*1024): |
295 maxSize=100*1024*1024): |
301 if request.stream.length == 0: |
296 if request.stream.length == 0: |
302 return defer.succeed(None) |
297 return defer.succeed(None) |
303 |
298 |
304 ctype = request.headers.getHeader('content-type') |
299 ctype = request.headers.getHeader('content-type') |
305 |
300 |
306 if ctype is None: |
301 if ctype is None: |
307 return defer.succeed(None) |
302 return defer.succeed(None) |
308 |
303 |
316 request.files.update(files) |
311 request.files.update(files) |
317 |
312 |
318 def error(f): |
313 def error(f): |
319 f.trap(fileupload.MimeFormatError) |
314 f.trap(fileupload.MimeFormatError) |
320 raise http.HTTPError(responsecode.BAD_REQUEST) |
315 raise http.HTTPError(responsecode.BAD_REQUEST) |
321 |
316 |
322 if ctype.mediaType == 'application' and ctype.mediaSubtype == 'x-www-form-urlencoded': |
317 if ctype.mediaType == 'application' and ctype.mediaSubtype == 'x-www-form-urlencoded': |
323 d = fileupload.parse_urlencoded(request.stream, keep_blank_values=True) |
318 d = fileupload.parse_urlencoded(request.stream, keep_blank_values=True) |
324 d.addCallbacks(updateArgs, error) |
319 d.addCallbacks(updateArgs, error) |
325 return d |
320 return d |
326 elif ctype.mediaType == 'multipart' and ctype.mediaSubtype == 'form-data': |
321 elif ctype.mediaType == 'multipart' and ctype.mediaSubtype == 'form-data': |