2 # -*- coding: utf-8 -*-
4 # PubSubHubbub subscriber for mygpo-feedservice
8 import urllib
, urllib2
, urlparse
, logging
9 from datetime
import timedelta
11 from google
.appengine
.ext
import webapp
, db
16 # increased expiry time for subscribed feeds
17 INCREASED_EXPIRY
= timedelta(days
=7)
20 class SubscriptionError(Exception):
24 class SubscribedFeed(db
.Model
):
25 url
= db
.StringProperty()
26 verify_token
= db
.StringProperty()
27 mode
= db
.StringProperty()
28 verified
= db
.BooleanProperty()
32 class Subscriber(webapp
.RequestHandler
):
33 """ request handler for pubsubhubbub subscriptions """
37 """ Callback used by the Hub to verify the subscription request """
39 # received arguments: hub.mode, hub.topic, hub.challenge,
40 # hub.lease_seconds, hub.verify_token
41 mode
= self
.request
.get('hub.mode')
42 feed_url
= self
.request
.get('hub.topic')
43 challenge
= self
.request
.get('hub.challenge')
44 lease_seconds
= self
.request
.get('hub.lease_seconds')
45 verify_token
= self
.request
.get('hub.verify_token')
47 logging
.debug(('received subscription-parameters: mode: %(mode)s, ' +
48 'topic: %(topic)s, challenge: %(challenge)s, lease_seconds: ' +
49 '%(lease_seconds)s, verify_token: %(verify_token)s') % \
50 dict(mode
=mode
, topic
=feed_url
, challenge
=challenge
,
51 lease_seconds
=lease_seconds
, verify_token
=verify_token
))
53 subscription
= Subscriber
.get_subscription(feed_url
)
55 if subscription
is None:
56 logging
.warn('subscription does not exist')
57 self
.response
.set_status(404)
60 if subscription
.mode
!= mode
:
61 logging
.warn('invalid mode, %s expected' %
63 self
.response
.set_status(404)
66 if subscription
.verify_token
!= verify_token
:
67 logging
.warn('invalid verify_token, %s expected' %
68 subscribe
.verify_token
)
69 self
.response
.set_status(404)
72 subscription
.verified
= True
75 logging
.info('subscription confirmed')
76 self
.response
.set_status(200)
77 self
.response
.out
.write(challenge
)
82 """ Callback to notify about a feed update """
84 feed_url
= self
.request
.get('url')
86 logging
.info('received notification for %s' % feed_url
)
88 subscription
= Subscriber
.get_subscription(feed_url
)
90 if subscription
is None:
91 logging
.warn('no subscription for this URL')
92 self
.response
.set_status(400)
95 if subscription
.mode
!= 'subscribe':
96 logging
.warn('invalid subscription mode: %s' % subscription
.mode
)
97 self
.response
.set_status(400)
100 if not subscription
.verified
:
101 logging
.warn('the subscription has not yet been verified')
102 self
.response
.set_status(400)
105 # The changed parts in the POST data are ignored -- we simply fetch the
107 # It is stored in memcache with all the normal (unsubscribed) feeds
108 # but with increased expiry time.
109 urlstore
.fetch_url(feed_url
, add_expires
=INCREASED_EXPIRY
)
111 self
.response
.set_status(200)
115 def get_subscription(feedurl
):
116 q
= SubscribedFeed
.all()
117 q
.filter('url =', feedurl
)
122 def subscribe(feedurl
, huburl
):
123 """ Subscribe to the feed at a Hub """
125 logging
.info('subscribing for %(feed)s at %(hub)s' %
126 dict(feed
=feedurl
, hub
=huburl
))
128 verify_token
= Subscriber
.generate_verify_token()
134 "hub.callback": Subscriber
.get_callback_url(feedurl
),
136 "hub.topic": feedurl
,
137 "hub.verify": verify
,
138 "hub.verify_token": verify_token
,
141 subscription
= Subscriber
.get_subscription(feedurl
)
142 if subscription
is not None:
144 if subscription
.mode
== mode
:
145 if subscription
.verified
:
146 logging
.info('subscription already exists')
150 logging
.info('subscription exists but has wrong mode: ' +
151 'old: %(oldmode)s, new: %(newmode)s. Overwriting.' %
152 dict(oldmode
=subscription
.mode
, newmode
=mode
))
155 subscription
= SubscribedFeed()
157 subscription
.url
= feedurl
158 subscription
.verify_token
= verify_token
159 subscription
.mode
= mode
160 subscription
.verified
= False
163 data
= urllib
.urlencode(data
)
164 logging
.debug('sending request: %s' % repr(data
))
167 resp
= urllib2
.urlopen(huburl
, data
)
168 except urllib2
.HTTPError
, e
:
169 if e
.code
!= 204: # we actually expect a 204 return code
170 msg
= 'Could not send subscription to Hub: HTTP Error %d' % e
.code
172 raise SubscriptionError(msg
)
174 msg
= 'Could not send subscription to Hub: %s' % repr(e
)
176 raise SubscriptionError(msg
)
181 logging
.error('received incorrect status %d' % status
)
182 raise SubscriptionError('Subscription has not been accepted by the Hub')
187 def get_callback_url(feedurl
):
189 url
= urlparse
.urljoin(settings
.BASE_URL
, 'subscribe')
191 param
= urllib
.urlencode([('url', feedurl
)])
192 return '%s?%s' % (url
, param
)
196 def generate_verify_token(length
=32):
197 import random
, string
198 return "".join(random
.sample(string
.letters
+string
.digits
, length
))