Add route printer
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d33e4a4..6f841ba 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -53,12 +53,13 @@
 #SET(CMAKE_EXE_LINKER_FLAGS  "${CMAKE_EXE_LINKER_FLAGS} -z noexecstack -z relro -z now")
 
 # Boost
+#add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY)
 #add_definitions(-DBOOST_ASIO_ENABLE_HANDLER_TRACKING)
 #add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY)
 add_definitions(-DBOOST_ALL_NO_LIB)
 set(Boost_USE_STATIC_LIBS ON)
-hunter_add_package(Boost COMPONENTS system thread)
-find_package(Boost REQUIRED system thread REQUIRED)
+hunter_add_package(Boost COMPONENTS system thread iostreams)
+find_package(Boost REQUIRED system thread iostreams REQUIRED)
 
 #Openssl
 hunter_add_package(OpenSSL)
@@ -108,13 +109,15 @@
     include/crow/g3_logger.hpp
     include/ssl_key_handler.hpp
     include/color_cout_g3_sink.hpp
-    include/webassets.hpp
 )
 
 set(GENERATED_SRC_FILES 
     ${CMAKE_BINARY_DIR}/generated/webassets.cpp
+    ${CMAKE_BINARY_DIR}/generated/webassets.hpp
 )
 
+include_directories(${CMAKE_BINARY_DIR}/generated)
+
 set_source_files_properties(${GENERATED_SRC_FILES} PROPERTIES GENERATED TRUE)
 
 if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
@@ -136,6 +139,9 @@
     src/gtest_main.cpp
     src/base64_test.cpp
     src/token_authorization_middleware_test.cpp
+    src/security_headers_middleware_test.cpp
+    src/webassets_test.cpp
+    src/crow_getroutes_test.cpp
     ${CMAKE_BINARY_DIR}/generated/blns.hpp
 )
 
@@ -150,10 +156,11 @@
     # googletest
     enable_testing()
     find_package(GTest REQUIRED)
+    find_package(GMock REQUIRED)
 
     add_executable(unittest ${HDR_FILES} ${SRC_FILES} ${UT_FILES})
-    target_link_libraries(unittest GTest::GTest GTest::Main)
-    target_link_libraries(unittest Boost::system Boost::thread)
+    target_link_libraries(unittest ${GMOCK_BOTH_LIBRARIES})
+    target_link_libraries(unittest Boost::system Boost::thread Boost::iostreams) 
     target_link_libraries(unittest ${CMAKE_THREAD_LIBS_INIT})
     target_link_libraries(unittest OpenSSL::SSL OpenSSL::Crypto)
     target_link_libraries(unittest g3logger)
diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake
new file mode 100644
index 0000000..ec76d4b
--- /dev/null
+++ b/cmake/FindGMock.cmake
@@ -0,0 +1,129 @@
+# Locate the Google C++ Mocking Framework.
+# (This file is almost an identical copy of the original FindGTest.cmake file,
+#  feel free to use it as it is or modify it for your own needs.)
+#
+#
+# Defines the following variables:
+#
+#   GMOCK_FOUND - Found the Google Testing framework
+#   GMOCK_INCLUDE_DIRS - Include directories
+#
+# Also defines the library variables below as normal
+# variables. These contain debug/optimized keywords when
+# a debugging library is found.
+#
+#   GMOCK_BOTH_LIBRARIES - Both libgmock & libgmock-main
+#   GMOCK_LIBRARIES - libgmock
+#   GMOCK_MAIN_LIBRARIES - libgmock-main
+#
+# Accepts the following variables as input:
+#
+#   GMOCK_ROOT - (as a CMake or environment variable)
+#                The root directory of the gmock install prefix
+#
+#   GMOCK_MSVC_SEARCH - If compiling with MSVC, this variable can be set to
+#                       "MD" or "MT" to enable searching a gmock build tree
+#                       (defaults: "MD")
+#
+#-----------------------
+# Example Usage:
+#
+#    find_package(GMock REQUIRED)
+#    include_directories(${GMOCK_INCLUDE_DIRS})
+#
+#    add_executable(foo foo.cc)
+#    target_link_libraries(foo ${GMOCK_BOTH_LIBRARIES})
+#
+#=============================================================================
+# This file is released under the MIT licence:
+#
+# Copyright (c) 2011 Matej Svec
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#=============================================================================
+
+
+function(_gmock_append_debugs _endvar _library)
+  if(${_library} AND ${_library}_DEBUG)
+    set(_output optimized ${${_library}} debug ${${_library}_DEBUG})
+  else()
+    set(_output ${${_library}})
+  endif()
+  set(${_endvar} ${_output} PARENT_SCOPE)
+endfunction()
+
+function(_gmock_find_library _name)
+  find_library(${_name}
+    NAMES ${ARGN}
+    HINTS
+      $ENV{GMOCK_ROOT}
+      ${GMOCK_ROOT}
+    PATH_SUFFIXES ${_gmock_libpath_suffixes}
+  )
+  mark_as_advanced(${_name})
+endfunction()
+
+
+if(NOT DEFINED GMOCK_MSVC_SEARCH)
+  set(GMOCK_MSVC_SEARCH MD)
+endif()
+
+set(_gmock_libpath_suffixes lib)
+if(MSVC)
+  if(GMOCK_MSVC_SEARCH STREQUAL "MD")
+    list(APPEND _gmock_libpath_suffixes
+      msvc/gmock-md/Debug
+      msvc/gmock-md/Release)
+  elseif(GMOCK_MSVC_SEARCH STREQUAL "MT")
+    list(APPEND _gmock_libpath_suffixes
+      msvc/gmock/Debug
+      msvc/gmock/Release)
+  endif()
+endif()
+
+find_path(GMOCK_INCLUDE_DIR gmock/gmock.h
+  HINTS
+    $ENV{GMOCK_ROOT}/include
+    ${GMOCK_ROOT}/include
+)
+mark_as_advanced(GMOCK_INCLUDE_DIR)
+
+if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD")
+  # The provided /MD project files for Google Mock add -md suffixes to the
+  # library names.
+  _gmock_find_library(GMOCK_LIBRARY            gmock-md  gmock)
+  _gmock_find_library(GMOCK_LIBRARY_DEBUG      gmock-mdd gmockd)
+  _gmock_find_library(GMOCK_MAIN_LIBRARY       gmock_main-md  gmock_main)
+  _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_main-mdd gmock_maind)
+else()
+  _gmock_find_library(GMOCK_LIBRARY            gmock)
+  _gmock_find_library(GMOCK_LIBRARY_DEBUG      gmockd)
+  _gmock_find_library(GMOCK_MAIN_LIBRARY       gmock_main)
+  _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_maind)
+endif()
+
+include(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY)
+
+if(GMOCK_FOUND)
+  set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR})
+  _gmock_append_debugs(GMOCK_LIBRARIES      GMOCK_LIBRARY)
+  _gmock_append_debugs(GMOCK_MAIN_LIBRARIES GMOCK_MAIN_LIBRARY)
+  set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARIES} ${GMOCK_MAIN_LIBRARIES})
+endif()
diff --git a/crow/include/crow/app.h b/crow/include/crow/app.h
index 9162352..e0e5d84 100644
--- a/crow/include/crow/app.h
+++ b/crow/include/crow/app.h
@@ -21,7 +21,7 @@
 #define CROW_ROUTE(app, url) app.route_dynamic(url)
 #else
 #define CROW_ROUTE(app, url) \
-  app.route<crow::black_magic::get_parameter_tag(url)>(url)
+  app.template route<crow::black_magic::get_parameter_tag(url)>(url)
 #endif
 
 namespace crow {
@@ -116,6 +116,10 @@
     router_.debug_print();
   }
 
+  std::vector<std::string> get_routes() {
+    return router_.get_routes();
+  }
+
 #ifdef CROW_ENABLE_SSL
   self_t& ssl_file(const std::string& crt_filename,
                    const std::string& key_filename) {
diff --git a/crow/include/crow/routing.h b/crow/include/crow/routing.h
index a82c34e..d07f09f 100644
--- a/crow/include/crow/routing.h
+++ b/crow/include/crow/routing.h
@@ -940,6 +940,16 @@
 
   void debug_print() { trie_.debug_print(); }
 
+  std::vector<std::string> get_routes() {
+      std::vector<std::string> ret;
+      for (auto& rule: rules_){
+          if (rule != nullptr){
+            ret.push_back(rule->rule_);
+          }
+      }
+      return ret;
+   }
+
  private:
   std::vector<std::unique_ptr<BaseRule>> rules_;
   Trie trie_;
diff --git a/crow/include/crow/socket_adaptors.h b/crow/include/crow/socket_adaptors.h
index fe1bb93..a0d8dfa 100644
--- a/crow/include/crow/socket_adaptors.h
+++ b/crow/include/crow/socket_adaptors.h
@@ -33,6 +33,32 @@
   tcp::socket socket_;
 };
 
+
+struct TestSocketAdaptor {
+  using context = void;
+  TestSocketAdaptor(boost::asio::io_service& io_service, context*)
+      : socket_(io_service) {}
+
+  boost::asio::io_service& get_io_service() { return socket_.get_io_service(); }
+
+  tcp::socket& raw_socket() { return socket_; }
+
+  tcp::socket& socket() { return socket_; }
+
+  tcp::endpoint remote_endpoint() { return socket_.remote_endpoint(); }
+
+  bool is_open() { return socket_.is_open(); }
+
+  void close() { socket_.close(); }
+
+  template <typename F>
+  void start(F f) {
+    f(boost::system::error_code());
+  }
+
+  tcp::socket socket_;
+};
+
 #ifdef CROW_ENABLE_SSL
 struct SSLAdaptor {
   using context = boost::asio::ssl::context;
diff --git a/include/webassets.hpp b/include/webassets.hpp
deleted file mode 100644
index 1994cf5..0000000
--- a/include/webassets.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include <string>
-
-#include <crow/app.h>
-#include <crow/http_request.h>
-#include <crow/http_response.h>
-
-#include <crow/routing.h>
-#include <crow/bmc_app_type.hpp>
-
-
-namespace crow {
-namespace webassets {
-void request_routes(BmcAppType& app);
-}
-}
\ No newline at end of file
diff --git a/scripts/build_web_assets.py b/scripts/build_web_assets.py
index 0521bd7..d820c5a 100755
--- a/scripts/build_web_assets.py
+++ b/scripts/build_web_assets.py
@@ -5,6 +5,7 @@
 import gzip
 import hashlib
 from subprocess import Popen, PIPE
+from collections import defaultdict
 import re
 
 THIS_DIR = os.path.dirname(os.path.realpath(__file__))
@@ -20,59 +21,37 @@
     '.woff': "application/x-font-woff",
 }
 
