Search code examples
google-app-engineweb2py

Web2py this not working with Google App Engine (GAE)


I'm having trouble using the web2py on google app server (gae). The error is giving when I insert the url localhost:8080 in browser is:

rafael@rafael-debian:~/rafael/google_appengine$ python dev_appserver.py ../web2py/
INFO     2015-03-20 03:38:20,075 sdk_update_checker.py:229] Checking for updates to the SDK.
INFO     2015-03-20 03:38:20,520 sdk_update_checker.py:257] The SDK is up to date.
INFO     2015-03-20 03:38:21,109 api_server.py:172] Starting API server at: http://localhost:41836
INFO     2015-03-20 03:38:21,202 dispatcher.py:186] Starting module "default" running at: http://localhost:8080
INFO     2015-03-20 03:38:21,203 admin_server.py:118] Starting admin server at: http://localhost:8000
INFO     2015-03-20 03:38:32,968 module.py:737] default: "GET / HTTP/1.1" 404 -
INFO     2015-03-20 03:38:33,172 module.py:737] default: "GET /favicon.ico HTTP/1.1" 404 -

The problem is in app.yaml. I used based on the app.example.yaml file, I made the necessary changes, but still does not work

Below is the simplifed structure like this the directories web2py and google_appengine and file app.yaml:

web2py/
├── anyserver.py
├── applications
│   ├── admin
│   ├── core
│   ├── examples
│   └── welcome
├── app.yaml
├── handlers
│   ├── cgihandler.py
│   ├── fcgihandler.py
│   ├── gaehandler.py
│   ├── isapiwsgihandler.py
│   ├── modpythonhandler.py
│   ├── README
│   ├── scgihandler.py
│   ├── web2py_on_gevent.py
│   └── wsgihandler.py
├── web2py.py
└── welcome.w2p
│
google_appengine/
├── dev_appserver.py

app.yaml

#  For Google App Engine deployment, copy this file to app.yaml
#  and edit as required
#  See http://code.google.com/appengine/docs/python/config/appconfig.html
#  and http://web2py.com/book/default/chapter/11?search=app.yaml

application: core
version: 1
api_version: 1

# use these lines for Python 2.7
# upload app with: appcfg.py update web2py (where 'web2py' is web2py's root directory)
#
runtime: python27
threadsafe: false    # true for WSGI & concurrent requests (Python 2.7 only)

default_expiration: "24h"   # for static files

handlers:

# Warning! Static mapping - below - isn't compatible with 
# the parametric router's language logic. 
# You cannot use them together.

- url: /(.+?)/static/_\d.\d.\d\/(.+)
  static_files: applications/\1/static/\2
  upload: applications/(.+?)/static/(.+)
  secure: optional
  expiration: "365d"

- url: /(.+?)/static/(.+)
  static_files: applications/\1/static/\2
  upload: applications/(.+?)/static/(.+)
  secure: optional

- url: /favicon.ico
  static_files: applications/core/static/favicon.ico
  upload: applications/core/static/favicon.ico

- url: /robots.txt
  static_files: applications/core/static/robots.txt
  upload: applications/welcome/static/robots.txt

- url: .*
  script: handlers/gaehandler.wsgiapp    # WSGI (Python 2.7 only)
  secure: optional

admin_console:
  pages:
  - name: Appstats
    url: /_ah/stats

