Skip to content

Commit

Permalink
Merge branch 'master' into simple_httpclient
Browse files Browse the repository at this point in the history
  • Loading branch information
bdarnell committed Oct 8, 2010
2 parents 48d698e + 160c961 commit a80333a
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 21 deletions.
3 changes: 2 additions & 1 deletion demos/chat/templates/message.html
@@ -1 +1,2 @@
<div class="message" id="m{{ message["id"] }}"><b>{{ escape(message["from"]) }}: </b>{{ escape(message["body"]) }}</div> {% import tornado.escape %}
<div class="message" id="m{{ message["id"] }}"><b>{{ escape(message["from"]) }}: </b>{{ tornado.escape.linkify(message["body"]) }}</div>
86 changes: 85 additions & 1 deletion tornado/escape.py
Expand Up @@ -49,7 +49,7 @@ def _json_decode(s):


def xhtml_escape(value): def xhtml_escape(value):
"""Escapes a string so it is valid within XML or XHTML.""" """Escapes a string so it is valid within XML or XHTML."""
return utf8(xml.sax.saxutils.escape(value, {'"': "&quot;"})) return xml.sax.saxutils.escape(value, {'"': "&quot;"})




def xhtml_unescape(value): def xhtml_unescape(value):
Expand Down Expand Up @@ -95,6 +95,90 @@ def utf8(value):
return value return value




# Regex from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
# Modified to capture protocol and to avoid HTML character entities other than &amp;
_URL_RE = re.compile(ur"""(?i)\b((?:([a-z][\w-]+):(?:(/{1,3})|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>&]+|&amp;|\(([^\s()<>&]+|(\([^\s()<>&]+\)))*\))+(?:\(([^\s()<>&]+|(\([^\s()<>&]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\xab\xbb\u201c\u201d\u2018\u2019&]))""")


def linkify(text, shorten=False, extra_params="",
require_protocol=False, permitted_protocols=["http", "https"]):
"""Converts plain text into HTML with links.
For example: linkify("Hello http://tornadoweb.org!") would return
Hello <a href="http://tornadoweb.org">http://tornadoweb.org</a>!
Parameters:
shorten: Long urls will be shortened for display.
extra_params: Extra text to include in the link tag,
e.g. linkify(text, extra_params='rel="nofollow" class="external"')
require_protocol: Only linkify urls which include a protocol. If this is
False, urls such as www.facebook.com will also be linkified.
permitted_protocols: List (or set) of protocols which should be linkified,
e.g. linkify(text, permitted_protocols=["http", "ftp", "mailto"]).
It is very unsafe to include protocols such as "javascript".
"""
if extra_params:
extra_params = " " + extra_params.strip()

def make_link(m):
url = m.group(1)
proto = m.group(2)
if require_protocol and not proto:
return url # not protocol, no linkify

if proto and proto not in permitted_protocols:
return url # bad protocol, no linkify

href = m.group(1)
if not proto:
href = "http://" + href # no proto specified, use http

params = extra_params

# clip long urls. max_len is just an approximation
max_len = 30
if shorten and len(url) > max_len:
before_clip = url
if proto:
proto_len = len(proto) + 1 + len(m.group(3) or "") # +1 for :
else:
proto_len = 0

parts = url[proto_len:].split("/")
if len(parts) > 1:
# Grab the whole host part plus the first bit of the path
# The path is usually not that interesting once shortened
# (no more slug, etc), so it really just provides a little
# extra indication of shortening.
url = url[:proto_len] + parts[0] + "/" + \
parts[1][:8].split('?')[0].split('.')[0]

if len(url) > max_len * 1.5: # still too long
url = url[:max_len]

if url != before_clip:
amp = url.rfind('&')
# avoid splitting html char entities
if amp > max_len - 5:
url = url[:amp]
url += "..."

if len(url) >= len(before_clip):
url = before_clip
else:
# full url is visible on mouse-over (for those who don't
# have a status bar, such as Safari by default)
params += ' title="%s"' % href

return u'<a href="%s"%s>%s</a>' % (href, params, url)

# First HTML-escape so that our strings are all safe.
# The regex is modified to avoid character entites other than &amp; so
# that we won't pick up &quot;, etc.
text = _unicode(xhtml_escape(text))
return _URL_RE.sub(make_link, text)


def _unicode(value): def _unicode(value):
if isinstance(value, str): if isinstance(value, str):
return value.decode("utf-8") return value.decode("utf-8")
Expand Down
16 changes: 10 additions & 6 deletions tornado/iostream.py
Expand Up @@ -21,6 +21,7 @@
import socket import socket


from tornado import ioloop from tornado import ioloop
from tornado import stack_context


