comparison src/server/pages.py @ 984:f0fc28b3bd1e

server: moved LiberviaPage code in its own module
author Goffi <goffi@goffi.org>
date Fri, 17 Nov 2017 12:10:56 +0100
parents src/server/server.py@bcacf970f970
children 64826e69f365
comparison
equal deleted inserted replaced
983:8c9fdb58de5f 984:f0fc28b3bd1e
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011-2017 Jérôme Poisson <goffi@goffi.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from twisted.web import server
20 from twisted.web import resource as web_resource
21 from twisted.web import util as web_util
22 from twisted.internet import defer
23 from twisted.python import failure
24
25 from sat.core.i18n import _
26 from sat.core import exceptions
27 from sat.tools.common import uri as common_uri
28 from sat.core.log import getLogger
29 log = getLogger(__name__)
30 from libervia.server.constants import Const as C
31 from libervia.server import session_iface
32 from libervia.server.utils import quote
33 import libervia
34
35 import os.path
36 import urllib
37
38
39 class LiberviaPage(web_resource.Resource):
40 isLeaf = True # we handle subpages ourself
41 named_pages = {}
42 uri_callbacks = {}
43 pages_redirects = {}
44
45 def __init__(self, host, root_dir, url, name=None, redirect=None, access=None, parse_url=None,
46 prepare_render=None, render=None, template=None, on_data_post=None):
47 """initiate LiberviaPages
48
49 LiberviaPages are the main resources of Libervia, using easy to set python files
50 The arguments are the variables found in page_meta.py
51 @param host(Libervia): the running instance of Libervia
52 @param root_dir(unicode): aboslute file path of the page
53 @param url(unicode): relative URL to the page
54 this URL may not be valid, as pages may require path arguments
55 @param name(unicode, None): if not None, a unique name to identify the page
56 can then be used for e.g. redirection
57 "/" is not allowed in names (as it can be used to construct URL paths)
58 @param redirect(unicode, None): if not None, this page will be redirected. A redirected
59 parameter is used as in self.pageRedirect. parse_url will not be skipped
60 using this redirect parameter is called "full redirection"
61 using self.pageRedirect is called "partial redirection" (because some rendering method
62 can still be used, e.g. parse_url)
63 @param access(unicode, None): permission needed to access the page
64 None means public access.
65 Pages inherit from parent pages: e.g. if a "settings" page is restricted to admins,
66 and if "settings/blog" is public, it still can only be accessed by admins.
67 see C.PAGES_ACCESS_* for details
68 @param parse_url(callable, None): if set it will be called to handle the URL path
69 after this method, the page will be rendered if noting is left in path (request.postpath)
70 else a the request will be transmitted to a subpage
71 @param prepare_render(callable, None): if set, will be used to prepare the rendering
72 that often means gathering data using the bridge
73 @param render(callable, None): if not template is set, this method will be called and
74 what it returns will be rendered.
75 This method is mutually exclusive with template and must return a unicode string.
76 @param template(unicode, None): path to the template to render.
77 This method is mutually exclusive with render
78 @param on_data_post(callable, None): method to call when data is posted
79 None if not post is handled
80 on_data_post can return a string with following value:
81 - C.POST_NO_CONFIRM: confirm flag will not be set
82 """
83
84 web_resource.Resource.__init__(self)
85 self.host = host
86 self.root_dir = root_dir
87 self.url = url
88 self.name = name
89 if name is not None:
90 if name in self.named_pages:
91 raise exceptions.ConflictError(_(u'a Libervia page named "{}" already exists'.format(name)))
92 if u'/' in name:
93 raise ValueError(_(u'"/" is not allowed in page names'))
94 if not name:
95 raise ValueError(_(u"a page name can't be empty"))
96 self.named_pages[name] = self
97 if access is None:
98 access = C.PAGES_ACCESS_PUBLIC
99 if access not in (C.PAGES_ACCESS_PUBLIC, C.PAGES_ACCESS_PROFILE, C.PAGES_ACCESS_NONE):
100 raise NotImplementedError(_(u"{} access is not implemented yet").format(access))
101 self.access = access
102 if redirect is not None:
103 # only page access and name make sense in case of full redirection
104 # so we check that rendering methods/values are not set
105 if not all(lambda x: x is not None
106 for x in (parse_url, prepare_render, render, template)):
107 raise ValueError(_(u"you can't use full page redirection with other rendering method,"
108 u"check self.pageRedirect if you need to use them"))
109 self.redirect = redirect
110 else:
111 self.redirect = None
112 self.parse_url = parse_url
113 self.prepare_render = prepare_render
114 self.template = template
115 self.render_method = render
116 self.on_data_post = on_data_post
117 if access == C.PAGES_ACCESS_NONE:
118 # none pages just return a 404, no further check is needed
119 return
120 if template is None:
121 if not callable(render):
122 log.error(_(u"render must be implemented and callable if template is not set"))
123 else:
124 if render is not None:
125 log.error(_(u"render can't be used at the same time as template"))
126 if parse_url is not None and not callable(parse_url):
127 log.error(_(u"parse_url must be a callable"))
128
129 @classmethod
130 def importPages(cls, host, parent=None, path=None):
131 """Recursively import Libervia pages"""
132 if path is None:
133 path = []
134 if parent is None:
135 root_dir = os.path.join(os.path.dirname(libervia.__file__), C.PAGES_DIR)
136 parent = host
137 else:
138 root_dir = parent.root_dir
139 for d in os.listdir(root_dir):
140 dir_path = os.path.join(root_dir, d)
141 if not os.path.isdir(dir_path):
142 continue
143 meta_path = os.path.join(dir_path, C.PAGES_META_FILE)
144 if os.path.isfile(meta_path):
145 page_data = {}
146 new_path = path + [d]
147 # we don't want to force the presence of __init__.py
148 # so we use execfile instead of import.
149 # TODO: when moved to Python 3, __init__.py is not mandatory anymore
150 # so we can switch to import
151 execfile(meta_path, page_data)
152 resource = LiberviaPage(
153 host,
154 dir_path,
155 u'/' + u'/'.join(new_path),
156 name=page_data.get('name'),
157 redirect=page_data.get('redirect'),
158 access=page_data.get('access'),
159 parse_url=page_data.get('parse_url'),
160 prepare_render=page_data.get('prepare_render'),
161 render=page_data.get('render'),
162 template=page_data.get('template'),
163 on_data_post=page_data.get('on_data_post'))
164 parent.putChild(d, resource)
165 log.info(u"Added /{path} page".format(path=u'[...]/'.join(new_path)))
166 if 'uri_handlers' in page_data:
167 if not isinstance(page_data, dict):
168 log.error(_(u'uri_handlers must be a dict'))
169 else:
170 for uri_tuple, cb_name in page_data['uri_handlers'].iteritems():
171 if len(uri_tuple) != 2 or not isinstance(cb_name, basestring):
172 log.error(_(u"invalid uri_tuple"))
173 continue
174 log.info(_(u'setting {}/{} URIs handler').format(*uri_tuple))
175 try:
176 cb = page_data[cb_name]
177 except KeyError:
178 log.error(_(u'missing {name} method to handle {1}/{2}').format(
179 name = cb_name, *uri_tuple))
180 continue
181 else:
182 cls.registerURI(uri_tuple, cb, new_path)
183
184 LiberviaPage.importPages(host, resource, new_path)
185
186 @classmethod
187 def registerURI(cls, uri_tuple, get_uri_cb, pre_path):
188 """register a URI handler
189
190 @param uri_tuple(tuple[unicode, unicode]): type or URIs handler
191 type/subtype as returned by tools/common/parseXMPPUri
192 @param get_uri_cb(callable): method which take uri_data dict as only argument
193 and return path with correct arguments relative to page itself
194 @param pre_path(list[unicode]): prefix path to reference the handler page
195 """
196 if uri_tuple in cls.uri_callbacks:
197 log.info(_(u"{}/{} URIs are already handled, replacing by the new handler").format(*uri_tuple))
198 cls.uri_callbacks[uri_tuple] = {u'callback': get_uri_cb,
199 u'pre_path': pre_path}
200
201 def getPagePathFromURI(self, uri):
202 """Retrieve page URL from xmpp: URI
203
204 @param uri(unicode): URI with a xmpp: scheme
205 @return (unicode,None): absolute path (starting from root "/") to page handling the URI
206 None is returned if not page has been registered for this URI
207 """
208 uri_data = common_uri.parseXMPPUri(uri)
209 try:
210 callback_data = self.uri_callbacks[uri_data['type'], uri_data.get('sub_type')]
211 except KeyError:
212 return
213 else:
214 url = os.path.join(u'/', u'/'.join(callback_data['pre_path']), callback_data['callback'](self, uri_data))
215 return url
216
217 @classmethod
218 def getPageByName(cls, name):
219 """retrieve page instance from its name
220
221 @param name(unicode): name of the page
222 @return (LiberviaPage): page instance
223 @raise KeyError: the page doesn't exist
224 """
225 return cls.named_pages[name]
226
227 def getPageRedirectURL(self, request, page_name=u'login', url=None):
228 """generate URL for a page with redirect_url parameter set
229
230 mainly used for login page with redirection to current page
231 @param request(server.Request): current HTTP request
232 @param page_name(unicode): name of the page to go
233 @param url(None, unicode): url to redirect to
234 None to use request path (i.e. current page)
235 @return (unicode): URL to use
236 """
237 return u'{root_url}?redirect_url={redirect_url}'.format(
238 root_url = self.getPageByName(page_name).url,
239 redirect_url=urllib.quote_plus(request.uri) if url is None else url.encode('utf-8'))
240
241 def getURL(self, *args):
242 """retrieve URL of the page set arguments
243
244 *args(list[unicode]): argument to add to the URL as path elements
245 """
246 url_args = [quote(a) for a in args]
247
248 if self.name is not None and self.name in self.pages_redirects:
249 # we check for redirection
250 redirect_data = self.pages_redirects[self.name]
251 args_hash = tuple(args)
252 for limit in xrange(len(args)+1):
253 current_hash = args_hash[:limit]
254 if current_hash in redirect_data:
255 url_base = redirect_data[current_hash]
256 remaining = args[limit:]
257 remaining_url = '/'.join(remaining)
258 return os.path.join('/', url_base, remaining_url)
259
260 return os.path.join(self.url, *url_args)
261
262 def getSubPageURL(self, request, page_name, *args):
263 """retrieve a page in direct children and build its URL according to request
264
265 request's current path is used as base (at current parsing point,
266 i.e. it's more prepath than path).
267 Requested page is checked in children and an absolute URL is then built
268 by the resulting combination.
269 This method is useful to construct absolute URLs for children instead of
270 using relative path, which may not work in subpages, and are linked to the
271 names of directories (i.e. relative URL will break if subdirectory is renamed
272 while getSubPageURL won't as long as page_name is consistent).
273 Also, request.path is used, keeping real path used by user,
274 and potential redirections.
275 @param request(server.Request): current HTTP request
276 @param page_name(unicode): name of the page to retrieve
277 it must be a direct children of current page
278 @param *args(list[unicode]): arguments to add as path elements
279 @return unicode: absolute URL to the sub page
280 """
281 # we get url in the following way (splitting request.path instead of using
282 # request.prepath) because request.prepath may have been modified by
283 # redirection (if redirection args have been specified), while path reflect
284 # the real request
285
286 # we ignore empty path elements (i.e. double '/' or '/' at the end)
287 path_elts = [p for p in request.path.split('/') if p]
288
289 if request.postpath:
290 if not request.postpath[-1]:
291 # we remove trailing slash
292 request.postpath = request.postpath[:-1]
293 if request.postpath:
294 # getSubPageURL must return subpage from the point where
295 # the it is called, so we have to remove remanining
296 # path elements
297 path_elts = path_elts[:-len(request.postpath)]
298
299 current_url = '/' + '/'.join(path_elts).decode('utf-8')
300
301 for path, child in self.children.iteritems():
302 try:
303 child_name = child.name
304 except AttributeError:
305 # LiberviaPage have a name, but maybe this is an other Resource
306 continue
307 if child_name == page_name:
308 return os.path.join(u'/', current_url, path, *args)
309 raise exceptions.NotFound(_(u'requested sub page has not been found'))
310
311 def getChildWithDefault(self, path, request):
312 # we handle children ourselves
313 raise exceptions.InternalError(u"this method should not be used with LiberviaPage")
314
315 def nextPath(self, request):
316 """get next URL path segment, and update request accordingly
317
318 will move first segment of postpath in prepath
319 @param request(server.Request): current HTTP request
320 @return (unicode): unquoted segment
321 @raise IndexError: there is no segment left
322 """
323 pathElement = request.postpath.pop(0)
324 request.prepath.append(pathElement)
325 return urllib.unquote(pathElement).decode('utf-8')
326
327 def HTTPRedirect(self, request, url):
328 """redirect to an URL using HTTP redirection
329
330 @param request(server.Request): current HTTP request
331 @param url(unicode): url to redirect to
332 """
333
334 web_util.redirectTo(url.encode('utf-8'), request)
335 request.finish()
336 raise failure.Failure(exceptions.CancelError(u'HTTP redirection is used'))
337
338 def redirectOrContinue(self, request, redirect_arg=u'redirect_url'):
339 """helper method to redirect a page to an url given as arg
340
341 if the arg is not present, the page will continue normal workflow
342 @param request(server.Request): current HTTP request
343 @param redirect_arg(unicode): argument to use to get redirection URL
344 @interrupt: redirect the page to requested URL
345 @interrupt pageError(C.HTTP_BAD_REQUEST): empty or non local URL is used
346 """
347 try:
348 url = self.getPostedData(request, 'redirect_url')
349 except KeyError:
350 pass
351 else:
352 # a redirection is requested
353 if not url or url[0] != u'/':
354 # we only want local urls
355 self.pageError(request, C.HTTP_BAD_REQUEST)
356 else:
357 self.HTTPRedirect(request, url)
358
359 def pageRedirect(self, page_path, request, skip_parse_url=True):
360 """redirect a page to a named page
361
362 the workflow will continue with the workflow of the named page,
363 skipping named page's parse_url method if it exist.
364 If you want to do a HTTP redirection, use HTTPRedirect
365 @param page_path(unicode): path to page (elements are separated by "/"):
366 if path starts with a "/":
367 path is a full path starting from root
368 else:
369 - first element is name as registered in name variable
370 - following element are subpages path
371 e.g.: "blog" redirect to page named "blog"
372 "blog/atom.xml" redirect to atom.xml subpage of "blog"
373 "/common/blog/atom.xml" redirect to the page at the fiven full path
374 @param request(server.Request): current HTTP request
375 @param skip_parse_url(bool): if True, parse_url method on redirect page will be skipped
376 @raise KeyError: there is no known page with this name
377 """
378 # FIXME: render non LiberviaPage resources
379 path = page_path.rstrip(u'/').split(u'/')
380 if not path[0]:
381 redirect_page = self.host.root
382 else:
383 redirect_page = self.named_pages[path[0]]
384
385 for subpage in path[1:]:
386 if redirect_page is self.host.root:
387 redirect_page = redirect_page.children[subpage]
388 else:
389 redirect_page = redirect_page.original.children[subpage]
390
391 redirect_page.renderPage(request, skip_parse_url=True)
392 raise failure.Failure(exceptions.CancelError(u'page redirection is used'))
393
394 def pageError(self, request, code=C.HTTP_NOT_FOUND):
395 """generate an error page and terminate the request
396
397 @param request(server.Request): HTTP request
398 @param core(int): error code to use
399 """
400 template = u'error/' + unicode(code) + '.html'
401
402 request.setResponseCode(code)
403
404 rendered = self.host.renderer.render(
405 template,
406 root_path = '/templates/',
407 error_code = code,
408 **request.template_data)
409
410 self.writeData(rendered, request)
411 raise failure.Failure(exceptions.CancelError(u'error page is used'))
412
413 def writeData(self, data, request):
414 """write data to transport and finish the request"""
415 if data is None:
416 self.pageError(request)
417 request.write(data.encode('utf-8'))
418 request.finish()
419
420 def _subpagesHandler(self, dummy, request):
421 """render subpage if suitable
422
423 this method checks if there is still an unmanaged part of the path
424 and check if it corresponds to a subpage. If so, it render the subpage
425 else it render a NoResource.
426 If there is no unmanaged part of the segment, current page workflow is pursued
427 """
428 if request.postpath:
429 subpage = self.nextPath(request)
430 try:
431 child = self.children[subpage]
432 except KeyError:
433 self.pageError(request)
434 else:
435 child.render(request)
436 raise failure.Failure(exceptions.CancelError(u'subpage page is used'))
437
438 def _prepare_render(self, dummy, request):
439 return defer.maybeDeferred(self.prepare_render, self, request)
440
441 def _render_method(self, dummy, request):
442 return defer.maybeDeferred(self.render_method, self, request)
443
444 def _render_template(self, dummy, request):
445 template_data = request.template_data
446
447 # if confirm variable is set in case of successfuly data post
448 session_data = self.host.getSessionData(request, session_iface.ISATSession)
449 if session_data.popPageFlag(self, C.FLAG_CONFIRM):
450 template_data[u'confirm'] = True
451
452 return self.host.renderer.render(
453 self.template,
454 root_path = '/templates/',
455 media_path = '/' + C.MEDIA_DIR,
456 **template_data)
457
458 def _renderEb(self, failure_, request):
459 """don't raise error on CancelError"""
460 failure_.trap(exceptions.CancelError)
461
462 def _internalError(self, failure_, request):
463 """called if an error is not catched"""
464 log.error(_(u"Uncatched error for HTTP request on {url}: {msg}").format(
465 url = request.URLPath(),
466 msg = failure_))
467 self.pageError(request, C.HTTP_INTERNAL_ERROR)
468
469 def _on_data_post_redirect(self, ret, request):
470 """called when page's on_data_post has been done successfuly
471
472 This will do a Post/Redirect/Get pattern.
473 this method redirect to the same page or to request.data['post_redirect_page']
474 post_redirect_page can be either a page or a tuple with page as first item, then a list of unicode arguments to append to the url.
475 if post_redirect_page is not used, initial request.uri (i.e. the same page as where the data have been posted) will be used for redirection.
476 HTTP status code "See Other" (303) is used as it is the recommanded code in this case.
477 @param ret(None, unicode, iterable): on_data_post return value
478 see LiberviaPage.__init__ on_data_post docstring
479 """
480 if ret is None:
481 ret = ()
482 elif isinstance(ret, basestring):
483 ret = (ret,)
484 else:
485 ret = tuple(ret)
486 raise NotImplementedError(_(u'iterable in on_data_post return value is not used yet'))
487 session_data = self.host.getSessionData(request, session_iface.ISATSession)
488 request_data = self.getRData(request)
489 if 'post_redirect_page' in request_data:
490 redirect_page_data = request_data['post_redirect_page']
491 if isinstance(redirect_page_data, tuple):
492 redirect_page = redirect_page_data[0]
493 redirect_page_args = redirect_page_data[1:]
494 redirect_uri = redirect_page.getURL(*redirect_page_args)
495 else:
496 redirect_page = redirect_page_data
497 redirect_uri = redirect_page.url
498 else:
499 redirect_page = self
500 redirect_uri = request.uri
501
502 if not C.POST_NO_CONFIRM in ret:
503 session_data.setPageFlag(redirect_page, C.FLAG_CONFIRM)
504 request.setResponseCode(C.HTTP_SEE_OTHER)
505 request.setHeader("location", redirect_uri)
506 request.finish()
507 raise failure.Failure(exceptions.CancelError(u'Post/Redirect/Get is used'))
508
509 def _on_data_post(self, dummy, request):
510 csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token
511 try:
512 given_csrf = self.getPostedData(request, u'csrf_token')
513 except KeyError:
514 given_csrf = None
515 if given_csrf is None or given_csrf != csrf_token:
516 log.warning(_(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format(
517 url=request.uri,
518 ip=request.getClientIP()))
519 self.pageError(request, C.HTTP_UNAUTHORIZED)
520 d = defer.maybeDeferred(self.on_data_post, self, request)
521 d.addCallback(self._on_data_post_redirect, request)
522 return d
523
524 def getPostedData(self, request, keys, multiple=False):
525 """get data from a POST request and decode it
526
527 @param request(server.Request): request linked to the session
528 @param keys(unicode, iterable[unicode]): name of the value(s) to get
529 unicode to get one value
530 iterable to get more than one
531 @param multiple(bool): True if multiple values are possible/expected
532 if False, the first value is returned
533 @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s)
534 @raise KeyError: one specific key has been requested, and it is missing
535 """
536 if isinstance(keys, basestring):
537 keys = [keys]
538 get_first = True
539 else:
540 get_first = False
541
542 ret = []
543 for key in keys:
544 gen = (urllib.unquote(v).decode('utf-8') for v in request.args.get(key,[]))
545 if multiple:
546 ret.append(gen)
547 else:
548 try:
549 ret.append(next(gen))
550 except StopIteration:
551 raise KeyError(key)
552
553 return ret[0] if get_first else ret
554
555 def getAllPostedData(self, request, except_=()):
556 """get all posted data
557
558 @param request(server.Request): request linked to the session
559 @param except_(iterable[unicode]): key of values to ignore
560 csrf_token will always be ignored
561 @return (dict[unicode, list[unicode]]): post values
562 """
563 except_ = tuple(except_) + (u'csrf_token',)
564 ret = {}
565 for key, values in request.args.iteritems():
566 key = urllib.unquote(key).decode('utf-8')
567 if key in except_:
568 continue
569 ret[key] = [urllib.unquote(v).decode('utf-8') for v in values]
570 return ret
571
572 def getProfile(self, request):
573 """helper method to easily get current profile
574
575 @return (unicode, None): current profile
576 None if no profile session is started
577 """
578 sat_session = self.host.getSessionData(request, session_iface.ISATSession)
579 return sat_session.profile
580
581 def getRData(self, request):
582 """helper method to get request data dict
583
584 this dictionnary if for the request only, it is not saved in session
585 It is mainly used to pass data between pages/methods called during request workflow
586 @return (dict): request data
587 """
588 try:
589 return request.data
590 except AttributeError:
591 request.data = {}
592 return request.data
593
594 def _checkAccess(self, data, request):
595 """Check access according to self.access
596
597 if access is not granted, show a HTTP_UNAUTHORIZED pageError and stop request,
598 else return data (so it can be inserted in deferred chain
599 """
600 if self.access == C.PAGES_ACCESS_PUBLIC:
601 pass
602 elif self.access == C.PAGES_ACCESS_PROFILE:
603 profile = self.getProfile(request)
604 if not profile:
605 # no session started
606 if not self.host.options["allow_registration"]:
607 # registration not allowed, access is not granted
608 self.pageError(request, C.HTTP_UNAUTHORIZED)
609 else:
610 # registration allowed, we redirect to login page
611 login_url = self.getPageRedirectURL(request)
612 self.HTTPRedirect(request, login_url)
613
614 return data
615
616 def renderPage(self, request, skip_parse_url=False):
617 """Main method to handle the workflow of a LiberviaPage"""
618 # template_data are the variables passed to template
619 if not hasattr(request, 'template_data'):
620 session_data = self.host.getSessionData(request, session_iface.ISATSession)
621 csrf_token = session_data.csrf_token
622 request.template_data = {u'csrf_token': csrf_token}
623
624 # XXX: here is the code which need to be executed once
625 # at the beginning of the request hanling
626 if request.postpath and not request.postpath[-1]:
627 # we don't differenciate URLs finishing with '/' or not
628 del request.postpath[-1]
629
630 d = defer.Deferred()
631 d.addCallback(self._checkAccess, request)
632
633 if self.redirect is not None:
634 self.pageRedirect(self.redirect, request, skip_parse_url=False)
635
636 if self.parse_url is not None and not skip_parse_url:
637 d.addCallback(self.parse_url, request)
638
639 d.addCallback(self._subpagesHandler, request)
640
641 if request.method not in (C.HTTP_METHOD_GET, C.HTTP_METHOD_POST):
642 # only HTTP GET and POST are handled so far
643 d.addCallback(lambda dummy: self.pageError(request, C.HTTP_BAD_REQUEST))
644
645 if request.method == C.HTTP_METHOD_POST:
646 if self.on_data_post is None:
647 # if we don't have on_data_post, the page was not expecting POST
648 # so we return an error
649 d.addCallback(lambda dummy: self.pageError(request, C.HTTP_BAD_REQUEST))
650 else:
651 d.addCallback(self._on_data_post, request)
652 # by default, POST follow normal behaviour after on_data_post is called
653 # this can be changed by a redirection or other method call in on_data_post
654
655 if self.prepare_render:
656 d.addCallback(self._prepare_render, request)
657
658 if self.template:
659 d.addCallback(self._render_template, request)
660 elif self.render_method:
661 d.addCallback(self._render_method, request)
662
663 d.addCallback(self.writeData, request)
664 d.addErrback(self._renderEb, request)
665 d.addErrback(self._internalError, request)
666 d.callback(self)
667 return server.NOT_DONE_YET
668
669 def render_GET(self, request):
670 return self.renderPage(request)
671
672 def render_POST(self, request):
673 return self.renderPage(request)