Web2py 这不适用于 Google App Engine (GAE)

Web2py this not working with Google App Engine (GAE)

我在 google 应用服务器 (gae) 上使用 web2py 时遇到问题。 当我在浏览器中插入 url localhost:8080 时出现的错误是:

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 -

问题出在app.yaml。我使用的是基于app.example.yaml文件,我做了必要的修改,但还是不行

下面是这样的简化结构,目录 web2pygoogle_appengine 以及文件 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//static/
  upload: applications/(.+?)/static/(.+)
  secure: optional
  expiration: "365d"

- url: /(.+?)/static/(.+)
  static_files: applications//static/
  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

版本: Python2.7.9; Web2py 2.9.12; GAE 1.9.18

有人可以帮助我 运行 带有 google 应用程序引擎的 web2py。我已经尝试了所有方法,我已经在论坛和群组中阅读了无数文章,在 youtube 上观看了视频,但没有任何效果。 谢谢关注

为了解决这个问题,我将参数 threadsafe 更改为 threadsafe: true The script script: handlers/gaehandler.wsgiapp for script: gaehandler.wsgiapp。并将文件handlers/gaehandler.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: 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//static/
  upload: applications/(.+?)/static/(.+)
  secure: optional
  expiration: "365d"

- url: /(.+?)/static/(.+)
  static_files: applications//static/
  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: 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

目录

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

404问题/归结为单行:

script: handlers/gaehandler.wsgiapp

这将目录符号(即“/”!)与 "Python module notation" 混合在一起,结果它将(线程安全为真)在目录 [=13] 中查找名为 gaehandler.wsgiapp 的文件=].

确保 handlers 目录是一个 Python (即它包含一个文件名 __init__.py,通常是空的)和然后将该行修复为

script: handlers.gaehandler.wsgiapp

看出区别了吗?一个点而不是一个斜线 -- 就是这样!-)

favicon.ico 上的 404 是一个单独的问题,应该与您的层次结构中名为 applications/core/static/favicon.ico 的缺失文件相关联——您确实说过您有 applications/core 但不是下面的文件它(事实上,你显示 nothing 在那个目录下?!)所以我想这是一种可能性(而且我想不出任何其他原因......)。

我是 运行 GAE 中的 Web2py:

您将需要,在 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//static/
  upload: applications/(.+?)/static/(.+)
  secure: optional
  expiration: "365d"

- url: /(.+?)/static/(.+)
  static_files: applications//static/
  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 <mdipierro@cs.depaul.edu>
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()

我想,这个文件必须包含在web2py的主线程中。