webmention: refactor storing Responses so we can do it earlier

pull/27/head
Ryan Barrett 2017-10-26 07:30:52 -07:00
rodzic 98cc01d6ae
commit 92798bc434
4 zmienionych plików z 46 dodań i 32 usunięć

Wyświetl plik

@ -29,8 +29,9 @@ class ResponsesHandler(TemplateHandler):
responses = Response.query().order(-Response.updated).fetch(20)
for r in responses:
r.source, r.target = [util.pretty_link(url)
for url in r.key.id().split(' ')]
r.source_link = util.pretty_link(r.source())
r.target_link = util.pretty_link(r.target())
# TODO: support inbound too
if r.direction == 'out' and r.updated >= VERSION_1_DEPLOYED:
r.log_url_path = '/log?' + urllib.urlencode({
'key': r.key.id(),

Wyświetl plik

@ -92,6 +92,12 @@ class Response(StringIdModel):
def get_or_create(cls, source=None, target=None, **kwargs):
return cls.get_or_insert(cls._id(source, target), **kwargs)
def source(self):
return self.key.id().split()[0]
def target(self):
return self.key.id().split()[1]
def proxy_url(self):
"""Returns the Bridgy Fed proxy URL to render this response as HTML."""
if self.source_mf2 or self.source_as2 or self.source_atom:

Wyświetl plik

@ -18,12 +18,11 @@
<tr><th>Source</th> <th>Target</th> <th>Protocol</th> <th>Status</th> <th>Time (click for log)</th></tr>
{% for r in responses %}
<tr>
<td>{{ r.source|safe }}</td>
<td>{{ r.target|safe }}</td>
<td>{{ r.source_link|safe }}</td>
<td>{{ r.target_link|safe }}</td>
<td>{{ r.protocol }}</td>
<td>{{ r.status }}</td>
<td>
{# TODO: support inbound too #}
{% if r.log_url_path %}<a href="{{ r.log_url_path }}">{% endif %}
{{ r.updated.replace(microsecond=0) }}
{% if r.log_url_path %}</a>{% endif %}

Wyświetl plik

@ -34,15 +34,13 @@ class WebmentionHandler(webapp2.RequestHandler):
"""Handles inbound webmention, converts to ActivityPub or Salmon.
Instance attributes:
response: Response
resp: Response
"""
def post(self):
logging.info('Params: %s', self.request.params.items())
source = util.get_required_param(self, 'source')
target = util.get_required_param(self, 'target')
logging.info('source target: %s %s', source, target)
logging.info('(Params: %s )', self.request.params.items())
source = util.get_required_param(self, 'source')
try:
msg = 'Bridgy Fed: new webmention from %s' % source
mail.send_mail(
@ -52,6 +50,22 @@ class WebmentionHandler(webapp2.RequestHandler):
except BaseException:
logging.warning('Error sending email', exc_info=True)
self.resp = None
try:
self.try_activitypub()
if self.resp:
self.resp.status = 'complete'
except:
if self.resp:
self.resp.status = 'error'
raise
finally:
if self.resp:
self.resp.put()
def try_activitypub(self):
source = util.get_required_param(self, 'source')
# fetch source page, convert to ActivityStreams
source_resp = common.requests_get(source)
source_url = source_resp.url or source
@ -71,18 +85,22 @@ class WebmentionHandler(webapp2.RequestHandler):
common.error(self, 'No u-in-reply-to, u-like-of, or u-repost-of '
'found in %s' % source_url)
logging.info('source target: %s %s', source, target)
try:
target_resp = common.get_as2(target)
except (requests.HTTPError, exc.HTTPBadGateway) as e:
if (e.response.status_code // 100 == 2 and
common.content_type(e.response).startswith('text/html')):
return self.send_salmon(source_obj, source_mf2, target_resp=e.response)
self.resp = Response.get_or_create(
source=source_url, target=e.response.url or target,
direction='out', source_mf2=json.dumps(source_mf2))
return self.send_salmon(source_obj, target_resp=e.response)
raise
target_url = target_resp.url or target
stored_response = Response.get_or_create(
self.resp = Response.get_or_create(
source=source_url, target=target_url, direction='out',
source_mf2=json.dumps(source_mf2))
protocol='activitypub', source_mf2=json.dumps(source_mf2))
# find actor's inbox
target_obj = target_resp.json()
@ -106,7 +124,7 @@ class WebmentionHandler(webapp2.RequestHandler):
# TODO: probably need a way to save errors like this so that we can
# return them if ostatus fails too.
# common.error(self, 'Target actor has no inbox')
return self.send_salmon(source_obj, source_mf2, target_resp=target_resp)
return self.send_salmon(source_obj, target_resp=target_resp)
# convert to AS2
source_domain = urlparse.urlparse(source_url).netloc
@ -114,7 +132,7 @@ class WebmentionHandler(webapp2.RequestHandler):
source_activity = common.postprocess_as2(
as2.from_as1(source_obj), target=target_obj, key=key)
if stored_response.status == 'complete':
if self.resp.status == 'complete':
source_activity['type'] = 'Update'
# prepare HTTP Signature (required by Mastodon)
@ -141,23 +159,17 @@ class WebmentionHandler(webapp2.RequestHandler):
'signature verification probably failed. :(\n')
self.response.write(resp.text)
stored_response.status = 'complete'
stored_response.protocol = 'activitypub'
stored_response.put()
def send_salmon(self, source_obj, target_resp=None):
self.resp.protocol = 'ostatus'
def send_salmon(self, source_obj, source_mf2, target_url=None, target_resp=None):
# fetch target HTML page, extract Atom rel-alternate link
if target_url:
assert not target_resp
target_resp = common.requests_get(target_url)
else:
assert target_resp
target_url = target_resp.url
if not target_resp:
target_resp = common.requests_get(self.resp.target())
parsed = BeautifulSoup(target_resp.content, from_encoding=target_resp.encoding)
atom_url = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
if not atom_url or not atom_url.get('href'):
common.error(self, 'Target post %s has no Atom link' % target_url,
common.error(self, 'Target post %s has no Atom link' % self.resp.target(),
status=400)
# fetch Atom target post, extract and inject id into source object
@ -190,7 +202,7 @@ class WebmentionHandler(webapp2.RequestHandler):
if not endpoint:
# try webfinger
parsed = urlparse.urlparse(target_url)
parsed = urlparse.urlparse(self.resp.target())
# TODO: test missing email
email = entry.author_detail.get('email') or '@'.join(
(entry.author_detail.name, parsed.netloc))
@ -208,7 +220,7 @@ class WebmentionHandler(webapp2.RequestHandler):
logging.info('Discovered Salmon endpoint %s', endpoint)
# construct reply Atom object
source_url = self.request.get('source')
source_url = self.resp.source()
activity = (source_obj if source_obj.get('verb') in source.VERBS_WITH_OBJECT
else {'object': source_obj})
entry = atom.activity_to_atom(activity, xml_base=source_url)
@ -226,10 +238,6 @@ class WebmentionHandler(webapp2.RequestHandler):
endpoint, data=common.XML_UTF8 + magic_envelope,
headers={'Content-Type': common.CONTENT_TYPE_MAGIC_ENVELOPE})
Response(source=source_url, target=target_url, direction='out',
protocol = 'ostatus', status = 'complete',
source_mf2=json.dumps(source_mf2)).put()
app = webapp2.WSGIApplication([
('/webmention', WebmentionHandler),