We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] Send `Cache-Control` headers from static files routes
### Issues Policy acknowledgement

- [X] I have read and agree to submit bug reports in accordance with the [issues policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md)

### Willingness to contribute

Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.

### MLflow version

- Client: 2.1.1 & master
- Tracking server: 2.1.1 & master


### System information

- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: all
- **Python version**: 3.10
- **yarn version, if running the dev UI**: 3.3.0


### Describe the problem

I was doing some performance profiling on the mlflow ui, as users were reporting slow load times both on initial page load and when navigating. The react app is quite a large bundle that's fairly split out via webpack.

I noticed the entirety of the js files are being re-fetched on every navigation. Since these files contain a hash, they are essentially immutable files and could be cached almost indefinitely by the browser. (I assume this is a content hash from webpack, but would need to confirm).

Seems the browser is re-fetching/re-validating the files due to flask setting a [`Cache-Control: no-cache` ](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives) header for static files. This can also cause upstream proxies (nginx etc) not to cache the content.

It seems to makes sense to add a `max_age` parameter to the [`send_from_directory`](https://github.com/mlflow/mlflow/blob/68e11fbc39a7e1c0d42ab6a38a6243dbc71e031f/mlflow/server/__init__.py#L77-L88) calls in `mlflow.server.__init__.py`. The [`max_age `](https://flask.palletsprojects.com/en/2.2.x/api/?highlight=send_file#flask.send_file) parameter would cause a `Cache-Control: public max_age=2419200` header to be sent. Browsers would then cache the static files which would improve performance on the front and and reduce server load.

This issue explains more about why the default is not to send cache headers: https://github.com/pallets/werkzeug/issues/1882. It seems the mlflow static files would want the inverse of the default behavior.

Ideally, something like a `Cache-Control: max-age=2419200, stale-while-revalidate=2419200` header could be sent. But it isn't possible via `send_from_directory` directly. One could edit the `Response`'s cache control policy manually for more fine grained control.

Example easy mode patch:

```patch
dex bbb5110e7..6264f5391 100644
--- a/mlflow/server/__init__.py
+++ b/mlflow/server/__init__.py
@@ -76,16 +76,17 @@ def serve_get_metric_history_bulk():

 # We expect the react app to be built assuming it is hosted at /static-files, so that requests for
 # CSS/JS resources will be made to e.g. /static-files/main.css and we can handle them here.
+# cache_timeout could be really high actually
 @app.route(_add_static_prefix("/static-files/<path:path>"))
 def serve_static_file(path):
-    return send_from_directory(STATIC_DIR, path)
+    return send_from_directory(STATIC_DIR, path, max_age=2419200)


 # Serve the index.html for the React App for all other routes.
 @app.route(_add_static_prefix("/"))
 def serve():
     if os.path.exists(os.path.join(STATIC_DIR, "index.html")):
-        return send_from_directory(STATIC_DIR, "index.html")
+        return send_from_directory(STATIC_DIR, "index.html", max_age=2419200)

     text = textwrap.dedent(
         """
```

Thoughts?

### Tracking information

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
Developer tools in browser.

### Code to reproduce issue

`Cache-Control: no-cache`

### Stack trace

n/a

### Other info / logs

n/a

### What component(s) does this bug affect?

- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/recipes`: Recipes, Recipe APIs, Recipe configs, Recipe Templates
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [X] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

### What interface(s) does this bug affect?

- [X] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

### What language(s) does this bug affect?

- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

### What integration(s) does this bug affect?

- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mlflow/server/__init__.py`
Content:
```
1 import os
2 import shlex
3 import sys
4 import textwrap
5 import importlib.metadata
6 
7 from flask import Flask, send_from_directory, Response
8 
9 from mlflow.exceptions import MlflowException
10 from mlflow.server import handlers
11 from mlflow.server.handlers import (
12     get_artifact_handler,
13     get_metric_history_bulk_handler,
14     STATIC_PREFIX_ENV_VAR,
15     _add_static_prefix,
16     get_model_version_artifact_handler,
17 )
18 from mlflow.utils.process import _exec_cmd
19 from mlflow.version import VERSION
20 
21 # NB: These are internal environment variables used for communication between
22 # the cli and the forked gunicorn processes.
23 BACKEND_STORE_URI_ENV_VAR = "_MLFLOW_SERVER_FILE_STORE"
24 REGISTRY_STORE_URI_ENV_VAR = "_MLFLOW_SERVER_REGISTRY_STORE"
25 ARTIFACT_ROOT_ENV_VAR = "_MLFLOW_SERVER_ARTIFACT_ROOT"
26 ARTIFACTS_DESTINATION_ENV_VAR = "_MLFLOW_SERVER_ARTIFACT_DESTINATION"
27 PROMETHEUS_EXPORTER_ENV_VAR = "prometheus_multiproc_dir"
28 SERVE_ARTIFACTS_ENV_VAR = "_MLFLOW_SERVER_SERVE_ARTIFACTS"
29 ARTIFACTS_ONLY_ENV_VAR = "_MLFLOW_SERVER_ARTIFACTS_ONLY"
30 
31 REL_STATIC_DIR = "js/build"
32 
33 app = Flask(__name__, static_folder=REL_STATIC_DIR)
34 STATIC_DIR = os.path.join(app.root_path, REL_STATIC_DIR)
35 
36 
37 for http_path, handler, methods in handlers.get_endpoints():
38     app.add_url_rule(http_path, handler.__name__, handler, methods=methods)
39 
40 if os.getenv(PROMETHEUS_EXPORTER_ENV_VAR):
41     from mlflow.server.prometheus_exporter import activate_prometheus_exporter
42 
43     prometheus_metrics_path = os.getenv(PROMETHEUS_EXPORTER_ENV_VAR)
44     if not os.path.exists(prometheus_metrics_path):
45         os.makedirs(prometheus_metrics_path)
46     activate_prometheus_exporter(app)
47 
48 
49 # Provide a health check endpoint to ensure the application is responsive
50 @app.route("/health")
51 def health():
52     return "OK", 200
53 
54 
55 # Provide an endpoint to query the version of mlflow running on the server
56 @app.route("/version")
57 def version():
58     return VERSION, 200
59 
60 
61 # Serve the "get-artifact" route.
62 @app.route(_add_static_prefix("/get-artifact"))
63 def serve_artifacts():
64     return get_artifact_handler()
65 
66 
67 # Serve the "model-versions/get-artifact" route.
68 @app.route(_add_static_prefix("/model-versions/get-artifact"))
69 def serve_model_version_artifact():
70     return get_model_version_artifact_handler()
71 
72 
73 # Serve the "metrics/get-history-bulk" route.
74 @app.route(_add_static_prefix("/ajax-api/2.0/mlflow/metrics/get-history-bulk"))
75 def serve_get_metric_history_bulk():
76     return get_metric_history_bulk_handler()
77 
78 
79 # We expect the react app to be built assuming it is hosted at /static-files, so that requests for
80 # CSS/JS resources will be made to e.g. /static-files/main.css and we can handle them here.
81 @app.route(_add_static_prefix("/static-files/<path:path>"))
82 def serve_static_file(path):
83     return send_from_directory(STATIC_DIR, path)
84 
85 
86 # Serve the index.html for the React App for all other routes.
87 @app.route(_add_static_prefix("/"))
88 def serve():
89     if os.path.exists(os.path.join(STATIC_DIR, "index.html")):
90         return send_from_directory(STATIC_DIR, "index.html")
91 
92     text = textwrap.dedent(
93         """
94     Unable to display MLflow UI - landing page (index.html) not found.
95 
96     You are very likely running the MLflow server using a source installation of the Python MLflow
97     package.
98 
99     If you are a developer making MLflow source code changes and intentionally running a source
100     installation of MLflow, you can view the UI by running the Javascript dev server:
101     https://github.com/mlflow/mlflow/blob/master/CONTRIBUTING.md#running-the-javascript-dev-server
102 
103     Otherwise, uninstall MLflow via 'pip uninstall mlflow', reinstall an official MLflow release
104     from PyPI via 'pip install mlflow', and rerun the MLflow server.
105     """
106     )
107     return Response(text, mimetype="text/plain")
108 
109 
110 def _find_app(app_name: str) -> str:
111     apps = importlib.metadata.entry_points().get("mlflow.app", [])
112     for app in apps:
113         if app.name == app_name:
114             return app.value
115 
116     raise MlflowException(
117         f"Failed to find app '{app_name}'. Available apps: {[a.name for a in apps]}"
118     )
119 
120 
121 def _build_waitress_command(waitress_opts, host, port, app_name):
122     opts = shlex.split(waitress_opts) if waitress_opts else []
123     return (
124         ["waitress-serve"]
125         + opts
126         + ["--host=%s" % host, "--port=%s" % port, "--ident=mlflow", app_name]
127     )
128 
129 
130 def _build_gunicorn_command(gunicorn_opts, host, port, workers, app_name):
131     bind_address = f"{host}:{port}"
132     opts = shlex.split(gunicorn_opts) if gunicorn_opts else []
133     return ["gunicorn"] + opts + ["-b", bind_address, "-w", "%s" % workers, app_name]
134 
135 
136 def _run_server(
137     file_store_path,
138     registry_store_uri,
139     default_artifact_root,
140     serve_artifacts,
141     artifacts_only,
142     artifacts_destination,
143     host,
144     port,
145     static_prefix=None,
146     workers=None,
147     gunicorn_opts=None,
148     waitress_opts=None,
149     expose_prometheus=None,
150     app_name=None,
151 ):
152     """
153     Run the MLflow server, wrapping it in gunicorn or waitress on windows
154     :param static_prefix: If set, the index.html asset will be served from the path static_prefix.
155                           If left None, the index.html asset will be served from the root path.
156     :return: None
157     """
158     env_map = {}
159     if file_store_path:
160         env_map[BACKEND_STORE_URI_ENV_VAR] = file_store_path
161     if registry_store_uri:
162         env_map[REGISTRY_STORE_URI_ENV_VAR] = registry_store_uri
163     if default_artifact_root:
164         env_map[ARTIFACT_ROOT_ENV_VAR] = default_artifact_root
165     if serve_artifacts:
166         env_map[SERVE_ARTIFACTS_ENV_VAR] = "true"
167     if artifacts_only:
168         env_map[ARTIFACTS_ONLY_ENV_VAR] = "true"
169     if artifacts_destination:
170         env_map[ARTIFACTS_DESTINATION_ENV_VAR] = artifacts_destination
171     if static_prefix:
172         env_map[STATIC_PREFIX_ENV_VAR] = static_prefix
173 
174     if expose_prometheus:
175         env_map[PROMETHEUS_EXPORTER_ENV_VAR] = expose_prometheus
176 
177     app_spec = f"{__name__}:app" if app_name is None else _find_app(app_name)
178     # TODO: eventually may want waitress on non-win32
179     if sys.platform == "win32":
180         full_command = _build_waitress_command(waitress_opts, host, port, app_spec)
181     else:
182         full_command = _build_gunicorn_command(gunicorn_opts, host, port, workers or 4, app_spec)
183     _exec_cmd(full_command, extra_env=env_map, capture_output=False)
184
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mlflow/server/__init__.py b/mlflow/server/__init__.py
--- a/mlflow/server/__init__.py
+++ b/mlflow/server/__init__.py
@@ -78,9 +78,10 @@
 
 # We expect the react app to be built assuming it is hosted at /static-files, so that requests for
 # CSS/JS resources will be made to e.g. /static-files/main.css and we can handle them here.
+# The files are hashed based on source code, so ok to send Cache-Control headers via max_age.
 @app.route(_add_static_prefix("/static-files/<path:path>"))
 def serve_static_file(path):
-    return send_from_directory(STATIC_DIR, path)
+    return send_from_directory(STATIC_DIR, path, max_age=2419200)
 
 
 # Serve the index.html for the React App for all other routes.

{"golden_diff": "diff --git a/mlflow/server/__init__.py b/mlflow/server/__init__.py\n--- a/mlflow/server/__init__.py\n+++ b/mlflow/server/__init__.py\n@@ -78,9 +78,10 @@\n \n # We expect the react app to be built assuming it is hosted at /static-files, so that requests for\n # CSS/JS resources will be made to e.g. /static-files/main.css and we can handle them here.\n+# The files are hashed based on source code, so ok to send Cache-Control headers via max_age.\n @app.route(_add_static_prefix(\"/static-files/<path:path>\"))\n def serve_static_file(path):\n-    return send_from_directory(STATIC_DIR, path)\n+    return send_from_directory(STATIC_DIR, path, max_age=2419200)\n \n \n # Serve the index.html for the React App for all other routes.\n", "issue": "[BUG] Send `Cache-Control` headers from static files routes\n### Issues Policy acknowledgement\n\n- [X] I have read and agree to submit bug reports in accordance with the [issues policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md)\n\n### Willingness to contribute\n\nYes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.\n\n### MLflow version\n\n- Client: 2.1.1 & master\r\n- Tracking server: 2.1.1 & master\r\n\n\n### System information\n\n- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: all\r\n- **Python version**: 3.10\r\n- **yarn version, if running the dev UI**: 3.3.0\r\n\n\n### Describe the problem\n\nI was doing some performance profiling on the mlflow ui, as users were reporting slow load times both on initial page load and when navigating. The react app is quite a large bundle that's fairly split out via webpack.\r\n\r\nI noticed the entirety of the js files are being re-fetched on every navigation. Since these files contain a hash, they are essentially immutable files and could be cached almost indefinitely by the browser. (I assume this is a content hash from webpack, but would need to confirm).\r\n\r\nSeems the browser is re-fetching/re-validating the files due to flask setting a [`Cache-Control: no-cache` ](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives) header for static files. This can also cause upstream proxies (nginx etc) not to cache the content.\r\n\r\nIt seems to makes sense to add a `max_age` parameter to the [`send_from_directory`](https://github.com/mlflow/mlflow/blob/68e11fbc39a7e1c0d42ab6a38a6243dbc71e031f/mlflow/server/__init__.py#L77-L88) calls in `mlflow.server.__init__.py`. The [`max_age `](https://flask.palletsprojects.com/en/2.2.x/api/?highlight=send_file#flask.send_file) parameter would cause a `Cache-Control: public max_age=2419200` header to be sent. Browsers would then cache the static files which would improve performance on the front and and reduce server load.\r\n\r\nThis issue explains more about why the default is not to send cache headers: https://github.com/pallets/werkzeug/issues/1882. It seems the mlflow static files would want the inverse of the default behavior.\r\n\r\nIdeally, something like a `Cache-Control: max-age=2419200, stale-while-revalidate=2419200` header could be sent. But it isn't possible via `send_from_directory` directly. One could edit the `Response`'s cache control policy manually for more fine grained control.\r\n\r\nExample easy mode patch:\r\n\r\n```patch\r\ndex bbb5110e7..6264f5391 100644\r\n--- a/mlflow/server/__init__.py\r\n+++ b/mlflow/server/__init__.py\r\n@@ -76,16 +76,17 @@ def serve_get_metric_history_bulk():\r\n\r\n # We expect the react app to be built assuming it is hosted at /static-files, so that requests for\r\n # CSS/JS resources will be made to e.g. /static-files/main.css and we can handle them here.\r\n+# cache_timeout could be really high actually\r\n @app.route(_add_static_prefix(\"/static-files/<path:path>\"))\r\n def serve_static_file(path):\r\n-    return send_from_directory(STATIC_DIR, path)\r\n+    return send_from_directory(STATIC_DIR, path, max_age=2419200)\r\n\r\n\r\n # Serve the index.html for the React App for all other routes.\r\n @app.route(_add_static_prefix(\"/\"))\r\n def serve():\r\n     if os.path.exists(os.path.join(STATIC_DIR, \"index.html\")):\r\n-        return send_from_directory(STATIC_DIR, \"index.html\")\r\n+        return send_from_directory(STATIC_DIR, \"index.html\", max_age=2419200)\r\n\r\n     text = textwrap.dedent(\r\n         \"\"\"\r\n```\r\n\r\nThoughts?\n\n### Tracking information\n\n<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->\r\nDeveloper tools in browser.\n\n### Code to reproduce issue\n\n`Cache-Control: no-cache`\n\n### Stack trace\n\nn/a\n\n### Other info / logs\n\nn/a\n\n### What component(s) does this bug affect?\n\n- [ ] `area/artifacts`: Artifact stores and artifact logging\n- [ ] `area/build`: Build and test infrastructure for MLflow\n- [ ] `area/docs`: MLflow documentation pages\n- [ ] `area/examples`: Example code\n- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry\n- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors\n- [ ] `area/recipes`: Recipes, Recipe APIs, Recipe configs, Recipe Templates\n- [ ] `area/projects`: MLproject format, project running backends\n- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs\n- [X] `area/server-infra`: MLflow Tracking server backend\n- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging\n\n### What interface(s) does this bug affect?\n\n- [X] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server\n- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models\n- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry\n- [ ] `area/windows`: Windows support\n\n### What language(s) does this bug affect?\n\n- [ ] `language/r`: R APIs and clients\n- [ ] `language/java`: Java APIs and clients\n- [ ] `language/new`: Proposals for new client languages\n\n### What integration(s) does this bug affect?\n\n- [ ] `integrations/azure`: Azure and Azure ML integrations\n- [ ] `integrations/sagemaker`: SageMaker integrations\n- [ ] `integrations/databricks`: Databricks integrations\n", "before_files": [{"content": "import os\nimport shlex\nimport sys\nimport textwrap\nimport importlib.metadata\n\nfrom flask import Flask, send_from_directory, Response\n\nfrom mlflow.exceptions import MlflowException\nfrom mlflow.server import handlers\nfrom mlflow.server.handlers import (\n    get_artifact_handler,\n    get_metric_history_bulk_handler,\n    STATIC_PREFIX_ENV_VAR,\n    _add_static_prefix,\n    get_model_version_artifact_handler,\n)\nfrom mlflow.utils.process import _exec_cmd\nfrom mlflow.version import VERSION\n\n# NB: These are internal environment variables used for communication between\n# the cli and the forked gunicorn processes.\nBACKEND_STORE_URI_ENV_VAR = \"_MLFLOW_SERVER_FILE_STORE\"\nREGISTRY_STORE_URI_ENV_VAR = \"_MLFLOW_SERVER_REGISTRY_STORE\"\nARTIFACT_ROOT_ENV_VAR = \"_MLFLOW_SERVER_ARTIFACT_ROOT\"\nARTIFACTS_DESTINATION_ENV_VAR = \"_MLFLOW_SERVER_ARTIFACT_DESTINATION\"\nPROMETHEUS_EXPORTER_ENV_VAR = \"prometheus_multiproc_dir\"\nSERVE_ARTIFACTS_ENV_VAR = \"_MLFLOW_SERVER_SERVE_ARTIFACTS\"\nARTIFACTS_ONLY_ENV_VAR = \"_MLFLOW_SERVER_ARTIFACTS_ONLY\"\n\nREL_STATIC_DIR = \"js/build\"\n\napp = Flask(__name__, static_folder=REL_STATIC_DIR)\nSTATIC_DIR = os.path.join(app.root_path, REL_STATIC_DIR)\n\n\nfor http_path, handler, methods in handlers.get_endpoints():\n    app.add_url_rule(http_path, handler.__name__, handler, methods=methods)\n\nif os.getenv(PROMETHEUS_EXPORTER_ENV_VAR):\n    from mlflow.server.prometheus_exporter import activate_prometheus_exporter\n\n    prometheus_metrics_path = os.getenv(PROMETHEUS_EXPORTER_ENV_VAR)\n    if not os.path.exists(prometheus_metrics_path):\n        os.makedirs(prometheus_metrics_path)\n    activate_prometheus_exporter(app)\n\n\n# Provide a health check endpoint to ensure the application is responsive\n@app.route(\"/health\")\ndef health():\n    return \"OK\", 200\n\n\n# Provide an endpoint to query the version of mlflow running on the server\n@app.route(\"/version\")\ndef version():\n    return VERSION, 200\n\n\n# Serve the \"get-artifact\" route.\n@app.route(_add_static_prefix(\"/get-artifact\"))\ndef serve_artifacts():\n    return get_artifact_handler()\n\n\n# Serve the \"model-versions/get-artifact\" route.\n@app.route(_add_static_prefix(\"/model-versions/get-artifact\"))\ndef serve_model_version_artifact():\n    return get_model_version_artifact_handler()\n\n\n# Serve the \"metrics/get-history-bulk\" route.\n@app.route(_add_static_prefix(\"/ajax-api/2.0/mlflow/metrics/get-history-bulk\"))\ndef serve_get_metric_history_bulk():\n    return get_metric_history_bulk_handler()\n\n\n# We expect the react app to be built assuming it is hosted at /static-files, so that requests for\n# CSS/JS resources will be made to e.g. /static-files/main.css and we can handle them here.\n@app.route(_add_static_prefix(\"/static-files/<path:path>\"))\ndef serve_static_file(path):\n    return send_from_directory(STATIC_DIR, path)\n\n\n# Serve the index.html for the React App for all other routes.\n@app.route(_add_static_prefix(\"/\"))\ndef serve():\n    if os.path.exists(os.path.join(STATIC_DIR, \"index.html\")):\n        return send_from_directory(STATIC_DIR, \"index.html\")\n\n    text = textwrap.dedent(\n        \"\"\"\n    Unable to display MLflow UI - landing page (index.html) not found.\n\n    You are very likely running the MLflow server using a source installation of the Python MLflow\n    package.\n\n    If you are a developer making MLflow source code changes and intentionally running a source\n    installation of MLflow, you can view the UI by running the Javascript dev server:\n    https://github.com/mlflow/mlflow/blob/master/CONTRIBUTING.md#running-the-javascript-dev-server\n\n    Otherwise, uninstall MLflow via 'pip uninstall mlflow', reinstall an official MLflow release\n    from PyPI via 'pip install mlflow', and rerun the MLflow server.\n    \"\"\"\n    )\n    return Response(text, mimetype=\"text/plain\")\n\n\ndef _find_app(app_name: str) -> str:\n    apps = importlib.metadata.entry_points().get(\"mlflow.app\", [])\n    for app in apps:\n        if app.name == app_name:\n            return app.value\n\n    raise MlflowException(\n        f\"Failed to find app '{app_name}'. Available apps: {[a.name for a in apps]}\"\n    )\n\n\ndef _build_waitress_command(waitress_opts, host, port, app_name):\n    opts = shlex.split(waitress_opts) if waitress_opts else []\n    return (\n        [\"waitress-serve\"]\n        + opts\n        + [\"--host=%s\" % host, \"--port=%s\" % port, \"--ident=mlflow\", app_name]\n    )\n\n\ndef _build_gunicorn_command(gunicorn_opts, host, port, workers, app_name):\n    bind_address = f\"{host}:{port}\"\n    opts = shlex.split(gunicorn_opts) if gunicorn_opts else []\n    return [\"gunicorn\"] + opts + [\"-b\", bind_address, \"-w\", \"%s\" % workers, app_name]\n\n\ndef _run_server(\n    file_store_path,\n    registry_store_uri,\n    default_artifact_root,\n    serve_artifacts,\n    artifacts_only,\n    artifacts_destination,\n    host,\n    port,\n    static_prefix=None,\n    workers=None,\n    gunicorn_opts=None,\n    waitress_opts=None,\n    expose_prometheus=None,\n    app_name=None,\n):\n    \"\"\"\n    Run the MLflow server, wrapping it in gunicorn or waitress on windows\n    :param static_prefix: If set, the index.html asset will be served from the path static_prefix.\n                          If left None, the index.html asset will be served from the root path.\n    :return: None\n    \"\"\"\n    env_map = {}\n    if file_store_path:\n        env_map[BACKEND_STORE_URI_ENV_VAR] = file_store_path\n    if registry_store_uri:\n        env_map[REGISTRY_STORE_URI_ENV_VAR] = registry_store_uri\n    if default_artifact_root:\n        env_map[ARTIFACT_ROOT_ENV_VAR] = default_artifact_root\n    if serve_artifacts:\n        env_map[SERVE_ARTIFACTS_ENV_VAR] = \"true\"\n    if artifacts_only:\n        env_map[ARTIFACTS_ONLY_ENV_VAR] = \"true\"\n    if artifacts_destination:\n        env_map[ARTIFACTS_DESTINATION_ENV_VAR] = artifacts_destination\n    if static_prefix:\n        env_map[STATIC_PREFIX_ENV_VAR] = static_prefix\n\n    if expose_prometheus:\n        env_map[PROMETHEUS_EXPORTER_ENV_VAR] = expose_prometheus\n\n    app_spec = f\"{__name__}:app\" if app_name is None else _find_app(app_name)\n    # TODO: eventually may want waitress on non-win32\n    if sys.platform == \"win32\":\n        full_command = _build_waitress_command(waitress_opts, host, port, app_spec)\n    else:\n        full_command = _build_gunicorn_command(gunicorn_opts, host, port, workers or 4, app_spec)\n    _exec_cmd(full_command, extra_env=env_map, capture_output=False)\n", "path": "mlflow/server/__init__.py"}], "after_files": [{"content": "import os\nimport shlex\nimport sys\nimport textwrap\nimport importlib.metadata\n\nfrom flask import Flask, send_from_directory, Response\n\nfrom mlflow.exceptions import MlflowException\nfrom mlflow.server import handlers\nfrom mlflow.server.handlers import (\n    get_artifact_handler,\n    get_metric_history_bulk_handler,\n    STATIC_PREFIX_ENV_VAR,\n    _add_static_prefix,\n    get_model_version_artifact_handler,\n)\nfrom mlflow.utils.process import _exec_cmd\nfrom mlflow.version import VERSION\n\n# NB: These are internal environment variables used for communication between\n# the cli and the forked gunicorn processes.\nBACKEND_STORE_URI_ENV_VAR = \"_MLFLOW_SERVER_FILE_STORE\"\nREGISTRY_STORE_URI_ENV_VAR = \"_MLFLOW_SERVER_REGISTRY_STORE\"\nARTIFACT_ROOT_ENV_VAR = \"_MLFLOW_SERVER_ARTIFACT_ROOT\"\nARTIFACTS_DESTINATION_ENV_VAR = \"_MLFLOW_SERVER_ARTIFACT_DESTINATION\"\nPROMETHEUS_EXPORTER_ENV_VAR = \"prometheus_multiproc_dir\"\nSERVE_ARTIFACTS_ENV_VAR = \"_MLFLOW_SERVER_SERVE_ARTIFACTS\"\nARTIFACTS_ONLY_ENV_VAR = \"_MLFLOW_SERVER_ARTIFACTS_ONLY\"\n\nREL_STATIC_DIR = \"js/build\"\n\napp = Flask(__name__, static_folder=REL_STATIC_DIR)\nSTATIC_DIR = os.path.join(app.root_path, REL_STATIC_DIR)\n\n\nfor http_path, handler, methods in handlers.get_endpoints():\n    app.add_url_rule(http_path, handler.__name__, handler, methods=methods)\n\nif os.getenv(PROMETHEUS_EXPORTER_ENV_VAR):\n    from mlflow.server.prometheus_exporter import activate_prometheus_exporter\n\n    prometheus_metrics_path = os.getenv(PROMETHEUS_EXPORTER_ENV_VAR)\n    if not os.path.exists(prometheus_metrics_path):\n        os.makedirs(prometheus_metrics_path)\n    activate_prometheus_exporter(app)\n\n\n# Provide a health check endpoint to ensure the application is responsive\n@app.route(\"/health\")\ndef health():\n    return \"OK\", 200\n\n\n# Provide an endpoint to query the version of mlflow running on the server\n@app.route(\"/version\")\ndef version():\n    return VERSION, 200\n\n\n# Serve the \"get-artifact\" route.\n@app.route(_add_static_prefix(\"/get-artifact\"))\ndef serve_artifacts():\n    return get_artifact_handler()\n\n\n# Serve the \"model-versions/get-artifact\" route.\n@app.route(_add_static_prefix(\"/model-versions/get-artifact\"))\ndef serve_model_version_artifact():\n    return get_model_version_artifact_handler()\n\n\n# Serve the \"metrics/get-history-bulk\" route.\n@app.route(_add_static_prefix(\"/ajax-api/2.0/mlflow/metrics/get-history-bulk\"))\ndef serve_get_metric_history_bulk():\n    return get_metric_history_bulk_handler()\n\n\n# We expect the react app to be built assuming it is hosted at /static-files, so that requests for\n# CSS/JS resources will be made to e.g. /static-files/main.css and we can handle them here.\n# The files are hashed based on source code, so ok to send Cache-Control headers via max_age.\n@app.route(_add_static_prefix(\"/static-files/<path:path>\"))\ndef serve_static_file(path):\n    return send_from_directory(STATIC_DIR, path, max_age=2419200)\n\n\n# Serve the index.html for the React App for all other routes.\n@app.route(_add_static_prefix(\"/\"))\ndef serve():\n    if os.path.exists(os.path.join(STATIC_DIR, \"index.html\")):\n        return send_from_directory(STATIC_DIR, \"index.html\")\n\n    text = textwrap.dedent(\n        \"\"\"\n    Unable to display MLflow UI - landing page (index.html) not found.\n\n    You are very likely running the MLflow server using a source installation of the Python MLflow\n    package.\n\n    If you are a developer making MLflow source code changes and intentionally running a source\n    installation of MLflow, you can view the UI by running the Javascript dev server:\n    https://github.com/mlflow/mlflow/blob/master/CONTRIBUTING.md#running-the-javascript-dev-server\n\n    Otherwise, uninstall MLflow via 'pip uninstall mlflow', reinstall an official MLflow release\n    from PyPI via 'pip install mlflow', and rerun the MLflow server.\n    \"\"\"\n    )\n    return Response(text, mimetype=\"text/plain\")\n\n\ndef _find_app(app_name: str) -> str:\n    apps = importlib.metadata.entry_points().get(\"mlflow.app\", [])\n    for app in apps:\n        if app.name == app_name:\n            return app.value\n\n    raise MlflowException(\n        f\"Failed to find app '{app_name}'. Available apps: {[a.name for a in apps]}\"\n    )\n\n\ndef _build_waitress_command(waitress_opts, host, port, app_name):\n    opts = shlex.split(waitress_opts) if waitress_opts else []\n    return (\n        [\"waitress-serve\"]\n        + opts\n        + [\"--host=%s\" % host, \"--port=%s\" % port, \"--ident=mlflow\", app_name]\n    )\n\n\ndef _build_gunicorn_command(gunicorn_opts, host, port, workers, app_name):\n    bind_address = f\"{host}:{port}\"\n    opts = shlex.split(gunicorn_opts) if gunicorn_opts else []\n    return [\"gunicorn\"] + opts + [\"-b\", bind_address, \"-w\", \"%s\" % workers, app_name]\n\n\ndef _run_server(\n    file_store_path,\n    registry_store_uri,\n    default_artifact_root,\n    serve_artifacts,\n    artifacts_only,\n    artifacts_destination,\n    host,\n    port,\n    static_prefix=None,\n    workers=None,\n    gunicorn_opts=None,\n    waitress_opts=None,\n    expose_prometheus=None,\n    app_name=None,\n):\n    \"\"\"\n    Run the MLflow server, wrapping it in gunicorn or waitress on windows\n    :param static_prefix: If set, the index.html asset will be served from the path static_prefix.\n                          If left None, the index.html asset will be served from the root path.\n    :return: None\n    \"\"\"\n    env_map = {}\n    if file_store_path:\n        env_map[BACKEND_STORE_URI_ENV_VAR] = file_store_path\n    if registry_store_uri:\n        env_map[REGISTRY_STORE_URI_ENV_VAR] = registry_store_uri\n    if default_artifact_root:\n        env_map[ARTIFACT_ROOT_ENV_VAR] = default_artifact_root\n    if serve_artifacts:\n        env_map[SERVE_ARTIFACTS_ENV_VAR] = \"true\"\n    if artifacts_only:\n        env_map[ARTIFACTS_ONLY_ENV_VAR] = \"true\"\n    if artifacts_destination:\n        env_map[ARTIFACTS_DESTINATION_ENV_VAR] = artifacts_destination\n    if static_prefix:\n        env_map[STATIC_PREFIX_ENV_VAR] = static_prefix\n\n    if expose_prometheus:\n        env_map[PROMETHEUS_EXPORTER_ENV_VAR] = expose_prometheus\n\n    app_spec = f\"{__name__}:app\" if app_name is None else _find_app(app_name)\n    # TODO: eventually may want waitress on non-win32\n    if sys.platform == \"win32\":\n        full_command = _build_waitress_command(waitress_opts, host, port, app_spec)\n    else:\n        full_command = _build_gunicorn_command(gunicorn_opts, host, port, workers or 4, app_spec)\n    _exec_cmd(full_command, extra_env=env_map, capture_output=False)\n", "path": "mlflow/server/__init__.py"}]}