-CPP_BEGIN_BUFFER = """
-#include <webassets.hpp>
-
-"""
-
-ROUTE_DECLARATION = """
-
-void crow::webassets::request_routes(BmcAppType& app){
-"""
-
-CACHE_FOREVER_HEADER = """
-    res.add_header("Cache-Control", "public, max-age=31556926");
-"""
-
-CPP_MIDDLE_BUFFER = """
-    CROW_ROUTE(app, "{relative_path_sha1}")([](const crow::request& req, crow::response& res) {{
-        {CACHE_FOREVER_HEADER}
-
-        res.add_header("ETag", "{sha1}");
-        if (req.headers.count("If-None-Match") == 1) {{
-            if (req.get_header_value("If-None-Match") == "{sha1}"){{
-                res.code = 304;
-                res.end();
-                return;
-            }}
-        }}
-
-        res.code = 200;
-        // TODO, if you have a browser from the dark ages that doesn't support gzip,
-        // unzip it before sending based on Accept-Encoding header
-        res.add_header("Content-Encoding", "{content_encoding}");
-        res.add_header("Content-Type", "{content_type}");
-
-        res.write({relative_path_escaped});
-        
+CPP_MIDDLE_BUFFER = """  CROW_ROUTE(app, "{relative_path_sha1}")
+  ([](const crow::request& req, crow::response& res) {{
+    {CACHE_FOREVER_HEADER}
+    res.add_header("ETag", "{sha1}");
+    if (req.headers.count("If-None-Match") == 1) {{
+      if (req.get_header_value("If-None-Match") == "{sha1}") {{
+        res.code = 304;
         res.end();
-    }});
+        return;
+      }}
+    }}
+
+    res.code = 200;
+    // TODO, if you have a browser from the dark ages that doesn't support gzip,
+    // unzip it before sending based on Accept-Encoding header
+    res.add_header("Content-Encoding", "{content_encoding}");
+    res.add_header("Content-Type", "{content_type}");
+
+    res.write(staticassets::{relative_path_escaped});
+
+    res.end();
+  }});
 """
 
 
 def twos_comp(val, bits):
     """compute the 2's compliment of int value val"""
-    if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
+    if (val & (1 << (bits - 1))) != 0:  # if sign bit is set e.g., 8bit: 128-255
         val = val - (1 << bits)        # compute negative value
     return val                         # return positive value as is
 
-CPP_END_BUFFER = """
-}
-"""
-
-CPP_END_BUFFER2 = """const static std::string {relative_path_escaped}{{{file_bytes}}};
-"""
-
 def get_relative_path(full_filepath):
     pathsplit = full_filepath.split(os.path.sep)
     relative_path = os.path.sep.join(pathsplit[pathsplit.index("static") + 1:])
@@ -85,6 +64,7 @@
 
     return relative_path, relative_path_escaped
 
+
 def get_sha1_path_from_relative(relative_path, sha1):
     if sha1 != "":
         path, extension = os.path.splitext(relative_path)
@@ -92,11 +72,13 @@
     else:
         return relative_path
 
+
 def filter_html(sha1_list, file_content):
     string_content = file_content.decode()
     for key, value in sha1_list.items():
         replace_name = get_sha1_path_from_relative(key, value)
-        string_content_new = re.sub("((src|href)=[\"'])(" + re.escape(key) + ")([\"'])", "\\1" + replace_name + "\\4", string_content)
+        string_content_new = re.sub(
+            "((src|href)=[\"'])(" + re.escape(key) + ")([\"'])", "\\1" + replace_name + "\\4", string_content)
         if string_content_new != string_content:
             print("    Replaced {}".format(key))
             print("        With {}".format(replace_name))
@@ -104,6 +86,7 @@
 
     return string_content.encode()
 
+
 def filter_js(sha1_list, file_content):
 
     string_content = file_content.decode()
@@ -117,23 +100,13 @@
             string_content = string_content_new
     return string_content.encode()
 
+
 def compute_sha1_and_update_dict(sha1_list, file_content, relative_path):
     sha = hashlib.sha1()
     sha.update(file_content)
-    sha_bytes = sha.digest()
-
-    sha_text = "".join("{:02x}".format(x) for x in sha_bytes)
+    sha_text = sha.hexdigest()
     sha1_list[relative_path] = sha_text
 
