docs: def create_server_app(provider, password=None, cache=True, cache_timeout=3600,
                        debug=False):
      """
      Create a DAAP server, based around a Flask application. The server requires
      a content provider, server name and optionally, a password. The content
      provider should return raw object data.
  
      Object responses can be cached. This may dramatically speed up connections
      for multiple clients. However, this is only limited to objects, not file
      servings.
  
      Note: in case the server is mounted as a WSGI app, make sure the server
      passes the authorization header.
      """
  
      # Create Flask App
      app = Flask(__name__, static_folder=None)
      app.debug = debug
  
      # Setup cache
      if cache:
          if type(cache) == bool:
              cache = SimpleCache()
          else:
              # Assume is a user-provided cache with a get-set method.
              pass
      else:
          cache = False
  
      #
      # Context-aware helpers and decorators
      #
  
      def daap_wsgi_app(func):
          """
          WSGI middleware which will modify the environment and strip 'daap://'
          from the path. This way, Flask can route the request properly.
          """
  
          @wraps(func)
          def _inner(environment, start_response):
              if environment["PATH_INFO"].startswith("daap://") or \
                      environment["PATH_INFO"].startswith("http://"):
                  environment["PATH_INFO"] = "/" + \
                      environment["PATH_INFO"].split("/", 3)[3]
              return func(environment, start_response)
          return _inner
      app.wsgi_app = daap_wsgi_app(app.wsgi_app)
  
      def daap_trace(func):
          """
          Utility method for tracing function calls. Helps debugging malicious
          requests (e.g. protocol changes). Is only enabled when `debug` is True.
          Normally, exceptions are caught by Flask and handled as Bad Requests.
          Any debugging is therefore lost.
          """
  
          # Do not apply when debug is False.
          if not debug:
              return func
  
          @wraps(func)
          def _inner(*args, **kwargs):
              try:
                  start = time.time()
                  result = func(*args, **kwargs)
                  logger.debug(
                      "Request handling took %.6f seconds",
                      time.time() - start)
  
                  return result
              except:
                  logger.exception(
                      "Caught exception before raising it to Flask.")
                  raise
  
          return _inner
  
      def daap_unpack_args(func):
          """
          Strip query string arguments and add them to the method as keyword
          arguments. Since the query string keys are defined, values will be
          converted to their approriate format. An exception will be thrown in
          case a requested argument is not available, or if the value could not
          be converted.
          """
  
          # Create a function specific mapping, only for arguments appearing in
          # the function declaration.
          args, _, _, _ = inspect.getargspec(func)
          mappings = [mapping for mapping in QS_MAPPING if mapping[1] in args]
  
          @wraps(func)
          def _inner(*args, **kwargs):
              for key, kwarg, casting in mappings:
                  kwargs[kwarg] = casting(request.args[key])
              return func(*args, **kwargs)
          return _inner
  
      def daap_authenticate(func):
          """
          Check authorization header, if authorization is given. Returns 401
          response if the authentication failed.
          """
  
          # Do not apply when no password is set
          if not password:
              return func
  
          @wraps(func)
          def _inner(*args, **kwargs):
              auth = request.authorization
  
              if not auth or not auth.password == password:
                  return Response(None, 401, {
                      "WWW-Authenticate": "Basic realm=\"%s\"" %
                      provider.server.name})
              return func(*args, **kwargs)
          return _inner
      app.authenticate = daap_authenticate
  
      def daap_cache_response(func):
          """
          Cache object responses if the cache has been initialized. The cache key
          is based on the request path and the semi-constant request arguments.
          The response is caches for as long as possible, which should not be a
          problem if the cache is cleared if the provider has new data.
          """
  
          # Do not apply when cache is False.
          if not cache:
              return func
  
          @wraps(func)
          def _inner(*args, **kwargs):
              # Create hash key via hashlib. We use MD5 since it is slightly
              # faster than SHA1. Note that we don't require cryptographically
              # strong hashes -- we just want to have a short and computationally
              # unique key.
              key = hashlib.md5()
  
              # Add basic info
              key.update(func.__name__)
              key.update(request.path)
  
              for k, v in request.args.iteritems():
                  if k not in QS_IGNORE_CACHE:
                      key.update(v)
  
              # Hit the cache
              key = key.digest()
              value = cache.get(key)
  
              if value is None:
                  value = func(*args, **kwargs)
                  cache.set(key, value, timeout=cache_timeout)
              elif debug:
                  logger.debug("Loaded response from cache.")
              return value
          return _inner
  
      #
      # Request handlers
      #
  
      @app.after_request
      def after_request(response):
          """
          Append default response headers, independent of the return type.
          """
  
          response.headers["DAAP-Server"] = provider.server.name
          response.headers["Content-Language"] = "en_us"
          response.headers["Accept-Ranges"] = "bytes"
  
          return response
  
      @app.route("/server-info", methods=["GET"])
      @daap_trace
      @daap_cache_response
      def server_info():
          """
          """
  
          data = responses.server_info(provider, provider.server.name, password)
  
          return ObjectResponse(data)
  
      @app.route("/content-codes", methods=["GET"])
      @daap_trace
      @daap_cache_response
      def content_codes():
          """
          """
  
          data = responses.content_codes(provider)
  
          return ObjectResponse(data)
  
      @app.route("/login", methods=["GET"])
      @daap_trace
      @daap_authenticate
      def login():
          """
          """
  
          session_id = provider.create_session(
              user_agent=request.headers.get("User-Agent"),
              remote_address=request.remote_addr,
              client_version=request.headers.get(
                  "Client-DAAP-Version"))
          data = responses.login(provider, session_id)
  
          return ObjectResponse(data)
  
      @app.route("/logout", methods=["GET"])
      @daap_trace
      @daap_authenticate
      @daap_unpack_args
      def logout(session_id):
          """
          """
  
          provider.destroy_session(session_id)
  
          return Response(None, status=204)
  
      @app.route("/activity", methods=["GET"])
      @daap_trace
      @daap_authenticate
      @daap_unpack_args
      def activity(session_id):
          """
          """
  
          return Response(None, status=200)
  
      @app.route("/update", methods=["GET"])
      @daap_trace
      @daap_authenticate
      @daap_unpack_args
      def update(session_id, revision, delta):
          """
          """
  
          revision = provider.get_next_revision(session_id, revision, delta)
  
          data = responses.update(provider, revision)
  
          return ObjectResponse(data)
  
      @app.route("/fp-setup", methods=["POST"])
      @daap_trace
      @daap_authenticate
      def fp_setup():
          """
          Fairplay validation, as sent by iTunes 11+. It will be unlikely this
          will be ever implemented.
          """
  
          raise NotImplementedError("Fairplay not supported.")
  
      @app.route("/databases", methods=["GET"])
      @daap_trace
      @daap_authenticate
      @daap_cache_response
      @daap_unpack_args
      def databases(session_id, revision, delta):
          """
          """
  
          new, old = provider.get_databases(session_id, revision, delta)
          added, removed, is_update = utils.diff(new, old)
  
          data = responses.databases(
              provider, new, old, added, removed, is_update)
  
          return ObjectResponse(data)
  
      @app.route(
          "/databases/<int:database_id>/items/<int:item_id>/extra_data/artwork",
          methods=["GET"])
      @daap_trace
      @daap_unpack_args
      def database_item_artwork(database_id, item_id, session_id):
          """
          """
  
          data, mimetype, total_length = provider.get_artwork(
              session_id, database_id, item_id)
  
          # Setup response
          response = Response(
              data, 200, mimetype=mimetype,
              direct_passthrough=not isinstance(data, basestring))
  
          if total_length:
              response.headers["Content-Length"] = total_length
  
          return response
  
      @app.route(
          "/databases/<int:database_id>/groups/<int:group_id>/extra_data/"
          "artwork", methods=["GET"])
      @daap_trace
      @daap_unpack_args
      def database_group_artwork(database_id, group_id, session_id, revision,
                                 delta):
          """
          """
          raise NotImplemented("Groups not supported.")
  
      @app.route(
          "/databases/<int:database_id>/items/<int:item_id>.<suffix>",
          methods=["GET"])
      @daap_trace
      @daap_unpack_args
      def database_item(database_id, item_id, suffix, session_id):
          """
          """
  
          range_header = request.headers.get("Range", None)
  
          if range_header:
              begin, end = http.parse_range_header(range_header).ranges[0]
              data, mimetype, total_length = provider.get_item(
                  session_id, database_id, item_id, byte_range=(begin, end))
              begin, end = (begin or 0), (end or total_length)
  
              # Setup response
              response = Response(
                  data, 206, mimetype=mimetype,
                  direct_passthrough=not isinstance(data, basestring))
  
              # A streaming response with unknown content lenght, Range x-*
              # as per RFC2616 section 14.16
              if total_length <= 0:
                  response.headers["Content-Range"] = "bytes %d-%d/*" % (
                      begin, end - 1)
              elif total_length > 0:
                  response.headers["Content-Range"] = "bytes %d-%d/%d" % (
                      begin, end - 1, total_length)
                  response.headers["Content-Length"] = end - begin
          else:
              data, mimetype, total_length = provider.get_item(
                  session_id, database_id, item_id)
  
              # Setup response
              response = Response(
                  data, 200, mimetype=mimetype,
                  direct_passthrough=not isinstance(data, basestring))
  
              if total_length > 0:
                  response.headers["Content-Length"] = total_length
  
          return response
  
      @app.route("/databases/<int:database_id>/items", methods=["GET"])
      @daap_trace
      @daap_authenticate
      @daap_cache_response
      @daap_unpack_args
      def database_items(database_id, session_id, revision, delta, type):
          """
          """
  
          new, old = provider.get_items(session_id, database_id, revision, delta)
          added, removed, is_update = utils.diff(new, old)
  
          data = responses.items(provider, new, old, added, removed, is_update)
  
          return ObjectResponse(data)
  
      @app.route("/databases/<int:database_id>/containers", methods=["GET"])
      @daap_trace
      @daap_authenticate
      @daap_cache_response
      @daap_unpack_args
      def database_containers(database_id, session_id, revision, delta):
          """
          """
  
          new, old = provider.get_containers(
              session_id, database_id, revision, delta)
          added, removed, is_update = utils.diff(new, old)
  
          data = responses.containers(
              provider, new, old, added, removed, is_update)
  
          return ObjectResponse(data)
  
      @app.route("/databases/<int:database_id>/groups", methods=["GET"])
      @daap_trace
      @daap_authenticate
      @daap_cache_response
      @daap_unpack_args
      def database_groups(database_id, session_id, revision, delta, type):
          """
          """
          raise NotImplementedError("Groups not supported.")
  
      @app.route(
          "/databases/<int:database_id>/containers/<int:container_id>/items",
          methods=["GET"])
      @daap_trace
      @daap_authenticate
      @daap_cache_response
      @daap_unpack_args
      def database_container_item(database_id, container_id, session_id,
                                  revision, delta):
          """
          """
  
          new, old = provider.get_container_items(
              session_id, database_id, container_id, revision, delta)
          added, removed, is_update = utils.diff(new, old)
  
          data = responses.container_items(
              provider, new, old, added, removed, is_update)
  
          return ObjectResponse(data)
  
      # Return the app
      return app