mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Content server: When WSGI embedding fix handling of empty URL
This commit is contained in:
parent
e03f31d7c0
commit
403eec1609
@ -10,28 +10,28 @@ from cherrypy.lib import http as _http
|
|||||||
|
|
||||||
class VirtualHost(object):
|
class VirtualHost(object):
|
||||||
"""Select a different WSGI application based on the Host header.
|
"""Select a different WSGI application based on the Host header.
|
||||||
|
|
||||||
This can be useful when running multiple sites within one CP server.
|
This can be useful when running multiple sites within one CP server.
|
||||||
It allows several domains to point to different applications. For example:
|
It allows several domains to point to different applications. For example:
|
||||||
|
|
||||||
root = Root()
|
root = Root()
|
||||||
RootApp = cherrypy.Application(root)
|
RootApp = cherrypy.Application(root)
|
||||||
Domain2App = cherrypy.Application(root)
|
Domain2App = cherrypy.Application(root)
|
||||||
SecureApp = cherrypy.Application(Secure())
|
SecureApp = cherrypy.Application(Secure())
|
||||||
|
|
||||||
vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
|
vhost = cherrypy._cpwsgi.VirtualHost(RootApp,
|
||||||
domains={'www.domain2.example': Domain2App,
|
domains={'www.domain2.example': Domain2App,
|
||||||
'www.domain2.example:443': SecureApp,
|
'www.domain2.example:443': SecureApp,
|
||||||
})
|
})
|
||||||
|
|
||||||
cherrypy.tree.graft(vhost)
|
cherrypy.tree.graft(vhost)
|
||||||
|
|
||||||
default: required. The default WSGI application.
|
default: required. The default WSGI application.
|
||||||
|
|
||||||
use_x_forwarded_host: if True (the default), any "X-Forwarded-Host"
|
use_x_forwarded_host: if True (the default), any "X-Forwarded-Host"
|
||||||
request header will be used instead of the "Host" header. This
|
request header will be used instead of the "Host" header. This
|
||||||
is commonly added by HTTP servers (such as Apache) when proxying.
|
is commonly added by HTTP servers (such as Apache) when proxying.
|
||||||
|
|
||||||
domains: a dict of {host header value: application} pairs.
|
domains: a dict of {host header value: application} pairs.
|
||||||
The incoming "Host" request header is looked up in this dict,
|
The incoming "Host" request header is looked up in this dict,
|
||||||
and, if a match is found, the corresponding WSGI application
|
and, if a match is found, the corresponding WSGI application
|
||||||
@ -39,17 +39,17 @@ class VirtualHost(object):
|
|||||||
separate entries for "example.com" and "www.example.com".
|
separate entries for "example.com" and "www.example.com".
|
||||||
In addition, "Host" headers may contain the port number.
|
In addition, "Host" headers may contain the port number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, default, domains=None, use_x_forwarded_host=True):
|
def __init__(self, default, domains=None, use_x_forwarded_host=True):
|
||||||
self.default = default
|
self.default = default
|
||||||
self.domains = domains or {}
|
self.domains = domains or {}
|
||||||
self.use_x_forwarded_host = use_x_forwarded_host
|
self.use_x_forwarded_host = use_x_forwarded_host
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
domain = environ.get('HTTP_HOST', '')
|
domain = environ.get('HTTP_HOST', '')
|
||||||
if self.use_x_forwarded_host:
|
if self.use_x_forwarded_host:
|
||||||
domain = environ.get("HTTP_X_FORWARDED_HOST", domain)
|
domain = environ.get("HTTP_X_FORWARDED_HOST", domain)
|
||||||
|
|
||||||
nextapp = self.domains.get(domain)
|
nextapp = self.domains.get(domain)
|
||||||
if nextapp is None:
|
if nextapp is None:
|
||||||
nextapp = self.default
|
nextapp = self.default
|
||||||
@ -61,10 +61,10 @@ class VirtualHost(object):
|
|||||||
|
|
||||||
|
|
||||||
class AppResponse(object):
|
class AppResponse(object):
|
||||||
|
|
||||||
throws = (KeyboardInterrupt, SystemExit)
|
throws = (KeyboardInterrupt, SystemExit)
|
||||||
request = None
|
request = None
|
||||||
|
|
||||||
def __init__(self, environ, start_response, cpapp, recursive=False):
|
def __init__(self, environ, start_response, cpapp, recursive=False):
|
||||||
self.redirections = []
|
self.redirections = []
|
||||||
self.recursive = recursive
|
self.recursive = recursive
|
||||||
@ -72,7 +72,7 @@ class AppResponse(object):
|
|||||||
self.start_response = start_response
|
self.start_response = start_response
|
||||||
self.cpapp = cpapp
|
self.cpapp = cpapp
|
||||||
self.setapp()
|
self.setapp()
|
||||||
|
|
||||||
def setapp(self):
|
def setapp(self):
|
||||||
try:
|
try:
|
||||||
self.request = self.get_request()
|
self.request = self.get_request()
|
||||||
@ -91,14 +91,14 @@ class AppResponse(object):
|
|||||||
if getattr(self.request, "throw_errors", False):
|
if getattr(self.request, "throw_errors", False):
|
||||||
self.close()
|
self.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
tb = _cperror.format_exc()
|
tb = _cperror.format_exc()
|
||||||
_cherrypy.log(tb, severity=40)
|
_cherrypy.log(tb, severity=40)
|
||||||
if not getattr(self.request, "show_tracebacks", True):
|
if not getattr(self.request, "show_tracebacks", True):
|
||||||
tb = ""
|
tb = ""
|
||||||
s, h, b = _cperror.bare_error(tb)
|
s, h, b = _cperror.bare_error(tb)
|
||||||
self.iter_response = iter(b)
|
self.iter_response = iter(b)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.start_response(s, h, _sys.exc_info())
|
self.start_response(s, h, _sys.exc_info())
|
||||||
except:
|
except:
|
||||||
@ -110,10 +110,10 @@ class AppResponse(object):
|
|||||||
_cherrypy.log(traceback=True, severity=40)
|
_cherrypy.log(traceback=True, severity=40)
|
||||||
self.close()
|
self.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def iredirect(self, path, query_string):
|
def iredirect(self, path, query_string):
|
||||||
"""Doctor self.environ and perform an internal redirect.
|
"""Doctor self.environ and perform an internal redirect.
|
||||||
|
|
||||||
When cherrypy.InternalRedirect is raised, this method is called.
|
When cherrypy.InternalRedirect is raised, this method is called.
|
||||||
It rewrites the WSGI environ using the new path and query_string,
|
It rewrites the WSGI environ using the new path and query_string,
|
||||||
and calls a new CherryPy Request object. Because the wsgi.input
|
and calls a new CherryPy Request object. Because the wsgi.input
|
||||||
@ -123,7 +123,7 @@ class AppResponse(object):
|
|||||||
formed from InternalRedirect.query_string when using that exception.
|
formed from InternalRedirect.query_string when using that exception.
|
||||||
If you need something more complicated, make and raise your own
|
If you need something more complicated, make and raise your own
|
||||||
exception and write your own AppResponse subclass to trap it. ;)
|
exception and write your own AppResponse subclass to trap it. ;)
|
||||||
|
|
||||||
It would be a bad idea to redirect after you've already yielded
|
It would be a bad idea to redirect after you've already yielded
|
||||||
response content, although an enterprising soul could choose
|
response content, although an enterprising soul could choose
|
||||||
to abuse this.
|
to abuse this.
|
||||||
@ -145,19 +145,19 @@ class AppResponse(object):
|
|||||||
if qs:
|
if qs:
|
||||||
qs = "?" + qs
|
qs = "?" + qs
|
||||||
self.redirections.append(sn + p + qs)
|
self.redirections.append(sn + p + qs)
|
||||||
|
|
||||||
# Munge environment and try again.
|
# Munge environment and try again.
|
||||||
env['REQUEST_METHOD'] = "GET"
|
env['REQUEST_METHOD'] = "GET"
|
||||||
env['PATH_INFO'] = path
|
env['PATH_INFO'] = path
|
||||||
env['QUERY_STRING'] = query_string
|
env['QUERY_STRING'] = query_string
|
||||||
env['wsgi.input'] = _StringIO.StringIO()
|
env['wsgi.input'] = _StringIO.StringIO()
|
||||||
env['CONTENT_LENGTH'] = "0"
|
env['CONTENT_LENGTH'] = "0"
|
||||||
|
|
||||||
self.setapp()
|
self.setapp()
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
try:
|
try:
|
||||||
chunk = self.iter_response.next()
|
chunk = self.iter_response.next()
|
||||||
@ -180,7 +180,7 @@ class AppResponse(object):
|
|||||||
if getattr(self.request, "throw_errors", False):
|
if getattr(self.request, "throw_errors", False):
|
||||||
self.close()
|
self.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
tb = _cperror.format_exc()
|
tb = _cperror.format_exc()
|
||||||
_cherrypy.log(tb, severity=40)
|
_cherrypy.log(tb, severity=40)
|
||||||
if not getattr(self.request, "show_tracebacks", True):
|
if not getattr(self.request, "show_tracebacks", True):
|
||||||
@ -188,7 +188,7 @@ class AppResponse(object):
|
|||||||
s, h, b = _cperror.bare_error(tb)
|
s, h, b = _cperror.bare_error(tb)
|
||||||
# Empty our iterable (so future calls raise StopIteration)
|
# Empty our iterable (so future calls raise StopIteration)
|
||||||
self.iter_response = iter([])
|
self.iter_response = iter([])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.start_response(s, h, _sys.exc_info())
|
self.start_response(s, h, _sys.exc_info())
|
||||||
except:
|
except:
|
||||||
@ -200,13 +200,13 @@ class AppResponse(object):
|
|||||||
_cherrypy.log(traceback=True, severity=40)
|
_cherrypy.log(traceback=True, severity=40)
|
||||||
self.close()
|
self.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return "".join(b)
|
return "".join(b)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Close and de-reference the current request and response. (Core)"""
|
"""Close and de-reference the current request and response. (Core)"""
|
||||||
self.cpapp.release_serving()
|
self.cpapp.release_serving()
|
||||||
|
|
||||||
def get_response(self):
|
def get_response(self):
|
||||||
"""Run self.request and return its response."""
|
"""Run self.request and return its response."""
|
||||||
meth = self.environ['REQUEST_METHOD']
|
meth = self.environ['REQUEST_METHOD']
|
||||||
@ -218,11 +218,11 @@ class AppResponse(object):
|
|||||||
rfile = self.environ['wsgi.input']
|
rfile = self.environ['wsgi.input']
|
||||||
response = self.request.run(meth, path, qs, rproto, headers, rfile)
|
response = self.request.run(meth, path, qs, rproto, headers, rfile)
|
||||||
return response.status, response.header_list, response.body
|
return response.status, response.header_list, response.body
|
||||||
|
|
||||||
def get_request(self):
|
def get_request(self):
|
||||||
"""Create a Request object using environ."""
|
"""Create a Request object using environ."""
|
||||||
env = self.environ.get
|
env = self.environ.get
|
||||||
|
|
||||||
local = _http.Host('', int(env('SERVER_PORT', 80)),
|
local = _http.Host('', int(env('SERVER_PORT', 80)),
|
||||||
env('SERVER_NAME', ''))
|
env('SERVER_NAME', ''))
|
||||||
remote = _http.Host(env('REMOTE_ADDR', ''),
|
remote = _http.Host(env('REMOTE_ADDR', ''),
|
||||||
@ -231,7 +231,7 @@ class AppResponse(object):
|
|||||||
scheme = env('wsgi.url_scheme')
|
scheme = env('wsgi.url_scheme')
|
||||||
sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
|
sproto = env('ACTUAL_SERVER_PROTOCOL', "HTTP/1.1")
|
||||||
request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
|
request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)
|
||||||
|
|
||||||
# LOGON_USER is served by IIS, and is the name of the
|
# LOGON_USER is served by IIS, and is the name of the
|
||||||
# user after having been mapped to a local account.
|
# user after having been mapped to a local account.
|
||||||
# Both IIS and Apache set REMOTE_USER, when possible.
|
# Both IIS and Apache set REMOTE_USER, when possible.
|
||||||
@ -241,14 +241,14 @@ class AppResponse(object):
|
|||||||
request.wsgi_environ = self.environ
|
request.wsgi_environ = self.environ
|
||||||
request.prev = env('cherrypy.previous_request', None)
|
request.prev = env('cherrypy.previous_request', None)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
|
headerNames = {'HTTP_CGI_AUTHORIZATION': 'Authorization',
|
||||||
'CONTENT_LENGTH': 'Content-Length',
|
'CONTENT_LENGTH': 'Content-Length',
|
||||||
'CONTENT_TYPE': 'Content-Type',
|
'CONTENT_TYPE': 'Content-Type',
|
||||||
'REMOTE_HOST': 'Remote-Host',
|
'REMOTE_HOST': 'Remote-Host',
|
||||||
'REMOTE_ADDR': 'Remote-Addr',
|
'REMOTE_ADDR': 'Remote-Addr',
|
||||||
}
|
}
|
||||||
|
|
||||||
def translate_headers(self, environ):
|
def translate_headers(self, environ):
|
||||||
"""Translate CGI-environ header names to HTTP header names."""
|
"""Translate CGI-environ header names to HTTP header names."""
|
||||||
for cgiName in environ:
|
for cgiName in environ:
|
||||||
@ -263,43 +263,47 @@ class AppResponse(object):
|
|||||||
|
|
||||||
class CPWSGIApp(object):
|
class CPWSGIApp(object):
|
||||||
"""A WSGI application object for a CherryPy Application.
|
"""A WSGI application object for a CherryPy Application.
|
||||||
|
|
||||||
pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
|
pipeline: a list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
|
||||||
constructor that takes an initial, positional 'nextapp' argument,
|
constructor that takes an initial, positional 'nextapp' argument,
|
||||||
plus optional keyword arguments, and returns a WSGI application
|
plus optional keyword arguments, and returns a WSGI application
|
||||||
(that takes environ and start_response arguments). The 'name' can
|
(that takes environ and start_response arguments). The 'name' can
|
||||||
be any you choose, and will correspond to keys in self.config.
|
be any you choose, and will correspond to keys in self.config.
|
||||||
|
|
||||||
head: rather than nest all apps in the pipeline on each call, it's only
|
head: rather than nest all apps in the pipeline on each call, it's only
|
||||||
done the first time, and the result is memoized into self.head. Set
|
done the first time, and the result is memoized into self.head. Set
|
||||||
this to None again if you change self.pipeline after calling self.
|
this to None again if you change self.pipeline after calling self.
|
||||||
|
|
||||||
config: a dict whose keys match names listed in the pipeline. Each
|
config: a dict whose keys match names listed in the pipeline. Each
|
||||||
value is a further dict which will be passed to the corresponding
|
value is a further dict which will be passed to the corresponding
|
||||||
named WSGI callable (from the pipeline) as keyword arguments.
|
named WSGI callable (from the pipeline) as keyword arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pipeline = []
|
pipeline = []
|
||||||
head = None
|
head = None
|
||||||
config = {}
|
config = {}
|
||||||
|
|
||||||
response_class = AppResponse
|
response_class = AppResponse
|
||||||
|
|
||||||
def __init__(self, cpapp, pipeline=None):
|
def __init__(self, cpapp, pipeline=None):
|
||||||
self.cpapp = cpapp
|
self.cpapp = cpapp
|
||||||
self.pipeline = self.pipeline[:]
|
self.pipeline = self.pipeline[:]
|
||||||
if pipeline:
|
if pipeline:
|
||||||
self.pipeline.extend(pipeline)
|
self.pipeline.extend(pipeline)
|
||||||
self.config = self.config.copy()
|
self.config = self.config.copy()
|
||||||
|
|
||||||
def tail(self, environ, start_response):
|
def tail(self, environ, start_response):
|
||||||
"""WSGI application callable for the actual CherryPy application.
|
"""WSGI application callable for the actual CherryPy application.
|
||||||
|
|
||||||
You probably shouldn't call this; call self.__call__ instead,
|
You probably shouldn't call this; call self.__call__ instead,
|
||||||
so that any WSGI middleware in self.pipeline can run first.
|
so that any WSGI middleware in self.pipeline can run first.
|
||||||
"""
|
"""
|
||||||
|
# Changed by Kovid as the routes dispatcher cannot handle an empty
|
||||||
|
# PATH_INFO
|
||||||
|
if not environ.get('PATH_INFO', True):
|
||||||
|
environ['PATH_INFO'] = '/'
|
||||||
return self.response_class(environ, start_response, self.cpapp)
|
return self.response_class(environ, start_response, self.cpapp)
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
head = self.head
|
head = self.head
|
||||||
if head is None:
|
if head is None:
|
||||||
@ -311,7 +315,7 @@ class CPWSGIApp(object):
|
|||||||
head = callable(head, **conf)
|
head = callable(head, **conf)
|
||||||
self.head = head
|
self.head = head
|
||||||
return head(environ, start_response)
|
return head(environ, start_response)
|
||||||
|
|
||||||
def namespace_handler(self, k, v):
|
def namespace_handler(self, k, v):
|
||||||
"""Config handler for the 'wsgi' namespace."""
|
"""Config handler for the 'wsgi' namespace."""
|
||||||
if k == "pipeline":
|
if k == "pipeline":
|
||||||
|
Loading…
x
Reference in New Issue
Block a user