~ubuntu-branches/ubuntu/warty/libapache2-mod-python/warty

« back to all changes in this revision

Viewing changes to lib/python/mod_python/psp.py

  • Committer: Bazaar Package Importer
  • Author(s): Thom May
  • Date: 2004-09-06 20:27:57 UTC
  • Revision ID: james.westby@ubuntu.com-20040906202757-yzpyu1bcabgpjtiu
Tags: upstream-3.1.3
ImportĀ upstreamĀ versionĀ 3.1.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 #
 
2
 # Copyright 2004 Apache Software Foundation 
 
3
 # 
 
4
 # Licensed under the Apache License, Version 2.0 (the "License"); you
 
5
 # may not use this file except in compliance with the License.  You
 
6
 # may obtain a copy of the License at
 
7
 #
 
8
 #      http://www.apache.org/licenses/LICENSE-2.0
 
9
 #
 
10
 # Unless required by applicable law or agreed to in writing, software
 
11
 # distributed under the License is distributed on an "AS IS" BASIS,
 
12
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 
13
 # implied.  See the License for the specific language governing
 
14
 # permissions and limitations under the License.
 
15
 #
 
16
 # This file originally written by Sterling Hughes
 
17
 #
 
18
 # $Id: psp.py,v 1.26 2004/02/16 19:47:27 grisha Exp $
 
19
 
 
20
import apache, Session, util, _psp
 
21
import _apache
 
22
 
 
23
import sys
 
24
import os
 
25
import marshal
 
26
import new
 
27
from cgi import escape
 
28
import anydbm, whichdb
 
29
import tempfile
 
30
 
 
31
# dbm types for cache
 
32
dbm_types = {}
 
33
 
 
34
tempdir = tempfile.gettempdir()
 
35
 
 
36
def path_split(filename):
 
37
 
 
38
    dir, fname = os.path.split(filename)
 
39
    if sys.platform.startswith("win"):
 
40
        dir += "\\"
 
41
    else:
 
42
        dir += "/"
 
43
 
 
44
    return dir, fname
 
45
 
 
46
def code2str(c):
 
47
 
 
48
    ctuple = (c.co_argcount, c.co_nlocals, c.co_stacksize, c.co_flags,
 
49
              c.co_code, c.co_consts, c.co_names, c.co_varnames, c.co_filename,
 
50
              c.co_name, c.co_firstlineno, c.co_lnotab)
 
51
    
 
52
    return marshal.dumps(ctuple)
 
53
 
 
54
def str2code(s):
 
55
 
 
56
    return new.code(*marshal.loads(s))
 
57
 
 
58
class PSPInterface:
 
59
 
 
60
    def __init__(self, req, filename, form):
 
61
        self.req = req
 
62
        self.filename = filename
 
63
        self.error_page = None
 
64
        self.form = form
 
65
 
 
66
    def set_error_page(self, page):
 
67
        if page and page[0] == '/':
 
68
            # relative to document root
 
69
            self.error_page = PSP(self.req, self.req.document_root() + page)
 
70
        else:
 
71
            # relative to same dir we're in
 
72
            dir = path_split(self.filename)[0]
 
73
            self.error_page = PSP(self.req, dir + page)
 
74
 
 
75
    def apply_data(self, object):
 
76
 
 
77
        if not self.form:
 
78
            self.form = util.FieldStorage(self.req, keep_blank_values=1)
 
79
 
 
80
        return util.apply_fs_data(object, self.form, req=self.req)
 
81
 
 
82
    def redirect(self, location, permanent=0):
 
83
 
 
84
        util.redirect(self.req, location, permanent)
 
85
 
 
86
class PSP:
 
87
 
 
88
    code = None
 
89
    dbmcache = None
 
90
 
 
91
    def __init__(self, req, filename=None, string=None, vars={}):
 
92
 
 
93
        if (string and filename):
 
94
            raise ValueError, "Must specify either filename or string"
 
95
 
 
96
        self.req, self.vars = req, vars
 
97
 
 
98
        if not filename and not string:
 
99
            filename = req.filename
 
100
 
 
101
        self.filename, self.string = filename, string
 
102
 
 
103
        if filename:
 
104
 
 
105
            # if filename is not absolute, default to our guess
 
106
            # of current directory
 
107
            if not os.path.isabs(filename):
 
108
                base = os.path.split(req.filename)[0]
 
109
                self.filename = os.path.join(base, filename)
 
110
 
 
111
            self.load_from_file()
 
112
        else:
 
113
 
 
114
            cached = strcache.get(string)
 
115
            if cached:
 
116
                self.code = cached
 
117
            else:
 
118
                self.code = _psp.parsestring(string)
 
119
                strcache.store(string)
 
120
 
 
121
    def cache_get(self, filename, mtime):
 
122
 
 
123
        opts = self.req.get_options()
 
124
        if opts.has_key("PSPDbmCache"):
 
125
            self.dbmcache = opts["PSPDbmCache"]
 
