Split up WebApp into webserver and RequestHandler part.

pull/7/head
Ferry Boender 9 years ago
parent f45d41af88
commit 82a9f1dad2
  1. 3
      src/scriptform.py
  2. 128
      src/webapp.py
  3. 114
      src/webserver.py

@ -47,7 +47,8 @@ import socket
from daemon import Daemon
from formdefinition import FormDefinition
from formconfig import FormConfig
from webapp import ThreadedHTTPServer, ScriptFormWebApp
from webserver import ThreadedHTTPServer
from webapp import ScriptFormWebApp
class ScriptFormError(Exception):

@ -3,10 +3,6 @@ The webapp part of Scriptform, which takes care of serving requests and
handling them.
"""
from SocketServer import ThreadingMixIn
import BaseHTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
import urlparse
import cgi
import logging
import tempfile
@ -15,6 +11,7 @@ import base64
import hashlib
from formrender import FormRender
from webserver import HTTPError, RequestHandler
HTML_HEADER = u'''<html>
@ -161,115 +158,9 @@ HTML_SUBMIT_RESPONSE = u'''
'''
class HTTPError(Exception):
class ScriptFormWebApp(RequestHandler):
"""
HTTPError may be thrown by routes to indicate HTTP errors such as 404, 301,
etc. They are caught by the 'framework' and sent to the client's browser.
"""
def __init__(self, status_code, msg, headers=None):
if headers is None:
headers = {}
self.status_code = status_code
self.msg = msg
self.headers = headers
Exception.__init__(self, status_code, msg, headers)
class ThreadedHTTPServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
"""
Base class for multithreaded HTTP servers.
"""
pass
class WebAppHandler(BaseHTTPRequestHandler):
"""
Basic web server request handler. Handles GET and POST requests. This class
should be extended with methods (starting with 'h_') to handle the actual
requests. If no path is set, it dispatches to the 'index' or 'default'
method.
"""
def log_message(self, fmt, *args):
"""Overrides BaseHTTPRequestHandler which logs to the console. We log
to our log file instead"""
fmt = "{} {}"
self.scriptform.log.info(fmt.format(self.address_string(), args))
def do_GET(self):
"""
Handle a GET request.
"""
self._call(*self._parse(self.path))
def do_POST(self):
"""
Handle a POST request.
"""
form_values = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST'})
self._call(self.path.strip('/'), params={'form_values': form_values})
def _parse(self, reqinfo):
"""
Parse information from a request.
"""
url_comp = urlparse.urlsplit(reqinfo)
path = url_comp.path
query_vars = urlparse.parse_qs(url_comp.query)
# Only return the first value of each query var. E.g. for
# "?foo=1&foo=2" return '1'.
var_values = dict([(k, v[0]) for k, v in query_vars.items()])
return (path.strip('/'), var_values)
def _call(self, path, params):
"""
Find a method to call on self.app_class based on `path` and call it.
The method that's called is in the form 'h_<PATH>'. If no path was
given, it will try to call the 'index' method. If no method could be
found but a `default` method exists, it is called. Otherwise 404 is
sent.
Methods should take care of sending proper headers and content
themselves using self.send_response(), self.send_header(),
self.end_header() and by writing to self.wfile.
"""
method_name = 'h_{0}'.format(path)
method_cb = None
try:
if hasattr(self, method_name) and \
callable(getattr(self, method_name)):
method_cb = getattr(self, method_name)
elif path == '' and hasattr(self, 'index'):
method_cb = getattr(self, 'index')
elif hasattr(self, 'default'):
method_cb = getattr(self, 'default')
else:
raise HTTPError(404, "Not found")
method_cb(**params)
except HTTPError, err:
# HTTP erors are generally thrown by the webapp on purpose. Send
# error to the browser.
if err.status_code not in (401, ):
self.scriptform.log.exception(err)
self.send_response(err.status_code)
for header_k, header_v in err.headers.items():
self.send_header(header_k, header_v)
self.end_headers()
self.wfile.write("Error {}: {}".format(err.status_code,
err.msg))
self.wfile.flush()
return False
except Exception, err:
self.scriptform.log.exception(err)
self.send_error(500, "Internal server error")
raise
class ScriptFormWebApp(WebAppHandler):
"""
This class is a request handler for WebSrv.
This class is a request handler for the webserver.
"""
def index(self):
"""
@ -459,8 +350,8 @@ class ScriptFormWebApp(WebAppHandler):
def h_submit(self, form_values):
"""
Handle the submitting of a form by validating the values and then doing
a callback to a script. How the output is
handled depends on settings in the form definition.
a callback to a script. How the output is handled depends on settings
in the form definition.
"""
username = self.auth()
@ -503,14 +394,13 @@ class ScriptFormWebApp(WebAppHandler):
# Field is a normal form field. Store its value.
values[field_name] = form_values.getfirst(field_name, None)
# Validate the form values
form_errors, form_values = form_def.validate(values)
if not form_errors:
# Call user's callback. If a result is returned, we wrap its output
# in some nice HTML. If no result is returned, the output was raw
# and the callback should have written its own response to the
# self.wfile filehandle.
# Call script. If a result is returned, we wrap its output in some
# nice HTML. If no result is returned, the output was raw and the
# callback should have written its own response to the self.wfile
# filehandle.
# Log the callback and its parameters for auditing purposes.
log = logging.getLogger('CALLBACK_AUDIT')

@ -0,0 +1,114 @@
"""
Basic web server / framework.
"""
from SocketServer import ThreadingMixIn
import BaseHTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
import urlparse
import cgi
class HTTPError(Exception):
"""
HTTPError may be thrown by routes to indicate HTTP errors such as 404, 301,
etc. They are caught by the 'framework' and sent to the client's browser.
"""
def __init__(self, status_code, msg, headers=None):
if headers is None:
headers = {}
self.status_code = status_code
self.msg = msg
self.headers = headers
Exception.__init__(self, status_code, msg, headers)
class ThreadedHTTPServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
"""
Base class for multithreaded HTTP servers.
"""
pass
class RequestHandler(BaseHTTPRequestHandler):
"""
Basic web server request handler. Handles GET and POST requests. You should
inherit from this class and implement h_ methods for handling requests.
If no path is set, it dispatches to the 'index' or 'default' method.
"""
def log_message(self, fmt, *args):
"""Overrides BaseHTTPRequestHandler which logs to the console. We log
to our log file instead"""
fmt = "{} {}"
self.scriptform.log.info(fmt.format(self.address_string(), args))
def do_GET(self):
"""
Handle a GET request.
"""
self._call(*self._parse(self.path))
def do_POST(self):
"""
Handle a POST request.
"""
form_values = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST'})
self._call(self.path.strip('/'), params={'form_values': form_values})
def _parse(self, reqinfo):
"""
Parse information from a request.
"""
url_comp = urlparse.urlsplit(reqinfo)
path = url_comp.path
query_vars = urlparse.parse_qs(url_comp.query)
# Only return the first value of each query var. E.g. for
# "?foo=1&foo=2" return '1'.
var_values = dict([(k, v[0]) for k, v in query_vars.items()])
return (path.strip('/'), var_values)
def _call(self, path, params):
"""
Find a method to call on self.app_class based on `path` and call it.
The method that's called is in the form 'h_<PATH>'. If no path was
given, it will try to call the 'index' method. If no method could be
found but a `default` method exists, it is called. Otherwise 404 is
sent.
Methods should take care of sending proper headers and content
themselves using self.send_response(), self.send_header(),
self.end_header() and by writing to self.wfile.
"""
method_name = 'h_{0}'.format(path)
method_cb = None
try:
if hasattr(self, method_name) and \
callable(getattr(self, method_name)):
method_cb = getattr(self, method_name)
elif path == '' and hasattr(self, 'index'):
method_cb = getattr(self, 'index')
elif hasattr(self, 'default'):
method_cb = getattr(self, 'default')
else:
raise HTTPError(404, "Not found")
method_cb(**params)
except HTTPError, err:
# HTTP erors are generally thrown by the webapp on purpose. Send
# error to the browser.
if err.status_code not in (401, ):
self.scriptform.log.exception(err)
self.send_response(err.status_code)
for header_k, header_v in err.headers.items():
self.send_header(header_k, header_v)
self.end_headers()
self.wfile.write("Error {}: {}".format(err.status_code,
err.msg))
self.wfile.flush()
return False
except Exception, err:
self.scriptform.log.exception(err)
self.send_error(500, "Internal server error")
raise
Loading…
Cancel
Save