blob: 6b3c32f480b036724b84664404fcca7bb723f4aa [file] [log] [blame]
#!/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.
#
"""A Python 2.7 runtime module.
Provides a Python 2.7 runtime that calls into user-provided code using a CGI or
WSGI interface.
"""
import cStringIO
import thread
import threading
import urlparse
from google.appengine.api.logservice import logservice
from google.appengine.runtime import cgi
from google.appengine.runtime import request_environment
from google.appengine.runtime import wsgi
def _MakeStartNewThread(base_start_new_thread):
"""Returns a replacement for start_new_thread that inherits environment.
Returns a function with an interface that matches thread.start_new_thread
where the new thread inherits the request environment of
request_environment.current_request and cleans it up when it terminates.
Args:
base_start_new_thread: The thread.start_new_thread function to call to
create a new thread.
Returns:
A replacement for start_new_thread.
"""
def StartNewThread(target, args, kw=None):
"""A replacement for thread.start_new_thread.
A replacement for thread.start_new_thread that inherits RequestEnvironment
state from its creator and cleans up that state when it terminates.
Args:
See thread.start_new_thread.
Returns:
See thread.start_new_thread.
"""
if kw is None:
kw = {}
cloner = request_environment.current_request.CloneRequestEnvironment()
def Run():
try:
cloner()
target(*args, **kw)
finally:
request_environment.current_request.Clear()
return base_start_new_thread(Run, ())
return StartNewThread
def PatchStartNewThread(thread_module=thread, threading_module=threading):
"""Installs a start_new_thread replacement created by _MakeStartNewThread."""
thread_module.start_new_thread = _MakeStartNewThread(
thread_module.start_new_thread)
reload(threading_module)
def HandleRequest(environ, handler_name, url, post_data, application_root,
python_lib, import_hook=None):
"""Handles a single request.
Handles a single request for handler_name by dispatching to the appropriate
server interface implementation (CGI or WSGI) depending on the form of
handler_name. The arguments are processed to fill url and post-data related
environ members.
Args:
environ: A dict containing the environ for this request (i.e. for
os.environ).
handler_name: A str containing the user-specified handler to use for this
request, i.e. the script parameter for a handler in app.yaml; e.g.
'package.module.application' for a WSGI handler or 'path/to/handler.py'
for a CGI handler.
url: The requested url.
post_data: The post data for this request.
application_root: A str containing the root path of the application.
python_lib: A str containing the root the Python App Engine library.
import_hook: Optional import hook (PEP 302 style loader).
Returns:
A dict containing:
error: App Engine error code. 0 for OK, 1 for error.
response_code: The HTTP response code.
headers: A list of str tuples (key, value) of HTTP headers.
body: A str of the body of the response
logs: A list of tuples (timestamp_usec, severity, message) of log entries.
timestamp_usec is a long, severity is int and message is str. Severity
levels are 0..4 for Debug, Info, Warning, Error, Critical.
"""
try:
error = logservice.LogsBuffer()
request_environment.current_request.Init(error, environ)
url = urlparse.urlsplit(url)
environ.update(CgiDictFromParsedUrl(url))
if post_data:
environ['CONTENT_LENGTH'] = str(len(post_data))
if 'HTTP_CONTENT_TYPE' in environ:
environ['CONTENT_TYPE'] = environ['HTTP_CONTENT_TYPE']
else:
environ['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
post_data = cStringIO.StringIO(post_data)
if '/' in handler_name or handler_name.endswith('.py'):
response = cgi.HandleRequest(environ, handler_name, url, post_data, error,
application_root, python_lib, import_hook)
else:
response = wsgi.HandleRequest(environ, handler_name, url, post_data,
error)
response['logs'] = error.parse_logs()
return response
finally:
request_environment.current_request.Clear()
def CgiDictFromParsedUrl(url):
"""Extract CGI variables from a parsed url into a dict.
Returns a dict containing the following CGI variables for the provided url:
SERVER_PORT, QUERY_STRING, SERVER_NAME and PATH_INFO.
Args:
url: An instance of urlparse.SplitResult.
Returns:
A dict containing the CGI variables derived from url.
"""
environ = {}
if url.port is not None:
environ['SERVER_PORT'] = str(url.port)
elif url.scheme == 'https':
environ['SERVER_PORT'] = '443'
elif url.scheme == 'http':
environ['SERVER_PORT'] = '80'
environ['QUERY_STRING'] = url.query
environ['SERVER_NAME'] = url.hostname
if url.path:
environ['PATH_INFO'] = urlparse.unquote(url.path)
else:
environ['PATH_INFO'] = '/'
return environ