From eae08e81f9a23e7eb8d9044fa904be4938b49000 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 10 Mar 2021 17:50:22 +0100 Subject: [PATCH] Shorten the form URL by compressing and b64 encoding it. In a real-world example, the baseline URL goes from 1086 characters to 620. This is good for the resulting QR barcode. --- cara/apps/calculator/__init__.py | 11 +++++++++++ cara/apps/calculator/report_generator.py | 22 +++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/cara/apps/calculator/__init__.py b/cara/apps/calculator/__init__.py index d22e8846..c31ca372 100644 --- a/cara/apps/calculator/__init__.py +++ b/cara/apps/calculator/__init__.py @@ -1,10 +1,12 @@ import datetime +import base64 import html import json import os from pathlib import Path import traceback import uuid +import zlib import jinja2 from tornado.web import Application, RequestHandler, StaticFileHandler @@ -131,6 +133,14 @@ class CalculatorForm(BaseRequestHandler): self.finish(report) +class CompressedCalculatorFormInputs(BaseRequestHandler): + def get(self, compressed_args: str): + # Convert a base64 zlib encoded shortened URL into a non compressed + # URL, and redirect. + args = zlib.decompress(base64.b64decode(compressed_args)).decode() + self.redirect(f'/calculator?{args}') + + class ReadmeHandler(BaseRequestHandler): def get(self): template = self.settings['template_environment'].get_template("userguide.html.j2") @@ -146,6 +156,7 @@ def make_app(debug=False, prefix='/calculator'): calculator_static_dir = Path(__file__).absolute().parent / 'static' urls = [ (r'/?', LandingPage), + (r'/c/(.*)', CompressedCalculatorFormInputs), (r'/static/(.*)', StaticFileHandler, {'path': static_dir}), (prefix + r'/?', CalculatorForm), (prefix + r'/report', ConcentrationModel), diff --git a/cara/apps/calculator/report_generator.py b/cara/apps/calculator/report_generator.py index 3eb65193..a96571e6 100644 --- a/cara/apps/calculator/report_generator.py +++ b/cara/apps/calculator/report_generator.py @@ -4,6 +4,7 @@ from datetime import datetime import io from pathlib import Path import typing +import zlib import qrcode import urllib @@ -70,7 +71,22 @@ def calculate_report_data(model: models.ExposureModel): def generate_qr_code(prefix, form: FormData): form_dict = FormData.to_dict(form) - url = prefix + "?" + urllib.parse.urlencode(form_dict) + + # Generate the calculator URL arguments that would be needed to re-create this + # form. + args = urllib.parse.urlencode(form_dict) + + # Then zlib compress + base64 encode the string. To be inverted by the + # /c/ endpoint. + qr_url = prefix + "/c/" + base64.b64encode( + zlib.compress(args.encode()) + ).decode() + + # We show the human-friendly URL when hovering over the link, but for now + # let's keep it the same as the QR URL to ensure everything continues to work + # as expected (it takes more effort to validate QR barcodes than it does links). + # url = prefix + "/calculator/?" + args + url = qr_url qr = qrcode.QRCode( version=1, @@ -78,7 +94,7 @@ def generate_qr_code(prefix, form: FormData): box_size=10, border=4, ) - qr.add_data(url) + qr.add_data(qr_url) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white").convert('RGB') @@ -272,7 +288,7 @@ def build_report(base_url: str, model: models.ExposureModel, form: FormData): context.update(calculate_report_data(model)) alternative_scenarios = manufacture_alternative_scenarios(form) context['alternative_scenarios'] = comparison_report(alternative_scenarios) - context['qr_code'] = generate_qr_code(f'{base_url}/calculator', form) + context['qr_code'] = generate_qr_code(base_url, form) cara_templates = Path(__file__).parent.parent / "templates" calculator_templates = Path(__file__).parent / "templates"