-FILE_PRECIDENCE = ['.woff', '.png' ,'.css', '.js', '.html']
-def sort_order(full_filepath):
-    # sort list based on users
-    path, ext = os.path.splitext(full_filepath)
-    if ext in FILE_PRECIDENCE:
-        return FILE_PRECIDENCE.index(ext) + 1
-    else:
-        return 0
-
 
 def get_dependencies(dependency_list, full_filepath):
     r = []
@@ -145,11 +118,13 @@
     r.extend(sub_deps)
     return r
 
+
 def remove_duplicates_preserve_order(seq):
     seen = set()
     seen_add = seen.add
     return [x for x in seq if not (x in seen or seen_add(x))]
 
+
 def main():
     """ Main Function """
 
@@ -164,11 +139,12 @@
     file_list = [os.path.realpath(f) for f in file_list]
 
     sha1_list = {}
+    content_dict = {}
 
-    file_list.sort(key=sort_order)
-    from collections import defaultdict
     depends_on = {}
 
+    gzip_content = not(args.debug)
+
     for full_filepath in file_list:
         relative_path, relative_path_escaped = get_relative_path(full_filepath)
         text_file_types = ['.css', '.js', '.html']
@@ -180,12 +156,14 @@
             for full_replacename in file_list:
                 relative_replacename, _ = get_relative_path(full_replacename)
                 if ext == ".html":
-                    match = re.search("((src|href)=[\"'])(" + relative_replacename + ")([\"'])", file_content)
+                    match = re.search(
+                        "((src|href)=[\"'])(" + relative_replacename + ")([\"'])", file_content)
                     if match:
                         depends_on[full_filepath].append(full_replacename)
 
                 elif ext == ".js" or ext == ".css":
-                    match = re.search("([\"'](\.\./)*)(" + relative_replacename + ")([\"'\?])", file_content)
+                    match = re.search(
+                        "([\"'](\.\./)*)(" + relative_replacename + ")([\"'\?])", file_content)
                     if match:
                         depends_on[full_filepath].append(full_replacename)
 
@@ -196,52 +174,70 @@
         dependency_ordered_file_list.extend(deps)
         dependency_ordered_file_list.append(full_filepath)
 
-    dependency_ordered_file_list = remove_duplicates_preserve_order(dependency_ordered_file_list)
+    dependency_ordered_file_list = remove_duplicates_preserve_order(
+        dependency_ordered_file_list)
 
-    with open(args.output, 'w') as cpp_output:
-        cpp_output.write(CPP_BEGIN_BUFFER)
+
+    for full_filepath in dependency_ordered_file_list:
+        # make sure none of the files are hidden
+        with open(full_filepath, 'rb') as input_file:
+            file_content = input_file.read()
+        relative_path, relative_path_escaped = get_relative_path(
+            full_filepath)
+        extension = os.path.splitext(relative_path)[1]
+
+        print("Including {:<40} size {:>7}".format(
+            relative_path, len(file_content)))
+
+        if extension == ".html" or relative_path == "/":
+            new_file_content = filter_html(sha1_list, file_content)
+        elif extension == ".js" or extension == ".css":
+            new_file_content = filter_js(sha1_list, file_content)
+        else:
+            new_file_content = file_content
+
+        file_content = new_file_content
+
+        if gzip_content:
+            file_content = gzip.compress(file_content)
+
+        compute_sha1_and_update_dict(
+            sha1_list, file_content, relative_path)
+        content_dict[full_filepath] = file_content
+
+    with open(args.output.replace("cpp", "hpp"), 'w') as hpp_output:
+        hpp_output.write("#pragma once\n"
+                         "\n"
+                         "#include <string>\n"
+                         "\n"
+                         "#include <crow/app.h>\n"
+                         "#include <crow/http_request.h>\n"
+                         "#include <crow/http_response.h>\n"
+                         "\n"
+                         "#include <crow/routing.h>\n"
+                         "#include <crow/bmc_app_type.hpp>\n"
+                         "\n"
+                         "namespace crow {\n"
+                         "namespace webassets {\n"
+                        )
+
+        hpp_output.write("struct staticassets {\n")
+        for full_filepath in dependency_ordered_file_list:
+            relative_path, relative_path_escaped = get_relative_path(
+                full_filepath)
+            hpp_output.write(
+                "  static const std::string {};\n".format(relative_path_escaped))
+        hpp_output.write("};\n\n")
+        hpp_output.write("template <typename... Middlewares>\n")
+        hpp_output.write("void request_routes(Crow<Middlewares...>& app) {\n")
 
         for full_filepath in dependency_ordered_file_list:
