16a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com#!/usr/bin/env python
26a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# Copyright (c) 2012 The Chromium Authors. All rights reserved.
36a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# Use of this source code is governed by a BSD-style license that can be
46a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# found in the LICENSE file.
56a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
66a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com"""A tiny web server.
76a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
86a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comThis is intended to be used for testing, and only run from within the examples
96a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comdirectory.
106a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com"""
116a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
126a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comimport BaseHTTPServer
136a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comimport logging
146a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comimport optparse
156a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comimport os
166a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comimport SimpleHTTPServer
176a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comimport SocketServer
186a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comimport sys
196a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comimport urlparse
206a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
216a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
226a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comEXAMPLE_PATH=os.path.dirname(os.path.abspath(__file__))
236a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comNACL_SDK_ROOT = os.getenv('NACL_SDK_ROOT', os.path.dirname(EXAMPLE_PATH))
246a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
256a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
266a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comif os.path.exists(NACL_SDK_ROOT):
276a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  sys.path.append(os.path.join(NACL_SDK_ROOT, 'tools'))
286a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  import decode_dump
296a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  import getos
306a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comelse:
316a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  NACL_SDK_ROOT=None
326a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
336a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comlast_nexe = None
346a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comlast_nmf = None
356a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
366a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comlogging.getLogger().setLevel(logging.INFO)
376a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
386a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# Using 'localhost' means that we only accept connections
396a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# via the loop back interface.
406a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comSERVER_PORT = 5103
416a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comSERVER_HOST = ''
426a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
436a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# We only run from the examples directory so that not too much is exposed
446a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# via this HTTP server.  Everything in the directory is served, so there should
456a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# never be anything potentially sensitive in the serving directory, especially
466a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# if the machine might be a multi-user machine and not all users are trusted.
476a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# We only serve via the loopback interface.
486a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comdef SanityCheckDirectory():
496a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  httpd_path = os.path.abspath(os.path.dirname(__file__))
506a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  serve_path = os.path.abspath(os.getcwd())
516a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
526a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  # Verify we are serving from the directory this script came from, or bellow
536a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  if serve_path[:len(httpd_path)] == httpd_path:
546a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    return
556a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  logging.error('For security, httpd.py should only be run from within the')
566a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  logging.error('example directory tree.')
576a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  logging.error('We are currently in %s.' % serve_path)
586a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  sys.exit(1)
596a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
606a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
616a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# An HTTP server that will quit when |is_running| is set to False.  We also use
626a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# SocketServer.ThreadingMixIn in order to handle requests asynchronously for
636a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# faster responses.
646a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comclass QuittableHTTPServer(SocketServer.ThreadingMixIn,
656a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com                          BaseHTTPServer.HTTPServer):
666a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  def serve_forever(self, timeout=0.5):
676a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.is_running = True
686a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.timeout = timeout
696a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    while self.is_running:
706a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      self.handle_request()
716a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
726a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  def shutdown(self):
736a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.is_running = False
746a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    return 1
756a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
766a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
776a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# "Safely" split a string at |sep| into a [key, value] pair.  If |sep| does not
786a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# exist in |str|, then the entire |str| is the key and the value is set to an
796a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# empty string.
806a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comdef KeyValuePair(str, sep='='):
816a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  if sep in str:
826a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    return str.split(sep)
836a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  else:
846a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    return [str, '']
856a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
866a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
876a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# A small handler that looks for '?quit=1' query in the path and shuts itself
886a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com# down if it finds that parameter.
896a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comclass QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
906a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  def send_head(self):
916a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    """Common code for GET and HEAD commands.
926a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
936a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    This sends the response code and MIME headers.
946a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
956a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    Return value is either a file object (which has to be copied
966a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    to the outputfile by the caller unless the command was HEAD,
976a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    and must be closed by the caller under all circumstances), or
986a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    None, in which case the caller has nothing further to do.
996a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1006a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    """
1016a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    path = self.translate_path(self.path)
1026a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    f = None
1036a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    if os.path.isdir(path):
1046a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        if not self.path.endswith('/'):
1056a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com            # redirect browser - doing basically what apache does
1066a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com            self.send_response(301)
1076a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com            self.send_header("Location", self.path + "/")
1086a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com            self.end_headers()
1096a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com            return None
1106a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        for index in "index.html", "index.htm":
1116a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com            index = os.path.join(path, index)
1126a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com            if os.path.exists(index):
1136a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com                path = index
1146a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com                break
1156a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        else:
1166a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com            return self.list_directory(path)
1176a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    ctype = self.guess_type(path)
1186a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    try:
1196a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        # Always read in binary mode. Opening files in text mode may cause
1206a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        # newline translations, making the actual size of the content
1216a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        # transmitted *less* than the content-length!
1226a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        f = open(path, 'rb')
1236a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    except IOError:
1246a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        self.send_error(404, "File not found")
1256a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        return None
1266a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.send_response(200)
1276a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.send_header("Content-type", ctype)
1286a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    fs = os.fstat(f.fileno())
1296a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.send_header("Content-Length", str(fs[6]))
1306a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
1316a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.send_header('Cache-Control','no-cache, must-revalidate')
1326a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.send_header('Expires','-1')
1336a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    self.end_headers()
1346a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    return f
1356a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1366a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  def do_GET(self):
1376a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    global last_nexe, last_nmf
1386a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    (_, _, path, query, _) = urlparse.urlsplit(self.path)
1396a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    url_params = dict([KeyValuePair(key_value)
1406a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com                      for key_value in query.split('&')])
1416a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    if 'quit' in url_params and '1' in url_params['quit']:
1426a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      self.send_response(200, 'OK')
1436a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      self.send_header('Content-type', 'text/html')
1446a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      self.send_header('Content-length', '0')
1456a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      self.end_headers()
1466a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      self.server.shutdown()
1476a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      return
1486a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1496a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    if path.endswith('.nexe'):
1506a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      last_nexe = path
1516a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    if path.endswith('.nmf'):
1526a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      last_nmf = path
1536a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1546a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
1556a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1566a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  def do_POST(self):
1576a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    (_, _,path, query, _) = urlparse.urlsplit(self.path)
1586a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    if 'Content-Length' in self.headers:
1596a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      if not NACL_SDK_ROOT:
1606a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        self.wfile('Could not find NACL_SDK_ROOT to decode trace.')
1616a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        return
1626a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      data = self.rfile.read(int(self.headers['Content-Length']))
1636a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      nexe = '.' + last_nexe
1646a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      nmf = '.' + last_nmf
1656a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      addr = os.path.join(NACL_SDK_ROOT, 'toolchain',
1666a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com                          getos.GetPlatform() + '_x86_newlib',
1676a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com                          'bin', 'x86_64-nacl-addr2line')
1686a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      decoder = decode_dump.CoreDecoder(nexe, nmf, addr, None, None)
1696a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      info = decoder.Decode(data)
1706a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      trace = decoder.StackTrace(info)
1716a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      decoder.PrintTrace(trace, sys.stdout)
1726a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com      decoder.PrintTrace(trace, self.wfile)
1736a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1746a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1756a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comdef Run(server_address,
1766a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        server_class=QuittableHTTPServer,
1776a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com        handler_class=QuittableHTTPHandler):
1786a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  httpd = server_class(server_address, handler_class)
1796a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  logging.info("Starting local server on port %d", server_address[1])
1806a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  logging.info("To shut down send http://localhost:%d?quit=1",
1816a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com               server_address[1])
1826a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  try:
1836a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    httpd.serve_forever()
1846a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  except KeyboardInterrupt:
1856a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    logging.info("Received keyboard interrupt.")
1866a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    httpd.server_close()
1876a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1886a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  logging.info("Shutting down local server on port %d", server_address[1])
1896a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1906a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
1916a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comdef main():
1926a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  usage_str = "usage: %prog [options] [optional_portnum]"
1936a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  parser = optparse.OptionParser(usage=usage_str)
1946a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  parser.add_option(
1956a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    '--no_dir_check', dest='do_safe_check',
1966a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    action='store_false', default=True,
1976a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    help='Do not ensure that httpd.py is being run from a safe directory.')
1986a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  (options, args) = parser.parse_args(sys.argv)
1996a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  if options.do_safe_check:
2006a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    SanityCheckDirectory()
2016a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  if len(args) > 2:
2026a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    print 'Too many arguments specified.'
2036a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    parser.print_help()
2046a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  elif len(args) == 2:
2056a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    Run((SERVER_HOST, int(args[1])))
2066a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  else:
2076a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com    Run((SERVER_HOST, SERVER_PORT))
2086a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  return 0
2096a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
2106a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com
2116a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.comif __name__ == '__main__':
2126a98b8c0b5ffe1a23902cdf7e692f702b703eaebborenet@google.com  sys.exit(main())
213