1e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
2e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)# found in the LICENSE file.
4e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
5e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)import calendar
6e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)import datetime
7e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)import json
87242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucciimport logging
9e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)import webapp2
10e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)import zlib
11e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
12e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)from google.appengine.api import memcache
13e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
147242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano TucciLOGGER = logging.getLogger(__name__)
157242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
16e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
17e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)class DateTimeEncoder(json.JSONEncoder):
18e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)    def default(self, obj):
19e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        if isinstance(obj, datetime.datetime):
20e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            return calendar.timegm(obj.timetuple())
217242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        # Let the base class default method raise the TypeError.
22e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        return json.JSONEncoder.default(self, obj)
23e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
24e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
25e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)class AlertsHandler(webapp2.RequestHandler):
26e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)    MEMCACHE_ALERTS_KEY = 'alerts'
27e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
287242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    # Has no 'response' member.
297242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    # pylint: disable=E1101
307242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def send_json_headers(self):
31e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        self.response.headers.add_header('Access-Control-Allow-Origin', '*')
32e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        self.response.headers['Content-Type'] = 'application/json'
337242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
347242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    # Has no 'response' member.
357242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    # pylint: disable=E1101
367242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def send_json_data(self, data):
377242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self.send_json_headers()
387242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self.response.write(data)
397242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
407242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def generate_json_dump(self, alerts):
417242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        return json.dumps(alerts, cls=DateTimeEncoder, indent=1)
427242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
437242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def get_from_memcache(self, memcache_key):
447242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        compressed = memcache.get(memcache_key)
45e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        if not compressed:
467242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            self.send_json_headers()
47e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            return
48e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        uncompressed = zlib.decompress(compressed)
497242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self.send_json_data(uncompressed)
50e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
517242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def get(self):
527242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self.get_from_memcache(AlertsHandler.MEMCACHE_ALERTS_KEY)
537242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
547242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    # Has no 'response' member.
557242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    # pylint: disable=E1101
567242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def post_to_memcache(self, memcache_key):
57e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        try:
58e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            alerts = json.loads(self.request.get('content'))
59e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        except ValueError:
607242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            warning = 'content field was not JSON'
617242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            self.response.set_status(400, warning)
627242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci            LOGGER.warn(warning)
63e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            return
64e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        alerts.update({
65e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            'date': datetime.datetime.utcnow(),
66e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)            'alerts': alerts['alerts']
67e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        })
687242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        uncompressed = self.generate_json_dump(alerts)
69e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        compression_level = 1
70e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)        compressed = zlib.compress(uncompressed, compression_level)
717242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        memcache.set(memcache_key, compressed)
727242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci
737242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci    def post(self):
747242dc3dbeb210b5e876a3c42d1ec1a667fc621aPrimiano Tucci        self.post_to_memcache(AlertsHandler.MEMCACHE_ALERTS_KEY)
75e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
76e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)
77e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)app = webapp2.WSGIApplication([
78e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)    ('/alerts', AlertsHandler)
79e38fbeeb576b5094e34e038ab88d9d6a5c5c2214Torne (Richard Coles)])
80