source: indico/indico/web/wsgi/indico_wsgi_handler.py @ e0a1a1

burotelhello-world-walkthroughipv6v0.98-seriesv0.98.2v0.98.3v0.98b1v0.98b2v0.99v1.0v1.1
Last change on this file since e0a1a1 was e0a1a1, checked in by Pedro Ferreira <jose.pedro.ferreira@…>, 3 years ago

[FIX] Wrong import

  • After renaming, the import wasn't changed;
  • Property mode set to 100644
File size: 19.7 KB
Line 
1# -*- coding: utf-8 -*-
2##
3##
4## This file is part of CDS Indico.
5## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 CERN.
6##
7## CDS Indico is free software; you can redistribute it and/or
8## modify it under the terms of the GNU General Public License as
9## published by the Free Software Foundation; either version 2 of the
10## License, or (at your option) any later version.
11##
12## CDS Indico is distributed in the hope that it will be useful, but
13## WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15## General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
19## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20
21"""
22Main wsgi handler
23
24This code has partially been taken from CDS Invenio,
25under the corresponding GNU GPL license.
26"""
27
28import sys
29import os
30from MaKaC.common import Config
31
32# Path update
33
34DIR_HTDOCS = Config.getInstance().getHtdocsDir()
35PATH = [os.path.join(DIR_HTDOCS, '../'), \
36        DIR_HTDOCS]
37for p in PATH:
38    if p not in sys.path:
39        sys.path.append(p)
40
41###
42
43from wsgiref.util import FileWrapper, guess_scheme
44
45from indico.web.wsgi.webinterface_handler_config import \
46     HTTP_STATUS_MAP, SERVER_RETURN, OK, DONE, \
47     HTTP_NOT_FOUND, HTTP_INTERNAL_SERVER_ERROR, \
48    REMOTE_HOST, REMOTE_NOLOOKUP
49from indico.web.wsgi.indico_wsgi_handler_utils import table, FieldStorage, \
50     registerException, _check_result
51from indico.web.wsgi.indico_wsgi_url_parser import is_mp_legacy_publisher_path
52
53# Legacy imports
54from MaKaC.plugins.base import RHMap
55
56if __name__ != "__main__":
57    # Chances are that we are inside mod_wsgi.
58    # You can't write to stdout in mod_wsgi, but instead to stderr
59    sys.stdout = sys.stderr
60
61def application(environ, start_response):
62    """
63    Entry point for wsgi.
64    """
65    ## Needed for mod_wsgi
66    ## see: <http://code.google.com/p/modwsgi/wiki/ApplicationIssues>
67    req = SimulatedModPythonRequest(environ, start_response)
68
69    possible_module, possible_handler = is_mp_legacy_publisher_path(req)
70
71    # The POST form processing has to be done after checking the path
72    req.get_post_form()
73
74    try:
75        try:
76            #if condition:
77            #    wsgi_publisher(possible_module, possible_handler)
78            if possible_module is not None:
79                mp_legacy_publisher(req, possible_module, possible_handler)
80            else:
81                # Let's try to load a plugin
82                # Replace URLFields with environ
83                path = req.URLFields['PATH_INFO'].split('/')
84                pluginMap = RHMap()._map
85                if path[1] != '' and pluginMap.has_key(path[1]):
86                    plugin_publisher(req, path[1])
87                else:
88                    # Finally, it might be a static file
89                    possible_static_path = is_static_path(environ['PATH_INFO'])
90                    if possible_static_path is not None:
91                        from indico.web.wsgi.indico_wsgi_file_handler import stream_file
92                        stream_file(req, possible_static_path)
93                    else:
94                        raise SERVER_RETURN, HTTP_NOT_FOUND
95            req.flush()
96        #Exception treatment
97        except SERVER_RETURN, status:
98            status = int(str(status))
99            if status not in (OK, DONE):
100                req.status = status
101                req.headers_out['content-type'] = 'text/html'
102                start_response(req.get_wsgi_status(),
103                               req.get_low_level_headers(),
104                               sys.exc_info())
105                return ('%s' % indicoErrorWebpage(status),)
106            else:
107                req.flush()
108        # If we reach this block, there was probably
109        # an error importing a legacy python module
110        except Exception:
111            req.status = HTTP_INTERNAL_SERVER_ERROR
112            req.headers_out['content-type'] = 'text/html'
113            start_response(req.get_wsgi_status(),
114                           req.get_low_level_headers(),
115                           sys.exc_info())
116            registerException()
117            return ('%s' % indicoErrorWebpage(500),)
118    finally:
119        for (callback, data) in req.get_cleanups():
120            callback(data)
121    return []
122
123def indicoErrorWebpage(status):
124    """
125    Calls the tpl containing the error webpage.
126    Returns a string with the error webpage we want to generate.
127    """
128    from MaKaC.webinterface.pages.error import WErrorWSGI
129    from MaKaC.i18n import _
130    errorTitleText = (_("Page not found"),
131                      _("The page you were looking for doesn't exist."))
132    if status != 404:
133        errorTitleText = (_("%s" % (HTTP_STATUS_MAP.get(status, "Unknown error"))),
134                          "An unexpected error ocurred.")
135    wsError = WErrorWSGI(errorTitleText)
136    return wsError.getHTML()
137
138def is_static_path(path):
139    """
140    Returns True if path corresponds to an existing file under DIR_HTDOCS.
141    @param path: the path.
142    @type path: string
143    @return: True if path corresponds to an existing file under DIR_HTDOCS.
144    @rtype: bool
145    """
146    path = os.path.abspath(DIR_HTDOCS + path)
147    if path.startswith(DIR_HTDOCS) and os.path.isfile(path):
148        return path
149    return None
150
151def plugin_publisher(req, path):
152    """
153    Publishes the plugin described in path
154    """
155    pluginMap = RHMap()._map
156    form = dict(req.form)
157
158    importList = pluginMap[path].split('.')
159    funcName = importList.pop()
160    callingFunction = __import__('.'.join(importList[1:]), None, None, importList[0])
161    _check_result(req, getattr(callingFunction, funcName)( req ).process( form ))
162
163def mp_legacy_publisher(req, possible_module, possible_handler):
164    """
165    mod_python legacy publisher minimum implementation.
166    """
167    the_module = open(possible_module).read()
168    module_globals = {}
169
170    try:
171        exec(the_module, module_globals)
172    except:
173        # Log which file caused the exec error (traceback won't do it for
174        # some reason) and relaunch the exception
175        registerException('Error executing the module %s' % possible_module)
176        raise
177
178    if possible_handler in module_globals and \
179           callable(module_globals[possible_handler]):
180        from indico.web.wsgi.indico_wsgi_handler_utils import _check_result
181        ## the req.form must be casted to dict because of Python 2.4 and earlier
182        ## otherwise any object exposing the mapping interface can be
183        ## used with the magic **
184        form = dict(req.form)
185
186        try:
187            return _check_result(req, module_globals[possible_handler](req, **form))
188        except TypeError, err:
189            if ("%s() got an unexpected keyword argument" % possible_handler) in \
190                   str(err) or ('%s() takes at least' % possible_handler) in str(err):
191                import inspect
192                inspected_args = inspect.getargspec(module_globals[possible_handler])
193                expected_args = list(inspected_args[0])
194                expected_defaults = list(inspected_args[3])
195                expected_args.reverse()
196                expected_defaults.reverse()
197                # Write the exception to Apache error log file
198                registerException("Wrong GET parameter set in calling a legacy "
199                                  "publisher handler for %s: expected_args=%s, "
200                                  "found_args=%s" % \
201                                  (possible_handler, repr(expected_args),
202                                   repr(req.form.keys())))
203                cleaned_form = {}
204                for index, arg in enumerate(expected_args):
205                    if arg == 'req':
206                        continue
207                    if index < len(expected_defaults):
208                        cleaned_form[arg] = form.get(arg, expected_defaults[index])
209                    else:
210                        cleaned_form[arg] = form.get(arg, None)
211                return _check_result(req,
212                                     module_globals[possible_handler](req,
213                                                                      **cleaned_form))
214            else:
215                raise
216    else:
217        raise SERVER_RETURN, HTTP_NOT_FOUND
218
219class InputProcessed(object):
220    """
221    Auxiliary class used when reading input.
222    @see: <http://www.wsgi.org/wsgi/Specifications/handling_post_forms>.
223    """
224    def read(self, *args):
225        raise EOFError('The wsgi.input stream has already been consumed')
226    readline = readlines = __iter__ = read
227
228
229class SimulatedModPythonRequest(object):
230    """
231    mod_python like request object.
232    Minimum and cleaned implementation to make moving out of mod_python
233    easy.
234    @see: <http://www.modpython.org/live/current/doc-html/pyapi-mprequest.html>
235    """
236    def __init__(self, environ, start_response):
237        from urllib import quote
238        self.__environ = environ
239        self.__start_response = start_response
240        self.__response_sent_p = False
241        self.__buffer = ''
242        self.__low_level_headers = []
243        self.__headers = table(self.__low_level_headers)
244        self.__headers.add = self.__headers.add_header
245        self.__status = "200 OK"
246        self.__filename = None
247        self.__disposition_type = None
248        self.__bytes_sent = 0
249        self.__allowed_methods = []
250        self.__cleanups = []
251        self.headers_out = self.__headers
252        ## See: <http://www.python.org/dev/peps/pep-0333/#the-write-callable>
253        self.__write = None
254        self.__errors = environ['wsgi.errors']
255        self.__headers_in = table([])
256        for key, value in environ.iteritems():
257            if key.startswith('HTTP_'):
258                self.__headers_in[key[len('HTTP_'):].replace('_', '-')] = value
259        if environ.get('CONTENT_LENGTH'):
260            self.__headers_in['content-length'] = environ['CONTENT_LENGTH']
261        if environ.get('CONTENT_TYPE'):
262            self.__headers_in['content-type'] = environ['CONTENT_TYPE']
263        ## HTTP headers variables
264        ## We might want to add some other fields in the future
265        self.URLFields = {'URL_SCHEME':   environ['wsgi.url_scheme'], \
266                    'HTTP_HOST':    environ.get('HTTP_HOST', ''), \
267                    'SERVER_NAME':  environ.get('SERVER_NAME', ''), \
268                    'SERVER_PORT':  environ['SERVER_PORT'], \
269                    'SCRIPT_NAME':  quote(environ.get('SCRIPT_NAME', '')), \
270                    'PATH_INFO':    quote(environ.get('PATH_INFO','')), \
271                    'QUERY_STRING': environ.get('QUERY_STRING', '')}
272
273    def get_wsgi_environ(self):
274        return self.__environ
275
276    def get_post_form(self):
277        post_form = self.__environ.get('wsgi.post_form')
278        input = self.__environ['wsgi.input']
279        if (post_form is not None
280            and post_form[0] is input):
281            return post_form[2]
282        # This must be done to avoid a bug in cgi.FieldStorage
283        self.__environ.setdefault('QUERY_STRING', '')
284        fs = FieldStorage(self, keep_blank_values=1)
285        if fs.wsgi_input_consumed:
286            new_input = InputProcessed()
287            post_form = (new_input, input, fs)
288            self.__environ['wsgi.post_form'] = post_form
289            self.__environ['wsgi.input'] = new_input
290        else:
291            post_form = (input, None, fs)
292            self.__environ['wsgi.post_form'] = post_form
293        return fs
294
295    def get_response_sent_p(self):
296        return self.__response_sent_p
297
298    def get_low_level_headers(self):
299        return self.__low_level_headers
300
301    def get_buffer(self):
302        return self.__buffer
303
304    def write(self, string, flush=1):
305        if isinstance(string, unicode):
306            self.__buffer += string.encode('utf8')
307        else:
308            self.__buffer += string
309        if flush:
310            self.flush()
311
312    def flush(self):
313        self.send_http_header()
314        if self.__buffer:
315            self.__bytes_sent += len(self.__buffer)
316            try:
317                self.__write(self.__buffer)
318            except IOError, err:
319                if "failed to write data" in str(err) \
320                       or "client connection closed" in str(err):
321                    registerException()
322                else:
323                    raise
324            self.__buffer = ''
325
326    def set_content_type(self, content_type):
327        self.__headers['content-type'] = content_type
328
329    def get_content_type(self):
330        return self.__headers['content-type']
331
332    def send_http_header(self):
333        if not self.__response_sent_p:
334            if self.__allowed_methods and self.__status.startswith('405 ') or \
335                   self.__status.startswith('501 '):
336                self.__headers['Allow'] = ', '.join(self.__allowed_methods)
337            ## See: <http://www.python.org/dev/peps/pep-0333/#the-write-callable>
338            self.__write = self.__start_response(self.__status,
339                                                 self.__low_level_headers)
340            self.__response_sent_p = True
341
342    def get_parsed_uri(self):
343        # The parsed_uri tuple is as follows
344        # (scheme, hostinfo, user, password, hostname, port, path, query, fragment)
345        return (self.URLFields['URL_SCHEME'], \
346                self.URLFields['HTTP_HOST'], None, None, \
347                self.URLFields['SERVER_NAME'], self.URLFields['SERVER_PORT'], \
348                self.URLFields['SCRIPT_NAME'] + self.URLFields['PATH_INFO'], \
349                self.URLFields['QUERY_STRING'], None)
350
351    def get_unparsed_uri(self):
352        url = self.URLFields['SCRIPT_NAME'] + self.URLFields['PATH_INFO']
353        if self.URLFields['QUERY_STRING']:
354            url += '?' + self.URLFields['QUERY_STRING']
355        return url
356
357    def get_uri(self):
358        return self.URLFields['SCRIPT_NAME'] + self.URLFields['PATH_INFO']
359
360    def get_headers_in(self):
361        return self.__headers_in
362
363    def get_subprocess_env(self):
364        return self.__environ
365
366    def add_common_vars(self):
367        pass
368
369    def get_args(self):
370        return self.URLFields['QUERY_STRING']
371
372    def get_remote_ip(self):
373        return self.__environ.get('REMOTE_ADDR')
374
375    def get_remote_host(self, type=REMOTE_HOST):
376        if type == REMOTE_NOLOOKUP:
377            return self.__environ.get('REMOTE_ADDR')
378        return self.__environ.get('')
379
380    def get_header_only(self):
381        return self.__environ['REQUEST_METHOD'] == 'HEAD'
382
383    def set_status(self, status):
384        self.__status = '%s %s' % (status,
385                                   HTTP_STATUS_MAP.get(int(status),
386                                                       'Explanation not available'))
387
388    def get_status(self):
389        return int(self.__status.split(' ')[0])
390
391    def get_wsgi_status(self):
392        return self.__status
393
394    def sendfile(self, path, offset=0, the_len=-1):
395        try:
396            self.send_http_header()
397            file_to_send = open(path)
398            file_to_send.seek(offset)
399            file_wrapper = FileWrapper(file_to_send)
400            count = 0
401            if the_len < 0:
402                for chunk in file_wrapper:
403                    count += len(chunk)
404                    self.__bytes_sent += len(chunk)
405                    self.__write(chunk)
406            else:
407                for chunk in file_wrapper:
408                    if the_len >= len(chunk):
409                        the_len -= len(chunk)
410                        count += len(chunk)
411                        self.__bytes_sent += len(chunk)
412                        self.__write(chunk)
413                    else:
414                        count += the_len
415                        self.__bytes_sent += the_len
416                        self.__write(chunk[:the_len])
417                        break
418        except IOError, err:
419            if "failed to write data" in str(err) or \
420                   "client connection closed" in str(err):
421                registerException()
422            else:
423                raise
424        return self.__bytes_sent
425
426    def set_content_length(self, content_length):
427        if content_length is not None:
428            self.__headers['content-length'] = str(content_length)
429        else:
430            del self.__headers['content-length']
431
432    def is_https(self):
433        return int(guess_scheme(self.__environ) == 'https')
434
435    def get_method(self):
436        return self.__environ['REQUEST_METHOD']
437
438    def get_hostname(self):
439        return self.URLFields['HTTP_HOST']
440
441    def set_filename(self, filename):
442        self.__filename = filename
443        if self.__disposition_type is None:
444            self.__disposition_type = 'inline'
445        self.__headers['content-disposition'] = '%s; filename=%s' % \
446                                                (self.__disposition_type,
447                                                 self.__filename)
448
449    def set_encoding(self, encoding):
450        if encoding:
451            self.__headers['content-encoding'] = str(encoding)
452        else:
453            del self.__headers['content-encoding']
454
455    def get_bytes_sent(self):
456        return self.__bytes_sent
457
458    def log_error(self, message):
459        self.__errors.write(message.strip() + '\n')
460
461    def get_content_type_set_p(self):
462        return bool(self.__headers['content-type'])
463
464    def allow_methods(self, methods, reset=0):
465        if reset:
466            self.__allowed_methods = []
467        self.__allowed_methods += [method.upper().strip() for method in methods]
468
469    def get_allowed_methods(self):
470        return self.__allowed_methods
471
472    def readline(self, hint=None):
473        ## the hint param is not part of wsgi pep, although
474        ## it's great to exploit it in when reading FORM
475        ## with large files, in order to avoid filling up the memory
476        ## Too bad it's not there :-(
477        return self.__environ['wsgi.input'].readline()
478
479    def readlines(self, hint=-1):
480        return self.__environ['wsgi.input'].readlines(hint)
481
482    def read(self, size=-1):
483        if size == -1:
484            size = int(self.__environ['CONTENT_LENGTH'])
485        return self.__environ['wsgi.input'].read(size)
486
487    def register_cleanup(self, callback, data=None):
488        self.__cleanups.append((callback, data))
489
490    def get_cleanups(self):
491        return self.__cleanups
492
493    def get_prev(self):
494        return self.__environ.get('HTTP_REFERER', '')
495
496    def construct_url(self, supplied_uri):
497        url = self.URLFields['URL_SCHEME']+'://'
498
499        if self.URLFields['HTTP_HOST']:
500            url += self.URLFields['HTTP_HOST']
501        else:
502            url += self.URLFields['SERVER_NAME']
503
504            if self.URLFields['URL_SCHEME'] == 'https':
505                if self.URLFields['SERVER_PORT'] != '443':
506                    url += ':' + self.URLFields['SERVER_PORT']
507            else:
508                if self.URLFields['SERVER_PORT'] != '80':
509                    url += ':' + self.URLFields['SERVER_PORT']
510
511        url += self.URLFields['SERVER_NAME']
512        url += supplied_uri
513
514        return url
515
516    content_type = property(get_content_type, set_content_type)
517    parsed_uri = property(get_parsed_uri)
518    unparsed_uri = property(get_unparsed_uri)
519    uri = property(get_uri)
520    headers_in = property(get_headers_in)
521    subprocess_env = property(get_subprocess_env)
522    args = property(get_args)
523    header_only = property(get_header_only)
524    status = property(get_status, set_status)
525    method = property(get_method)
526    hostname = property(get_hostname)
527    filename = property(fset=set_filename)
528    encoding = property(fset=set_encoding)
529    bytes_sent = property(get_bytes_sent)
530    content_type_set_p = property(get_content_type_set_p)
531    allowed_methods = property(get_allowed_methods)
532    response_sent_p = property(get_response_sent_p)
533    form = property(get_post_form)
534    remote_ip = property(get_remote_ip)
535    remote_host = property(get_remote_host)
536    prev = property(get_prev)
537
Note: See TracBrowser for help on using the repository browser.