iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
0

url clean

難免會有無聊的使用者這樣玩網址

/owner//////proj1//pro2/blog////

如果對這種request也想正常服務,可以設置gin.Engine的RemoveExtraSlash為true,gin會在內部呼叫cleanPath這個function

但還有更重要的問題,我們的路由定義/:owner/*project,這代表就算是/owner/也會rout到GetProject,而cleanPath雖然在處理的過程中會把最後的"/"刪掉,但最後要輸出時會再加"/"回去...

這問題不能在middleware處理,在進入middleware時gin就已經決定好路由了,所以我們要在ServeHTTP處理這問題

因為cleanPath沒有export,所以我直接把它複製出來用了,嫌麻煩就直接改源碼吧,只要把"/"加回去的那段程式碼刪掉就好了

更改host_switch.go(礙於篇幅,我把大部分的註解刪掉了,只留下要我修改的註解,想搞懂可以去看源碼)

// Implement the ServeHTTP method on our new type
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Check if a http.Handler is registered for the given host.
	// If yes, use it to handle the request.
	if handler := hs[r.Host]; handler != nil {
		// clean path
		path := cleanPath(r.URL.Path)

		// update to request
		r2 := new(http.Request)
		*r2 = *r
		r2.URL = new(url.URL)
		*r2.URL = *r.URL
		r2.URL.Path = path

		handler.ServeHTTP(w, r2)
	} else {
		// Handle host names for which no handler is registered
		http.Error(w, "Forbidden", 403) // Or Redirect?
	}
}


func cleanPath(p string) string {
	const stackBufSize = 128
	if p == "" {
		return "/"
	}

	buf := make([]byte, 0, stackBufSize)

	n := len(p)

	r := 1
	w := 1

	if p[0] != '/' {
		r = 0

		if n+1 > stackBufSize {
			buf = make([]byte, n+1)
		} else {
			buf = buf[:n+1]
		}
		buf[0] = '/'
	}

	// trailing := n > 1 && p[n-1] == '/'  這裡


	for r < n {
		switch {
		case p[r] == '/':
			r++

		case p[r] == '.' && r+1 == n:
			// trailing = true 這裡
			r++

		case p[r] == '.' && p[r+1] == '/':
			r += 2

		case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
			r += 3

			if w > 1 {
				w--

				if len(buf) == 0 {
					for w > 1 && p[w] != '/' {
						w--
					}
				} else {
					for w > 1 && buf[w] != '/' {
						w--
					}
				}
			}

		default:
			if w > 1 {
				bufApp(&buf, p, w, '/')
				w++
			}

			for r < n && p[r] != '/' {
				bufApp(&buf, p, w, p[r])
				w++
				r++
			}
		}
	}

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

	if len(buf) == 0 {
		return p[:w]
	}
	return string(buf[:w])
}

func bufApp(buf *[]byte, s string, w int, c byte) {
	b := *buf
	if len(b) == 0 {
		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
}

解釋:

  • r2:golang團隊成員在這篇有提到ServeHTTP(http.ResponseWriter, *http.Request)的規則,所以我依照 net/http.StripPrefix來寫

// Except for reading the body, handlers should not modify the
// provided Request.

測測看結果

curl GET "http://127.0.0.1:8000//wew6////1/2/3/////4/5/6/7/8/////////"

{"blog":"8","owner":"wew6","project":["1","2","3","4","5","6","7"]}

總結

api的雛型架構就這樣了,明天開始來寫通用的middleware

今天工作環境沒變動


上一篇
Day2 路由配置
下一篇
Day4 錯誤處理(上)
系列文
從coding到上線-打造自己的blog系統31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言