126
 
 
127
        if self.dbmcache:
 
128
            cached = dbm_cache_get(self.req.server, self.dbmcache,
 
129
                                   filename, mtime)
 
130
            if cached:
 
131
                return cached
 
132
 
 
133
        cached = mem_fcache.get(filename, mtime)
 
134
        if cached:
 
135
            return cached
 
136
 
 
137
    def cache_store(self, filename, mtime, code):
 
138
 
 
139
        if self.dbmcache:
 
140
            dbm_cache_store(self.req.server, self.dbmcache,
 
141
                            filename, mtime, code)
 
142
        else:
 
143
            mem_fcache.store(filename, mtime, code)
 
144
 
 
145
    def cfile_get(self, filename, mtime):
 
146
 
 
147
        # check for a file ending with 'c' (precompiled file)
 
148
        name, ext = os.path.splitext(filename)
 
149
        cname = name + ext[:-1] + 'c'
 
150
 
 
151
        if os.path.isfile(cname):
 
152
            cmtime = os.path.getmtime(cname)
 
153
 
 
154
            if cmtime >= mtime:
 
155
                return str2code(open(cname).read())
 
156
 
 
157
    def load_from_file(self):
 
158
 
 
159
        filename = self.filename
 
160
 
 
161
        if not os.path.isfile(filename):
 
162
            raise ValueError, "%s is not a file" % filename
 
163
 
 
164
        mtime = os.path.getmtime(filename)
 
165
 
 
166
        # check cache
 
167
        code = self.cache_get(filename, mtime)
 
168
 
 
169
        # check for precompiled file
 
170
        if not code:
 
171
            code = self.cfile_get(filename, mtime)
 
172
 
 
173
        # finally parse and compile
 
174
        if not code:
 
175
            dir, fname = path_split(self.filename)
 
176
            source = _psp.parse(fname, dir)
 
177
            code = compile(source, filename, "exec")
 
178
 
 
179
        # store in cache
 
180
        self.cache_store(filename, mtime, code)
 
181
 
 
182
        self.code = code
 
183
 
 
184
    def run(self, vars={}):
 
185
 
 
186
        code, req = self.code, self.req
 
187
 
 
188
        # does this code use session?
 
189
        session = None
 
190
        if "session" in code.co_names:
 
191
            session = Session.Session(req)
 
192
 
 
193
        # does this code use form?
 
194
        form = None
 
195
        if "form" in code.co_names:
 
196
            form = util.FieldStorage(req, keep_blank_values=1)
 
197
 
 
198
        # create psp interface object
 
199
        psp = PSPInterface(req, self.filename, form)
 
200
 
 
201
        try:
 
202
            global_scope = globals().copy()
 
203
            global_scope.update({"req":req, "session":session,
 
204
                                 "form":form, "psp":psp})
 
205
            global_scope.update(self.vars) # passed in __init__()
 
206
            global_scope.update(vars)      # passed in run()
 
207
            try:
 
208
                exec code in global_scope
 
209
                req.flush()
 
210
                
 
211
                # the mere instantiation of a session changes it
 
212
                # (access time), so it *always* has to be saved
 
213
                if session is not None:
 
214
                    session.save()
 
215
            except:
 
216
                et, ev, etb = sys.exc_info()
 
217
                if psp.error_page:
 
218
                    # run error page
 
219
                    psp.error_page.run({"exception": (et, ev, etb)})
 
220
                else:
 
221
                    raise et, ev, etb
 
222
        finally:
 
223
            if session is not None:
 
224
                    session.unlock()
 
225
 
 
226
    def __str__(self):
 
227
        self.req.content_type = 'text/html'
 
228
        self.run()
 
229
        return ""
 
230
 
 
231
    def display_code(self):
 
232
        """
 
233
        Display a niceliy HTML-formatted side-by-side of
 
234
        what PSP generated next to orinial code.
 
235
        """
 
236
 
 
237
        req, filename = self.req, self.filename
 
238
 
 
239
        # Because of caching, source code is most often not
 
240
        # available in this object, so we read it here
 
241
        # (instead of trying to get it in __init__ somewhere)
 
242
 
 
243
        dir, fname = path_split(filename)
 
244
 
 
245
        source = open(filename).read().splitlines()
 
246
        pycode = _psp.parse(fname, dir).splitlines()
 
247
 
 
248
        source = [s.rstrip() for s in source]
 
249
        pycode = [s.rstrip() for s in pycode]
 
250
 
 
251
        req.write("<table>\n<tr>")
 
252
        for s in ("", "&nbsp;PSP-produced Python Code:",
 
253
                  "&nbsp;%s:" % filename):
 
254
            req.write("<td><tt>%s</tt></td>" % s)
 
255
        req.write("</tr>\n")
 
256
 
 
257
        n = 1
 
258
        for line in pycode:
 
259
            req.write("<tr>")
 
260
            left = escape(line).replace("\t", " "*4).replace(" ", "&nbsp;")
 
261
            if len(source) < n:
 
