[gin框架]深入解析 CleanPath 方法:消除 URL 路径中的多余元素

引言:
在 Web 开发中,处理 URL 路径是一项常见的任务。但由于用户输入、路由规则等原因,URL 路径可能会包含多余的斜杠、点号.和点点…等元素,导致路径不规范。为了确保正确的路径解析和处理,httprouter 实现了 CleanPath 方法。本文将深入解析 CleanPath 方法的实现原理,重点关注其处理逻辑和处理边界情况的技巧。

1. 实现原理

CleanPath 函数是 httprouter 包中的一部分,用于消除 URL 路径中的多余元素。它通过逐个字符遍历路径,根据不同的字符情况,对路径进行规范化和清理。 CleanPath 函数定义。

// CleanPath is the URL version of path.Clean, it returns a canonical URL path
// for p, eliminating . and .. elements.
func CleanPath(p string) string {
    // ... CleanPath 的实现代码 ...
}

2. 处理逻辑

CleanPath 函数的处理逻辑可以拆分为以下几个步骤:

2.1 处理多个连续斜杠
如果路径中存在多个连续的斜杠(),将它们替换为单个斜杠(/)。例如:/path//to///resource -> /path/to/resource

for r < n {
    switch {
    case p[r] == '/':
        // 处理多个连续斜杠,将它们替换为单个斜杠
        r++
    // 其他分支...
    }
}

2.2 消除当前目录路径
将路径中的 . 删除,表示当前目录。例如:/path/./to/resource -> /path/to/resource

for r < n {
    switch {
    // .是最后一个元素时,要在链接最后的链接添加/。例如:/foot/tar/.-> /foot/tar/
    case p[r] == '.' && r+1 == n:
        // 消除当前目录路径,将 `.` 删除
        trailing = true
        r++
    // 其他分支...
    }
}

2.3. 消除上级目录路径
将路径中的 … 及其前面的路径元素删除,表示上级目录。例如:/path/to/…/resource -> /path/resource

for r < n {
    switch {
    // 其他分支...
    case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
        // 消除上级目录路径,将 `..` 及其前面的路径元素删除
        r += 3

        if w > 1 {
            // 可以回退,将 `w` 向前移动一位,直到找到前一个 `/` 为止
            w--
            //buf空间未缓冲字符是,此时相当于用p[:w]这个空间缓存着字符
            if len(buf) == 0 {
                for w > 1 && p[w] != '/' {
                    w--
                }
            } else {
                for w > 1 && buf[w] != '/' {
                    w--
                }
            }
        }
    // 其他分支...
    }
}

2.4. 处理空路径
如果输入路径为空字符串,则 CleanPath 函数会将其转换为根路径(/)。
例如: -> /

// 处理空路径,将其转换为根路径 `/`
if p == "" {
    return "/"
}

2.5 处理路径不以斜杠(/)开头
如果路径不以斜杠(/)开头,函数会自动添加斜杠,确保输出的路径以斜杠开头。
例如:path/to ->/path/to

// 只有当遇到待处理字符时,如./ // ../等是,才会使用到buf,其他情况,不使用buf
// r 是读取索引,用于指示当前正在处理的 URL 路径的下一个字节的位置。
// w 是写入索引,用于指示要写入到 buf 切片中的下一个字节的位置,用途是buf中没有内容时,如果p[:w]字符串与p待写入的字符串相同时,就不用向buf空间中写入内容一值标记着p[:w]位置。
// 为何从1开始,而不是从0开始?认为第一个通过将 r 和 w 从 1 开始,我们可以在处理路径时更方便地处理第一个路径元素,而不需要特殊处理开头的斜杠。这样可以简化代码逻辑,并且在处理后续路径元素时也能保持一致的处理方式。
r:=1
w:=1
// 处理路径不以 `/` 开头,添加 `/` 到输出路径的开头
if p[0] != '/' {
    // r对应执行的是处理路径中的字符,最初时赋值为1,表示跳过了/,此时第一个字符不是/,所以需要将r赋值为0
    // 让r每次都对应着字符的处理,而不包含/处理,可以使逻辑更为简化
    r = 0
    // 长度超过buff,重新分配空间
    if n+1 > stackBufSize {
        buf = make([]byte, n+1)
    } else {
        // buff大小是当前字符串长度+1就足以。通过buf[:n+1]来分配空间,相比make
        // 避免额外的内存分配: 切片切取的方式不会创建新的底层数组,而是调整现有底层数组的长度。这意味着不需要额外的内存分配,避免了内存的浪费。注意:buff原有空间的长度要>=n+1时,才可如此操作
        buf = buf[:n+1]
    }
    buf[0] = '/'
}

