Browse Source

fix: can not connect with `http` prefix (#940)

Signed-off-by: shanghaikid <jiangruiyi@gmail.com>
ryjiang 1 week ago
parent
commit
92575a7567
2 changed files with 155 additions and 25 deletions
  1. 132 20
      client/src/pages/connect/AuthForm.tsx
  2. 23 5
      server/src/milvus/milvus.service.ts

+ 132 - 20
client/src/pages/connect/AuthForm.tsx

@@ -31,6 +31,12 @@ type Connection = AuthReq & {
   time: number;
 };
 
+// Utility function to clean address by preserving protocol prefixes
+const cleanAddress = (address: string): string => {
+  // Keep http:// or https:// prefixes, just trim whitespace
+  return address.trim();
+};
+
 // Parse server list from environment variables
 const parseFixedConnections = (): Connection[] => {
   const serverList = MILVUS_SERVERS || '';
@@ -115,16 +121,46 @@ export const AuthForm = () => {
     value: string | boolean
   ) => {
     if (key === 'address' && typeof value === 'string') {
+      // Clean the address by preserving protocol prefixes
+      const cleanedAddress = cleanAddress(value);
+
       // Check if address contains database name (format: address/database)
-      const parts = value.split('/');
-      if (parts.length === 2) {
-        setAuthReq(v => ({
-          ...v,
-          address: parts[0],
-          database: parts[1],
-        }));
-        return;
+      // Handle URLs with protocols like http://127.0.0.1:19530/default
+      if (
+        cleanedAddress.includes('/') &&
+        !cleanedAddress.startsWith('http://') &&
+        !cleanedAddress.startsWith('https://')
+      ) {
+        // Simple format without protocol: address/database
+        const parts = cleanedAddress.split('/');
+        if (parts.length === 2) {
+          setAuthReq(v => ({
+            ...v,
+            address: parts[0],
+            database: parts[1],
+          }));
+          return;
+        }
+      } else if (
+        cleanedAddress.includes('/') &&
+        (cleanedAddress.startsWith('http://') ||
+          cleanedAddress.startsWith('https://'))
+      ) {
+        // URL format with protocol: http://address:port/database or https://address:port/database
+        const urlMatch = cleanedAddress.match(/^(https?:\/\/[^\/]+)\/(.+)$/);
+        if (urlMatch) {
+          setAuthReq(v => ({
+            ...v,
+            address: urlMatch[1],
+            database: urlMatch[2],
+          }));
+          return;
+        }
       }
+
+      // Set the cleaned address without database parsing
+      setAuthReq(v => ({ ...v, address: cleanedAddress }));
+      return;
     }
     setAuthReq(v => ({ ...v, [key]: value }));
   };
@@ -401,12 +437,50 @@ export const AuthForm = () => {
             if (newValue) {
               if (typeof newValue === 'string') {
                 // Handle free text input
-                const [address, database] = newValue.split('/');
-                setAuthReq(v => ({
-                  ...v,
-                  address: address.trim(),
-                  database: database?.trim() || MILVUS_DATABASE,
-                }));
+                const cleanedValue = cleanAddress(newValue);
+
+                // Handle URLs with protocols like http://127.0.0.1:19530/default
+                if (
+                  cleanedValue.includes('/') &&
+                  !cleanedValue.startsWith('http://') &&
+                  !cleanedValue.startsWith('https://')
+                ) {
+                  // Simple format without protocol: address/database
+                  const [address, database] = cleanedValue.split('/');
+                  setAuthReq(v => ({
+                    ...v,
+                    address: address.trim(),
+                    database: database?.trim() || MILVUS_DATABASE,
+                  }));
+                } else if (
+                  cleanedValue.includes('/') &&
+                  (cleanedValue.startsWith('http://') ||
+                    cleanedValue.startsWith('https://'))
+                ) {
+                  // URL format with protocol: http://address:port/database or https://address:port/database
+                  const urlMatch = cleanedValue.match(
+                    /^(https?:\/\/[^\/]+)\/(.+)$/
+                  );
+                  if (urlMatch) {
+                    setAuthReq(v => ({
+                      ...v,
+                      address: urlMatch[1],
+                      database: urlMatch[2],
+                    }));
+                  } else {
+                    setAuthReq(v => ({
+                      ...v,
+                      address: cleanedValue,
+                      database: MILVUS_DATABASE,
+                    }));
+                  }
+                } else {
+                  setAuthReq(v => ({
+                    ...v,
+                    address: cleanedValue,
+                    database: MILVUS_DATABASE,
+                  }));
+                }
               } else {
                 handleClickOnHisotry(newValue);
               }
@@ -423,12 +497,50 @@ export const AuthForm = () => {
               handleClickOnHisotry(matchingConnection);
             } else if (newInputValue) {
               // Handle free text input
-              const [address, database] = newInputValue.split('/');
-              setAuthReq(v => ({
-                ...v,
-                address: address.trim(),
-                database: database?.trim() || MILVUS_DATABASE,
-              }));
+              const cleanedValue = cleanAddress(newInputValue);
+
+              // Handle URLs with protocols like http://127.0.0.1:19530/default
+              if (
+                cleanedValue.includes('/') &&
+                !cleanedValue.startsWith('http://') &&
+                !cleanedValue.startsWith('https://')
+              ) {
+                // Simple format without protocol: address/database
+                const [address, database] = cleanedValue.split('/');
+                setAuthReq(v => ({
+                  ...v,
+                  address: address.trim(),
+                  database: database?.trim() || MILVUS_DATABASE,
+                }));
+              } else if (
+                cleanedValue.includes('/') &&
+                (cleanedValue.startsWith('http://') ||
+                  cleanedValue.startsWith('https://'))
+              ) {
+                // URL format with protocol: http://address:port/database or https://address:port/database
+                const urlMatch = cleanedValue.match(
+                  /^(https?:\/\/[^\/]+)\/(.+)$/
+                );
+                if (urlMatch) {
+                  setAuthReq(v => ({
+                    ...v,
+                    address: urlMatch[1],
+                    database: urlMatch[2],
+                  }));
+                } else {
+                  setAuthReq(v => ({
+                    ...v,
+                    address: cleanedValue,
+                    database: MILVUS_DATABASE,
+                  }));
+                }
+              } else {
+                setAuthReq(v => ({
+                  ...v,
+                  address: cleanedValue,
+                  database: MILVUS_DATABASE,
+                }));
+              }
             }
           }}
           filterOptions={(options, state) => {

+ 23 - 5
server/src/milvus/milvus.service.ts

@@ -39,13 +39,29 @@ export class MilvusService {
     // Format the address to remove the http prefix
     const milvusAddress = MilvusService.formatAddress(address);
 
-    // if client exists, return the client
+    // if client exists, validate the connection before returning
     if (clientCache.has(clientId)) {
       const cache = clientCache.get(clientId);
-      return {
-        clientId: cache.milvusClient.clientId,
-        database: cache.database,
-      };
+      try {
+        // validate the cached client is still connected and healthy
+        if (checkHealth) {
+          const healthRes = await cache.milvusClient.checkHealth();
+          if (!healthRes.isHealthy) {
+            // remove unhealthy client from cache
+            clientCache.delete(clientId);
+            throw new Error('Cached client is not healthy');
+          }
+        }
+
+        return {
+          clientId: cache.milvusClient.clientId,
+          database: cache.database,
+        };
+      } catch (error) {
+        // if cached client validation fails, remove it and continue with new connection
+        clientCache.delete(clientId);
+        console.warn(`Cached client validation failed for ${clientId}, creating new connection`);
+      }
     }
 
     try {
@@ -120,7 +136,9 @@ export class MilvusService {
         await milvusClient.use({ db_name: db });
         await milvusClient.listDatabases();
       } catch (e) {
+        // ensure proper cleanup on permission failure
         await milvusClient.closeConnection();
+        clientCache.delete(milvusClient.clientId);
         throw HttpErrors(
           HTTP_STATUS_CODE.FORBIDDEN,
           `You don't have permission to access the database: ${db}.`