325 changes: 325 additions & 0 deletions tests/src/python/qgis_local_server_spawn/flup/ChangeLog
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
2011-04-04 Allan Saddi <allan@saddi.com>

* Add threadpool options to Paste factories.

2011-02-19 Allan Saddi <allan@saddi.com>

* When deriving PATH_INFO from REQUEST_URI, take SCRIPT_NAME into account.

2011-01-11 Allan Saddi <allan@saddi.com>

* Use HTTP status code 500 for error pages. Thanks to
Yohann Gabory for pointing out this issue and providing a patch.

2010-10-14 Allan Saddi <allan@saddi.com>

* Don't try to restore signal handlers if they weren't installed in
the first place.

2010-10-05 Allan Saddi <allan@saddi.com>

* Improvements to *_app._getConnection methods suggested by
Andrej A Antonov. Thanks!

2009-10-27 Allan Saddi <allan@saddi.com>

* Exit gracefully if a thread cannot be started when adding a new
job.

2009-10-21 Allan Saddi <allan@saddi.com>

* Add configurable timeout (default: no timeout) to be used when the
WSGI application is called. Only applies to forked servers!

2009-06-05 Allan Saddi <allan@saddi.com>

* Fix bug in scgi servers that occurs when SCRIPT_NAME is missing.
Thanks to Jon Nelson for finding the problem!

2009-05-29 Allan Saddi <allan@saddi.com>

* Let all the active requests to finish before quitting. Thanks
to Anand Chitipothu for the patch!

2009-05-26 Allan Saddi <allan@saddi.com>

* Release 1.0.2

2009-05-18 Allan Saddi <allan@saddi.com>

* Import Paste factories (and dependencies...) from PasteScript

2009-05-04 Allan Saddi <allan@saddi.com>

* Be tolerant of EAGAIN when sending messages to parent process.

2009-02-02 Allan Saddi <allan@saddi.com>

* Add forceCGI keyword argument to FastCGI servers to
programmatically force CGI behavior.

* Merge Tommi Virtanen's "single server" (sequential server)
patch.

2008-12-03 Allan Saddi <allan@saddi.com>

* Update ez_setup.py.

2008-09-26 Allan Saddi <allan@saddi.com>

* Re-seed random module after each fork.

2008-09-11 Allan Saddi <allan@saddi.com>

* Add an indication as to which header fails assertion when
passing in non-string header names and/or values.

2008-08-20 Allan Saddi <allan@saddi.com>

* Add support for setting umask for UNIX domain sockets from
paste.server_factory implementations. Thanks to Michal Suszko
for the patch.

2008-07-23 Allan Saddi <allan@saddi.com>

* Add support for configuring UNIX domain sockets (for servers that
support them) in the paste.server_factory implementations. Thanks
to Dan Roberts for the code.

2008-07-22 Allan Saddi <allan@saddi.com>

* Release 1.0.1

* Attempt to deduce missing PATH_INFO and/or QUERY_STRING from
REQUEST_URI, if present. Patch provided by Richard Davies.

2007-09-10 Allan Saddi <allan@saddi.com>

* Fix readline implementations so size argument is checked
earlier.

2007-07-14 Allan Saddi <allan@saddi.com>

* Prevent ThreadPool inconsistences if an exception is
actually raised. Thanks to Tim Chen for the patch.

2007-06-05 Allan Saddi <allan@saddi.com>

* Remove publisher and middleware packages.
* Add cgi server for completeness.

2007-05-17 Allan Saddi <allan@saddi.com>

* Fix fcgi_fork so it can run on Solaris. Thanks to
Basil Crow for the patch.

2007-01-22 Allan Saddi <allan@saddi.com>

* Fix eunuchs import issue.

2007-01-10 Allan Saddi <allan@saddi.com>

* Support gzip compression of XHTML pages using the
correct MIME type.

2006-12-29 Allan Saddi <allan@saddi.com>

* Deprecate WSGI_SCRIPT_NAME and scriptName in scgi_base.
Modern versions of mod_scgi correctly set SCRIPT_NAME &
PATH_INFO.

2006-12-13 Allan Saddi <allan@saddi.com>

* Fix problem in session.py seen when optimization is on.

2006-12-05 Allan Saddi <allan@saddi.com>

* Update servers to default to an empty QUERY_STRING if
not present in the environ.
* Update gzip.py: compresslevel -> compress_level
* Update gzip.py by updating docstrings and renaming
classes/methods/functions to better follow Python naming
conventions. NB: mimeTypes keyword parameter is now
mime_types.

2006-12-02 Allan Saddi <allan@saddi.com>

* Change intra-package imports into absolute imports.

2006-12-02 Allan Saddi <allan@saddi.com>

* Add forceCookieOutput attribute to SessionService to
force Set-Cookie output for the current request.

2006-12-01 Allan Saddi <allan@saddi.com>

* Update setup script.

2006-11-26 Allan Saddi <allan@saddi.com>

* Don't attempt to install signal handlers under Windows
to improve compatibility.

2006-11-24 Allan Saddi <allan@saddi.com>

* Add *_thread egg entry-point aliases.
* Add UNIX domain socket support to scgi, scgi_fork,
scgi_app.
* Add flup.client package which contains various
WSGI -> connector client implentations. (So far: FastCGI,
and SCGI.)

2006-11-19 Allan Saddi <allan@saddi.com>

* Change mime-type matching algorithm in GzipMiddleware.
Strip parameters (e.g. "encoding") and accept a list of
regexps. By default, compress 'text/.*' mime-types.

2006-11-10 Allan Saddi <allan@saddi.com>

* Add cookieAttributes to SessionService to make it easier
to customize the generated cookie's attributes.

2006-08-28 Allan Saddi <allan@saddi.com>

* Add support for FastCGI roles other than FCGI_RESPONDER.
Patch provided by Seairth Jacobs.

2006-08-02 Allan Saddi <allan@saddi.com>

* Add cookieExpiration keyword to SessionService /
SessionMiddleware to adjust the session cookie's expiration.
Thanks to Blaise Laflamme for the suggestion.

2006-06-27 Allan Saddi <allan@saddi.com>

* Set close-on-exec flag on all server sockets. Thanks to
Ralf Schmitt for reporting the problem.

2006-06-18 Allan Saddi <allan@saddi.com>

* Stop ignoring EPIPE exceptions, as this is probably the
wrong thing to do. (Application is unaware of disconnected
clients and the CPU spins when sending large files to a
disconnected client.) Thanks to Ivan Sagalaev for bringing
this to my attention.

NB: Existing applications that use the flup servers may begin
seeing socket.error exceptions...

2006-05-18 Allan Saddi <allan@saddi.com>

* Added umask keyword parameter to fcgi and fcgi_fork,
for use when binding to a UNIX socket.

2006-05-03 Allan Saddi <allan@saddi.com>

* Fix illusive problem with AJP implementation. Thanks to
Moshe Van der Sterre for explaining the problem and
providing a fix.

2006-04-06 Allan Saddi <allan@saddi.com>