skip_files: |
 ^(.*/)?(
 (app\.yaml)|
 (app\.yml)|
 (index\.yaml)|
 (index\.yml)|
 (#.*#)|
 (.*~)|
 (.*\.py[co])|
 (.*/RCS/.*)|
 (\..*)|
 (applications/examples/.*)|
 ((examples|welcome)\.(w2p|tar))|
 (applications/.*?/(cron|databases|errors|cache|sessions)/.*)|
 ((logs|scripts)/.*)|
 (anyserver\.py)|
 (web2py\.py)|
 ((cgi|fcgi|modpython|wsgi)handler\.py)|
 (epydoc\.(conf|css))|
 (httpserver\.log)|
 (logging\.example\.conf)|
 (route[rs]\.example\.py)|
 (setup_(app|exe)\.py)|
 (splashlogo\.gif)|
 (parameters_\d+\.py)|
 (options_std.py)|
 (gluon/tests/.*)|
 (gluon/rocket\.py)|
 (contrib/(gateways|markdown|memcache|pymysql)/.*)|
 (contrib/(populate|taskbar_widget)\.py)|
 (google_appengine/.*)|
 (.*\.(bak|orig))|
 )$

builtins:
- remote_api: on
- appstats: on
- admin_redirect: on
- deferred: on

Version: Python 2.7.9; Web2py 2.9.12; GAE 1.9.18

Could someone help me run the web2py with google app engine. I've tried everything I read countless articles already in forums and groups, videos on youtube but nothing worked. Thank you for attention


Solution

  • I am running Web2py in GAE:

    You will need, in the same directory of web2py.py:

    App.yaml

    #  For Google App Engine deployment, copy this file to app.yaml
    #  and edit as required
    #  See http://code.google.com/appengine/docs/python/config/appconfig.html
    #  and http://web2py.com/book/default/chapter/11?search=app.yaml
    
    application: yourapplicaton
    version: 22
    api_version: 1
    
    # use these lines for Python 2.7
    # upload app with: appcfg.py update web2py (where 'web2py' is web2py's root directory)
    #
    runtime: python27
    threadsafe: true    # true for WSGI & concurrent requests (Python 2.7 only)
    
    default_expiration: "24h"   # for static files
    
    handlers:
    
    # Warning! Static mapping - below - isn't compatible with 
    # the parametric router's language logic. 
    # You cannot use them together.
    
    - url: /(.+?)/static/_\d.\d.\d\/(.+)
      static_files: applications/\1/static/\2
      upload: applications/(.+?)/static/(.+)
      secure: optional
      expiration: "365d"
    
    - url: /(.+?)/static/(.+)
      static_files: applications/\1/static/\2
      upload: applications/(.+?)/static/(.+)
      secure: optional
    
    - url: /favicon.ico
      static_files: applications/init/static/favicon.ico
      upload: applications/init/static/favicon.ico
    
    - url: /robots.txt
      static_files: applications/init/static/robots.txt
      upload: applications/init/static/robots.txt
    
    - url: .*
      script: gaehandler.wsgiapp    # WSGI (Python 2.7 only)
      secure: optional
    
    # All URLs beginning with /stylesheets are treated as paths to static files in
    # the stylesheets/ directory.
    - url: /init/static/css/
      static_dir: applications/init/static/css
      mime_type: "text/css"
    
    - url: /init/static/
      static_dir: applications/init/static
      mime_type: "text/css"
      
    - url: /init/static/js/
      static_dir: applications/init/static/js
      mime_type: "text/javascript"  
    
    admin_console:
      pages:
      - name: Appstats
        url: /_ah/stats
    
    skip_files: |
     ^(.*/)?(
     (app\.yaml)|
     (app\.yml)|
     (index\.yaml)|
     (index\.yml)|
     (#.*#)|
     (.*~)|
     (.*\.py[co])|
     (.*/RCS/.*)|
     (\..*)|
     (applications/examples/.*)|
     ((examples|welcome)\.(w2p|tar))|
     (applications/.*?/(cron|databases|errors|cache|sessions)/.*)|
     ((logs|scripts)/.*)|
     (anyserver\.py)|
     (web2py\.py)|
     ((cgi|fcgi|modpython|wsgi)handler\.py)|
     (epydoc\.(conf|css))|
     (httpserver\.log)|
     (logging\.example\.conf)|
     (route[rs]\.example\.py)|
     (setup_(app|exe)\.py)|
     (splashlogo\.gif)|
     (parameters_\d+\.py)|
     (options_std.py)|
     (gluon/tests/.*)|
     (gluon/rocket\.py)|
     (contrib/(gateways|markdown|memcache|pymysql)/.*)|
     (contrib/(populate|taskbar_widget)\.py)|
     (google_appengine/.*)|
     (.*\.(bak|orig))|
     )$
    
    builtins:
    - remote_api: on
    - appstats: on
    - admin_redirect: on
    - deferred: on

    appengine_config.py

    #!/usr/bin/env python
    #
    # Copyright 2007 Google Inc.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    #
    
    """Sample Appstats Configuration.
    
    There are four sections:
    
    0) WSGI middleware declaration.
    1) Django version declaration.
    2) Configuration constants.
    3) Configuration functions.
    
    Also a section at the end for the remote_api handler.
    
    """
    
    
    import logging
    import random
    import re
    
    # 0) WSGI middleware declaration.
    
    # Only use this if you're not Django; with Django, it's easier to add
    #   'google.appengine.ext.appstats.recording.AppstatsDjangoMiddleware',
    # to your Django settings.py file.
    
    def webapp_add_wsgi_middleware(app):
        from google.appengine.ext.appstats import recording
        app = recording.appstats_wsgi_middleware(app)
        return app
    
    
    # 1) Django version declaration.
    
    # If your application uses Django and requires a specific version of
    # Django, uncomment the following block of three lines.  Currently
    # supported values for the Django version are '0.96' (the default),
    # '1.0', and '1.1'.
    
    # # from google.appengine.dist import use_library
    # # use_library('django', '1.0')
    # # import django
    
    
    # 2) Configuration constants.
    
    # DEBUG: True of False.  When True, verbose messages are logged at the
    # DEBUG level.  Also, this flag is causes tracebacks to be shown in
    # the web UI when an exception occurs.  (Tracebacks are always logged
    # at the ERROR level as well.)
    
    appstats_DEBUG = False
    
    # DUMP_LEVEL: -1, 0, 1 or 2.  Controls how much debug output is
    # written to the logs by the internal dump() function during event
    # recording.  -1 dumps nothing; 0 dumps one line of information; 1
    # dumps more informat and 2 dumps the maximum amount of information.
    # You would only need to change this if you were debugging the
    # recording implementation.
    
    appstats_DUMP_LEVEL = -1
    
    # The following constants control the resolution and range of the
    # memcache keys used to record information about individual requests.
    # Two requests that are closer than KEY_DISTANCE milliseconds will be
    # mapped to the same key (thus losing all information about the
    # earlier of the two requests).  Up to KEY_MODULUS distinct keys are
    # generated; after KEY_DISTANCE * KEY_MODULUS milliseconds the key
    # values roll over.  Increasing KEY_MODULUS causes a proportional
    # increase of the amount of data saved in memcache.  Increasing
    # KEY_DISTANCE causes a requests during a larger timespan to be
    # recorded, at the cost of increasing risk of assigning the same key
    # to two adjacent requests.
    
    appstats_KEY_DISTANCE = 100
    appstats_KEY_MODULUS = 1000
    
    # The following constants control the namespace and key values used to
    # store information in memcache.  You can safely leave this alone.
    
    appstats_KEY_NAMESPACE = '__appstats__'
    appstats_KEY_PREFIX = '__appstats__'
    appstats_KEY_TEMPLATE = ':%06d'
    appstats_PART_SUFFIX = ':part'
    appstats_FULL_SUFFIX = ':full'
    appstats_LOCK_SUFFIX = '<lock>'
    
    # Numerical limits on how much information is saved for each event.
    # MAX_STACK limits the number of stack frames saved; MAX_LOCALS limits
    # the number of local variables saved per stack frame.  MAX_REPR
    # limits the length of the string representation of each variable
    # saved; MAX_DEPTH limits the nesting depth used when computing the
    # string representation of structured variables (e.g. lists of lists).
    
    appstats_MAX_STACK = 10
    appstats_MAX_LOCALS = 10
    appstats_MAX_REPR = 100
    appstats_MAX_DEPTH = 10
    
    # Regular expressions.  These are matched against the 'code key' of a
    # stack frame, which is a string of the form
    # '<filename>:<function>:<lineno>'.  If the code key of a stack frame
    # matches RE_STACK_BOTTOM, it and all remaining stack frames are
    # skipped.  If the code key matches RE_STACK_SKIP, that frame is not
    # saved but subsequent frames may be saved.
    
    appstats_RE_STACK_BOTTOM = r'dev_appserver\.py'
    appstats_RE_STACK_SKIP = r'recording\.py|apiproxy_stub_map\.py'
    
    # Timeout for memcache lock management, in seconds.
    
    appstats_LOCK_TIMEOUT = 1
    
    # Timezone offset.  This is used to convert recorded times (which are
    # all in UTC) to local time.  The default is US/Pacific winter time.   8*3600
    #Colombia es: 
    appstats_TZOFFSET = 5*3600
    
    # URL path (sans host) leading to the stats UI.  Should match app.yaml.
    # If "builtins: - appstats: on" is used, the path should be /_ah/stats.
    
    appstats_stats_url = '/_ah/stats'
    
    # Fraction of requests to record.  Set this to a float between 0.0
    # and 1.0 to record that fraction of all requests.
    
    appstats_RECORD_FRACTION = 1.0
    
    # List of dicts mapping env vars to regular expressions.  Each dict
    # specifies a set of filters to be 'and'ed together.  The keys are
    # environment variables, the values are *match* regular expressions.
    # A request is recorded if it matches all filters of at least one
    # dict.  If the FILTER_LIST variable is empty, all requests are
    # recorded.  Missing environment variables are considered to have
    # the empty string as value.  If a regular expression starts with
    # '!', the sense of the match is negated (the value should *not*
    # match the expression).
    
    appstats_FILTER_LIST = []
    
    # 3) Configuration functions.
    
    # should_record() can be used to record a random percentage of calls.
    # The argument is the CGI or WSGI environment dict.  The default
    # implementation returns True iff the request matches FILTER_LIST (see
    # above) *and* random.random() < RECORD_FRACTION.
    
    def appstats_should_record(env):
      if appstats_FILTER_LIST:
        logging.debug('FILTER_LIST: %r', appstats_FILTER_LIST)
        for filter_dict in appstats_FILTER_LIST:
          for key, regex in filter_dict.iteritems():
            negated = isinstance(regex, str) and regex.startswith('!')
            if negated:
              regex = regex[1:]
            value = env.get(key, '')
            if bool(re.match(regex, value)) == negated:
              logging.debug('No match on %r for %s=%r', regex, key, value)
              break
          else:
            logging.debug('Match on %r', filter_dict)
            break
        else:
          logging.debug('Non-empty FILTER_LIST, but no filter matches')
          return False
      if appstats_RECORD_FRACTION >= 1.0:
        return True
      return random.random() < appstats_RECORD_FRACTION
    
    # The following functions are called by the UI code only; they don't
    # affect the recorded information.
    
    # normalize_path() takes a path and returns an 'path key'.  The path
    # key is used by the UI to compute statistics for similar URLs.  If
    # your application has a large or infinite URL space (e.g. each issue
    # in an issue tracker might have its own numeric URL), this function
    # can be used to produce more meaningful statistics.
    
    def appstats_normalize_path(path):
      return path
    
    # extract_key() is a lower-level function with the same purpose as
    # normalize_key().  It can be used to lump different request methods
    # (e.g. GET and POST) together, or conversely to use other information
    # on the request object (mostly the query string) to produce a more
    # fine-grained path key.  The argument is a StatsProto object; this is
    # a class defined in recording.py.  Useful methods are:
    # #   - http_method()
    #   - http_path()
    #   - http_query()
    #   - http_status()
    # # Note that the StatsProto argument is loaded only with summary
    # information; this means you cannot access the request headers.
    
    def appstats_extract_key(request):
      key = appstats_normalize_path(request.http_path())
      if request.http_method() != 'GET':
        key = '%s %s' % (request.http_method(), key)
      return key
    
    
    # ########################################
    # Remote_API Authentication configuration.
    # # See google/appengine/ext/remote_api/handler.py for more information.
    # In most cases, you will not want to configure this.
    # # remoteapi_CUSTOM_ENVIRONMENT_AUTHENTICATION = (
    #     'HTTP_X_APPENGINE_INBOUND_APPID', ['a trusted appid here'])

    gaehandler.py

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    """
    This file is part of the web2py Web Framework
    Copyrighted by Massimo Di Pierro <[email protected]>
    License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
    """
    
    ##############################################################################
    # Configuration parameters for Google App Engine
    ##############################################################################
    LOG_STATS = False      # web2py level log statistics
    APPSTATS = True         # GAE level usage statistics and profiling
    DEBUG = False          # debug mode
    #
    # Read more about APPSTATS here
    #   http://googleappengine.blogspot.com/2010/03/easy-performance-profiling-with.html
    # can be accessed from:
    #   http://localhost:8080/_ah/stats
    ##############################################################################
    # All tricks in this file developed by Robin Bhattacharyya
    ##############################################################################
    
    
    import time
    import os
    import sys
    import logging
    import cPickle
    import pickle
    import wsgiref.handlers
    import datetime
    
    path = os.path.dirname(os.path.abspath(__file__))
    
    # os.chdir(path) ?
    
    if not os.path.isdir('applications'):
        raise RuntimeError('Running from the wrong folder')
    
    sys.path = [path] + [p for p in sys.path if not p == path]
    
    sys.modules['cPickle'] = sys.modules['pickle']
    
    
    from gluon.settings import global_settings
    from google.appengine.ext import webapp
    from google.appengine.ext.webapp.util import run_wsgi_app
    
    
    global_settings.web2py_runtime_gae = True
    global_settings.db_sessions = True
    if os.environ.get('SERVER_SOFTWARE', '').startswith('Devel'):
        (global_settings.web2py_runtime, DEBUG) = \
            ('gae:development', True)
    else:
        (global_settings.web2py_runtime, DEBUG) = \
            ('gae:production', False)
    
    
    import gluon.main
    
    
    def log_stats(fun):
        """Function that will act as a decorator to make logging"""
        def newfun(env, res):
            """Log the execution time of the passed function"""
            timer = lambda t: (t.time(), t.clock())
            (t0, c0) = timer(time)
            executed_function = fun(env, res)
            (t1, c1) = timer(time)
            log_info = """**** Request: %.2fms/%.2fms (real time/cpu time)"""
            log_info = log_info % ((t1 - t0) * 1000, (c1 - c0) * 1000)
            logging.info(log_info)
            return executed_function
        return newfun
    
    
    logging.basicConfig(level=logging.INFO)
    
    
    def wsgiapp(env, res):
        """Return the wsgiapp"""
        env['PATH_INFO'] = env['PATH_INFO'].decode('latin1').encode('utf8')
    
        #when using the blobstore image uploader GAE dev SDK passes these as unicode
        # they should be regular strings as they are parts of URLs
        env['wsgi.url_scheme'] = str(env['wsgi.url_scheme'])
        env['QUERY_STRING'] = str(env['QUERY_STRING'])
        env['SERVER_NAME'] = str(env['SERVER_NAME'])
    
        #this deals with a problem where GAE development server seems to forget
        # the path between requests
        if global_settings.web2py_runtime == 'gae:development':
            gluon.admin.create_missing_folders()
    
        web2py_path = global_settings.applications_parent  # backward compatibility
    
        return gluon.main.wsgibase(env, res)
    
    
    if LOG_STATS or DEBUG:
        wsgiapp = log_stats(wsgiapp)
    
    
    def main():
        """Run the wsgi app"""
        run_wsgi_app(wsgiapp)
    
    if __name__ == '__main__':
        main()

    I think, this files must be included in the main thread of web2py.