Content server: Allow running of content server as a WSGI application within another server. Add tutorial for this to the User Manual.

This commit is contained in:
Kovid Goyal 2010-10-28 15:26:19 -06:00
parent 3da2ef7230
commit 89e7c15ea5
7 changed files with 154 additions and 30 deletions

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' Code to manage ebook library'''
def db():
def db(path=None):
from calibre.library.database2 import LibraryDatabase2
from calibre.utils.config import prefs
return LibraryDatabase2(prefs['library_path'])
return LibraryDatabase2(path if path else prefs['library_path'])

View File

@ -28,11 +28,13 @@ from calibre.library.server.browse import BrowseServer
class DispatchController(object): # {{{
def __init__(self, prefix):
def __init__(self, prefix, wsgi=False):
self.dispatcher = cherrypy.dispatch.RoutesDispatcher()
self.funcs = []
self.seen = set([])
self.prefix = prefix if prefix else ''
if wsgi:
self.prefix = ''
def __call__(self, name, route, func, **kwargs):
if name in self.seen:
@ -96,7 +98,9 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
server_name = __appname__ + '/' + __version__
def __init__(self, db, opts, embedded=False, show_tracebacks=True):
def __init__(self, db, opts, embedded=False, show_tracebacks=True,
wsgi=False):
self.is_wsgi = bool(wsgi)
self.opts = opts
self.embedded = embedded
self.state_callback = None
@ -124,25 +128,36 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
'server.socket_timeout' : opts.timeout, #seconds
'server.thread_pool' : opts.thread_pool, # number of threads
})
if embedded:
if embedded or wsgi:
cherrypy.config.update({'engine.SIGHUP' : None,
'engine.SIGTERM' : None,})
self.config = {'global': {
'tools.gzip.on' : True,
'tools.gzip.mime_types': ['text/html', 'text/plain', 'text/xml', 'text/javascript', 'text/css'],
}}
if opts.password:
self.config['/'] = {
'tools.digest_auth.on' : True,
'tools.digest_auth.realm' : (_('Password to access your calibre library. Username is ') + opts.username.strip()).encode('ascii', 'replace'),
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
}
self.config = {}
self.is_running = False
self.exception = None
self.setup_loggers()
cherrypy.engine.bonjour.subscribe()
if not wsgi:
self.setup_loggers()
cherrypy.engine.bonjour.subscribe()
self.config['global'] = {
'tools.gzip.on' : True,
'tools.gzip.mime_types': ['text/html', 'text/plain',
'text/xml', 'text/javascript', 'text/css'],
}
if opts.password:
self.config['/'] = {
'tools.digest_auth.on' : True,
'tools.digest_auth.realm' : (
_('Password to access your calibre library. Username is ')
+ opts.username.strip()),
'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()},
}
self.__dispatcher__ = DispatchController(self.opts.url_prefix, wsgi)
for x in self.__class__.__bases__:
if hasattr(x, 'add_routes'):
x.add_routes(self, self.__dispatcher__)
root_conf = self.config.get('/', {})
root_conf['request.dispatch'] = self.__dispatcher__.dispatcher
self.config['/'] = root_conf
def set_database(self, db):
self.db = db
@ -183,14 +198,6 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
def start(self):
self.is_running = False
d = DispatchController(self.opts.url_prefix)
for x in self.__class__.__bases__:
if hasattr(x, 'add_routes'):
x.add_routes(self, d)
root_conf = self.config.get('/', {})
root_conf['request.dispatch'] = d.dispatcher
self.config['/'] = root_conf
cherrypy.tree.mount(root=None, config=self.config)
try:
try:

View File

