Skip to content

Commit

Permalink
Bug Fix: Updated examples per new infrastructure
Browse files Browse the repository at this point in the history
- Bug Fix: Updated the examples to use the non-instances of the
  StackInABox services.
- Enhancement: Added an example for Python's wsgiref.
- Enhancement: Added logging to the examples.
- Enhancement: Added note about limitations.
- Bug Fix: Corrected the behavior of the Status line so that it
  reports correctly against the HTTP standard.
BenjamenMeyer committed Jul 8, 2016
1 parent 7361360 commit ef807b0
Showing 15 changed files with 366 additions and 10 deletions.
32 changes: 32 additions & 0 deletions examples/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
========
Examples
========

The foldlers here contain examples of how to run the
StackInAWSGI functionality in some common WSGI servers.

gunicorn
--------

Shows how to run StackInAWSGI using Gunicorn.

uwsgi
-----

Shows how to run StackInAWSGI using uWSGI.

python-wsgiref
--------------

Shows how to run StackInAWSGI using the built-in wsgiref.


Known Issues
============

- Only 1 worker can be used for any given WSGI server platform.
This is due to the fact that the session information is not
shared between workers. In theory all the workers should be able
to see the data; however, they may be running as separate processes
which would limit their value of what they can see. More research
is needed to overcome this limitation.
11 changes: 9 additions & 2 deletions examples/gunicorn/app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
"""
Gunicorn Example App
"""
import logging

from stackinabox.services.hello import HelloService

from stackinawsgi import App

app = App([HelloService()])
app.StackInABoxUriUpdate('localhost:8081')
lf = logging.FileHandler('stackinawsgi.log')
lf.setLevel(logging.DEBUG)
log = logging.getLogger()
log.addHandler(lf)
log.setLevel(logging.DEBUG)

app = App([HelloService])
app.StackInABoxUriUpdate('http://localhost:8081')
36 changes: 34 additions & 2 deletions examples/gunicorn/gunicorn_start.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
#!/bin/bash

# Note: Until Issue #11 (Session Bug) is fully resolved,
# Stack-In-A-WSGI only supports a single worker,
# each worker will have its own session data completely
# independent of all other workers.
WORKER_COUNT=1
VENV_DIR="gunicorn_example_app"

for ARG in ${@}
do
echo "Found argument: ${ARG}"
if [ "${ARG}" == "--reset" ]; then
echo " User requested virtualenv reset, long argument name"
let -i RESET_VENV=1
elif ["${ARG}" == "-r" ]; then
echo " User requested virtualenv reset, short argument name"
let -i RESET_VENV=2
fi
done

MD5SUM_ROOT=`ls / | md5sum | cut -f 1 -d ' '`
MD5SUM_VENV=`ls ${VENV_DIR} | md5sum | cut -f 1 -d ' '`
if [ "${MD5SUM_ROOT}" == "${MD5SUM_VENV}" ]; then
echo "Virtual Environment target is root. Configuration not supported."
exit 1
fi

if [ -v RESET_VENV ]; then
echo "Checking for existing virtualenv to remove..."
if [ -d ${VENV_DIR} ]; then
echo "Removing virtualenv ${VENV_DIR}..."
rm -Rf ${VENV_DIR}
fi
fi

if [ ! -d ${VENV_DIR} ]; then
echo "Building virtualenv..."
virtualenv ${VENV_DIR}

INITIALIZE_VENV=1
fi


source ${VENV_DIR}/bin/activate

if [ -v INITIALIZE_VENV ]; then
pip install -r requirements.txt
fi

