debian-koji/koji/server.py
2014-10-28 23:54:21 -04:00

190 lines
6.2 KiB
Python

# common server code for koji
#
# Copyright (c) 2012-2014 Red Hat, Inc.
#
# Koji is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this software; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Mike McLean <mikem@redhat.com>
import koji
import sys
import traceback
from koji.util import LazyDict
try:
from mod_python import apache
except ImportError:
apache = None
class ServerError(Exception):
"""Base class for our server-side-only exceptions"""
class ServerRedirect(ServerError):
"""Used to handle redirects"""
class WSGIWrapper(object):
"""A very thin wsgi compat layer for mod_python
This class is highly specific to koji and is not fit for general use.
It does not support the full wsgi spec
"""
def __init__(self, req):
self.req = req
self._env = None
host, port = req.connection.remote_addr
environ = {
'REMOTE_ADDR' : req.connection.remote_ip,
# or remote_addr[0]?
# or req.get_remote_host(apache.REMOTE_NOLOOKUP)?
'REMOTE_PORT' : str(req.connection.remote_addr[1]),
'REMOTE_USER' : req.user,
'REQUEST_METHOD' : req.method,
'REQUEST_URI' : req.uri,
'PATH_INFO' : req.path_info,
'SCRIPT_FILENAME' : req.filename,
'QUERY_STRING' : req.args or '',
'SERVER_NAME' : req.hostname,
'SERVER_PORT' : str(req.connection.local_addr[1]),
'wsgi.version' : (1, 0),
'wsgi.input' : InputWrapper(req),
'wsgi.errors' : sys.stderr,
#TODO - file_wrapper support
}
environ = LazyDict(environ)
environ.lazyset('wsgi.url_scheme', self.get_scheme, [])
environ.lazyset('modpy.env', self.env, [])
environ.lazyset('modpy.opts', req.get_options, [])
environ.lazyset('modpy.conf', req.get_config, [])
environ.lazyset('SCRIPT_NAME', self.script_name, [], cache=True)
env_keys = ['SSL_CLIENT_VERIFY', 'HTTPS', 'SSL_CLIENT_S_DN']
for key in env_keys:
environ.lazyset(key, self.envget, [key])
# The component of the DN used for the username is usually the CN,
# but it is configurable.
# Allow retrieval of some common DN components from the environment.
for comp in ['C', 'ST', 'L', 'O', 'OU', 'CN', 'Email']:
key = 'SSL_CLIENT_S_DN_' + comp
environ.lazyset(key, self.envget, [key])
#gather the headers we care about
for key in req.headers_in:
k2 = key.upper()
k2 = k2.replace('-', '_')
if k2 not in ['CONTENT_TYPE', 'CONTENT_LENGTH']:
k2 = 'HTTP_' + k2
environ[k2] = req.headers_in[key]
self.environ = environ
self.set_headers = False
def env(self):
if self._env is None:
self.req.add_common_vars()
self._env = self.req.subprocess_env
return self._env
def envget(self, *args):
return self.env().get(*args)
def script_name(self):
uri = self.req.uri
path_info = self.req.path_info
if uri.endswith(path_info):
uri = uri[:-len(path_info)]
uri = uri.rstrip('/')
return uri
def get_scheme(self):
if self.envget('HTTPS') in ('yes', 'on', '1'):
return 'https'
else:
return 'http'
def no_write(self, string):
"""a fake write() callable returned by start_response
we don't use the write() callable in koji, so it will raise an error if called
"""
raise RuntimeError, "wsgi write() callable not supported"
def start_response(self, status, headers, exc_info=None):
#XXX we don't deal with exc_info
if self.set_headers:
raise RuntimeError, "start_response() already called"
self.req.status = int(status[:3])
for key, val in headers:
if key.lower() == 'content-length':
self.req.set_content_length(int(val))
elif key.lower() == 'content-type':
self.req.content_type = val
else:
self.req.headers_out.add(key, val)
self.set_headers = True
return self.no_write
def run(self, handler):
try:
result = handler(self.environ, self.start_response)
self.write_result(result)
return apache.OK
except:
sys.stderr.write(''.join(traceback.format_exception(*sys.exc_info())))
sys.stderr.flush()
raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR
def write_result(self, result):
"""called by run() to handle the application's result value"""
req = self.req
write = req.write
if self.set_headers:
for chunk in result:
write(chunk)
else:
#slower version -- need to check for set_headers
for chunk in result:
if chunk and not self.set_headers:
raise RuntimeError, "write() called before start_response()"
write(data)
if not req.bytes_sent:
#application sent nothing back
req.set_content_length(0)
class InputWrapper(object):
def __init__(self, req):
self.req = req
def close(self):
pass
def read(self, size=-1):
return self.req.read(size)
def readline(self):
return self.req.readline()
def readlines(self, hint=-1):
return self.req.readlines(hint)
def __iter__(self):
line = self.readline()
while line:
yield line
line = self.readline()