-            # make sure none of the files are hidden
-            with open(full_filepath, 'rb') as input_file:
-                file_content = input_file.read()
-            relative_path, relative_path_escaped = get_relative_path(full_filepath)
-            extension = os.path.splitext(relative_path)[1]
-
-            print("Including {:<40} size {:>7}".format(relative_path, len(file_content)))
-
-            if extension == ".html" or relative_path == "/":
-                new_file_content = filter_html(sha1_list, file_content)
-            elif extension == ".js" or extension == ".css":
-                new_file_content = filter_js(sha1_list, file_content)
-            else:
-                new_file_content = file_content
-
-            file_content = new_file_content
-
-            if not args.debug:
-                file_content = gzip.compress(file_content)
-                #file_content = file_content[:10]
-                # compute the 2s complement.  If you don't, you get narrowing warnings from gcc/clang
-
-            compute_sha1_and_update_dict(sha1_list, file_content, relative_path)
-            array_binary_text = ', '.join(str(twos_comp(x, 8)) for x in file_content)
-
-            cpp_output.write(
-                CPP_END_BUFFER2.format(
-                    relative_path=relative_path,
-                    file_bytes=array_binary_text,
-                    relative_path_escaped=relative_path_escaped
-                )
-            )
-
-        cpp_output.write(ROUTE_DECLARATION)
-
-        for full_filepath in dependency_ordered_file_list:
-            relative_path, relative_path_escaped = get_relative_path(full_filepath)
+            relative_path, relative_path_escaped = get_relative_path(
+                full_filepath)
             sha1 = sha1_list.get(relative_path, '')
 
-            content_type = CONTENT_TYPES.get(os.path.splitext(relative_path)[1], "")
+            content_type = CONTENT_TYPES.get(
+                os.path.splitext(relative_path)[1], "")
             if content_type == "":
                 print("unknown content type for {}".format(relative_path))
 
@@ -250,34 +246,56 @@
                 relative_path = "/"
                 relative_path_sha1 = "/"
             else:
-                relative_path_sha1 = "/" + get_sha1_path_from_relative(relative_path, sha1)
-
-            content_encoding = 'none' if args.debug else 'gzip'
+                relative_path_sha1 = "/" + \
+                    get_sha1_path_from_relative(relative_path, sha1)
+            #print("relative_path_sha1: " + relative_path_sha1)
+            #print("sha1: " + sha1)
+            content_encoding = 'gzip' if gzip_content else 'none'
 
             environment = {
-                'relative_path':relative_path,
+                'relative_path': relative_path,
                 'relative_path_escaped': relative_path_escaped,
                 'relative_path_sha1': relative_path_sha1,
                 'sha1': sha1,
                 'sha1_short': sha1[:20],
                 'content_type': content_type,
-                'content_encoding': content_encoding
+                'content_encoding': content_encoding,
+                "CACHE_FOREVER_HEADER": ""
             }
-            environment["CACHE_FOREVER_HEADER"] = ""
+
             if ENABLE_CACHING:
                 # if we have a valid sha1, and we have a unique path to the resource
                 # it can be safely cached forever
                 if sha1 != "" and relative_path != relative_path_sha1:
-                    environment["CACHE_FOREVER_HEADER"] = CACHE_FOREVER_HEADER
+                    environment["CACHE_FOREVER_HEADER"] = "res.add_header(\"Cache-Control\", \"public, max-age=31556926\");\n"
 
