# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-"""
-CGI gallery server for a picture directory organized along::
+"""Gallery server for a picture directory organized along::
pics
|-- some_directory
Note that you can store a caption for ``<PICTURE>`` as plain text in
``<PICTURE>.txt``.
-See RFC 3875 for more details on the the Common Gateway Interface.
+See RFC 3875 for more details on the the Common Gateway Interface (CGI).
+
+Besides the CGI interface, this script can also be run as:
-This script can also be run as a Simple Common Gateway Interface
-(SCGI) with the ``--scgi`` option.
+* a Simple Common Gateway Interface (SCGI) with the ``--mode=scgi`` option
+* a stand-alone server with the ``--mode=wsgi`` option
"""
import collections as _collections
class ProcessingComplete(Exception):
- pass
+ def __init__(self, headers=None):
+ self.headers = headers
def invoke(args, stdin=None, stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
def __init__(self, base_path='.',
base_url='/',
cache_path='/tmp/gallery-cache/',
- serve_originals=True):
+ serve_originals=True,
+ write_http_headers=True):
self._base_path = _os_path.abspath(base_path)
self._base_url = base_url
self._cache_path = cache_path
self._serve_originals = serve_originals
+ self._write_http_headers = write_http_headers
self._text_charset = 'UTF-8'
self._url_regexp = _re.compile('^[a-zA-Z0-9._/-]*$')
self._rows = 3
if charset:
content = content.encode(charset)
stream.write(content)
- raise ProcessingComplete()
+ raise ProcessingComplete(headers=headers)
def _response_stream(self, headers=None, content=None, stream=None,
chunk_size=1024):
if not chunk:
break
stream.write(chunk)
- raise ProcessingComplete()
+ raise ProcessingComplete(headers=headers)
def _error(self, status=404, content=None, stream=None):
headers = self._http_headers(status=status)
except HTTPError as e:
LOG.error(e.message)
self._error(e.status, content=e.content, stream=stream)
- except ProcessingComplete:
- pass
+ except ProcessingComplete as e:
+ return e
def page_from_query(self, query=None, query_string=None):
"""Extract the requested page from a query string
LOG.info('serving SCGI on {}:{}'.format(host, port))
s.serve()
+def serve_wsgi(server, host='localhost', port=4000):
+ import io
+ import wsgiref.simple_server
+
+ server._write_http_headers = False
+
+ def app(environ, start_response):
+ url = environ.get('PATH_INFO', None)
+ page = server.page_from_query(
+ query_string=environ.get('QUERY_STRING', ''))
+ status = '200 OK'
+ headers = {}
+ stream = io.BytesIO()
+ try:
+ try:
+ url = server.relative_url(url=url)
+ except HTTPError as e:
+ LOG.error(e.message)
+ server._error(e.status, content=e.content, stream=stream)
+ except ProcessingComplete as e:
+ headers = e.headers
+ else:
+ e = server.serve(url=url, page=page, stream=stream)
+ headers = e.headers
+ output = stream.getvalue()
+ status = headers.pop('Status')
+ start_response(status, list(headers.items()))
+ return [output]
+
+ wsgi = wsgiref.simple_server.make_server(host=host, port=port, app=app)
+ LOG.info('serving WSGI on {}:{}'.format(host, port))
+ wsgi.serve_forever()
+
if __name__ == '__main__':
import argparse as _argparse
description=__doc__, version=__version__,
formatter_class=_argparse.RawDescriptionHelpFormatter)
parser.add_argument(
- '--scgi', default=False, action='store_const', const=True,
- help='Run as a SCGI server (vs. serving a single CGI call)')
+ '--mode', default='cgi', choices=['cgi', 'scgi', 'wsgi'],
+ help='Server mode (defaults to CGI)')
parser.add_argument(
'--port', default=4000, type=int,
help='Port to listen to (if runing as a SCGI server)')
s.header = [open(_os_path.join(shared, 'header.shtml'), 'r').read()]
s.footer = [open(_os_path.join(shared, 'footer.shtml'), 'r').read()]
- if args.scgi:
+ if args.mode == 'scgi':
serve_scgi(server=s, port=args.port)
+ if args.mode == 'wsgi':
+ serve_wsgi(server=s, port=args.port)
else:
serve_cgi(server=s)