om@3
|
1 |
"""
|
om@3
|
2 |
HTTP Utilities
|
om@3
|
3 |
(from web.py)
|
om@3
|
4 |
"""
|
om@3
|
5 |
|
om@3
|
6 |
__all__ = [
|
om@3
|
7 |
"expires", "lastmodified",
|
om@3
|
8 |
"prefixurl", "modified",
|
om@3
|
9 |
"changequery", "url",
|
om@3
|
10 |
"profiler",
|
om@3
|
11 |
]
|
om@3
|
12 |
|
om@3
|
13 |
import sys, os, threading, urllib, urlparse
|
om@3
|
14 |
try: import datetime
|
om@3
|
15 |
except ImportError: pass
|
om@3
|
16 |
import net, utils, webapi as web
|
om@3
|
17 |
|
om@3
|
18 |
def prefixurl(base=''):
|
om@3
|
19 |
"""
|
om@3
|
20 |
Sorry, this function is really difficult to explain.
|
om@3
|
21 |
Maybe some other time.
|
om@3
|
22 |
"""
|
om@3
|
23 |
url = web.ctx.path.lstrip('/')
|
om@3
|
24 |
for i in xrange(url.count('/')):
|
om@3
|
25 |
base += '../'
|
om@3
|
26 |
if not base:
|
om@3
|
27 |
base = './'
|
om@3
|
28 |
return base
|
om@3
|
29 |
|
om@3
|
30 |
def expires(delta):
|
om@3
|
31 |
"""
|
om@3
|
32 |
Outputs an `Expires` header for `delta` from now.
|
om@3
|
33 |
`delta` is a `timedelta` object or a number of seconds.
|
om@3
|
34 |
"""
|
om@3
|
35 |
if isinstance(delta, (int, long)):
|
om@3
|
36 |
delta = datetime.timedelta(seconds=delta)
|
om@3
|
37 |
date_obj = datetime.datetime.utcnow() + delta
|
om@3
|
38 |
web.header('Expires', net.httpdate(date_obj))
|
om@3
|
39 |
|
om@3
|
40 |
def lastmodified(date_obj):
|
om@3
|
41 |
"""Outputs a `Last-Modified` header for `datetime`."""
|
om@3
|
42 |
web.header('Last-Modified', net.httpdate(date_obj))
|
om@3
|
43 |
|
om@3
|
44 |
def modified(date=None, etag=None):
|
om@3
|
45 |
"""
|
om@3
|
46 |
Checks to see if the page has been modified since the version in the
|
om@3
|
47 |
requester's cache.
|
om@3
|
48 |
|
om@3
|
49 |
When you publish pages, you can include `Last-Modified` and `ETag`
|
om@3
|
50 |
with the date the page was last modified and an opaque token for
|
om@3
|
51 |
the particular version, respectively. When readers reload the page,
|
om@3
|
52 |
the browser sends along the modification date and etag value for
|
om@3
|
53 |
the version it has in its cache. If the page hasn't changed,
|
om@3
|
54 |
the server can just return `304 Not Modified` and not have to
|
om@3
|
55 |
send the whole page again.
|
om@3
|
56 |
|
om@3
|
57 |
This function takes the last-modified date `date` and the ETag `etag`
|
om@3
|
58 |
and checks the headers to see if they match. If they do, it returns
|
om@3
|
59 |
`True`, or otherwise it raises NotModified error. It also sets
|
om@3
|
60 |
`Last-Modified` and `ETag` output headers.
|
om@3
|
61 |
"""
|
om@3
|
62 |
try:
|
om@3
|
63 |
from __builtin__ import set
|
om@3
|
64 |
except ImportError:
|
om@3
|
65 |
# for python 2.3
|
om@3
|
66 |
from sets import Set as set
|
om@3
|
67 |
|
om@3
|
68 |
n = set([x.strip('" ') for x in web.ctx.env.get('HTTP_IF_NONE_MATCH', '').split(',')])
|
om@3
|
69 |
m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0])
|
om@3
|
70 |
validate = False
|
om@3
|
71 |
if etag:
|
om@3
|
72 |
if '*' in n or etag in n:
|
om@3
|
73 |
validate = True
|
om@3
|
74 |
if date and m:
|
om@3
|
75 |
# we subtract a second because
|
om@3
|
76 |
# HTTP dates don't have sub-second precision
|
om@3
|
77 |
if date-datetime.timedelta(seconds=1) <= m:
|
om@3
|
78 |
validate = True
|
om@3
|
79 |
|
om@3
|
80 |
if date: lastmodified(date)
|
om@3
|
81 |
if etag: web.header('ETag', '"' + etag + '"')
|
om@3
|
82 |
if validate:
|
om@3
|
83 |
raise web.notmodified()
|
om@3
|
84 |
else:
|
om@3
|
85 |
return True
|
om@3
|
86 |
|
om@3
|
87 |
def urlencode(query, doseq=0):
|
om@3
|
88 |
"""
|
om@3
|
89 |
Same as urllib.urlencode, but supports unicode strings.
|
om@3
|
90 |
|
om@3
|
91 |
>>> urlencode({'text':'foo bar'})
|
om@3
|
92 |
'text=foo+bar'
|
om@3
|
93 |
>>> urlencode({'x': [1, 2]}, doseq=True)
|
om@3
|
94 |
'x=1&x=2'
|
om@3
|
95 |
"""
|
om@3
|
96 |
def convert(value, doseq=False):
|
om@3
|
97 |
if doseq and isinstance(value, list):
|
om@3
|
98 |
return [convert(v) for v in value]
|
om@3
|
99 |
else:
|
om@3
|
100 |
return utils.safestr(value)
|
om@3
|
101 |
|
om@3
|
102 |
query = dict([(k, convert(v, doseq)) for k, v in query.items()])
|
om@3
|
103 |
return urllib.urlencode(query, doseq=doseq)
|
om@3
|
104 |
|
om@3
|
105 |
def changequery(query=None, **kw):
|
om@3
|
106 |
"""
|
om@3
|
107 |
Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return
|
om@3
|
108 |
`/foo?a=3&b=2` -- the same URL but with the arguments you requested
|
om@3
|
109 |
changed.
|
om@3
|
110 |
"""
|
om@3
|
111 |
if query is None:
|
om@3
|
112 |
query = web.rawinput(method='get')
|
om@3
|
113 |
for k, v in kw.iteritems():
|
om@3
|
114 |
if v is None:
|
om@3
|
115 |
query.pop(k, None)
|
om@3
|
116 |
else:
|
om@3
|
117 |
query[k] = v
|
om@3
|
118 |
out = web.ctx.path
|
om@3
|
119 |
if query:
|
om@3
|
120 |
out += '?' + urlencode(query, doseq=True)
|
om@3
|
121 |
return out
|
om@3
|
122 |
|
om@3
|
123 |
def url(path=None, doseq=False, **kw):
|
om@3
|
124 |
"""
|
om@3
|
125 |
Makes url by concatenating web.ctx.homepath and path and the
|
om@3
|
126 |
query string created using the arguments.
|
om@3
|
127 |
"""
|
om@3
|
128 |
if path is None:
|
om@3
|
129 |
path = web.ctx.path
|
om@3
|
130 |
if path.startswith("/"):
|
om@3
|
131 |
out = web.ctx.homepath + path
|
om@3
|
132 |
else:
|
om@3
|
133 |
out = path
|
om@3
|
134 |
|
om@3
|
135 |
if kw:
|
om@3
|
136 |
out += '?' + urlencode(kw, doseq=doseq)
|
om@3
|
137 |
|
om@3
|
138 |
return out
|
om@3
|
139 |
|
om@3
|
140 |
def profiler(app):
|
om@3
|
141 |
"""Outputs basic profiling information at the bottom of each response."""
|
om@3
|
142 |
from utils import profile
|
om@3
|
143 |
def profile_internal(e, o):
|
om@3
|
144 |
out, result = profile(app)(e, o)
|
om@3
|
145 |
return list(out) + ['<pre>' + net.websafe(result) + '</pre>']
|
om@3
|
146 |
return profile_internal
|
om@3
|
147 |
|
om@3
|
148 |
if __name__ == "__main__":
|
om@3
|
149 |
import doctest
|
om@3
|
150 |
doctest.testmod()
|