This is an automated email from the ASF dual-hosted git repository.

dengliming pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new f07b5568 fix: preserve HTTPS environment host (#672)
f07b5568 is described below

commit f07b5568745074185a3b187483d16ff04678f6c8
Author: Efe <[email protected]>
AuthorDate: Sun Sep 27 07:18:58 2026 +0300

    fix: preserve HTTPS environment host (#672)
    
    * fix: preserve HTTPS environment host ports
    
    Signed-off-by: Efe Gökdemir <[email protected]>
    
    * fix: accept HTTP URLs without explicit ports
    
    Signed-off-by: Efe Gökdemir <[email protected]>
    
    ---------
    
    Signed-off-by: Efe Gökdemir <[email protected]>
---
 src/routes/Document/components/ApiDebug.js      |  12 ++-
 src/routes/Document/components/ApiDebug.test.js | 133 ++++++++++++++++++++++++
 src/utils/UrlUtils.js                           |  34 ++++++
 3 files changed, 176 insertions(+), 3 deletions(-)

diff --git a/src/routes/Document/components/ApiDebug.js 
b/src/routes/Document/components/ApiDebug.js
index 8eb8ad25..ee196954 100644
--- a/src/routes/Document/components/ApiDebug.js
+++ b/src/routes/Document/components/ApiDebug.js
@@ -50,6 +50,7 @@ import HeadersEditor from "./HeadersEditor";
 import { getIntlContent } from "../../../utils/IntlUtils";
 import AuthButton from "../../../utils/AuthButton";
 import { Method } from "./globalData";
+import { buildEnvironmentHost, isValidHttpUrl } from "../../../utils/UrlUtils";
 
 const { Title, Text, Paragraph } = Typography;
 const { TabPane } = Tabs;
@@ -262,7 +263,13 @@ const FCForm = forwardRef(({ form, onSubmit }, ref) => {
             {
               type: "string",
               required: true,
-              pattern: /^https?:\/\/([^:]+):(\d+)(\/.+)$/,
+              validator: (_, value, callback) => {
+                if (!isValidHttpUrl(value)) {
+                  callback("Please enter a valid HTTP(S) URL");
+                  return;
+                }
+                callback();
+              },
             },
           ],
         })(
@@ -271,8 +278,7 @@ const FCForm = forwardRef(({ form, onSubmit }, ref) => {
               style={{ width: "40%" }}
               onChange={(envId) => {
                 const env = Object.values(envProps)[envId];
-                const url = new URL(env.addressUrl);
-                const host = `${url.protocol}//${url.hostname}:${url.port || 
"80"}`;
+                const host = buildEnvironmentHost(env.addressUrl);
                 setInitialValue({ ...initialValue, host, envId });
                 const requestUrl = `${host}${initialValue.url ?? ""}`;
                 form.setFieldsValue({ requestUrl });
diff --git a/src/routes/Document/components/ApiDebug.test.js 
b/src/routes/Document/components/ApiDebug.test.js
new file mode 100644
index 00000000..dec22674
--- /dev/null
+++ b/src/routes/Document/components/ApiDebug.test.js
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from "react";
+import { fireEvent, render, waitFor } from "@testing-library/react";
+import fetch from "dva/fetch";
+import ApiContext from "./ApiContext";
+import ApiDebug from "./ApiDebug";
+import { buildEnvironmentHost, isValidHttpUrl } from "../../../utils/UrlUtils";
+
+jest.mock("dva/fetch", () => jest.fn());
+jest.mock("../../../services/api", () => ({
+  createOrUpdateMockRequest: jest.fn(),
+  deleteMockRequest: jest.fn(),
+  getApiMockRequest: jest.fn(),
+  sandboxProxyGateway: jest.fn(() => "/sandbox"),
+}));
+jest.mock("./HeadersEditor", () => () => <div />);
+jest.mock(
+  "../../../utils/AuthButton",
+  () =>
+    ({ children }) =>
+      children,
+);
+jest.mock("react-json-view", () => () => <div />);
+
+const contextValue = {
+  apiDetail: { id: "api-1", apiPath: "/orders", httpMethod: 0 },
+  apiMock: {
+    id: null,
+    host: "",
+    port: "",
+    url: "https://api.example.test/orders";,
+    pathVariable: "",
+    query: "[]",
+    header: {},
+    body: "{}",
+  },
+  apiData: { envProps: [] },
+};
+
+beforeEach(() => {
+  fetch.mockResolvedValue({
+    text: async () => "{}",
+    headers: { get: () => null },
+  });
+});
+
+it("does not add port 80 to an HTTPS environment without an explicit port", () 
=> {
+  expect(buildEnvironmentHost("https://api.example.test";)).toBe(
+    "https://api.example.test";,
+  );
+});
+
+it("preserves an explicitly configured environment port", () => {
+  expect(buildEnvironmentHost("http://api.example.test:8080";)).toBe(
+    "http://api.example.test:8080";,
+  );
+});
+
+it.each([
+  "https://api.example.test/orders";,
+  "http://api.example.test/orders";,
+  "https://api.example.test:8443/orders";,
+  "http://api.example.test:8080/orders";,
+])(
+  "submits a request URL without requiring an explicit port: %s",
+  async (requestUrl) => {
+    const { container } = render(
+      <ApiContext.Provider
+        value={{
+          ...contextValue,
+          apiDetail: { ...contextValue.apiDetail, apiPath: requestUrl },
+          apiMock: { ...contextValue.apiMock, url: requestUrl },
+        }}
+      >
+        <ApiDebug />
+      </ApiContext.Provider>,
+    );
+
+    fireEvent.submit(container.querySelector("form"));
+
+    await waitFor(() => expect(fetch).toHaveBeenCalled());
+    expect(fetch.mock.calls[0][1].body).toContain(requestUrl);
+  },
+);
+
+it("rejects a non-HTTP request URL before submission", async () => {
+  const { container } = render(
+    <ApiContext.Provider
+      value={{
+        ...contextValue,
+        apiDetail: {
+          ...contextValue.apiDetail,
+          apiPath: "ftp://api.example.test/orders";,
+        },
+        apiMock: {
+          ...contextValue.apiMock,
+          url: "ftp://api.example.test/orders";,
+        },
+      }}
+    >
+      <ApiDebug />
+    </ApiContext.Provider>,
+  );
+
+  fireEvent.submit(container.querySelector("form"));
+
+  await new Promise((resolve) => {
+    setTimeout(resolve, 0);
+  });
+  expect(fetch).not.toHaveBeenCalled();
+});
+
+it("validates HTTP(S) URLs with or without an explicit port", () => {
+  expect(isValidHttpUrl("https://api.example.test/orders";)).toBe(true);
+  expect(isValidHttpUrl("http://api.example.test:8080/orders";)).toBe(true);
+  expect(isValidHttpUrl("ftp://api.example.test/orders";)).toBe(false);
+});
diff --git a/src/utils/UrlUtils.js b/src/utils/UrlUtils.js
new file mode 100644
index 00000000..48098b3a
--- /dev/null
+++ b/src/utils/UrlUtils.js
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const buildEnvironmentHost = (addressUrl) => {
+  const url = new URL(addressUrl);
+  return `${url.protocol}//${url.host}`;
+};
+
+export const isValidHttpUrl = (value) => {
+  if (typeof value !== "string" || !value) {
+    return false;
+  }
+
+  try {
+    const url = new URL(value);
+    return ["http:", "https:"].includes(url.protocol) && Boolean(url.hostname);
+  } catch (e) {
+    return false;
+  }
+};

Reply via email to