* Catch a strange FieldStorage case. Seen in production.
Not quite sure what causes it.

2006-03-21 Allan Saddi <allan@saddi.com>

* Add maxRequests option to PreforkServer. Patch provided by
Wojtek Sobczuk.

2006-02-23 Allan Saddi <allan@saddi.com>

* Add paste.server_factory-compliant factories and respective
egg entry points. Thanks to Luis Bruno for the code.

Add debug option to servers, which is True by default.
Currently, only server-level error handling is affected.

2006-01-15 Allan Saddi <allan@saddi.com>

* Change the behavior of ImportingModuleResolver when dealing
with ImportErrors. Previously, it would act as if the module
did not exist. Now, it propagates the exception to another
level (outer middleware or WSGI). Reported by Scot Doyle.

2006-01-05 Allan Saddi <allan@saddi.com>

* Improve Windows compatibility by conditionally installing
SIGHUP handler. Thanks to Brad Miller for pointing out the
problem and providing a fix.

2005-12-19 Allan Saddi <allan@saddi.com>

* Fix socket leak in eunuchs socketpair() wrapper. Thanks to
Georg Bauer for pointing this out.

2005-12-16 Allan Saddi <allan@saddi.com>

* Switch to setuptools for egg support.
* Add higher-level 404 error page support. Thanks to Scot Doyle
for suggesting the idea and providing code. If you previously
subclassed Publisher to provide a custom 404 error page, this
is now broken. It will have to be massaged to fit the new
calling convention.

2005-11-28 Allan Saddi <allan@saddi.com>

* Fix issue with FCGI_GET_VALUES handling. Thanks to
Timothy Wright for pointing this out.

2005-11-18 Allan Saddi <allan@saddi.com>

* When running under Python < 2.4, attempt to use socketpair()
from eunuchs module.

2005-09-07 Allan Saddi <allan@saddi.com>

* Python 2.3 doesn't define socket.SHUT_WR, which affected
the closing of the FastCGI socket with the server. This would
cause output to hang. Thanks to Eugene Lazutkin for bringing
the problem to my attention and going out of his way to help
me debug it!

2005-07-03 Allan Saddi <allan@saddi.com>

* Ensure session identifiers only contain ASCII characters when
using a non-ASCII locale. Thanks to Ksenia Marasanova for the
the fix.

2005-06-12 Allan Saddi <allan@saddi.com>

* Cleanly close connection socket to avoid sending a TCP RST to
the web server. (fcgi_base) Fix suggested by Dima Barsky.

2005-05-31 Allan Saddi <allan@saddi.com>

* Take scriptName from the WSGI_SCRIPT_NAME environment variable
passed from the web server, if present.
* Check if scriptName is None, and if so, don't modify SCRIPT_NAME
& PATH_INFO. For better compatibility with cgi2scgi. (scgi_base)

2005-05-18 Allan Saddi <allan@saddi.com>

* Change default allowedServers for ajp and scgi to ['127.0.0.1'].
* Accept PATH_INFO from environment for scgi servers, in case
cgi2scgi is being used. Submitted by Ian Bicking.
* Change threaded servers so wsgi.multiprocess is False by default.
Allow it to be changed by keyword argument.
* Fix wsgi.multiprocess for scgi_fork. (Set to True.)

2005-05-15 Allan Saddi <allan@saddi.com>

* Prevent possible deadlock related to DiskSessionStore locking.
* Add logic to SessionStore so that it will block if attempting to
check out a Session that's already been checked out.

2005-05-14 Allan Saddi <allan@saddi.com>

* Convert the use of decorators in session.py to something
compatible with Python <2.4.

2005-04-23 Allan Saddi <allan@saddi.com>

* Ensure that SessionStore.checkOutSession() never returns an
invalidated Session. Reported by Rene Dudfield.
18 changes: 18 additions & 0 deletions tests/src/python/qgis_local_server_spawn/flup/PKG-INFO
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Metadata-Version: 1.0
Name: flup
Version: 1.0.3.dev-20110405
Summary: Random assortment of WSGI servers
Home-page: http://www.saddi.com/software/flup/
Author: Allan Saddi
Author-email: allan@saddi.com
License: BSD
Description: UNKNOWN
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server
Classifier: Topic :: Software Development :: Libraries :: Python Modules
6 changes: 6 additions & 0 deletions tests/src/python/qgis_local_server_spawn/flup/README_QGIS.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
2013-09-01, Larry Shaffer <larrys@dakotacarto.com>

This is a stripped-down source of flup-1.0.3.dev-20110405, specifically to act
only as a client to a locally spawned QGIS Server fcgi process, for unit tests.

flup available at http://trac.saddi.com/flup under a BSD-style license
1 change: 1 addition & 0 deletions tests/src/python/qgis_local_server_spawn/flup/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#
class NoDefault(object):
pass

__all__ = [ 'NoDefault', ]
207 changes: 207 additions & 0 deletions tests/src/python/qgis_local_server_spawn/flup/server/ajp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
"""
.. highlight:: python
:linenothreshold: 5
.. highlight:: bash
:linenothreshold: 5
ajp - an AJP 1.3/WSGI gateway.
:copyright: Copyright (c) 2005, 2006 Allan Saddi <allan@saddi.com>
All rights reserved.
:license:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS **AS IS** AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
For more information about AJP and AJP connectors for your web server, see
http://jakarta.apache.org/tomcat/connectors-doc/.
For more information about the Web Server Gateway Interface, see
http://www.python.org/peps/pep-0333.html.
Example usage::
#!/usr/bin/env python
import sys
from myapplication import app # Assume app is your WSGI application object
from ajp import WSGIServer
ret = WSGIServer(app).run()
sys.exit(ret and 42 or 0)
See the documentation for WSGIServer for more information.
About the bit of logic at the end:
Upon receiving SIGHUP, the python script will exit with status code 42. This
can be used by a wrapper script to determine if the python script should be
re-run. When a SIGINT or SIGTERM is received, the script exits with status
code 0, possibly indicating a normal exit.
Example wrapper script::
#!/bin/sh
STATUS=42
while test $STATUS -eq 42; do
python "$@" that_script_above.py
STATUS=$?
done
Example workers.properties (for mod_jk)::
worker.list=foo
worker.foo.port=8009
worker.foo.host=localhost
worker.foo.type=ajp13
Example httpd.conf (for mod_jk)::
JkWorkersFile /path/to/workers.properties
JkMount /* foo
Note that if you mount your ajp application anywhere but the root ("/"), you
SHOULD specifiy scriptName to the WSGIServer constructor. This will ensure
that SCRIPT_NAME/PATH_INFO are correctly deduced.
"""

__author__ = 'Allan Saddi <allan@saddi.com>'
__version__ = '$Revision$'

import socket
import logging

from flup.server.ajp_base import BaseAJPServer, Connection
from flup.server.threadedserver import ThreadedServer

__all__ = ['WSGIServer']

class WSGIServer(BaseAJPServer, ThreadedServer):
"""
AJP1.3/WSGI server. Runs your WSGI application as a persistant program
that understands AJP1.3. Opens up a TCP socket, binds it, and then
waits for forwarded requests from your webserver.
Why AJP? Two good reasons are that AJP provides load-balancing and
fail-over support. Personally, I just wanted something new to
implement. :)
Of course you will need an AJP1.3 connector for your webserver (e.g.
mod_jk) - see http://jakarta.apache.org/tomcat/connectors-doc/.
"""
def __init__(self, application, scriptName='', environ=None,
multithreaded=True, multiprocess=False,
bindAddress=('localhost', 8009), allowedServers=None,
loggingLevel=logging.INFO, debug=False, **kw):
"""
scriptName is the initial portion of the URL path that "belongs"
to your application. It is used to determine PATH_INFO (which doesn't
seem to be passed in). An empty scriptName means your application
is mounted at the root of your virtual host.
environ, which must be a dictionary, can contain any additional
environment variables you want to pass to your application.
bindAddress is the address to bind to, which must be a tuple of
length 2. The first element is a string, which is the host name
or IPv4 address of a local interface. The 2nd element is the port
number.
allowedServers must be None or a list of strings representing the
IPv4 addresses of servers allowed to connect. None means accept
connections from anywhere.
loggingLevel sets the logging level of the module-level logger.
"""
BaseAJPServer.__init__(self, application,
scriptName=scriptName,
environ=environ,
multithreaded=multithreaded,
multiprocess=multiprocess,
bindAddress=bindAddress,
allowedServers=allowedServers,
loggingLevel=loggingLevel,
debug=debug)
for key in ('jobClass', 'jobArgs'):
if kw.has_key(key):
del kw[key]
ThreadedServer.__init__(self, jobClass=Connection,
jobArgs=(self, None), **kw)

def run(self):
"""
Main loop. Call this after instantiating WSGIServer. SIGHUP, SIGINT,
SIGQUIT, SIGTERM cause it to cleanup and return. (If a SIGHUP
is caught, this method returns True. Returns False otherwise.)
"""
self.logger.info('%s starting up', self.__class__.__name__)

try:
sock = self._setupSocket()
except socket.error, e:
self.logger.error('Failed to bind socket (%s), exiting', e[1])
return False

ret = ThreadedServer.run(self, sock)

self._cleanupSocket(sock)
# AJP connections are more or less persistent. .shutdown() will
# not return until the web server lets go. So don't bother calling
# it...
#self.shutdown()

self.logger.info('%s shutting down%s', self.__class__.__name__,
self._hupReceived and ' (reload requested)' or '')

return ret

if __name__ == '__main__':
def test_app(environ, start_response):
"""Probably not the most efficient example."""
import cgi
start_response('200 OK', [('Content-Type', 'text/html')])
yield '<html><head><title>Hello World!</title></head>\n' \
'<body>\n' \
'<p>Hello World!</p>\n' \
'<table border="1">'
names = environ.keys()
names.sort()
for name in names:
yield '<tr><td>%s</td><td>%s</td></tr>\n' % (
name, cgi.escape(`environ[name]`))

form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ,
keep_blank_values=1)
if form.list:
yield '<tr><th colspan="2">Form data</th></tr>'

for field in form.list:
yield '<tr><td>%s</td><td>%s</td></tr>\n' % (
field.name, field.value)

yield '</table>\n' \
'</body></html>\n'

from wsgiref import validate
test_app = validate.validator(test_app)
# Explicitly set bindAddress to *:8009 for testing.
WSGIServer(test_app,
bindAddress=('', 8009), allowedServers=None,
loggingLevel=logging.DEBUG).run()
978 changes: 978 additions & 0 deletions tests/src/python/qgis_local_server_spawn/flup/server/ajp_base.py

Large diffs are not rendered by default.

180 changes: 180 additions & 0 deletions tests/src/python/qgis_local_server_spawn/flup/server/threadedserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Copyright (c) 2005 Allan Saddi <allan@saddi.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $Id$

__author__ = 'Allan Saddi <allan@saddi.com>'
__version__ = '$Revision$'

import sys
import socket
import select
import signal
import errno

try:
import fcntl
except ImportError:
def setCloseOnExec(sock):
pass
else:
def setCloseOnExec(sock):
fcntl.fcntl(sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC)

from flup.server.threadpool import ThreadPool

__all__ = ['ThreadedServer']

class ThreadedServer(object):
def __init__(self, jobClass=None, jobArgs=(), **kw):
self._jobClass = jobClass
self._jobArgs = jobArgs

self._threadPool = ThreadPool(**kw)

def run(self, sock, timeout=1.0):
"""
The main loop. Pass a socket that is ready to accept() client
connections. Return value will be True or False indiciating whether
or not the loop was exited due to SIGHUP.
"""
# Set up signal handlers.
self._keepGoing = True
self._hupReceived = False

# Might need to revisit this?
if not sys.platform.startswith('win'):
self._installSignalHandlers()

# Set close-on-exec
setCloseOnExec(sock)

# Main loop.
while self._keepGoing:
try:
r, w, e = select.select([sock], [], [], timeout)
except select.error, e:
if e[0] == errno.EINTR:
continue
raise

if r:
try:
clientSock, addr = sock.accept()
except socket.error, e:
if e[0] in (errno.EINTR, errno.EAGAIN):
continue
raise

setCloseOnExec(clientSock)

if not self._isClientAllowed(addr):
clientSock.close()
continue

# Hand off to Connection.
conn = self._jobClass(clientSock, addr, *self._jobArgs)
if not self._threadPool.addJob(conn, allowQueuing=False):
# No thread left, immediately close the socket to hopefully
# indicate to the web server that we're at our limit...
# and to prevent having too many opened (and useless)
# files.
clientSock.close()

self._mainloopPeriodic()

# Restore signal handlers.
if not sys.platform.startswith('win'):
self._restoreSignalHandlers()

# Return bool based on whether or not SIGHUP was received.
return self._hupReceived

def shutdown(self):
"""Wait for running threads to finish."""
self._threadPool.shutdown()

def _mainloopPeriodic(self):
"""
Called with just about each iteration of the main loop. Meant to
be overridden.
"""
pass

def _exit(self, reload=False):
"""
Protected convenience method for subclasses to force an exit. Not
really thread-safe, which is why it isn't public.
"""
if self._keepGoing:
self._keepGoing = False
self._hupReceived = reload

def _isClientAllowed(self, addr):
"""Override to provide access control."""
return True

# Signal handlers

def _hupHandler(self, signum, frame):
self._hupReceived = True
self._keepGoing = False

def _intHandler(self, signum, frame):
self._keepGoing = False

def _installSignalHandlers(self):
supportedSignals = [signal.SIGINT, signal.SIGTERM]
if hasattr(signal, 'SIGHUP'):
supportedSignals.append(signal.SIGHUP)

self._oldSIGs = [(x,signal.getsignal(x)) for x in supportedSignals]

for sig in supportedSignals:
if hasattr(signal, 'SIGHUP') and sig == signal.SIGHUP:
signal.signal(sig, self._hupHandler)
else:
signal.signal(sig, self._intHandler)

