1. 程式人生 > >『Go 內建庫第一季:net/url』

『Go 內建庫第一季:net/url』

TEACHING_GOPHER.png

大家好,我叫謝偉,是一名程式設計師。

近期會同步內建庫的學習,主要參考文獻官方文件和原始碼

本節的主題:url

其實這是一個比較小的內建函式,主要用在網路請求方面上,可能最多的用途也就是用來處理網路請求的引數。當然如何你經常在專案中編寫restfulAPI, 那麼你也可能經常用到。

大綱:

  • 原理知識
  • 基本的用法
  • 學習到了什麼

1. 原理知識

URL: Uniform Resource Location, 稱之為統一資源定位符。

出現的背景是:打個比方,比如你要找家裡的東西,首先,你是不是會對東西的歸屬分析,比如,一把菜刀,你最大的可能是去廚房吧,這樣能大概率的找到。網路上的資源也是這樣,為了精準的找到伺服器上的資源,有了 url。

那關於 url 有哪些知識呢?

  • 代表的含義
  • 組成部分:你要知道 url 的具體形式是什麼吧
  • 語法

1.1 代表的含義

就字面上的意思:統一資源定位符,唯一定位網路上的資源。

1.2 組成的部分

首先給個例項:


https://www.google.com

複製程式碼
  • 方案 scheme: 主要表示的是使用的是何種協議,比如 HTTP,FTP 等
  • 伺服器的地址: 你可以使用 ip 地址,也可以使用域名,所以IP 地址到域名之間存在一個對映關係
  • 資源路徑: 這部分就針對的是網路具體的資源的地址

這個好理解吧,和我們日常中的家庭地址、公司地址一樣的含義,先定位省份,再定位市區,繼而繼續定位下去,直到找到你的地址。

網路的上資源,基本上還是借用這套思路:先定位到伺服器上的地址,繼而定位到具體的資源的地址。URL 就是這個意思。

1.3 語法組成

為了規範這些網路上的資源的地址,需要有一套規範,這套語法到底包含哪些東西?

  • 方案: scheme ,具體指訪問伺服器上的資源使用的哪種協議
  • 使用者: 有些協議可以傳入明文使用者名稱和密碼獲取資源,比如 FTP
  • 密碼
  • 主機: 伺服器地址,可以是 IP 地址,也可以是域名資訊
  • 埠: 一串數字
  • 路徑: 資源的路徑,使用 “/” 分隔
  • 引數: example=one&hello=world 類似於這樣的鍵值對
  • 查詢: 識別符號是 “?” 和引數配合使用
  • 片段: 識別符號是 ”#“

好,不好理解,舉個例子:

https://godoc.org/net/url#example-Values
複製程式碼
  • scheme: https
  • 使用者: 無
  • 密碼: 無
  • 主機: godoc.org
  • 埠: 無
  • 路徑: net/url
  • 引數: 無
  • 查詢: 無
  • 片段: example-Values

有些是可選項,所以到最後,常用的是這幾個概念:

  • scheme(方案、協議)
  • host(伺服器地址)
  • port(伺服器埠)
  • path(路徑)
  • params(引數)
  • fragment(片段)

另外在請求的過程中還存在一個問題:編碼,用來在URL 中表示各種不安全的字元

常見的編碼:

字元 示例
~ %7
空格 %20
% %25

2. 基本用法

和根據上文的解釋,我們明白 URL 的含義,但最終的它其實是一串字串,只不過在網路資源請求層面,這串字串賦予了更多的含義。

先撇開,官方的內建庫的用法,我們首先想要自己實現,如何操作?

根據 url 的組成, 我們可能會設計如下面這個樣子


type Url struct {
	Scheme   string 
	User     string
	Password string
	Host     string
	Port     string
	Path     string
	Params   map[string][]string
	Fragment string
}

複製程式碼

好,假如設計成這樣,我們將一串字串轉化成 我們定義的型別 Url, 如何得到各個部分呢?

https://godoc.org/net/url#example-Values
複製程式碼

對照著各個含義,那麼我們的思路應該是對這串字元的處理,比如按:,//,/,# 等劃分得到我們需要的內容。

以上是我們自己的思考,如果感興趣,可以自己單獨實現下,想想:自己會提供哪些公開的方法?又會設計些什麼輔助的功能?

下面檢視官方的實現:

示例:

package main

import (
	"fmt"
	"net/url"
)

var urlCollection struct {
	urlOne   string
	urlTwo   string
	urlThree string
	urlFour  string
	urlFive  string
}