262
                right = ""
 
263
            else:
 
264
                right = escape(source[n-1]).replace("\t", " "*4).replace(" ", "&nbsp;")
 
265
            for s in ("%d.&nbsp;" % n,
 
266
                      "<font color=blue>%s</font>" % left,
 
267
                      "&nbsp;<font color=green>%s</font>" % right):
 
268
                req.write("<td><tt>%s</tt></td>" % s)
 
269
            req.write("</tr>\n")
 
270
 
 
271
            n += 1
 
272
        req.write("</table>\n")
 
273
 
 
274
 
 
275
def parse(filename, dir=None):
 
276
    if dir:
 
277
        return _psp.parse(filename, dir)
 
278
    else:
 
279
        return _psp.parse(filename)
 
280
 
 
281
def parsestring(str):
 
282
 
 
283
    return _psp.parsestring(str)
 
284
 
 
285
def handler(req):
 
286
 
 
287
    req.content_type = "text/html"
 
288
 
 
289
    config = req.get_config()
 
290
    debug = debug = int(config.get("PythonDebug", 0))
 
291
 
 
292
    if debug and req.filename[-1] == "_":
 
293
        p = PSP(req, req.filename[:-1])
 
294
        p.display_code()
 
295
    else:
 
296
        p = PSP(req)
 
297
        p.run()
 
298
 
 
299
    return apache.OK
 
300
 
 
301
def dbm_cache_type(dbmfile):
 
302
 
 
303
    global dbm_types
 
304
 
 
305
    if dbm_types.has_key(dbmfile):
 
306
        return dbm_types[dbmfile]
 
307
 
 
308
    module = whichdb.whichdb(dbmfile)
 
309
    if module:
 
310
        dbm_type = __import__(module)
 
311
        dbm_types[dbmfile] = dbm_type
 
312
        return dbm_type
 
313
    else:
 
314
        # this is a new file
 
315
        return anydbm
 
316
 
 
317
def dbm_cache_store(srv, dbmfile, filename, mtime, val):
 
318
    
 
319
    dbm_type = dbm_cache_type(dbmfile)
 
320
    _apache._global_lock(srv, "pspcache")
 
321
    try:
 
322
        dbm = dbm_type.open(dbmfile, 'c')
 
323
        dbm[filename] = "%d %s" % (mtime, code2str(val))
 
324
    finally:
 
325
        try: dbm.close()
 
326
        except: pass
 
327
        _apache._global_unlock(srv, "pspcache")
 
328
 
 
329
def dbm_cache_get(srv, dbmfile, filename, mtime):
 
330
 
 
331
    dbm_type = dbm_cache_type(dbmfile)
 
332
    _apache._global_lock(srv, "pspcache")
 
333
    try:
 
334
        dbm = dbm_type.open(dbmfile, 'c')
 
335
        try:
 
336
            entry = dbm[filename]
 
337
            t, val = entry.split(" ", 1)
 
338
            if long(t) == mtime:
 
339
                return str2code(val)
 
340
        except KeyError:
 
341
            return None
 
342
    finally:
 
343
        try: dbm.close()
 
344
        except: pass
 
345
        _apache._global_unlock(srv, "pspcache")
 
346
 
 
347
 
 
348
class HitsCache:
 
349
 
 
350
    def __init__(self, size=512):
 
351
        self.cache = {}
 
352
        self.size = size
 
353
 
 
354
    def store(self, key, val):
 
355
        self.cache[key] = (1, val)
 
356
        if len(self.cache) > self.size:
 
357
            self.clean()
 
358
 
 
359
    def get(self, key):
 
360
        if self.cache.has_key(key):
 
361
            hist, val = self.cache[key]
 
362
            self.cache[key] = (hits+1, code)
 
363
            return val
 
364
        else:
 
365
            return None
 
366
 
 
367
    def clean(self):
 
368
        
 
369
        byhits = [(n[1], n[0]) for n in self.cache.items()]
 
370
        byhits.sort()
 
371
 
 
372
        # delete enough least hit entries to make cache 75% full
 
373
        for item in byhits[:len(self.cache)-int(self.size*.75)]:
 
374
            val, key = item
 
375
            del self.cache[key]
 
376
 
 
377
mem_scache = HitsCache()
 
378
 
 
379
class FileCache(HitsCache):
 
380
 
 
381
    def store(self, filename, mtime, code):
 
382
        self.cache[filename] = (1, mtime, code)
 
383
        if len(self.cache) > self.size:
 
384
            self.clean()
 
385
 
 
386
    def get(self, filename, mtime):
 
387
        try:
 
388
            hits, c_mtime, code = self.cache[filename]
 
389
            if mtime != c_mtime:
 
390
                del self.cache[filename]
 
391
                return None
 
392
            else:
 
393
                self.cache[filename] = (hits+1, mtime, code)
 
394
                return code
 
395
        except KeyError:
 
396
            return None
 
397
 
 
398
mem_fcache = FileCache()
 
399