def _restoreSignalHandlers(self):
for signum,handler in self._oldSIGs:
signal.signal(signum, handler)

if __name__ == '__main__':
class TestJob(object):
def __init__(self, sock, addr):
self._sock = sock
self._addr = addr
def run(self):
print "Client connection opened from %s:%d" % self._addr
self._sock.send('Hello World!\n')
self._sock.setblocking(1)
self._sock.recv(1)
self._sock.close()
print "Client connection closed from %s:%d" % self._addr
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 8080))
sock.listen(socket.SOMAXCONN)
ThreadedServer(maxThreads=10, jobClass=TestJob).run(sock)
150 changes: 150 additions & 0 deletions tests/src/python/qgis_local_server_spawn/flup/server/threadpool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Copyright (c) 2005 Allan Saddi <allan@saddi.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $Id$

__author__ = 'Allan Saddi <allan@saddi.com>'
__version__ = '$Revision$'

import sys
import thread
import threading

class ThreadPool(object):
"""
Thread pool that maintains the number of idle threads between
minSpare and maxSpare inclusive. By default, there is no limit on
the number of threads that can be started, but this can be controlled
by maxThreads.
"""
def __init__(self, minSpare=1, maxSpare=5, maxThreads=sys.maxint):
self._minSpare = minSpare
self._maxSpare = maxSpare
self._maxThreads = max(minSpare, maxThreads)

self._lock = threading.Condition()
self._workQueue = []
self._idleCount = self._workerCount = maxSpare

self._threads = []
self._stop = False

# Start the minimum number of worker threads.
for i in range(maxSpare):
self._start_new_thread()

def _start_new_thread(self):
t = threading.Thread(target=self._worker)
self._threads.append(t)
t.setDaemon(True)
t.start()
return t

def shutdown(self):
"""shutdown all workers."""
self._lock.acquire()
self._stop = True
self._lock.notifyAll()
self._lock.release()

# wait for all threads to finish
for t in self._threads[:]:
t.join()

def addJob(self, job, allowQueuing=True):
"""
Adds a job to the work queue. The job object should have a run()
method. If allowQueuing is True (the default), the job will be
added to the work queue regardless if there are any idle threads
ready. (The only way for there to be no idle threads is if maxThreads
is some reasonable, finite limit.)
Otherwise, if allowQueuing is False, and there are no more idle
threads, the job will not be queued.
Returns True if the job was queued, False otherwise.
"""
self._lock.acquire()
try:
# Maintain minimum number of spares.
while self._idleCount < self._minSpare and \
self._workerCount < self._maxThreads:
try:
self._start_new_thread()
except thread.error:
return False
self._workerCount += 1
self._idleCount += 1

# Hand off the job.
if self._idleCount or allowQueuing:
self._workQueue.append(job)
self._lock.notify()
return True
else:
return False
finally:
self._lock.release()

def _worker(self):
"""
Worker thread routine. Waits for a job, executes it, repeat.
"""
self._lock.acquire()
try:
while True:
while not self._workQueue and not self._stop:
self._lock.wait()

if self._stop:
return

# We have a job to do...
job = self._workQueue.pop(0)

assert self._idleCount > 0
self._idleCount -= 1

self._lock.release()

try:
job.run()
except:
# FIXME: This should really be reported somewhere.
# But we can't simply report it to stderr because of fcgi
pass

self._lock.acquire()

if self._idleCount == self._maxSpare:
break # NB: lock still held
self._idleCount += 1
assert self._idleCount <= self._maxSpare

# Die off...
assert self._workerCount > self._maxSpare
self._threads.remove(threading.currentThread())
self._workerCount -= 1
finally:
self._lock.release()
473 changes: 473 additions & 0 deletions tests/src/python/qgis_local_server_spawn/flup_fcgi_client.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python
"""
################################################################################
# 2013-09-01 Larry Shaffer <larrys@dakotacarto.com>
#
# NOTE: THIS IS ONLY A DEVELOPMENT TESTING SCRIPT. TO BE REMOVED AT LATER DATE.
################################################################################
<description>
This file is part of ZTC and distributed under the same license.
http://bitbucket.org/rvs/ztc/
Copyright (c) 2011 Vladimir Rusinov <vladimir@greenmice.info>
"""

import flup_fcgi_client as fcgi_client
# from flup.client.fcgi_app import FCGIApp as fcgi_client

def load_page(fcgi_host,fcgi_port, script, query):
""" load fastcgi page """
try:
fcgi = fcgi_client.FCGIApp(host = fcgi_host,
port = fcgi_port)
env = {
'SCRIPT_FILENAME': script,
'QUERY_STRING': query,
'REQUEST_METHOD': 'GET',
'SCRIPT_NAME': script,
'REQUEST_URI': script + '?' + query,
'GATEWAY_INTERFACE': 'CGI/1.1',
'SERVER_SOFTWARE': '',
'REDIRECT_STATUS': '200',
'CONTENT_TYPE': '',
'CONTENT_LENGTH': '0',
'DOCUMENT_ROOT': '/qgisserver/',
'SERVER_ADDR': fcgi_host,
'SERVER_PORT': str(fcgi_port),
'SERVER_PROTOCOL': 'HTTP/1.0',
'SERVER_NAME': fcgi_host
}
ret = fcgi(env)
return ret
except:
print 'fastcgi load failed'
return '500', [], '', ''


if __name__ == "__main__":
fcgi_host = '127.0.0.1'
fcgi_port = '8448'
script = 'qgis_mapserv.fcgi'
query = 'SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities&MAP=/test-projects/tests/pal_test.qgs'
print load_page(fcgi_host,fcgi_port, script, query)[2]
2 changes: 2 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
jan kneschke <jan@kneschke.de>
stefan bühler <lighttpd@stbuehler.de>
20 changes: 20 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0 FATAL_ERROR)

cmake_policy(VERSION 2.6.0)

PROJECT(spawn-fcgi)
SET(PACKAGE_NAME ${CMAKE_PROJECT_NAME})
SET(PACKAGE_VERSION 1.6.4)

SET(CMAKE_MAN_DIR "share/man" CACHE STRING
"Install location for man pages (relative to prefix).")
MARK_AS_ADVANCED(CMAKE_MAN_DIR)

# Set Default build type to RelWithDebInfo
IF("${CMAKE_BUILD_TYPE}" STREQUAL "")
SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE)
ENDIF("${CMAKE_BUILD_TYPE}" STREQUAL "")

ADD_SUBDIRECTORY(src)

INSTALL(FILES spawn-fcgi.1 DESTINATION ${CMAKE_MAN_DIR}/man1)
31 changes: 31 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/COPYING
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@


Copyright (c) 2004, Jan Kneschke, incremental
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

- Neither the name of the 'incremental' nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SUBDIRS=src doc

EXTRA_DIST=autogen.sh spawn-fcgi.1 CMakeLists.txt
man1_MANS=spawn-fcgi.1
44 changes: 44 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/NEWS
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

====
NEWS
====