func init() {
	urlCollection.urlOne = "https://www.google.com"
	urlCollection.urlTwo = "http://localhost:8887/v1/api/cloud_api/[email protected]"
	// https://developer.readsense.cn/docs/retail/retailv2/regions.html#刪除區域
	urlCollection.urlThree = "https://developer.readsense.cn/docs/retail/retailv2/regions.html#%E5%88%A0%E9%99%A4%E5%8C%BA%E5%9F%9F"
	urlCollection.urlFour = "https://joe:[email protected]/share_info.txt"
	urlCollection.urlFive = "https://godoc.org/net/url#example-Values"
}

func main() {
	OpUrl(urlCollection.urlOne)
	OpUrl(urlCollection.urlTwo)
	OpUrl(urlCollection.urlThree)
	OpUrl(urlCollection.urlFour)
	OpUrl(urlCollection.urlFive)

}
func OpUrl(urlString string) {

	URL, _ := url.Parse(urlString)
	fmt.Println("user", URL.User)
	fmt.Println("scheme", URL.Scheme)
	fmt.Println("host", URL.Host)
	fmt.Println("port", URL.Port())
	fmt.Println("rawQuery", URL.RawQuery)
	fmt.Println("rawPath", URL.RawPath)
	fmt.Println("path", URL.Path)
	fmt.Println("forceQuery", URL.ForceQuery)
	fmt.Println("fragment", URL.Fragment)

}
複製程式碼

可以看出:url.Parse 可以將字串轉化成 URL 物件,該物件包含:User,Scheme,Host,Path,RawPath,ForceQuery,Fragment 欄位和一些方法。

檢視原始碼,看URL 型別物件是如何定義?

type URL struct {
Scheme     string
Opaque     string    // encoded opaque data
User       *Userinfo // username and password information
Host       string    // host or host:port
Path       string    // path (relative paths may omit leading slash)
RawPath    string    // encoded path hint (see EscapedPath method)
ForceQuery bool      // append a query ('?') even if RawQuery is empty
RawQuery   string    // encoded query values, without '?'
Fragment   string    // fragment for references, without '#'
}
複製程式碼

看上去,和我們預想的差別不大,但作者想的比我們深,比如把編碼也考慮進去了,所有會有RawQuery,RawPath 等欄位。

繼續檢視:

func PathEscape(s string) string
func PathUnescape(s string) (string, error)
func QueryEscape(s string) string
func QueryUnescape(s string) (string, error)
type Error
func (e *Error) Error() string
func (e *Error) Temporary() bool
func (e *Error) Timeout() bool
type EscapeError
func (e EscapeError) Error() string
type InvalidHostError
func (e InvalidHostError) Error() string
type URL
func Parse(rawurl string) (*URL, error)
func ParseRequestURI(rawurl string) (*URL, error)
func (u *URL) EscapedPath() string
func (u *URL) Hostname() string
func (u *URL) IsAbs() bool
func (u *URL) MarshalBinary() (text []byte, err error)
func (u *URL) Parse(ref string) (*URL, error)
func (u *URL) Port() string
func (u *URL) Query() Values
func (u *URL) RequestURI() string
func (u *URL) ResolveReference(ref *URL) *URL
func (u *URL) String() string
func (u *URL) UnmarshalBinary(text []byte) error
type Userinfo
func User(username string) *Userinfo
func UserPassword(username, password string) *Userinfo
func (u *Userinfo) Password() (string, bool)
func (u *Userinfo) String() string
func (u *Userinfo) Username() string
type Values
func ParseQuery(query string) (Values, error)
func (v Values) Add(key, value string)
func (v Values) Del(key string)
func (v Values) Encode() string
func (v Values) Get(key string) string
func (v Values) Set(key, value string)
複製程式碼

可以看出,重要的用法有:

  • 將字串轉化成 URL 物件,URL 物件獲取相應的組成成分,存在相應的方法
  • URL 中的引數 Values 很重要,特別是我們編寫 restfulAPI 的過程,也會思考這個問題,請求引數。 她的底層是 map[string][]string , 所以可以Add, Del, Get,Set等方法,這個東西需要記住,下次我們分析 net/http 庫的一個重要部分就是:對請求引數的處理

最後,再看下,這個庫對錯誤的處理:


type EscapeError string

func (e EscapeError) Error() string {
	return "invalid URL escape " + strconv.Quote(string(e))
}

type InvalidHostError string

func (e InvalidHostError) Error() string {
	return "invalid character " + strconv.Quote(string(e)) + " in host name"
}
複製程式碼
  • 定義一個結構體
  • 實現 Error 方法,繼而實現 error 介面

3. 學到了什麼

  1. 站在設計者的角度思考,我應該怎麼設計?
  2. 如何設計的思路來源於原理,而不是胡亂思考。
  3. 返過來再去看書本中的原理

<完>