echo "Starting new instances..."
gunicorn -b 127.0.0.1:8081 -w 16 --error-logfile app-errors.log --access-logfile app-access.log --log-level DEBUG -D app:app
gunicorn -b 127.0.0.1:8081 -w ${WORKER_COUNT} --error-logfile app-errors.log --access-logfile app-access.log --log-level DEBUG -D app:app
Empty file.
17 changes: 17 additions & 0 deletions examples/python-wsgi/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
Python WSGI-Ref Example App
"""
import logging

from stackinabox.services.hello import HelloService

from stackinawsgi import App

lf = logging.FileHandler('python-wsgi-ref.log')
lf.setLevel(logging.DEBUG)
log = logging.getLogger()
log.addHandler(lf)
log.setLevel(logging.DEBUG)

app = App([HelloService])
app.StackInABoxUriUpdate('localhost:8081')
4 changes: 4 additions & 0 deletions examples/python-wsgi/pywsgiref_restart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

pywsgiref_stop.sh
pywsgiref_start.sh
51 changes: 51 additions & 0 deletions examples/python-wsgi/pywsgiref_start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash

# Note: Until Issue #11 (Session Bug) is fully resolved,
# Stack-In-A-WSGI only supports a single worker,
# each worker will have its own session data completely
# independent of all other workers.
WORKER_COUNT=1
VENV_DIR="pywsgiref_example_app"

for ARG in ${@}
do
echo "Found argument: ${ARG}"
if [ "${ARG}" == "--reset" ]; then
echo " User requested virtualenv reset, long argument name"
let -i RESET_VENV=1
elif ["${ARG}" == "-r" ]; then
echo " User requested virtualenv reset, short argument name"
let -i RESET_VENV=2
fi
done

MD5SUM_ROOT=`ls / | md5sum | cut -f 1 -d ' '`
MD5SUM_VENV=`ls ${VENV_DIR} | md5sum | cut -f 1 -d ' '`
if [ "${MD5SUM_ROOT}" == "${MD5SUM_VENV}" ]; then
echo "Virtual Environment target is root. Configuration not supported."
exit 1
fi

if [ -v RESET_VENV ]; then
echo "Checking for existing virtualenv to remove..."
if [ -d ${VENV_DIR} ]; then
echo "Removing virtualenv ${VENV_DIR}..."
rm -Rf ${VENV_DIR}
fi
fi

if [ ! -d ${VENV_DIR} ]; then
echo "Building virtualenv..."
virtualenv ${VENV_DIR}

INITIALIZE_VENV=1
fi

source ${VENV_DIR}/bin/activate

if [ -v INITIALIZE_VENV ]; then
pip install -r requirements.txt
fi

echo "Starting new instances..."
python wsgiserver.py
4 changes: 4 additions & 0 deletions examples/python-wsgi/pywsgiref_stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

echo "Stopping existing instances..."
kill -3 `ps -Aef | grep wsgiserver.py | grep -v grep | tr -s ' ' ';' | cut -f 2 -d ';'`
2 changes: 2 additions & 0 deletions examples/python-wsgi/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-e ../../
-e git+https://github.com/TestInABox/stackInABox#egg=stackinabox-0.10a
9 changes: 9 additions & 0 deletions examples/python-wsgi/wsgiserver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import absolute_import

from wsgiref.simple_server import make_server

import app


httpd = make_server('localhost', 8081, app.app)
httpd.serve_forever()
11 changes: 9 additions & 2 deletions examples/uwsgi/app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
"""
Gunicorn Example App
"""
import logging

from stackinabox.services.hello import HelloService

from stackinawsgi import App

app = App([HelloService()])
app.StackInABoxUriUpdate('localhost:8081')
lf = logging.FileHandler('stackinawsgi.log')
lf.setLevel(logging.DEBUG)
log = logging.getLogger()
log.addHandler(lf)
log.setLevel(logging.DEBUG)

app = App([HelloService])
app.StackInABoxUriUpdate('http://localhost:8081')
6 changes: 5 additions & 1 deletion examples/uwsgi/uwsgi_app.ini
Original file line number Diff line number Diff line change
@@ -7,8 +7,12 @@ lazy = 1
memory-report = 1
need-app = 1

; Note: Until Issue #11 (Session Bug) is fully resolved,
; Stack-In-A-WSGI only supports a single worker,
; each worker will have its own session data completely
; independent of all other workers.
[app]
http-socket = 127.0.0.1:8081
processes = 5
processes = 1
module = app:app
master = 1
29 changes: 28 additions & 1 deletion examples/uwsgi/uwsgi_start.sh
Original file line number Diff line number Diff line change
@@ -2,13 +2,40 @@

VENV_DIR="uwsgi_example_app"

for ARG in ${@}
do
echo "Found argument: ${ARG}"
if [ "${ARG}" == "--reset" ]; then
echo " User requested virtualenv reset, long argument name"
let -i RESET_VENV=1
elif ["${ARG}" == "-r" ]; then
echo " User requested virtualenv reset, short argument name"
let -i RESET_VENV=2
fi
done

MD5SUM_ROOT=`ls / | md5sum | cut -f 1 -d ' '`
MD5SUM_VENV=`ls ${VENV_DIR} | md5sum | cut -f 1 -d ' '`
if [ "${MD5SUM_ROOT}" == "${MD5SUM_VENV}" ]; then
echo "Virtual Environment target is root. Configuration not supported."
exit 1
fi

if [ -v RESET_VENV ]; then
echo "Checking for existing virtualenv to remove..."
if [ -d ${VENV_DIR} ]; then
echo "Removing virtualenv ${VENV_DIR}..."
rm -Rf ${VENV_DIR}
fi
fi

if [ ! -d ${VENV_DIR} ]; then
echo "Building virtualenv..."
virtualenv ${VENV_DIR}

INITIALIZE_VENV=1
fi


source ${VENV_DIR}/bin/activate

if [ -v INITIALIZE_VENV ]; then
25 changes: 24 additions & 1 deletion stackinawsgi/test/test_wsgi_app.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,8 @@

import unittest

import ddt

from stackinabox.services.hello import HelloService
from stackinabox.stack import StackInABox

@@ -18,6 +20,7 @@
)


@ddt.ddt
class TestWsgiApp(unittest.TestCase):
"""
Stack-In-A-WSGI's wsgi.app.App test suite
@@ -200,5 +203,25 @@ def test_handle_as_callable(self):