2.6 将字符存储于惰性缓冲区

// Internal helper to lazily create a buffer if necessary.
// Calls to this function get inlined.
func bufApp(buf *[]byte, s string, w int, c byte) {
	b := *buf
	if len(b) == 0 {
		// 如果原始字符串未被修改过
		// 如果下一个字符与原始字符串中的字符相同,表示无需修改,直接返回
		// 相当于s[:w]这个空间充当了一个临时缓冲区,存储相应字符
		if s[w] == c {
			return
		}

		// 否则,根据需要使用栈缓冲区或在堆上分配新缓冲区,并将之前的字符复制进去
		if l := len(s); l > cap(b) {
			*buf = make([]byte, len(s))
		} else {
			*buf = (*buf)[:l]
		}
		b = *buf

		// 将之前的字符复制进新缓冲区
		copy(b, s[:w])
	}
	
	// 将新字符追加到缓冲区
	b[w] = c
}

3. 处理特殊情况和边界条件

CleanPath 函数还需考虑一些特殊情况和边界条件,以确保输出的路径正确和合理。

3.1 处理路径结尾的斜杠
如果输入路径结尾有斜杠(/path/to/resource/),则在输出路径结尾也添加一个斜杠,以保持一致。例如:/path/to/resource/ -> /path/to/resource/

// Re-append trailing slash
if trailing && w > 1 {
    bufApp(&buf, p, w, '/')
    w++
}

3.2. 消除以 … 开头的路径元素
如果输入路径以 … 开头(…/path/to/resource),表示位于上级目录之外,将其删除。例如:…/path/to/resource -> /path/to/resource

// 其他分支...
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
    // 消除以 `..` 开头的路径元素
    r += 3

    if w > 1 {
        // 可以回退,将 `w` 向前移动一位,直到找到前一个 `/` 为止
        w--
        if len(buf) == 0 {
            for w > 1 && p[w] != '/' {
                w--
            }
        } else {
            for w > 1 && buf[w] != '/' {
                w--
            }
        }
    }
// 其他分支...

4. 性能优化与内存管理

CleanPath 函数在实现中采用了一些性能优化技巧,使用缓冲区来减少内存分配。避免不必要的字符串拼接,从而提高处理效率。我们将深入探讨这些优化措施,并了解它们如何提升 CleanPath 函数的性能。

6. 与标准库 path.Clean 的对比

在 Go 语言标准库中,也提供了 path.Clean 函数用于清理路径。虽然 CleanPath 和 path.Clean 都用于路径规范化,但它们之间存在一些细微的差异。

path.Clean 是标准库中的函数,用于规范化文件路径。而 CleanPath 是 httprouter 包中的函数,用于规范化 URL 路径。它们的功能相似,但适用的场景不同。

path.Clean 是对文件路径进行规范化的函数,它主要用于处理文件系统中的路径,比如读取文件、写入文件等操作。

CleanPath 则是专门用于处理 URL 路径的规范化函数,它适用于 Web 开发中的路由处理和 URL 解析等场景。

7. 结束语

我们深入解析了 CleanPath 方法,它作为一个简单的 URL 路径规范化工具,背后却涉及较多的处理逻辑。在 Web 开发中,处理 URL 路径可能看似简单,但实际上涉及到许多细节和边界条件的考虑。写代码就是如此,我们必须仔细思考各种场景和条件,确保代码的正确性和稳定性。

写代码不仅仅是实现功能,更重要的是要考虑到各种场景和条件。在 Web 开发中,URL 路径处理可能只是一个看似简单的环节,但它直接影响着系统的稳定性和性能。因此,我们应该在写代码时注重细节,善于思考和排查各种可能出现的情况,保证代码的鲁棒性和可靠性。

参考

httprouter

你可能感兴趣的:(gin,go)