-            content = CPP_MIDDLE_BUFFER.format(
-                **environment
+            content = CPP_MIDDLE_BUFFER.format(**environment)
+            hpp_output.write(content)
+
+        hpp_output.write("}\n}\n}")
+
+    with open(args.output, 'w') as cpp_output:
+        cpp_output.write("#include <webassets.hpp>\n"
+                         "namespace crow{\n"
+                         "namespace webassets{\n")
+
+        for full_filepath in dependency_ordered_file_list:
+            file_content = content_dict[full_filepath]
+            relative_path, relative_path_escaped = get_relative_path(
+                full_filepath)
+            # compute the 2s complement for negative numbers.
+            # If you don't, you get narrowing warnings from gcc/clang
+            array_binary_text = ', '.join(str(twos_comp(x, 8))
+                                          for x in file_content)
+            cpp_end_buffer = "const std::string staticassets::{relative_path_escaped}{{{file_bytes}}};\n"
+            cpp_output.write(
+                cpp_end_buffer.format(
+                    relative_path=relative_path,
+                    file_bytes=array_binary_text,
+                    relative_path_escaped=relative_path_escaped
+                )
             )
-            cpp_output.write(content)
-
-        cpp_output.write(CPP_END_BUFFER)
-
-
+        cpp_output.write("}\n}\n")
 
 if __name__ == "__main__":
     main()
diff --git a/src/crow_getroutes_test.cpp b/src/crow_getroutes_test.cpp
new file mode 100644
index 0000000..6a9f538
--- /dev/null
+++ b/src/crow_getroutes_test.cpp
@@ -0,0 +1,38 @@
+#include <crow/app.h>
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace crow;
+using namespace std;
+
+TEST(GetRoutes, TestEmptyRoutes) {
+  SimpleApp app;
+  decltype(app)::server_t server(&app, "127.0.0.1", 45451);
+
+  EXPECT_THAT(app.get_routes(), testing::IsEmpty());
+}
+
+// Tests that static urls are correctly passed
+TEST(GetRoutes, TestOneRoute) {
+  SimpleApp app;
+  decltype(app)::server_t server(&app, "127.0.0.1", 45451);
+  CROW_ROUTE(app, "/")([]() { return 200; });
+
+  EXPECT_THAT(app.get_routes(), testing::ElementsAre("/"));
+}
+
+// Tests that static urls are correctly passed
+TEST(GetRoutes, TestlotsOfRoutes) {
+  SimpleApp app;
+  decltype(app)::server_t server(&app, "127.0.0.1", 45451);
+  CROW_ROUTE(app, "/")([]() { return 200; });
+  CROW_ROUTE(app, "/foo")([]() { return 200; });
+  CROW_ROUTE(app, "/bar")([]() { return 200; });
+  CROW_ROUTE(app, "/baz")([]() { return 200; });
+  CROW_ROUTE(app, "/boo")([]() { return 200; });
+  CROW_ROUTE(app, "/moo")([]() { return 200; });
+
+  EXPECT_THAT(app.get_routes(),
+              testing::UnorderedElementsAre("/", "/foo", "/bar", "/baz", "/boo",
+                                            "/moo"));
+}
\ No newline at end of file
diff --git a/src/security_headers_middleware_test.cpp b/src/security_headers_middleware_test.cpp
new file mode 100644
index 0000000..2364ab5
--- /dev/null
+++ b/src/security_headers_middleware_test.cpp
@@ -0,0 +1,74 @@
+#include <crow/app.h>
+#include <gmock/gmock.h>
+#include <security_headers_middleware.hpp>
+#include "gtest/gtest.h"
+
+using namespace crow;
+using namespace std;
+
+// Tests that the security headers are added correctly
+TEST(SecurityHeaders, TestHeadersExist) {
+  App<SecurityHeadersMiddleware> app;
+  app.bindaddr("127.0.0.1").port(45451);
+  CROW_ROUTE(app, "/")([]() { return 200; });
+  auto _ = async(launch::async, [&] { app.run(); });
+
+  asio::io_service is;
+  std::array<char, 2048> buf;
+  std::string sendmsg;
+
+  {
+    // Retry a couple of times waiting for the server to come up
+    // TODO(ed)  This is really unfortunate, and should use some form of mock
+    asio::ip::tcp::socket c(is);
+    for (int i = 0; i < 200; i++) {
+      try {
+        c.connect(asio::ip::tcp::endpoint(
+            asio::ip::address::from_string("127.0.0.1"), 45451));
+        c.close();
+        break;
+      } catch (std::exception e) {
+        // do nothing.  We expect this to fail while the server is starting up
+      }
+    }
+  }
+
+  // Test correct login credentials
+  sendmsg = "GET /\r\n\r\n";
+
+  asio::ip::tcp::socket c(is);
+  c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"),
+                                    45451));
+  c.send(asio::buffer(sendmsg));
+  auto received_count = c.receive(asio::buffer(buf));
+  c.close();
+  auto return_code = std::string(&buf[9], &buf[12]);
+  EXPECT_EQ("200", return_code);
+  std::string response(std::begin(buf), std::end(buf));
+
+  // This is a routine to split strings until a newline is hit
+  // TODO(ed) this should really use the HTTP parser
+  std::vector<std::string> headers;
+  std::string::size_type pos = 0;
+  std::string::size_type prev = 0;
+  while ((pos = response.find("\r\n", prev)) != std::string::npos) {
+    auto this_string = response.substr(prev, pos - prev);
+    if (this_string == "") {
+      break;
+    }
+    headers.push_back(this_string);
+    prev = pos + 2;
+  }
+  headers.push_back(response.substr(prev));
+
+  EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
+  EXPECT_THAT(headers, ::testing::Contains("Strict-Transport-Security: "
+                                           "max-age=31536000; "
+                                           "includeSubdomains; preload"));
+  EXPECT_THAT(headers, ::testing::Contains("X-UA-Compatible: IE=11"));
+  EXPECT_THAT(headers, ::testing::Contains("X-Frame-Options: DENY"));
+  EXPECT_THAT(headers, ::testing::Contains("X-XSS-Protection: 1; mode=block"));
+  EXPECT_THAT(headers, ::testing::Contains(
+                           "X-Content-Security-Policy: default-src 'self'"));
+  app.stop();
+}
diff --git a/src/token_authorization_middleware.cpp b/src/token_authorization_middleware.cpp
index bf5efe1..a5ecd2d 100644
--- a/src/token_authorization_middleware.cpp
+++ b/src/token_authorization_middleware.cpp
@@ -75,6 +75,7 @@
         x["token"] = auth_token;
 
         res.write(json::dump(x));