- 1.6.4 -
* Use octal mode for -M (patch by dfjoerg)
* Add -b backlog option (fixes #2422, patch by aschmitz)

- 1.6.3 - 2009-09-23
* Fix unix socket mode change to work without specifying user/group for socket
* Add some ./run script examples for use with daemontools or runit
* Fix Invalid Argument in chmod if mode=-1 (fixes #2033)
* Add deprecated and /bin/sh info for -f option; wrap syntax output (fixes #2044)
* Add run script examples in automake dist build

- 1.6.2 - 2009-04-18
* Add homepage to README
* Add IPv6 support
* Fix problems with usernames starting with a digit and non-existent uids; add warning if only user privs are dropped. (fixes #1959)
* Add check to link against socket/nsl if needed (fixes #1960)
* List IPv6 as feature after the version if it is supported

- 1.6.1 - 2009-03-29

* Add build date to show-version
* Added options to chown/chmod the socket and to create the socket before chroot() (fixes #1906)
* Updated man page
* Add proper SUID bit detection
* Added option to change the directory before spawning (fixes #1847)

- 1.6.0 - 2009-02-28

* Separated spawn-fcgi from lighttpd
* Remove limits for php children; per default the PHP_FCGI_CHILDREN var is not changed (php defaults to no children, one worker)
* Modified the log messages format (more details on errors, no source line)
* Only try to connect to unix socket (not tcp) before spawning (fixes again #1575)
* Only disconnect from terminal in fork mode (keep stderr/stdout open in nofork mode)
* Allow numerical user and group ids for -u/-g (fixes #1141)
* Ignore pid-file option in no-fork mode (instead of producing empty file)
* Fix error handling for unix-socket-connect test
* Man page update
* Use header include order from 1.4.x
* Fix segfault due to uninitialized var
41 changes: 41 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

==========
spawn-fcgi
==========

:authors: Jan Kneschke, Stefan Bühler

:homepage:
http://redmine.lighttpd.net/projects/spawn-fcgi

:abstract:
spawn-fcgi is used to spawn FastCGI applications

Features
--------
- binds to IPv4/IPv6 and Unix domain sockets
- supports privilege separation: chmod/chown socket, drop to uid/gid
- supports chroot
- supports daemontools supervise

Build
=====

If ./configure is missing, run ./autogen.sh.

./configure
make
make install

Alternatively you can use the cmake build system (may not work
on every platform):

cmake .
make
make install


Usage
=====

See man page.
24 changes: 24 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/autogen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh
# Run this to generate all the initial makefiles, etc.

ACLOCAL=${ACLOCAL:-aclocal}
AUTOHEADER=${AUTOHEADER:-autoheader}
AUTOMAKE=${AUTOMAKE:-automake}
AUTOMAKE_FLAGS="--add-missing --copy"
AUTOCONF=${AUTOCONF:-autoconf}

ARGV0=$0

set -e


run() {
echo "$ARGV0: running \`$@'"
$@
}

run $ACLOCAL $ACLOCAL_FLAGS
run $AUTOHEADER
run $AUTOMAKE $AUTOMAKE_FLAGS
run $AUTOCONF
echo "Now type './configure ...' and 'make' to compile."
88 changes: 88 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/configure.ac
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.61)
AC_INIT([spawn-fcgi],[1.6.4])
AC_CONFIG_SRCDIR([src/spawn-fcgi.c])
AC_CONFIG_HEADER([config.h])

AM_INIT_AUTOMAKE([-Wall -Werror foreign])

# Checks for programs.
AC_PROG_CC
AC_PROG_MAKE_SET

# Checks for libraries.

# Checks for header files.
AC_HEADER_STDC
AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS([arpa/inet.h errno.h fcntl.h getopt.h grp.h netdb.h \
netinet/in.h netinet/tcp.h pwd.h stdio.h stdlib.h \
string.h sys/ioctl.h sys/socket.h sys/stat.h sys/time.h \
sys/types.h sys/un.h sys/wait.h unistd.h])


# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
AC_TYPE_UID_T
AC_TYPE_PID_T
AC_HEADER_TIME
AC_CHECK_TYPES(socklen_t,,,[#include <sys/types.h>
#include <sys/socket.h>])

## solaris needs -lsocket -lnsl
AC_SEARCH_LIBS([socket],[socket])
AC_SEARCH_LIBS([inet_addr],[nsl socket])

# Checks for library functions.
AC_FUNC_CHOWN
AC_FUNC_FORK
AC_FUNC_MALLOC
AC_FUNC_SELECT_ARGTYPES
AC_FUNC_STAT
AC_CHECK_FUNCS([dup2 memset putenv select socket strerror strtol issetugid inet_pton])


# Check for IPv6 support

AC_ARG_ENABLE(ipv6,
AC_HELP_STRING([--disable-ipv6],[disable IPv6 support]),
[case "${enableval}" in
yes) ipv6=true ;;
no) ipv6=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-ipv6) ;;
esac],[ipv6=true])

if test x$ipv6 = xtrue; then
AC_CACHE_CHECK([for IPv6 support], ac_cv_ipv6_support,
[AC_TRY_LINK([ #include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>], [struct sockaddr_in6 s; struct in6_addr t=in6addr_any; int i=AF_INET6; s; t.s6_addr[0] = 0; ],
[ac_cv_ipv6_support=yes], [ac_cv_ipv6_support=no])])

if test "$ac_cv_ipv6_support" = yes; then
AC_DEFINE(HAVE_IPV6,1,[Whether to enable IPv6 support])
fi
fi


# check for extra compiler options (warning options)
if test "${GCC}" = "yes"; then
CFLAGS="${CFLAGS} -Wall -W -Wshadow -pedantic -std=gnu99"
fi

AC_ARG_ENABLE(extra-warnings,
AC_HELP_STRING([--enable-extra-warnings],[enable extra warnings (gcc specific)]),
[case "${enableval}" in
yes) extrawarnings=true ;;
no) extrawarnings=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-extra-warnings) ;;
esac],[extrawarnings=false])

if test x$extrawarnings = xtrue; then
CFLAGS="${CFLAGS} -g -O2 -g2 -Wall -Wmissing-declarations -Wdeclaration-after-statement -Wcast-align -Winline -Wsign-compare -Wnested-externs -Wpointer-arith -Wl,--as-needed -Wformat-security"
fi

AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile])
AC_OUTPUT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXTRA_DIST=run-generic run-php run-rails
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
# Use this as a run script with daemontools or runit

## ABSOLUTE path to the spawn-fcgi binary
SPAWNFCGI="/usr/bin/spawn-fcgi"

## ABSOLUTE path to the FastCGI application (php-cgi, dispatch.fcgi, ...)
FCGIPROGRAM="/usr/bin/php5-cgi"

## bind to unix socket
FCGISOCKET="/var/run/lighttpd/your-fcgi-app.sock"

# allowed environment variables separated by spaces
ALLOWED_ENV="PATH USER"

## if this script is run as root switch to the following user
USERID=xxx
SOCKUSERID=www-data
#CHROOT=/home/www/

#RAILS_ENV="production"
#export RAILS_ENV


################## no config below this line

exec 2>&1

if test x$PHP_FCGI_CHILDREN = x; then
PHP_FCGI_CHILDREN=4
fi

ALLOWED_ENV="$ALLOWED_ENV RAILS_ENV"

if test x$UID = x0; then
EX="$SPAWNFCGI -n -s $FCGISOCKET -u $USERID -U $SOCKUSERID -C $PHP_FCGI_CHILDREN -- $FCGIPROGRAM"
else
EX="$SPAWNFCGI -n -s $FCGISOCKET -C $PHP_FCGI_CHILDREN -- $FCGIPROGRAM"
fi

# copy the allowed environment variables
E=

for i in $ALLOWED_ENV; do
E="$E $i=${!i}"
done

# clean environment and set up a new one
exec env - $E $EX
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
# Use this as a ./run script with daemontools or runit
# You should replace xxx with the user you want php to run as (and www-data with the user lighty runs as)

exec 2>&1
PHP_FCGI_CHILDREN=2 \
PHP_FCGI_MAX_REQUESTS=1000 \
exec /usr/bin/spawn-fcgi -n -s /var/run/lighttpd/php-xxx.sock -n -u xxx -U www-data -- /usr/bin/php5-cgi
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
# Use this as a ./run script with daemontools or runit
# You should replace xxx with the user you want rails to run as (and www-data with the user lighty runs as)
# /path-to-rails should be replaced with the correct path too :)

exec 2>&1
RAILS_ENV="production" \
exec /usr/bin/spawn-fcgi -n -s /var/run/lighttpd/rails-xxx.sock -u xxx -U www-data -- /path-to-rails/public/dispatch.fcgi
122 changes: 122 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/spawn-fcgi.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
.TH spawn-fcgi 1 "21 November 2012"
.
.SH NAME
.
spawn-fcgi \- Spawns FastCGI processes
.
.SH SYNOPSIS
.
.B spawn-fcgi
[options] [ -- <fcgiapp> [fcgi app arguments]]
.P
.B spawn-fcgi
\-v
.P
.B spawn-fcgi
\-h
.
.SH DESCRIPTION
.
\fIspawn-fcgi\fP is used to spawn remote and local FastCGI processes.
.P
While it is obviously needed to spawn remote FastCGI backends (the web server
can only spawn local ones), it is recommended to spawn local backends
with spawn-fcgi, too.
.P
Reasons why you may want to use spawn-fcgi instead of something else:
.IP * 3
Privilege separation without needing a suid-binary or running a server as root.
.IP * 3
You can restart your web server and the FastCGI applications without restarting the others.
.IP * 3
You can run them in different chroot()s.
.IP * 3
Running your FastCGI applications doesn't depend on the web server you are running,
which allows for easier testing of other web servers.
.
.SH OPTIONS
.
\fIspawn-fcgi\fP accepts the following options:
.TP 8
.B \-f <path>
Filename of the FastCGI application to spawn. This option is deprecated and it
is recommend to always specify the application (absolute path) and its parameters after "--";
the fcgiapp parameter is directly used for the exec() call, while for starting the binary given
with \-f /bin/sh is needed (which may not be available in a chroot).
.IP
This option is ignored if fcgiapp is given.
.TP 8
.B \-d <path>
Change the current directory before spawning the application.
.TP 8
.B \-a <address>
IPv4/IPv6 address to bind to; only used if \-p is given too. Defaults to "0.0.0.0" (IPv4).
.TP 8
.B \-p <port>
TCP port to bind to; you cannot combine this with the \-s option.
.TP 8
.B \-s <path>
Path to the Unix domain socket to bind to; you cannot combine this with the \-p option.
.TP 8
.B \-C <children>
(PHP only) Number of children to spawn by setting the PHP_FCGI_CHILDREN
environment variable. Default is not to overwrite the environment variable;
php will spawn no children if the variable is not set (same as setting it to 0).
.TP 8
.B \-F <children>
Number of children to fork, defaults to 1. This option doesn't work with \-n,
have a look at
.BR multiwatch(1)
if you want to supervise multiple forks on the same socket.
.TP 8
.B \-b <backlog>
backlog to allow on the socket (default 1024). This is usually limited by the kernel too,
check sysctl net.core.somaxconn (default 128) for linux.
.IP
backlog is the queue of connections that the kernel accepts before the userspace application sees them.
.TP 8
.B \-P <path>
Name of the PID file for spawned processes (ignored in no-fork mode)
.TP 8
.B \-n
No forking should take place (for daemontools)
.TP 8
.B \-M <mode>
Change file mode of the Unix domain socket; only used if \-s is given too.
.TP 8
.B \-?, \-h
General usage instructions
.TP 8
.B \-v
Shows version information and exits
.P
.
The following options are only available if you invoke spawn-fcgi as root:
.TP 8
.B \-c <directory>
Chroot to specified directory; the Unix domain socket is created inside the chroot unless \-S is given.
.TP 8
.B \-S
Create Unix domain socket before chroot().
.TP 8
.B \-u
User ID to change to.
.TP 8
.B \-g
Group ID to change to. Defaults to primary group of the user given for \-u.
.TP 8
.B \-U
Change user of the Unix domain socket, defaults to the value of \-u. (only used if \-s is given)
.TP 8
.B \-G
Change group of the Unix domain socket, defaults to the primary group of the user given for \-U;
if \-U wasn't given, defaults to the value of \-g. (only used if \-s is given)
.
.SH "SEE ALSO"
.
.BR svc(8),
.BR supervise(8),
see http://cr.yp.to/daemontools.html
.P
.BR multiwatch(1),
see http://cgit.stbuehler.de/gitosis/multiwatch/about/
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
INCLUDE(CheckCSourceCompiles)
INCLUDE(CheckIncludeFiles)
INCLUDE(CheckFunctionExists)
INCLUDE(CheckVariableExists)
INCLUDE(CheckTypeSize)
INCLUDE(CMakeDetermineCCompiler)

IF(CMAKE_COMPILER_IS_GNUCC)
OPTION(BUILD_EXTRA_WARNINGS "extra warnings")

IF(BUILD_EXTRA_WARNINGS)
SET(WARN_FLAGS "-g -O2 -g2 -Wall -Wmissing-declarations -Wdeclaration-after-statement -Wcast-align -Winline -Wsign-compare -Wnested-externs -Wpointer-arith -Wl,--as-needed -Wformat-security")
# -Wno-pointer-sign -Werror -Wbad-function-cast -Wmissing-prototypes
ELSE(BUILD_EXTRA_WARNINGS)
SET(WARN_FLAGS "")
ENDIF(BUILD_EXTRA_WARNINGS)
ENDIF(CMAKE_COMPILER_IS_GNUCC)

# awk '/#include <(.*)>/ { h = substr($2,2,length($2)-2); h2=toupper(h); gsub("\\.|\\/", "_", h2); printf "%s%s%s%s%s", "CHECK_INCLUDE_FILES(", h, " HAVE_", h2, ")\n" }' spawn-fcgi.c | sort
CHECK_INCLUDE_FILES(arpa/inet.h HAVE_ARPA_INET_H)
CHECK_INCLUDE_FILES(errno.h HAVE_ERRNO_H)
CHECK_INCLUDE_FILES(fcntl.h HAVE_FCNTL_H)
CHECK_INCLUDE_FILES(getopt.h HAVE_GETOPT_H)
CHECK_INCLUDE_FILES(grp.h HAVE_GRP_H)
CHECK_INCLUDE_FILES(netdb.h HAVE_NETDB_H)
CHECK_INCLUDE_FILES(netinet/in.h HAVE_NETINET_IN_H)
CHECK_INCLUDE_FILES(netinet/tcp.h HAVE_NETINET_TCP_H)
CHECK_INCLUDE_FILES(pwd.h HAVE_PWD_H)
CHECK_INCLUDE_FILES(stdio.h HAVE_STDIO_H)
CHECK_INCLUDE_FILES(stdlib.h HAVE_STDLIB_H)
CHECK_INCLUDE_FILES(string.h HAVE_STRING_H)
CHECK_INCLUDE_FILES(sys/ioctl.h HAVE_SYS_IOCTL_H)
CHECK_INCLUDE_FILES(sys/socket.h HAVE_SYS_SOCKET_H)
CHECK_INCLUDE_FILES(sys/stat.h HAVE_SYS_STAT_H)
CHECK_INCLUDE_FILES(sys/time.h HAVE_SYS_TIME_H)
CHECK_INCLUDE_FILES(sys/types.h HAVE_SYS_TYPES_H)
CHECK_INCLUDE_FILES(sys/un.h HAVE_SYS_UN_H)
CHECK_INCLUDE_FILES(sys/wait.h HAVE_SYS_WAIT_H)
CHECK_INCLUDE_FILES(unistd.h HAVE_UNISTD_H)
CHECK_INCLUDE_FILES(winsock2.h HAVE_WINSOCK2_H)

CHECK_FUNCTION_EXISTS(issetugid HAVE_ISSETUGID)
CHECK_FUNCTION_EXISTS(inet_pton HAVE_INET_PTON)

CHECK_C_SOURCE_COMPILES("
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
struct sockaddr_in6 s; struct in6_addr t=in6addr_any; int i=AF_INET6; s; t.s6_addr[0] = 0;
return 0;
}" HAVE_IPV6)

SET(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h)
CHECK_TYPE_SIZE(socklen_t HAVE_SOCKLEN_T)
SET(CMAKE_EXTRA_INCLUDE_FILES)

## Write out config.h
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)

ADD_DEFINITIONS(-DHAVE_CONFIG_H)

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})

