Repository: bentoml/BentoML

Issue:
Support URL location prefix in yatai-service gRPC/Web server
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->

It is common to run YataiService behind a reverse proxy server, and the user may want to add a location prefix to the URL being proxied. Currently, the YataiService Web UI does not work properly when a location prefix is specified, and we should support it.

**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->

Add a parameter to YataiService entry point to set a custom URL prefix, e.g.:

```
bentoml yatai-service-start --base-url="/yatai"
# or
docker run -p 50051:50051 -p 3000:3000 bentoml/yatai-service:latest --base-url="/yatai"
```

**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
n/a

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Related discussion #1057 https://github.com/bentoml/BentoML/discussions/1057



Discussion:
Besides yatai-service, would this also address the User-Defined API.

I notice the following behavior behind a reverse proxy:

`http://<friendly-url>/<some_prefix>/<api_name>- correctly gets routed to the Gunicorn / Flask API endpoint`

but

`http://<friendly-url>/<some_prefix>/`

does NOT serve the swagger

My thinking is that both issues with yatai-webui and swagger-ui - have the same type of cause / fix.
Let me know if your thinking is also the same.
With regards to #1057, I wanted to confirm whether I understood the issue correctly. I had tried reproducing it by setting up a reverse proxy, and the issue was that the requests were being routed to reverseproxyurl/requesturl instead of reverseproxyurl/prefix/requesturl. Is this the issue? I wasn't able to understand the given example (how are requests to https://friendly-url.com/yatai routed to 127.0.0.1/:3000/webui ?  Shouldn't they be routed to 127.0.0.1/:3000 as configured?
@kishore-ganesh you are right, I think it was a typo. For Yatai service webui, it should routes to `127.0.0.1:3000`

Resolving patch:
diff --git a/bentoml/cli/yatai_service.py b/bentoml/cli/yatai_service.py
--- a/bentoml/cli/yatai_service.py
+++ b/bentoml/cli/yatai_service.py
@@ -68,11 +68,25 @@ def add_yatai_service_sub_command(cli):
         'compatible with Amazon S3, such as MinIO',
         envvar='BENTOML_S3_ENDPOINT_URL',
     )
+    @click.option(
+        '--web-prefix-path',
+        type=click.STRING,
+        default='.',
+        help='Add a location prefix to the URL when running YataiService'
+        'behind a reverse proxy server',
+        envvar='BENTOML_YATAI_WEB_PREFIX_PATH',
+    )
     def yatai_service_start(
-        db_url, repo_base_url, grpc_port, ui_port, ui, s3_endpoint_url
+        db_url, repo_base_url, grpc_port, ui_port, ui, s3_endpoint_url, web_prefix_path
     ):
         from bentoml.yatai.yatai_service import start_yatai_service_grpc_server
 
         start_yatai_service_grpc_server(
-            db_url, repo_base_url, grpc_port, ui_port, ui, s3_endpoint_url
+            db_url,
+            repo_base_url,
+            grpc_port,
+            ui_port,
+            ui,
+            s3_endpoint_url,
+            web_prefix_path,
         )
diff --git a/bentoml/yatai/yatai_service.py b/bentoml/yatai/yatai_service.py
--- a/bentoml/yatai/yatai_service.py
+++ b/bentoml/yatai/yatai_service.py
@@ -64,7 +64,7 @@ def get_yatai_service(
 
 
 def start_yatai_service_grpc_server(
-    db_url, repo_base_url, grpc_port, ui_port, with_ui, s3_endpoint_url
+    db_url, repo_base_url, grpc_port, ui_port, with_ui, s3_endpoint_url, base_url
 ):
     from bentoml.yatai.yatai_service_impl import YataiService
 
@@ -101,14 +101,16 @@ def start_yatai_service_grpc_server(
         ensure_node_available_or_raise()
         yatai_grpc_server_address = f'localhost:{grpc_port}'
         async_start_yatai_service_web_ui(
-            yatai_grpc_server_address, ui_port, web_ui_log_path, debug_mode
+            yatai_grpc_server_address, ui_port, web_ui_log_path, debug_mode, base_url
         )
 
     # We don't import _echo function from click_utils because of circular dep
     click.echo(
         f'* Starting BentoML YataiService gRPC Server\n'
         f'* Debug mode: { "on" if debug_mode else "off"}\n'
-        f'* Web UI: {f"running on http://127.0.0.1:{ui_port}" if with_ui else "off"}\n'
+        f'''* Web UI: {f"running on http://127.0.0.1:{ui_port}/{base_url}"
+        if (with_ui and base_url!=".")
+        else f"running on http://127.0.0.1:{ui_port}" if with_ui else "off"}\n'''
         f'* Running on 127.0.0.1:{grpc_port} (Press CTRL+C to quit)\n'
         f'* Usage:\n'
         f'*  Set config: `bentoml config set yatai_service.url=127.0.0.1:{grpc_port}`\n'
@@ -135,7 +137,7 @@ def _is_web_server_debug_tools_available(root_dir):
 
 
 def async_start_yatai_service_web_ui(
-    yatai_server_address, ui_port, base_log_path, debug_mode
+    yatai_server_address, ui_port, base_log_path, debug_mode, web_prefix_path
 ):
     if ui_port is not None:
         ui_port = ui_port if isinstance(ui_port, str) else str(ui_port)
@@ -153,6 +155,7 @@ def async_start_yatai_service_web_ui(
                 yatai_server_address,
                 ui_port,
                 base_log_path,
+                web_prefix_path,
             ]
         else:
             web_ui_command = [
@@ -161,6 +164,7 @@ def async_start_yatai_service_web_ui(
                 yatai_server_address,
                 ui_port,
                 base_log_path,
+                web_prefix_path,
             ]
     else:
         if not os.path.exists(os.path.join(web_ui_dir, 'dist', 'bundle.js')):
@@ -175,6 +179,7 @@ def async_start_yatai_service_web_ui(
             yatai_server_address,
             ui_port,
             base_log_path,
+            web_prefix_path,
         ]
 
     web_proc = subprocess.Popen(


Test patch: