diff --git a/src/02-shipping/shipping.py b/src/02-shipping/shipping.py index be5573a..e580bb1 100644 --- a/src/02-shipping/shipping.py +++ b/src/02-shipping/shipping.py @@ -1,37 +1,66 @@ +""" +standalone WSGI application for serving plaintext and xml suitable +for the Adobe Flex "Getting Started" tutorial, "Part II. Exchanging Data" + +For the plaintext data flex app, set the HTTPService url to: + + http://:/text + +For the xml data flex app, set the HTTPService url to: + + http://:/xml + +""" from __future__ import print_function import sys import cgi +import optparse from wsgiref.simple_server import make_server +__author__ = 'Dan Buch dbuch@ag.com' +__license__ = 'AGI' + +USAGE = """\ +%prog [options] +Run a standalone WSGI app for the Adobe Flex Tutorial +"Getting Started", "Part II. Exchanging Data" +""" + def main(sysargs=sys.argv[:]): - port = 18080 - server = make_server('0.0.0.0', port, shipping_app) - print('serving {0.__name__} on port {1}'.format(shipping_app, port)) + parser = optparse.OptionParser(usage=USAGE) + parser.add_option('-p', '--port', dest='port', type='int', + help='port number on which to serve app, default=%default', + default=18080) + parser.add_option('-i', '--interface', dest='interface', + help='interface on which to serve app, default=%default', + default='0.0.0.0') + + opts = parser.parse_args(sysargs[1:])[0] + + server = make_server(opts.interface, opts.port, ShippingCostApp()) + print('serving {0.__name__} on ' + 'port {1}'.format(ShippingCostApp, opts.port)) server.serve_forever() return 0 -def shipping_app(environ, start_response): - path_info = environ.get('PATH_INFO', '').strip('/') +class NotFoundError(ValueError): + pass - handler = { - 'text': plaintext_handler, - 'xml': xml_handler, - 'crossdomain.xml': crossdomain_xml_handler, - }.get(path_info) - if handler: +class ShippingCostApp(object): + + def __call__(self, environ, start_response): try: - return handler(environ, start_response) - except Exception: - start_response('500 Internal Server Error', [ - ('content-type', 'text/plain'), - ]) - return ['OUCH'] - else: - ret = 'nothin at {0!r}'.format(path_info) + return ShippingCostHandler(environ, start_response).handle() + except NotFoundError: + return self._handle_404(environ, start_response) + + @classmethod + def _handle_404(cls, environ, start_response): + ret = 'nothin at {0!r}'.format(environ.get('PATH_INFO', '/')) start_response('404 Not Found', [ ('content-type', 'text/plain'), ('content-length', str(len(ret))) @@ -39,72 +68,105 @@ def shipping_app(environ, start_response): return [ret] -def get_params(environ): - params = cgi.parse(fp=environ.get('wsgi.input'), environ=environ) - for key, value in params.iteritems(): - if len(value) == 1: - params[key] = value[0] - return params +class ShippingCostHandler(object): + _shipping_options = None + _zipcode = 0 + _pounds = 0 + _handler_method = '' + + def __init__(self, environ, start_response): + self.environ = environ + self.start_response = start_response + self._path_info = self.environ.get('PATH_INFO', '').strip('/') + self._handler_method = \ + '_handle_{0}'.format(self._path_info.replace('.', '_')) + + def _get_params(self): + """some dumb param fetching, ignores multiple values for + same var name, e.g. query of "zipcode=12345&zipcode=90210" + would effectively discard the 90210 value + """ + params = cgi.parse_qs(self.environ.get('QUERY_STRING', '')) + + for intvar in ('zipcode', 'pounds'): + if params.get(intvar): + value = int(float(params.get(intvar)[0])) + setattr(self, '_{0}'.format(intvar), value) + + def _get_shipping_options(self): + base_cost = (float(self._zipcode) / 10000.0) + (self._pounds * 5.0) + self._shipping_options = { + "Next Day": int(base_cost * 4), + "Two Day Air": int(base_cost * 2), + "Saver Ground": int(base_cost) + } + + def handle(self): + if hasattr(self, self._handler_method): + self._get_params() + self._get_shipping_options() + return getattr(self, self._handler_method)() + else: + raise NotFoundError + + def _handle_xml(self): + response = XMLShippingOptionsResponse(self._shipping_options) + return response(self.environ, self.start_response) + + def _handle_text(self): + response = PlaintextShippingOptionsResponse(self._shipping_options) + return response(self.environ, self.start_response) + + def _handle_crossdomain_xml(self): + response = CrossdomainXMLResponse() + return response(self.environ, self.start_response) -def xml_handler(environ, start_response): - params = get_params(environ) - zipcode = int(float(params.get('zipcode', 0))) - pounds = int(float(params.get('pounds', 0))) +class PlaintextShippingOptionsResponse(object): + _head = '' + _record = '{service}: {price} USD' + _tail = '' + _content_type = 'text/plain' - ret = [''] - for service, price in get_shipping_options(zipcode, pounds).iteritems(): - ret.append(''.format(**locals())) - ret.append('') - body = '\n'.join(ret) + def __init__(self, shipping_options): + self._shipping_options = shipping_options - start_response('200 OK', [ - ('content-type', 'text/xml'), - ('content-length', str(len(body))) - ]) - return [body] + def __call__(self, environ, start_response): + body = str(self) + start_response('200 OK', [ + ('content-type', self._content_type), + ('content-length', str(len(body))), + ]) + return [body] + + def __str__(self): + ret = [self._head] + for service, price in self._shipping_options.iteritems(): + ret.append(self._record.format(service=service, price=price)) + ret.append(self._tail) + return '\n'.join(ret) -def plaintext_handler(environ, start_response): - params = get_params(environ) - zipcode = int(float(params.get('zipcode', 0))) - pounds = int(float(params.get('pounds', 0))) - - ret = [] - for service, price in get_shipping_options(zipcode, pounds).iteritems(): - ret.append('{service}: {price} USD'.format(**locals())) - body = '\n'.join(ret) - - start_response('200 OK', [ - ('content-type', 'text/plain'), - ('content-length', str(len(body))) - ]) - return [body] +class XMLShippingOptionsResponse(PlaintextShippingOptionsResponse): + _head = '' + _record = ('') + _tail = '' + _content_type = 'text/xml' -def crossdomain_xml_handler(environ, start_response): - start_response('200 OK', [ - ('content-type', 'text/xml'), - ('content-length', str(len(XD_XML))), - ]) - return [XD_XML] +class CrossdomainXMLResponse(PlaintextShippingOptionsResponse): + _content_type = 'text/xml' + def __init__(self): + pass -XD_XML = """\ - - - -""" - - -def get_shipping_options(zipcode, pounds): - base_cost = (float(zipcode) / 10000.0) + (pounds * 5.0) - return { - "Next Day": int(base_cost * 4), - "Two Day Air": int(base_cost * 2), - "Saver Ground": int(base_cost) - } + def __str__(self): + return ( + '' + '' + '' + ) if __name__ == '__main__':