@ -460,13 +460,14 @@ class BrowseServer(object):
@Endpoint()
def browse_catalog(self, category=None, category_sort=None):
'Entry point for top-level, categories and sub-categories'
prefix = '' if self.is_wsgi else self.opts.url_prefix
if category == None:
ans = self.browse_toplevel()
elif category == 'newest':
raise cherrypy.InternalRedirect(self.opts.url_prefix +
raise cherrypy.InternalRedirect(prefix +
'/browse/matches/newest/dummy')
elif category == 'allbooks':
raise cherrypy.InternalRedirect(self.opts.url_prefix +
raise cherrypy.InternalRedirect(prefix +
'/browse/matches/allbooks/dummy')
else:
ans = self.browse_category(category, category_sort)

View File

@ -24,6 +24,17 @@ def stop_threaded_server(server):
server.exit()
server.thread = None
def create_wsgi_app(path_to_library=None, prefix=''):
'WSGI entry point'
from calibre.library import db
cherrypy.config.update({'environment': 'embedded'})
db = db(path_to_library)
parser = option_parser()
opts, args = parser.parse_args(['calibre-server'])
opts.url_prefix = prefix
server = LibraryServer(db, opts, wsgi=True, show_tracebacks=True)
return cherrypy.Application(server, script_name=None, config=server.config)
def option_parser():
parser = config().option_parser('%prog '+ _(
'''[options]

View File

@ -121,7 +121,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS,
A(
fmt.lower(),
href=prefix+'/get/%s/%s-%s_%d.%s' % (fmt, a, t,
book['id'], fmt)
book['id'], fmt.lower())
),
CLASS('button'))
s.tail = u''

View File

@ -0,0 +1,104 @@
.. include:: global.rst
.. _servertutorial:
Integrating the |app| content server into other servers
==========================================================
Here, we will show you how to integrate the |app| content server into another server. The most common reason for this is to make use of SSL or more sophisticated authentication. There are two main techniques: Running the |app| content server as a standalone process and using a reverse proxy to connect it with your main server or running the content server in process in your main server with WSGI. The examples below are all for Apache 2.x on linux, but should be easily adaptable to other platforms.
.. contents:: Contents
:depth: 2
:local:
Using a reverse proxy
-----------------------
This is the simplest approach as it allows you to use the binary calibre install with no external dependencies/system integration requirements.
First start the |app| content server as shown below::
calibre-server --url-prefix /calibre --port 8080
Now suppose you are using Apache as your main server. First enable the proxy modules in apache, by adding the following to :file:`httpd.conf`::
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
The exact technique for enabling the proxy modules will vary depending on your Apache installation. Once you have the proxy modules enabled, add the following rules to httpd.conf (or if you are using virtual hosts to the conf file for the virtual host in question::
RewriteEngine on
RewriteRule ^/calibre/(.*) http://localhost:8080/calibre/$1 [proxy]
RewriteRule ^/calibre http://localhost:8080 [proxy]
That's all, you will now be able to access the |app| Content Server under the /calibre URL in your apache server.
.. note:: If you are willing to devote an entire VirtualHost to the content server, then there is no need to use --url-prefix and RewriteRule, instead just use the ProxyPass directive.
Using WSGI
------------
The calibre content server can be run directly, in process, inside a host server like Apache using the WSGI framework.
First, we have to create a WSGI *adapter* for the calibre content server. Here is a template you can use for the purpose. Replace the paths as directed in the comments
.. code-block:: python
# WSGI script file to run calibre content server as a WSGI app
import sys, os
# You can get the paths referenced here by running
# calibre-debug --paths
# on your server
# The first entry from CALIBRE_PYTHON_PATH
sys.path.insert(0, '/home/kovid/work/calibre/src')
# CALIBRE_RESOURCES_PATH
sys.resources_location = '/home/kovid/work/calibre/resources'
# CALIBRE_EXTENSIONS_PATH
sys.extensions_location = '/home/kovid/work/calibre/src/calibre/plugins'
# Path to directory containing calibre executables
sys.executables_location = '/usr/bin'
# Path to a directory for which the server has read/write permissions
# calibre config will be stored here
os.environ['CALIBRE_CONFIG_DIRECTORY'] = '/var/www/localhost/calibre-config'
del sys
del os
from calibre.library.server.main import create_wsgi_app
application = create_wsgi_app(
# The mount point of this WSGI application (i.e. the first argument to
# the WSGIScriptAlias directive). Set to empty string is mounted at /
prefix='/calibre',
# Path to the calibre library to be served
# The server process must have write permission for all files/dirs
# in this directory or BAD things will happen
path_to_library='/home/kovid/documents/demo library'
)
del create_wsgi_app
Save this adapter as :file:`calibre-wsgi-adpater.py` somewhere your server will have access to it.
Let's suppose that we want to use WSGI in Apache. First enable WSGI in Apache by adding the following to :file:`httpd.conf`::
LoadModule proxy_module modules/mod_wsgi.so
The exact technique for enabling the wsgi module will vary depending on your Apache installation. Once you have the proxy modules enabled, add the following rules to httpd.conf (or if you are using virtual hosts to the conf file for the virtual host in question::
WSGIScriptAlias /calibre /var/www/localhost/cgi-bin/calibre-wsgi-adapter.py
Change the path to :file:`calibre-wsgi-adapter.py` to wherever you saved it previously (make sure Apache has access to it).
That's all, you will now be able to access the |app| Content Server under the /calibre URL in your apache server.
.. note:: For more help with using mod_wsgi in Apache, see `mod_wsgi <http://code.google.com/p/modwsgi/wiki/WhereToGetHelp>`_.

View File

@ -16,4 +16,5 @@ Here you will find tutorials to get you started using |app|'s more advanced feat
template_lang
regexp
portable
server