wsgi_mock = WsgiMock()
response_body = ''.join(the_app(environment, wsgi_mock))
self.assertEqual(wsgi_mock.status, '200')
self.assertEqual(wsgi_mock.status, '200 OK')
self.assertEqual(response_body, 'Hello')

@ddt.data(
(160, "Unknown Informational Status"),
(260, "Unknown Success Status"),
(360, "Unknown Redirection Status"),
(460, "Unknown Client Error"),
(560, "Unknown Server Error"),
(660, "Unknown Status")
)
@ddt.unpack
def test_response_for_status(self, status, expected_value):
"""
Validate that the generic Unknown status text is returned for each
range of values.
"""
the_app = App([HelloService])
self.assertEqual(
the_app.response_for_status(status),
expected_value
)
139 changes: 138 additions & 1 deletion stackinawsgi/wsgi/app.py
Original file line number Diff line number Diff line change
@@ -24,6 +24,110 @@ class App(object):
A WSGI Application for running StackInABox under a WSGI host
"""

# List of well-known status codes
status_values = {
# Official Status Codes
100: "Continue",
101: "Switching Protocols",
102: "Processing",
200: "OK",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
207: "Multi-Status Response",
208: "Already Reported",
226: "IM Used",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
306: "Switch Proxy",
307: "Temporary Redirect",
308: "Permanent Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Payload Too Large",
414: "URI Too Long",
415: "Unsupported Media Type",
416: "Range Not Satisfiable",
417: "Expectation Failed",
418: "I'm a teapot",
421: "Misdirected Request",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
426: "Upgrade Required",
428: "Precondition Required",
429: "Too Many Requests",
431: "Requested Header Fields Too Large",
451: "Unavailable for Legal Reasons",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
506: "Variant Also Negotiates",
507: "Insufficient Storage",
508: "Loop Detected",
510: "Not Extended",
511: "Network Authentication Required",

# Unofficial Status Codes:
103: "Checkpoint",
420: "Method Failure",
450: "Blocked by Windows Parental Control (MS)",
498: "Invalid Token",
499: "Token Required",
509: "Bandwidth Limit Exceeded",
530: "Site Frozen",
440: "Login Timeout",
449: "Retry With",
# 451 - Redirect (re-defined)

444: "No Response",
495: "SSL Certificate Error",
496: "SSL Certificate Required",
497: "HTTP Request Sent to HTTPS Port",
499: "Client Closed Request",

520: "Unknown Error",
521: "Web Server Is Down",
522: "Connection Timed Out",
523: "Origin Is Unreachable",
524: "A Timeout Occurred",
525: "SSL Handshake Failed",
526: "Invalid SSL Certificate",

# The below codes are specific cases for the infrastructure
# supported here and should not conflict with anything above.

# StackInABox Status Codes
595: "Route Not Handled",
596: "Unhandled Exception",
597: "URI Is For Service That Is Unknown",

# StackInAWSGI Status Codes
593: "Session ID Missing from URI",
594: "Invalid Session ID"
}

def __init__(self, services=None):
"""
Create the WSGI Application
@@ -138,6 +242,34 @@ def CallStackInABox(self, request, response):
result[2]
)

def response_for_status(cls, status):
"""
Generate a status string for the status code
:param int status: the status code to look-up
:returns: string for the value or an appropriate Unknown value
"""
if status in cls.status_values:
return cls.status_values[status]

elif status >= 100 and status < 200:
return "Unknown Informational Status"

elif status >= 200 and status < 300:
return "Unknown Success Status"

elif status >= 300 and status < 400:
return "Unknown Redirection Status"

elif status >= 400 and status < 500:
return "Unknown Client Error"

elif status >= 500 and status < 600:
return "Unknown Server Error"

else:
return "Unknown Status"

def __call__(self, environ, start_response):
"""
Callable entry per the PEP-3333 WSGI spec
@@ -153,7 +285,12 @@ def __call__(self, environ, start_response):
response = Response()
self.CallStackInABox(request, response)
start_response(
str(response.status),
"{0} {1}".format(
response.status,
self.response_for_status(
response.status
)
),
[(k, v) for k, v in response.headers.items()]
)
yield response.body

0 comments on commit ef807b0

Please sign in to comment.