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;
+ }
+};