ADD_EXECUTABLE(spawn-fcgi spawn-fcgi.c)

IF(CMAKE_COMPILER_IS_GNUCC)
SET_TARGET_PROPERTIES(spawn-fcgi PROPERTIES COMPILE_FLAGS "-std=gnu99 -Wall -g -Wshadow -W -pedantic -fPIC -D_GNU_SOURCE ${WARN_FLAGS}")
ENDIF(CMAKE_COMPILER_IS_GNUCC)

INSTALL(TARGETS spawn-fcgi DESTINATION bin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

bin_PROGRAMS=spawn-fcgi
spawn_fcgi_SOURCES=spawn-fcgi.c

EXTRA_DIST=CMakeLists.txt config.h.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

/* generated by cmake, do not modify. modify config.h.cmake instead */

/* Package data */

#define PACKAGE_NAME "${PACKAGE_NAME}"
#define PACKAGE_VERSION "${PACKAGE_VERSION}"

/* grep HAVE_ CMakeLists.txt | sed -e 's/.*\(HAVE_[A-Z_]*\).*/#cmakedefine \1/' */

#cmakedefine HAVE_ARPA_INET_H
#cmakedefine HAVE_ERRNO_H
#cmakedefine HAVE_FCNTL_H
#cmakedefine HAVE_GETOPT_H
#cmakedefine HAVE_GRP_H
#cmakedefine HAVE_NETDB_H
#cmakedefine HAVE_NETINET_IN_H
#cmakedefine HAVE_NETINET_TCP_H
#cmakedefine HAVE_PWD_H
#cmakedefine HAVE_STDIO_H
#cmakedefine HAVE_STDLIB_H
#cmakedefine HAVE_STRING_H
#cmakedefine HAVE_SYS_IOCTL_H
#cmakedefine HAVE_SYS_SOCKET_H
#cmakedefine HAVE_SYS_STAT_H
#cmakedefine HAVE_SYS_TIME_H
#cmakedefine HAVE_SYS_TYPES_H
#cmakedefine HAVE_SYS_UN_H
#cmakedefine HAVE_SYS_WAIT_H
#cmakedefine HAVE_UNISTD_H
#cmakedefine HAVE_WINSOCK
#cmakedefine HAVE_SOCKLEN_T

#cmakedefine HAVE_ISSETUGID
#cmakedefine HAVE_INET_PTON

#cmakedefine HAVE_IPV6
630 changes: 630 additions & 0 deletions tests/src/python/qgis_local_server_spawn/spawn-fcgi/src/spawn-fcgi.c

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

################################################################################
# 2013-09-01 Larry Shaffer <larrys@dakotacarto.com>
#
# NOTE: THIS IS ONLY A DEVELOPMENT TESTING SCRIPT. TO BE REMOVED AT LATER DATE.
################################################################################

/usr/local/bin/spawn-fcgi -p 8448 -u $UID -- /qgisserver/qgis_mapserv.fcgi


33 changes: 0 additions & 33 deletions tests/src/python/test_qgspallabeling_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ class TestQgsPalLabeling(TestCase):
_PalDataDir = os.path.join(_TestDataDir, 'labeling')
_PalFeaturesDb = os.path.join(_PalDataDir, 'pal_features_v3.sqlite')
_TestFont = TESTFONT
_TestProj = None
_MapRegistry = None
_MapRenderer = None
_Canvas = None
Expand Down Expand Up @@ -242,38 +241,6 @@ def renderCheck(self, mismatch=0, imgpath='', grpprefix=''):
msg = '\nRender check failed for "{0}"'.format(self._Test)
return res, msg

def defaultWmsParams(self, projpath, layername):
return {
'SERVICE': 'WMS',
'VERSION': '1.3.0',
'REQUEST': 'GetMap',
'MAP': str(projpath).strip(),
# layer stacking order for rendering: bottom,to,top
'LAYERS': ['background', str(layername).strip()], # or 'name,name'
'STYLES': ',',
'CRS': 'EPSG:32613', # self._CRS, # authid str or QgsCoordinateReferenceSystem obj
'BBOX': '606510,4823130,612510,4827130', # self.aoiExtent(),
'FORMAT': 'image/png', # or: 'image/png; mode=8bit'
'WIDTH': '600',
'HEIGHT': '400',
'DPI': '72',
'MAP_RESOLUTION': '72',
'FORMAT_OPTIONS': 'dpi:72',
'TRANSPARENT': 'FALSE',
'IgnoreGetMapUrl': '1'
}

@classmethod
def setUpServerProjectAndDir(cls, testprojpath, testdir):
cls._TestProj = QgsProject.instance()
cls._TestProj.setFileName(testprojpath)
try:
shutil.copy(cls._PalFeaturesDb, testdir)
for qml in glob.glob(cls._PalDataDir + os.sep + '*.qml'):
shutil.copy(qml, testdir)
except IOError, e:
raise IOError(str(e) + '\nCould not set up test server directory')


class TestPALConfig(TestQgsPalLabeling):

Expand Down
115 changes: 100 additions & 15 deletions tests/src/python/test_qgspallabeling_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

import sys
import os
import glob
import shutil
import tempfile
from PyQt4.QtCore import *
from PyQt4.QtGui import *

Expand All @@ -35,18 +38,43 @@
from test_qgspallabeling_base import TestQgsPalLabeling, runSuite
from test_qgspallabeling_tests import TestPointBase
from qgis_local_server import (QgisLocalServerConfig,
ServerConfigNotAccessibleError)
ServerConfigNotAccessibleError,
ServerSpawnNotAccessibleError)

SERVER = None
SPAWN = False
TESTPROJDIR = ''
TESTPROJPATH = ''

# TODO [LS]: attempt to spawn local server; add function in qgis_local_server.py

try:
SERVER = \
QgisLocalServerConfig(str(QgsApplication.qgisSettingsDirPath()), True)
# first try to connect to locally spawned server
# http://127.0.0.1:8448/qgis_mapserv.fcgi (see qgis_local_server.py)
SERVER = QgisLocalServerConfig(
tempfile.mkdtemp(),
chkcapa=True, spawn=True)
SPAWN = True
print '\n------------ Using SPAWNED local test server ------------\n'
except ServerSpawnNotAccessibleError, e:
SERVER = None
# print e
# TODO [LS]: add some error output if server is spawned, but not working
pass # may have no local spawn fcgi setup

if SERVER is None:
try:
# next try to connect to configured local server
SERVER = QgisLocalServerConfig(
str(QgsApplication.qgisSettingsDirPath()),
chkcapa=True, spawn=False)
except ServerConfigNotAccessibleError, e:
SERVER = None
print e

if SERVER is not None:
TESTPROJDIR = SERVER.projectDir()
TESTPROJPATH = os.path.join(TESTPROJDIR, 'pal_test.qgs')
except ServerConfigNotAccessibleError, e:
print e


def skipUnlessHasServer(): # skip test class decorator
Expand All @@ -55,23 +83,76 @@ def skipUnlessHasServer(): # skip test class decorator
return unittest.skip('\nConfigured local QGIS Server is not accessible\n\n')


@skipUnlessHasServer()
class TestServerPoint(TestQgsPalLabeling, TestPointBase):
class TestServerBase(TestQgsPalLabeling):

_TestProj = None
""":type: QgsProject"""
_TestProjSetup = False

@classmethod
def setUpClass(cls):
TestQgsPalLabeling.setUpClass()
cls.setUpServerProjectAndDir(TESTPROJPATH, TESTPROJDIR)
cls.layer = TestQgsPalLabeling.loadFeatureLayer('background')

cls._TestProj = QgsProject.instance()
cls._TestProj.setFileName(str(TESTPROJPATH).strip())
if not cls._TestProjSetup:
try:
shutil.copy(cls._PalFeaturesDb, TESTPROJDIR)
for qml in glob.glob(cls._PalDataDir + os.sep + '*.qml'):
shutil.copy(qml, TESTPROJDIR)
except IOError, e:
raise IOError(str(e) +
'\nCould not set up test server directory')
cls._TestProjSetup = True

# the blue background (set via layer style) to match renderchecker's
cls._BkgrdLayer = TestQgsPalLabeling.loadFeatureLayer('background')
cls._CheckMismatch = 200 # default for server tests; mismatch expected
cls._CheckGroup = '' # default '' will check against server control

@classmethod
def tearDownClass(cls):
"""Run after all tests"""
TestQgsPalLabeling.tearDownClass()
# layers removed, save empty project file
cls._TestProj.write()

def defaultWmsParams(self, layername):
return {
'SERVICE': 'WMS',
'VERSION': '1.3.0',
'REQUEST': 'GetMap',
'MAP': str(TESTPROJPATH).strip(),
# layer stacking order for rendering: bottom,to,top
'LAYERS': ['background', str(layername).strip()], # or 'name,name'
'STYLES': ',',
# authid str or QgsCoordinateReferenceSystem obj
'CRS': 'EPSG:32613', # self._CRS
'BBOX': '606510,4823130,612510,4827130', # self.aoiExtent(),
'FORMAT': 'image/png', # or: 'image/png; mode=8bit'
'WIDTH': '600',
'HEIGHT': '400',
'DPI': '72',
'MAP_RESOLUTION': '72',
'FORMAT_OPTIONS': 'dpi:72',
'TRANSPARENT': 'FALSE',
'IgnoreGetMapUrl': '1'
}


@skipUnlessHasServer()
class TestServerPoint(TestServerBase, TestPointBase):

@classmethod
def setUpClass(cls):
TestServerBase.setUpClass()
cls.layer = TestQgsPalLabeling.loadFeatureLayer('point')
cls.checkmismatch = 1000
cls.checkgroup = ''

def setUp(self):
"""Run before each test."""
self.configTest('pal_server', 'sp')
self.lyr = self.defaultSettings()
self.params = self.defaultWmsParams(TESTPROJPATH, 'point')
self.params = self.defaultWmsParams('point')
self._TestImage = ''

def tearDown(self):
Expand All @@ -83,13 +164,15 @@ def checkTest(self, **kwargs):
# save project file
self._TestProj.write()
# get server results
# print self.params.__repr__()
res, self._TestImage = SERVER.getMap(self.params, False)
# print self._TestImage.__repr__()
self.saveContolImage(self._TestImage)
self.assertTrue(res, 'Failed to retrieve/save image from test server')
# gp = kwargs['grpprefix'] if 'grpprefix' in kwargs else ''
self.assertTrue(*self.renderCheck(mismatch=self.checkmismatch,
self.assertTrue(*self.renderCheck(mismatch=self._CheckMismatch,
imgpath=self._TestImage,
grpprefix=self.checkgroup))
grpprefix=self._CheckGroup))


@skipUnlessHasServer()
Expand All @@ -98,7 +181,7 @@ class TestServerVsCanvasPoint(TestServerPoint):
@classmethod
def setUpClass(cls):
TestServerPoint.setUpClass()
cls.checkgroup = 'pal_canvas'
cls._CheckGroup = 'pal_canvas'


if __name__ == '__main__':
Expand All @@ -108,4 +191,6 @@ def setUpClass(cls):
'TestServerVsCanvasPoint.test_text_size_map_unit'
]
res = runSuite(sys.modules[__name__], suite)
# if SPAWN:
# os.remove(TESTPROJDIR) # remove temp directory (why does this error?)
sys.exit(not res.wasSuccessful())