Home | History | Annotate | Download | only in server
      1 #!/usr/bin/python
      2 # Copyright (c) 2010 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import cgi
      7 import logging
      8 import re
      9 import os
     10 
     11 from google.appengine.ext import webapp
     12 from google.appengine.ext.webapp.util import run_wsgi_app
     13 from google.appengine.api import memcache
     14 from google.appengine.api import urlfetch
     15 
     16 # TODO(nickbaum): unit tests
     17 
     18 
     19 # TODO(nickbaum): is this the right way to do constants?
     20 class Channel():
     21   def __init__(self, name, tag):
     22     self.name = name
     23     self.tag = tag
     24     
     25   # TODO(nickbaum): unit test this
     26   def matchPath(self, path):
     27     match = "/" + self.name + "/"
     28     if path[0:len(match)] == match:
     29       return true
     30     else:
     31       return false
     32     
     33 Channel.DEV = Channel("dev", "2.0-dev")
     34 Channel.BETA = Channel("beta", "1.1-beta")
     35 Channel.STABLE = Channel("stable", "")
     36 Channel.CHANNELS = [Channel.DEV, Channel.BETA, Channel.STABLE]
     37 Channel.TRUNK = Channel("trunk", "")
     38 Channel.DEFAULT = Channel.STABLE
     39 
     40 
     41 DEFAULT_CACHE_TIME = 300
     42 
     43 
     44 class MainPage(webapp.RequestHandler):
     45   # get page from memcache, or else fetch it from src
     46   def get(self):
     47     path = os.path.realpath(os.path.join('/', self.request.path))
     48     # special path to invoke the unit tests
     49     # TODO(nickbaum): is there a less ghetto way to invoke the unit test?
     50     if path == "/test":
     51       self.unitTest()
     52       return
     53     # if root, redirect to index.html
     54     # TODO(nickbaum): this doesn't handle /chrome/extensions/trunk, etc
     55     if (path == "/chrome/extensions") or (path == "chrome/extensions/"):
     56       self.redirect("/chrome/extensions/index.html")
     57       return
     58     # else remove prefix
     59     if(path[:18] == "/chrome/extensions"):
     60       path = path[18:]
     61     # TODO(nickbaum): there's a subtle bug here: if there are two instances of the app,
     62     # their default caches will override each other. This is bad!
     63     result = memcache.get(path)
     64     if result is None:
     65       logging.info("Cache miss: " + path)
     66       url = self.getSrcUrl(path)
     67       if (url[1] is not Channel.TRUNK) and (url[0] != "http://src.chromium.org/favicon.ico"):
     68         branch = self.getBranch(url[1])
     69         url = url[0] % branch
     70       else:
     71         url = url[0]
     72       logging.info("Path: " + self.request.path)
     73       logging.info("Url: " + url)
     74       try:
     75         result = urlfetch.fetch(url + self.request.query_string)
     76         if result.status_code != 200:
     77           logging.error("urlfetch failed: " + url)
     78           # TODO(nickbaum): what should we do when the urlfetch fails?
     79       except:
     80         logging.error("urlfetch failed: " + url)
     81         # TODO(nickbaum): what should we do when the urlfetch fails?
     82       try:
     83         if not memcache.add(path, result, DEFAULT_CACHE_TIME):
     84           logging.error("Memcache set failed.")
     85       except:
     86         logging.error("Memcache set failed.")
     87     for key in result.headers:
     88       self.response.headers[key] = result.headers[key]
     89     self.response.out.write(result.content)
     90   
     91   def head(self):
     92     self.get()
     93    
     94   # get the src url corresponding to the request
     95   # returns a tuple of the url and the branch
     96   # this function is the only part that is unit tested
     97   def getSrcUrl(self, path):
     98     # from the path they provided, figure out which channel they requested
     99     # TODO(nickbaum) clean this logic up
    100     # find the first subdirectory of the path
    101     path = path.split('/', 2)
    102     url = "http://src.chromium.org/viewvc/chrome/"
    103     channel = None
    104     # if there's no subdirectory, choose the default channel
    105     # otherwise, figure out if the subdirectory corresponds to a channel
    106     if len(path) == 2:
    107       path.append("")
    108     if path[1] == "":
    109       channel = Channel.DEFAULT 
    110       if(Channel.DEFAULT == Channel.TRUNK):
    111         url = url + "trunk/src/chrome/"
    112       else:
    113         url = url + "branches/%s/src/chrome/"
    114       path = ""
    115     elif path[1] == Channel.TRUNK.name:
    116       url = url + "trunk/src/chrome/"
    117       channel = Channel.TRUNK
    118       path = path[2]
    119     else:
    120       # otherwise, run through the different channel options
    121       for c in Channel.CHANNELS:
    122         if(path[1] == c.name):
    123           channel = c
    124           url = url + "branches/%s/src/chrome/"
    125           path = path[2]
    126           break
    127       # if the subdirectory doesn't correspond to a channel, use the default
    128       if channel is None:
    129         channel = Channel.DEFAULT
    130         if(Channel.DEFAULT == Channel.TRUNK):
    131           url = url + "trunk/src/chrome/"
    132         else:
    133           url = url + "branches/%s/src/chrome/"
    134         if path[2] != "":
    135           path = path[1] + "/" + path[2]
    136         else:
    137           path = path[1]
    138     # special cases
    139     # TODO(nickbaum): this is super cumbersome to maintain
    140     if path == "third_party/jstemplate/jstemplate_compiled.js":
    141       url = url + path
    142     elif path == "api/extension_api.json":
    143       url = url + "common/extensions/" + path
    144     elif path == "favicon.ico":
    145       url = "http://src.chromium.org/favicon.ico"
    146     else:
    147       if path == "":
    148         path = "index.html"
    149       url = url + "common/extensions/docs/" + path
    150     return [url, channel]
    151     
    152   # get the current version number for the channel requested (dev, beta or stable)
    153   # TODO(nickbaum): move to Channel object
    154   def getBranch(self, channel):
    155     branch = memcache.get(channel.name)
    156     if branch is None:
    157       # query Omaha to figure out which version corresponds to this channel
    158       postdata = """<?xml version="1.0" encoding="UTF-8"?>
    159                     <o:gupdate xmlns:o="http://www.google.com/update2/request" protocol="2.0" testsource="crxdocs">
    160                     <o:app appid="{8A69D345-D564-463C-AFF1-A69D9E530F96}" version="0.0.0.0" lang="">
    161                     <o:updatecheck tag="%s" installsource="ondemandcheckforupdates" />
    162                     </o:app>
    163                     </o:gupdate>
    164                     """ % channel.tag
    165       result = urlfetch.fetch(url="https://tools.google.com/service/update2",
    166                         payload=postdata,
    167                         method=urlfetch.POST,
    168                         headers={'Content-Type': 'application/x-www-form-urlencoded',
    169                                  'X-USER-IP': '72.1.1.1'})
    170       if result.status_code != 200:
    171         logging.error("urlfetch failed.")
    172         # TODO(nickbaum): what should we do when the urlfetch fails?
    173       # find branch in response
    174       match = re.search(r'<updatecheck Version="\d+\.\d+\.(\d+)\.\d+"', result.content)
    175       if match is None:
    176         logging.error("Version number not found: " + result.content)
    177         #TODO(nickbaum): should we fall back on trunk in this case?
    178       branch = match.group(1)
    179       # TODO(nickbaum): make cache time a constant
    180       if not memcache.add(channel.name, branch, DEFAULT_CACHE_TIME):
    181         logging.error("Memcache set failed.")
    182     return branch
    183 
    184   # TODO(nickbaum): is there a more elegant way to write this unit test?
    185   # I deliberately kept it dumb to avoid errors sneaking in, but it's so verbose...
    186   # TODO(nickbaum): should I break this up into multiple files?
    187   def unitTest(self):
    188     self.response.out.write("Testing TRUNK<br/>")
    189     self.check("/trunk/", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/index.html", Channel.TRUNK)
    190     self.check("/trunk/index.html", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/index.html", Channel.TRUNK)
    191     self.check("/trunk/getstarted.html", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/getstarted.html", Channel.TRUNK)
    192     self.check("/trunk/images/toolstrip.png", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/images/toolstrip.png", Channel.TRUNK)
    193 
    194     self.response.out.write("<br/>Testing DEV<br/>")
    195     self.check("/dev/", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/index.html", Channel.DEV)
    196     self.check("/dev/index.html", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/index.html", Channel.DEV)
    197     self.check("/dev/getstarted.html", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/getstarted.html", Channel.DEV)
    198     self.check("/dev/images/toolstrip.png", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/images/toolstrip.png", Channel.DEV)
    199     
    200     self.response.out.write("<br/>Testing BETA<br/>")
    201     self.check("/beta/", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/index.html", Channel.BETA)
    202     self.check("/beta/index.html", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/index.html", Channel.BETA)
    203     self.check("/beta/getstarted.html", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/getstarted.html", Channel.BETA)
    204     self.check("/beta/images/toolstrip.png", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/images/toolstrip.png", Channel.BETA)
    205 
    206     self.response.out.write("<br/>Testing STABLE<br/>")
    207     self.check("/stable/", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/index.html", Channel.STABLE)
    208     self.check("/stable/index.html", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/index.html", Channel.STABLE)
    209     self.check("/stable/getstarted.html", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/getstarted.html", Channel.STABLE)
    210     self.check("/stable/images/toolstrip.png", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/images/toolstrip.png", Channel.STABLE)
    211     
    212     self.response.out.write("<br/>Testing jstemplate_compiled.js<br/>")
    213     self.check("/trunk/third_party/jstemplate/jstemplate_compiled.js", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/third_party/jstemplate/jstemplate_compiled.js", Channel.TRUNK)
    214     self.check("/dev/third_party/jstemplate/jstemplate_compiled.js", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/third_party/jstemplate/jstemplate_compiled.js", Channel.DEV)
    215     self.check("/beta/third_party/jstemplate/jstemplate_compiled.js", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/third_party/jstemplate/jstemplate_compiled.js", Channel.BETA)
    216     self.check("/stable/third_party/jstemplate/jstemplate_compiled.js", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/third_party/jstemplate/jstemplate_compiled.js", Channel.STABLE)
    217    
    218     self.response.out.write("<br/>Testing extension_api.json<br/>") 
    219     self.check("/trunk/api/extension_api.json", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension_api.json", Channel.TRUNK)
    220     self.check("/dev/api/extension_api.json", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/api/extension_api.json", Channel.DEV)
    221     self.check("/beta/api/extension_api.json", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/api/extension_api.json", Channel.BETA)
    222     self.check("/stable/api/extension_api.json", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/api/extension_api.json", Channel.STABLE)
    223     
    224     self.response.out.write("<br/>Testing favicon.ico<br/>") 
    225     self.check("/trunk/favicon.ico", "http://src.chromium.org/favicon.ico", Channel.TRUNK)
    226     self.check("/dev/favicon.ico", "http://src.chromium.org/favicon.ico", Channel.DEV)
    227     self.check("/beta/favicon.ico", "http://src.chromium.org/favicon.ico", Channel.BETA)
    228     self.check("/stable/favicon.ico", "http://src.chromium.org/favicon.ico", Channel.STABLE)
    229     
    230     self.response.out.write("<br/>Testing DEFAULT<br/>")
    231     temp = Channel.DEFAULT
    232     Channel.DEFAULT = Channel.DEV
    233     self.check("/", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/index.html", Channel.DEV)
    234     self.check("/index.html", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/index.html", Channel.DEV)
    235     self.check("/getstarted.html", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/getstarted.html", Channel.DEV)
    236     self.check("/images/toolstrip.png", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/images/toolstrip.png", Channel.DEV)
    237     self.check("/third_party/jstemplate/jstemplate_compiled.js", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/third_party/jstemplate/jstemplate_compiled.js", Channel.DEV)
    238     self.check("/api/extension_api.json", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/api/extension_api.json", Channel.DEV)
    239     self.check("/css/ApiRefStyles.css", "http://src.chromium.org/viewvc/chrome/branches/%s/src/chrome/common/extensions/docs/css/ApiRefStyles.css", Channel.DEV)
    240     self.check("/favicon.ico", "http://src.chromium.org/favicon.ico", Channel.DEV)
    241     
    242     self.response.out.write("<br/>Testing DEFAULT (trunk)<br/>")
    243     Channel.DEFAULT = Channel.TRUNK
    244     self.check("/", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/index.html", Channel.TRUNK)
    245     self.check("/index.html", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/index.html", Channel.TRUNK)
    246     self.check("/getstarted.html", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/getstarted.html", Channel.TRUNK)
    247     self.check("/images/toolstrip.png", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/images/toolstrip.png", Channel.TRUNK)
    248     self.check("/third_party/jstemplate/jstemplate_compiled.js", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/third_party/jstemplate/jstemplate_compiled.js", Channel.TRUNK)
    249     self.check("/api/extension_api.json", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/api/extension_api.json", Channel.TRUNK)
    250     self.check("/css/ApiRefStyles.css", "http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/css/ApiRefStyles.css", Channel.TRUNK)
    251     self.check("/favicon.ico", "http://src.chromium.org/favicon.ico", Channel.TRUNK)
    252     Channel.DEFAULT = temp
    253     
    254     return
    255 
    256   # utility function for my unit test
    257   # checks that getSrcUrl(path) returns the expected values
    258   # TODO(nickbaum): can this be replaced by assert or something similar?
    259   def check(self, path, expectedUrl, expectedChannel):
    260     actual = self.getSrcUrl(path)
    261     if (actual[0] != expectedUrl):
    262       self.response.out.write('<span style="color:#f00;">Failure:</span> path ' + path + " gave url " + actual[0] + "<br/>")
    263     elif (actual[1] != expectedChannel):
    264       self.response.out.write('<span style="color:#f00;">Failure:</span> path ' + path + " gave branch " + actual[1].name + "<br/>")
    265     else:
    266       self.response.out.write("Path " + path + ' <span style="color:#0f0;">OK</span><br/>')
    267     return
    268 
    269 
    270 application = webapp.WSGIApplication([
    271   ('/.*', MainPage),
    272 ], debug=False)
    273   
    274 
    275 def main():
    276   run_wsgi_app(application)
    277 
    278 
    279 if __name__ == '__main__':
    280   main()
    281