+        res.add_header("Content-Type", "application/json");
         res.end();
       } else {
         return_unauthorized();
diff --git a/src/token_authorization_middleware_test.cpp b/src/token_authorization_middleware_test.cpp
index 9f8e626..68efe8f 100644
--- a/src/token_authorization_middleware_test.cpp
+++ b/src/token_authorization_middleware_test.cpp
@@ -1,6 +1,7 @@
 #include "token_authorization_middleware.hpp"
 #include <crow/app.h>
 #include "gtest/gtest.h"
+#include "gmock/gmock.h"
 
 using namespace crow;
 using namespace std;
@@ -217,11 +218,31 @@
       "\"password\": \"dude\"}\r\n";
   {
     send_to_localhost(sendmsg);
-    auto return_code = std::string(&buf[9], &buf[12]);
-    EXPECT_EQ("200", return_code);
+    std::string response(std::begin(buf), std::end(buf));
+    // This is a routine to split strings until a newline is hit
+    // TODO(ed) this should really use the HTTP parser
+    std::vector<std::string> headers;
+    std::string::size_type pos = 0;
+    std::string::size_type prev = 0;
+    int content_length = 0;
+    std::string content_encoding("");
+    while ((pos = response.find("\r\n", prev)) != std::string::npos) {
+      auto this_string = response.substr(prev, pos - prev);
+      if (this_string == "") {
+        prev = pos + 2;
+        break;
+      }
+
+      headers.push_back(this_string);
+      prev = pos + 2;
+    }
+    EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
+    EXPECT_THAT(headers, testing::Contains("Content-Type: application/json"));
+    auto http_content = response.substr(prev);
   }
 
 
+
   // Try to use those login credentials to access a resource
   sendmsg =
       "GET /\r\nAuthorization: token\r\n\r\n{\"username\": \"dude\", "
diff --git a/src/udpclient.cpp b/src/udpclient.cpp
index 5f04a9a..772739b 100644
--- a/src/udpclient.cpp
+++ b/src/udpclient.cpp
@@ -59,7 +59,7 @@
     uint8_t session_id = 0;
 
     uint8_t seq_number2 = 0;
-    uint8_t seq_number_lun = seq_number << 2 + lun;
+    uint8_t seq_number_lun = (seq_number << 2) + lun;
 
     uint8_t seq2 = 0xff;  //????
     uint8_t rmcp_class = 0x07;
diff --git a/src/webassets_test.cpp b/src/webassets_test.cpp
new file mode 100644
index 0000000..2157f03
--- /dev/null
+++ b/src/webassets_test.cpp
@@ -0,0 +1,207 @@
+#include <crow/app.h>
+#include <gmock/gmock.h>
+#include <zlib.h>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/iostreams/copy.hpp>
+#include <boost/iostreams/filter/gzip.hpp>
+#include <boost/iostreams/filtering_streambuf.hpp>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+#include <webassets.hpp>
+#include "gtest/gtest.h"
+using namespace crow;
+using namespace std;
+using namespace testing;
+
+bool gzipInflate(const std::string& compressedBytes,
+                 std::string& uncompressedBytes) {
+  if (compressedBytes.size() == 0) {
+    uncompressedBytes = compressedBytes;
+    return true;
+  }
+
+  uncompressedBytes.clear();
+
+  unsigned full_length = compressedBytes.size();
+  unsigned half_length = compressedBytes.size() / 2;
+
+  unsigned uncompLength = full_length;
+  char* uncomp = (char*)calloc(sizeof(char), uncompLength);
+
+  z_stream strm;
+  strm.next_in = (Bytef*)compressedBytes.c_str();
+  strm.avail_in = compressedBytes.size();
+  strm.total_out = 0;
+  strm.zalloc = Z_NULL;
+  strm.zfree = Z_NULL;
+
+  bool done = false;
+
+  if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) {
+    free(uncomp);
+    return false;
+  }
+
+  while (!done) {
+    // If our output buffer is too small
+    if (strm.total_out >= uncompLength) {
+      // Increase size of output buffer
+      char* uncomp2 = (char*)calloc(sizeof(char), uncompLength + half_length);
+      memcpy(uncomp2, uncomp, uncompLength);
+      uncompLength += half_length;
+      free(uncomp);
+      uncomp = uncomp2;
+    }
+
+    strm.next_out = (Bytef*)(uncomp + strm.total_out);
+    strm.avail_out = uncompLength - strm.total_out;
+
+    // Inflate another chunk.
+    int err = inflate(&strm, Z_SYNC_FLUSH);
+    if (err == Z_STREAM_END)
+      done = true;
+    else if (err != Z_OK) {
+      break;
+    }
+  }
+
+  if (inflateEnd(&strm) != Z_OK) {
+    free(uncomp);
+    return false;
+  }
+
+  for (size_t i = 0; i < strm.total_out; ++i) {
+    uncompressedBytes += uncomp[i];
+  }
+  free(uncomp);
+  return true;
+}
+
+
+
+
+// Tests static files are loaded correctly
+TEST(Webassets, StaticFilesFixedRoutes) {
+  std::array<char, 2048> buf;
+  SimpleApp app;
+  webassets::request_routes(app);
+  Server<SimpleApp> server(&app, "127.0.0.1", 45451);
+  auto _ = async(launch::async, [&] { server.run(); });
+
+  // Get the homepage
+  std::string sendmsg = "GET /\r\n\r\n";
+
+  asio::io_service is;
+
+  asio::ip::tcp::socket c(is);
+  c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"),
+                                    45451));
+
+  c.send(asio::buffer(sendmsg));
+
+  c.receive(asio::buffer(buf, 2048));
+  c.close();
+
+  std::string response(std::begin(buf), std::end(buf));
+  // This is a routine to split strings until a newline is hit
+  // TODO(ed) this should really use the HTTP parser
+  std::vector<std::string> headers;
+  std::string::size_type pos = 0;
+  std::string::size_type prev = 0;
+  int content_length = 0;
+  std::string content_encoding("");
+  while ((pos = response.find("\r\n", prev)) != std::string::npos) {
+    auto this_string = response.substr(prev, pos - prev);
+    if (this_string == "") {
+      prev = pos + 2;
+      break;
+    }
+
+    if (boost::starts_with(this_string, "Content-Length: ")) {
+      content_length = boost::lexical_cast<int>(this_string.substr(16));
+      // TODO(ed) This is an unfortunate test, but it's all we have at this
+      // point
+      // Realistically, the index.html will be more than 500 bytes.  This
+      // test will need to be improved at some point
+      EXPECT_GT(content_length, 500);
+    }
+    if (boost::starts_with(this_string, "Content-Encoding: ")) {
+      content_encoding = this_string.substr(18);
+    }
+
+    headers.push_back(this_string);
+    prev = pos + 2;
+  }
+
+  auto http_content = response.substr(prev);
+  // TODO(ed) ideally the server should support non-compressed gzip assets.
+  // Once this
+  // occurs, this line will be obsolete
+  std::string ungziped_content = http_content;
+  if (content_encoding == "gzip") {
+    EXPECT_TRUE(gzipInflate(http_content, ungziped_content));
+  }
+
+  EXPECT_EQ(headers[0], "HTTP/1.1 200 OK");
+  EXPECT_THAT(headers,
+              ::testing::Contains("Content-Type: text/html;charset=UTF-8"));
+
+  EXPECT_EQ(ungziped_content.substr(0, 21), "<!DOCTYPE html>\n<html");
+
+  server.stop();
+}
+
+
+
+// Tests static files are loaded correctly
+TEST(Webassets, EtagIsSane) {
+  std::array<char, 2048> buf;
+  SimpleApp app;
+  webassets::request_routes(app);
+  Server<SimpleApp> server(&app, "127.0.0.1", 45451);
+  auto _ = async(launch::async, [&] { server.run(); });
+
+  // Get the homepage
+  std::string sendmsg = "GET /\r\n\r\n";
+
+  asio::io_service is;
+
+  asio::ip::tcp::socket c(is);
+  c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"),
+                                    45451));
+
+  c.send(asio::buffer(sendmsg));
+
+  c.receive(asio::buffer(buf, 2048));
+  c.close();
+
+  std::string response(std::begin(buf), std::end(buf));
+  // This is a routine to split strings until a newline is hit
+  // TODO(ed) this should really use the HTTP parser
+  std::vector<std::string> headers;
+  std::string::size_type pos = 0;
+  std::string::size_type prev = 0;
+  int content_length = 0;
+  std::string content_encoding("");
+  while ((pos = response.find("\r\n", prev)) != std::string::npos) {
+    auto this_string = response.substr(prev, pos - prev);
+    if (this_string == "") {
+      prev = pos + 2;
+      break;
+    }
+
+    if (boost::starts_with(this_string, "ETag: ")) {
+      auto etag = this_string.substr(6);
+      // ETAG should not be blank
+      EXPECT_NE(etag, "");
+      // SHa1 is 20 characters long
+      EXPECT_EQ(etag.size(), 40);
+      EXPECT_THAT(etag, MatchesRegex("^[a-f0-9]+$"));
+    }
+
+    headers.push_back(this_string);
+    prev = pos + 2;
+  }
+
+  server.stop();
+}
\ No newline at end of file
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index 32a1192..cf8500f 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -58,7 +58,7 @@
   crow::kvm::request_routes(app);
 
   crow::logger::setLogLevel(crow::LogLevel::INFO);
-
+  app.debug_print();
   CROW_ROUTE(app, "/systeminfo")
   ([]() {
 
diff --git a/static/css/font-awesome.css b/static/css/font-awesome.css
index 4ce929a..a67a323 100644
--- a/static/css/font-awesome.css
+++ b/static/css/font-awesome.css
@@ -7,7 +7,7 @@
 @font-face {
   font-family: 'FontAwesome';
   /* WARNING: This line is modified from stock FA, to make cachign work*/
-  src: url('../../static/fonts/fontawesome-webfont.woff?v=4.7.0') format('woff');
+  src: url('../../static/fonts/fontawesome-webfont.woff') format('woff');
   font-weight: normal;
   font-style: normal;
 }
diff --git a/static/index.html b/static/index.html
index 391bf93..f0f4148 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html">
+<!DOCTYPE html>
 <html lang="en" ng-app="bmcApp">
 
 <head>