Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 280 lines (243 sloc) 8.368 kB
089a01a web.py 0.2
aaronsw authored
1 """
2 HTTP Utilities
3 (from web.py)
4 """
5
6 __all__ = [
7 "expires", "lastmodified",
8 "prefixurl", "modified",
9 "redirect", "found", "seeother", "tempredirect",
10 "write",
874feb3 bug fixes and web.url
anand authored
11 "changequery", "url",
089a01a web.py 0.2
aaronsw authored
12 "background", "backgrounder",
13 "Reloader", "reloader", "profiler",
14 ]
15
16 import sys, os, threading, urllib, urlparse
17 try: import datetime
18 except ImportError: pass
19 import net, utils, webapi as web
20
21 def prefixurl(base=''):
22 """
23 Sorry, this function is really difficult to explain.
24 Maybe some other time.
25 """
26 url = web.ctx.path.lstrip('/')
27 for i in xrange(url.count('/')):
28 base += '../'
29 if not base:
30 base = './'
31 return base
32
33 def expires(delta):
34 """
35 Outputs an `Expires` header for `delta` from now.
36 `delta` is a `timedelta` object or a number of seconds.
37 """
38 if isinstance(delta, (int, long)):
39 delta = datetime.timedelta(seconds=delta)
40 date_obj = datetime.datetime.utcnow() + delta
41 web.header('Expires', net.httpdate(date_obj))
42
43 def lastmodified(date_obj):
44 """Outputs a `Last-Modified` header for `datetime`."""
45 web.header('Last-Modified', net.httpdate(date_obj))
46
47 def modified(date=None, etag=None):
48 n = web.ctx.env.get('HTTP_IF_NONE_MATCH')
49 m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0])
50 validate = False
51 if etag:
52 raise NotImplementedError, "no etag support yet"
53 # should really be a warning
54 if date and m:
55 # we subtract a second because
56 # HTTP dates don't have sub-second precision
57 if date-datetime.timedelta(seconds=1) <= m:
58 validate = True
59
60 if validate: web.ctx.status = '304 Not Modified'
61 return not validate
62
63 """
64 By default, these all return simple error messages that send very short messages
65 (like "bad request") to the user. They can and should be overridden
66 to return nicer ones.
67 """
68 def redirect(url, status='301 Moved Permanently'):
69 """
70 Returns a `status` redirect to the new URL.
71 `url` is joined with the base URL so that things like
72 `redirect("about") will work properly.
73 """
53fb28d fixed bugs in changequery and redirect
anand authored
74 newloc = urlparse.urljoin(web.ctx.path, url)
75
76 # if newloc is relative then make it absolute
77 if newloc.startswith('/'):
78 newloc = web.ctx.home + newloc
874feb3 bug fixes and web.url
anand authored
79
089a01a web.py 0.2
aaronsw authored
80 web.ctx.status = status
81 web.ctx.output = ''
82 web.header('Content-Type', 'text/html')
83 web.header('Location', newloc)
84 # seems to add a three-second delay for some reason:
85 # web.output('<a href="'+ newloc + '">moved permanently</a>')
86
87 def found(url):
88 """A `302 Found` redirect."""
89 return redirect(url, '302 Found')
90
91 def seeother(url):
92 """A `303 See Other` redirect."""
93 return redirect(url, '303 See Other')
94
95 def tempredirect(url):
96 """A `307 Temporary Redirect` redirect."""
97 return redirect(url, '307 Temporary Redirect')
98
99 def write(cgi_response):
100 """
101 Converts a standard CGI-style string response into `header` and
102 `output` calls.
103 """
104 cgi_response = str(cgi_response)
105 cgi_response.replace('\r\n', '\n')
106 head, body = cgi_response.split('\n\n', 1)
107 lines = head.split('\n')
108
109 for line in lines:
110 if line.isspace():
111 continue
112 hdr, value = line.split(":", 1)
113 value = value.strip()
114 if hdr.lower() == "status":
115 web.ctx.status = value
116 else:
117 web.header(hdr, value)
118
119 web.output(body)
120
db64ef3 fix unicode bugs with urlencode
aaronsw authored
121 def urlencode(query):
122 """
123 Same as urllib.urlencode, but supports unicode strings.
124
125 >>> urlencode({'text':'foo bar'})
126 'text=foo+bar'
127 """
9d2a3e9 fixed unicode bugs
anand authored
128 query = dict([(k, utils.utf8(v)) for k, v in query.items()])
db64ef3 fix unicode bugs with urlencode
aaronsw authored
129 return urllib.urlencode(query)
130
3962c18 changequery takes optional query argument.
anand authored
131 def changequery(query=None, **kw):
089a01a web.py 0.2
aaronsw authored
132 """
133 Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return
134 `/foo?a=3&b=2` -- the same URL but with the arguments you requested
135 changed.
136 """
3962c18 changequery takes optional query argument.
anand authored
137 if query is None:
138 query = web.input(_method='get')
089a01a web.py 0.2
aaronsw authored
139 for k, v in kw.iteritems():
140 if v is None:
141 query.pop(k, None)
142 else:
143 query[k] = v
53fb28d fixed bugs in changequery and redirect
anand authored
144 out = web.ctx.path
089a01a web.py 0.2
aaronsw authored
145 if query:
db64ef3 fix unicode bugs with urlencode
aaronsw authored
146 out += '?' + urlencode(query)
089a01a web.py 0.2
aaronsw authored
147 return out
148
3962c18 changequery takes optional query argument.
anand authored
149 def url(path=None, **kw):
874feb3 bug fixes and web.url
anand authored
150 """
151 Makes url by concatinating web.ctx.homepath and path and the
152 query string created using the arguments.
153 """
3962c18 changequery takes optional query argument.
anand authored
154 if path is None:
155 path = web.ctx.path
874feb3 bug fixes and web.url
anand authored
156 if path.startswith("/"):
157 out = web.ctx.homepath + path
158 else:
159 out = path
160
161 if kw:
db64ef3 fix unicode bugs with urlencode
aaronsw authored
162 out += '?' + urlencode(kw)
874feb3 bug fixes and web.url
anand authored
163
164 return out
165
089a01a web.py 0.2
aaronsw authored
166 def background(func):
07ae511 @anandology fix for web.background gotcha (Bug#133079)
anandology authored
167 """A function decorator to run a long-running function as a background thread.
168
169 GOTCHA in postgres: Until the foreground task ends, any db access by background
170 task will necessarily get old data from before the foreground task started
171 because psycopg2 begins a transaction in the foreground task until it quits.
172 """
089a01a web.py 0.2
aaronsw authored
173 def internal(*a, **kw):
174 web.data() # cache it
175
176 tmpctx = web._context[threading.currentThread()]
177 web._context[threading.currentThread()] = utils.storage(web.ctx.copy())
178
179 def newfunc():
180 web._context[threading.currentThread()] = tmpctx
07ae511 @anandology fix for web.background gotcha (Bug#133079)
anandology authored
181 # Create new db cursor if there is one else background thread
182 # overwrites foreground cursor causing rubbish data into dbase
183 if web.config.get('db_parameters'):
184 import db
185 db.connect(**web.config.db_parameters)
089a01a web.py 0.2
aaronsw authored
186 func(*a, **kw)
b5b1548 a little cleaner version
aaronsw authored
187 myctx = web._context[threading.currentThread()]
b6163fb another bug fix
aaronsw authored
188 for k in myctx.keys():
b5b1548 a little cleaner version
aaronsw authored
189 if k not in ['status', 'headers', 'output']:
d093bf7 another bug fix
aaronsw authored
190 try: del myctx[k]
191 except KeyError: pass
b5b1548 a little cleaner version
aaronsw authored
192
089a01a web.py 0.2
aaronsw authored
193 t = threading.Thread(target=newfunc)
194 background.threaddb[id(t)] = t
195 t.start()
196 web.ctx.headers = []
197 return seeother(changequery(_t=id(t)))
198 return internal
199 background.threaddb = {}
200
201 def backgrounder(func):
202 def internal(*a, **kw):
203 i = web.input(_method='get')
204 if '_t' in i:
205 try:
206 t = background.threaddb[int(i._t)]
207 except KeyError:
208 return web.notfound()
209 web._context[threading.currentThread()] = web._context[t]
210 return
211 else:
212 return func(*a, **kw)
213 return internal
214
215 class Reloader:
216 """
217 Before every request, checks to see if any loaded modules have changed on
218 disk and, if so, reloads them.
219 """
220 def __init__(self, func):
221 self.func = func
222 self.mtimes = {}
223 # cheetah:
224 # b = _compiletemplate.bases
225 # _compiletemplate = globals()['__compiletemplate']
226 # _compiletemplate.bases = b
227
228 web.loadhooks['reloader'] = self.check
229 # todo:
230 # - replace relrcheck with a loadhook
231 #if reloader in middleware:
232 # relr = reloader(None)
233 # relrcheck = relr.check
234 # middleware.remove(reloader)
235 #else:
236 # relr = None
237 # relrcheck = lambda: None
238 # if relr:
239 # relr.func = wsgifunc
240 # return wsgifunc
241 #
242
243
244 def check(self):
245 for mod in sys.modules.values():
246 try:
247 mtime = os.stat(mod.__file__).st_mtime
248 except (AttributeError, OSError, IOError):
249 continue
250 if mod.__file__.endswith('.pyc') and \
251 os.path.exists(mod.__file__[:-1]):
252 mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime)
253 if mod not in self.mtimes:
254 self.mtimes[mod] = mtime
255 elif self.mtimes[mod] < mtime:
256 try:
257 reload(mod)
6794ccb fixed reloading issue
anand authored
258 self.mtimes[mod] = mtime
089a01a web.py 0.2
aaronsw authored
259 except ImportError:
260 pass
261 return True
262
263 def __call__(self, e, o):
264 self.check()
265 return self.func(e, o)
266
267 reloader = Reloader
268
269 def profiler(app):
270 """Outputs basic profiling information at the bottom of each response."""
cb2115c fix profiling bug (tx spez)
aaronsw authored
271 from utils import profile
089a01a web.py 0.2
aaronsw authored
272 def profile_internal(e, o):
273 out, result = profile(app)(e, o)
274 return out + ['<pre>' + net.websafe(result) + '</pre>']
275 return profile_internal
276
277 if __name__ == "__main__":
278 import doctest
279 doctest.testmod()
Something went wrong with that request. Please try again.