try: try:
import ssl # Python 2.6+ import ssl # Python 2.6+
Expand Down Expand Up @@ -79,14 +80,15 @@ def __init__(self, socket, io_loop=None, max_buffer_size=104857600,
self._write_callback = None self._write_callback = None
self._close_callback = None self._close_callback = None
self._state = self.io_loop.ERROR self._state = self.io_loop.ERROR
self.io_loop.add_handler( with stack_context.NullContext():
self.socket.fileno(), self._handle_events, self._state) self.io_loop.add_handler(
self.socket.fileno(), self._handle_events, self._state)


def read_until(self, delimiter, callback): def read_until(self, delimiter, callback):
"""Call callback when we read the given delimiter.""" """Call callback when we read the given delimiter."""
assert not self._read_callback, "Already reading" assert not self._read_callback, "Already reading"
self._read_delimiter = delimiter self._read_delimiter = delimiter
self._read_callback = callback self._read_callback = stack_context.wrap(callback)
while True: while True:
# See if we've already got the data from a previous read # See if we've already got the data from a previous read
if self._read_from_buffer(): if self._read_from_buffer():
Expand All @@ -103,7 +105,7 @@ def read_bytes(self, num_bytes, callback):
callback("") callback("")
return return
self._read_bytes = num_bytes self._read_bytes = num_bytes
self._read_callback = callback self._read_callback = stack_context.wrap(callback)
while True: while True:
if self._read_from_buffer(): if self._read_from_buffer():
return return
Expand All @@ -123,11 +125,11 @@ def write(self, data, callback=None):
self._check_closed() self._check_closed()
self._write_buffer += data self._write_buffer += data
self._add_io_state(self.io_loop.WRITE) self._add_io_state(self.io_loop.WRITE)
self._write_callback = callback self._write_callback = stack_context.wrap(callback)


def set_close_callback(self, callback): def set_close_callback(self, callback):
"""Call the given callback when the stream is closed.""" """Call the given callback when the stream is closed."""
self._close_callback = callback self._close_callback = stack_context.wrap(callback)


def close(self): def close(self):
"""Close this stream.""" """Close this stream."""
Expand Down Expand Up @@ -177,6 +179,8 @@ def _run_callback(self, callback, *args, **kwargs):
try: try:
callback(*args, **kwargs) callback(*args, **kwargs)
except: except:
logging.error("Uncaught exception, closing connection.",
exc_info=True)
# Close the socket on an uncaught exception from a user callback # Close the socket on an uncaught exception from a user callback
# (It would eventually get closed when the socket object is # (It would eventually get closed when the socket object is
# gc'd, but we don't want to rely on gc happening before we # gc'd, but we don't want to rely on gc happening before we
Expand Down
2 changes: 2 additions & 0 deletions tornado/stack_context.py
Expand Up @@ -101,6 +101,8 @@ def wrap(fn):
different execution context (either in a different thread or different execution context (either in a different thread or
asynchronously in the same thread). asynchronously in the same thread).
''' '''
if fn is None:
return None
# functools.wraps doesn't appear to work on functools.partial objects # functools.wraps doesn't appear to work on functools.partial objects
#@functools.wraps(fn) #@functools.wraps(fn)
def wrapped(callback, contexts, *args, **kwargs): def wrapped(callback, contexts, *args, **kwargs):
Expand Down
14 changes: 1 addition & 13 deletions website/templates/index.html
Expand Up @@ -45,18 +45,6 @@ <h2>Hello, world</h2>
<p>See the <a href="/documentation">Tornado documentation</a> for a detailed walkthrough of the framework.</p> <p>See the <a href="/documentation">Tornado documentation</a> for a detailed walkthrough of the framework.</p>


<h2>Discussion and support</h2> <h2>Discussion and support</h2>
<p>You can discuss Tornado and report bugs on <a href="http://groups.google.com/group/python-tornado">the Tornado developer mailing list</a>. <p>You can discuss Tornado and report bugs on <a href="http://groups.google.com/group/python-tornado">the Tornado developer mailing list</a>. Links to additional resources can be found on the <a href="http://github.com/facebook/tornado/wiki/Links">Tornado wiki</a>.

<h2>Links and resources</h2>
<ul>
<li><a href="http://tornado.poweredsites.org">tornado.poweredsites.org</a> - listing of sites using Tornado</li>
<li><a href="http://github.com/szabotshark/t3">T3</a> - Tornado ported to Python 3</li>
<li><a href="http://github.com/fiorix/cyclone">Cyclone</a> - Tornado ported to the Twisted event loop</li>
<li><a href="http://github.com/bdarnell/tornado_tracing">Tornado Tracing</a> - Performance tracing library for Tornado</li>
</ul>

<h2>Updates</h2>
<p>Follow us on <a href="http://www.facebook.com/pages/Tornado-Web-Server/144144048921">Facebook</a>, <a href="http://twitter.com/tornadoweb">Twitter</a>, or <a href="http://friendfeed.com/tornado-web">FriendFeed</a> to get updates and announcements:</p>
<div style="margin-top:1em"><a href="http://www.facebook.com/pages/Tornado-Web-Server/144144048921" style="margin-right:10px"><img src="/static/facebook.png" style="width:64px;height:64px" alt="Facebook"/></a><a href="http://twitter.com/tornadoweb" style="margin-right:10px"><img src="/static/twitter.png" style="width:64px;height:64px" alt="Twitter"/></a><a href="http://friendfeed.com/tornado-web" style="margin-right:10px"><img src="/static/friendfeed.png" style="width:64px;height:64px" alt="Facebook"/></a></div>


{% end %} {% end %}

0 comments on commit a80333a

Please sign in to comment.