Web Golang:解析URL
#go #100daysofgolang

介绍

我们已经完成了大约32个有关Golang的基本概念的文章,而这是基本的基础,我想从本系列的新部分开始,这将是web-development的主要内容。本节将有近40-50个帖子,这将涵盖Web开发的基本概念,例如API,数据库集成,身份验证和授权,Web应用程序,静态站点等。

什么是URL?

URL是统一的资源定位器。它是一串字符,可以标识Internet上的资源。 URL是网络的构建块,允许我们仅单击即可访问网站,文档和数据。 URL到处都是,如果我们想在网络开发中建立强大的基础,那么了解URL的实际含义以及它们可以存储的内容非常重要。

一个URL看起来像这样:

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

并非所有的URL都是这样,常见用户所看到的大多数URL只是使用schemehostpaths的URL。但是,其他组件同样重要,对于通过网络交换信息至关重要。

  • scheme是用于访问httphttpsftp等资源的协议。
  • userinfo是用于访问资源的用户名和密码。
  • host是资源的域名。
  • path是资源的路径或文件夹。
  • query是资源的查询字符串。它通常是键值对作为访问资源的参数。
  • fragment用作资源中的参考。

我们将在本系列中看到大多数用例,例如,userinfo通常用于通过Internet/Cloud访问数据库。查询参数将用于进行动态API调用等。

基本的URL解析

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // simple url
    urlString := "http://www.google.com"
    parsedUrl, err := url.Parse(urlString)
    if err != nil {
        panic(err)
    }
    fmt.Println(parsedUrl)
}
$ go run main.go

http://www.google.com

因此,在这里被解析的是,我们将URL作为字符串我们恢复了,唯一的区别是,而不是URL是字符串,而是现在是组件的结构。例如,我们希望该协议从URL中进行主机名,端口等。

fmt.Printf("%T\n", parsedUrl)
// *url.URL

parsedUrl是指向url.URL结构的指针。该结构url.URL具有很多组件,例如SchemeHostUserPathRawQuery等。我们很快就会潜入其中的每一个。

我们可以自己得到这些特定的组件,但这有点乏味,甚至可能容易出现错误。

让我们尝试从没有任何功能的情况下从URL获取这些组件,只需字符串操纵即可。

package main

import (
    "fmt"
)

func main() {
    urlString := "http://www.google.com"
    protocol := urlString.split(":")[0]
    hostName := urlString.split("//")[1]
    fmt.Println(protocol)
    fmt.Println(hostName)
}

这可能适用于简单的URL,但是如果我们拥有更复杂的URL,该URL具有路径,查询参数,片段,用户名,端口等,该怎么办?如果我们试图自己获取URL的各个部分,这可能会很快弄乱。因此,Golang有一个称为net/url的包装,以明确解析URL。

package main

import (
    "fmt"
    "strings"
)

func main() {
    urlString := []string{"http://www.google.com",
        "http://www.google.com/about/",
        "http://www.google.com/about?q=hello",
        "http://www.google.com:8080/about?q=hello",
        "http://user:password@example.com:8080/path/to/resource?query=value#fragment",
    }
    for _, url := range urlString {
        hostStr := strings.Split(url, "//")[1]
        hostName := strings.Split(hostStr, "/")[0]
        fmt.Println(hostName)
    }
}
$ go run main.go

www.google.com
www.google.com
www.google.com
www.google.com:8080
user:password@example.com:8080

上面的代码可能适用于大多数URL,但是如果我们具有像portuser的更复杂的URL那样,它不会给出我们想要的东西。在上面的示例中,我们创建了一个URL列表作为字符串,并简单地在urlString中的每个url上迭代。此后,我们在//上将url分开,因此我们获得了https:www.google.com,如果我们想要主机/域名,我们可以简单地将1索引在切片中获取,因为strings.Split方法与提供的分离器分开后返回片段。可以从1索引中获取hostName。但是,这次是列表中的第二个元素,我们有https://www.google.com/about,它将返回www.google.com/about作为主机名,这不是理想的选择,因此我们将不得不再次使用/拆分此字符串并抓住第一部分,即0th Index。

上面的代码适用于pathsquery参数,但是如果我们有端口,用户名和密码,则它不会像列表中的最后两个示例那样可用。

所以,现在我们知道手动解析URL的弊端,我们可以使用net/url软件包为我们做。

解析DB URL

数据库具有连接URL或连接字符串,该字符串提供了连接到数据库/数据库服务器的标准方法。 URL的格式仅是URL,所有组件从schemepath。某些数据库连接URL的常见示例可能包括:

# PostgreSQL DB Connection URL/string
postgresql://username:password@hostname:port/database_name

# MongoDB Connection URL/string
mongodb://username:password@hostname:port/database_name

以上是Postgres和MongoDB连接URL的示例,它们具有一个protocol,通常用于数据库,是其缩略名称,用户凭据,即usernamepasswordpasswordhostname,即服务器IP地址,该数据库的数据库,正在运行,最后是database name的路径。

我们可以在Golang中构造一个简单的片段,以使用net/url软件包从简单连接URL字符串中获取所有详细信息。

package main

import (
    "fmt"
    "net/url"
)

func main() {
    //postgres db url
    dbUrl, err := url.Parse("postgres://admin:pass1234@localhost:5432/mydb")
    if err != nil {
        panic(err)
    }
    fmt.Println(dbUrl)
    fmt.Println("Scheme/Protocol = ", dbUrl.Scheme)
    fmt.Println("User = ", dbUrl.User)
    //fmt.Println("User = ", dbUrl.User.String())
    fmt.Println("Username = ", dbUrl.User.Username())
    password, _ := dbUrl.User.Password()
    fmt.Println("Password = ", password)
    fmt.Println("Host = ", dbUrl.Host)
    fmt.Println("HostName = ", dbUrl.Hostname())
    fmt.Println("Port = ", dbUrl.Port())
    fmt.Println("DB Name = ", dbUrl.Path)
}
$ go run main.go

postgres://postgres:pass1234@localhost:5432/mydb
Scheme/Protocol =  postgres
User = admin:pass1234
Username =  admin
Password =  pass1234
Host =  localhost:5432
HostName =  localhost
Port =  5432
DB Name =  mydb

在上面的代码中,我们给了字符串postgres://admin:pass1234@localhost:5432/mydb,并且使用net/url软件包解析了URL。结果是我们有一个parsedUrl对象,该对象具有可以作为字段或方法访问的所有组件。让我们分解上述示例中使用的每个字段/方法:

  • Scheme只是代表资源协议(url)的字符串。
  • User是具有不变用户名和密码字段的UserInfo对象。
  • UsernameUserInfo上的方法,它返回代表URL用户名的字符串。
  • PasswordUserInfo上的方法,它返回代表URL密码的字符串。
  • HostURL上的字段,作为代表主机的字符串:URL的端口。
  • HostnameURL上的方法,它返回代表URL主机名的字符串。
  • PortURL上的方法,它返回代表URL端口的字符串。
  • Path是代表URL路径的字符串。

因此,这就是我们几乎可以从数据库连接字符串URL中获取几乎所有细节,例如db protocolusernamepasswordpasswordhostnameportdatabase name

解析查询参数

我们甚至可以使用URL对象上的Query方法获取URL的查询参数。 Query方法返回一个map[string][]string,它是用键为string的映射,值为[]string slice slice。例如,如果URL类似于https://google.com/?q=hello,则Query方法将返回map[q:[hello]],这意味着密钥是q,值是唯一元素的字符串列表。


package main

import (
    "fmt"
    "net/url"
)

func main() {
    // a complex url with query params
    urlStr := "http://www.google.com/?q=hello&q=world&lang=en"
    parsedUrl, err := url.Parse(urlStr)
    if err != nil {
        panic(err)
    }
    fmt.Println(parsedUrl)
    fmt.Println(parsedUrl.Query())
    for k, v := range parsedUrl.Query() {
        fmt.Println("KEY:", k)
        for _, vv := range v {
            fmt.Println(vv)
        }
    }
}
$ go run main.go

http://www.google.com/?q=hello+world&lang=en&q=gopher
map[lang:[en] q:[hello world gopher]]
KEY: q
hello world
gopher
KEY: lang
en

我们取了一个复杂的示例,可能涵盖了Query方法的许多用例。我们的URL为http://www.google.com/?q=hello&q=world&lang=enQuery方法返回map[lang:[en] q:[hello world]],这意味着q键具有带有elements hello worldgopher的字符串切片,而lang键具有en的值。在这里,第一个参数q=hello+world基本上是hello worldhello%20world,即逃脱URL中的空间。我们可以具有相同键的多个值,这很明显,因为我们在URL末尾添加了q=gopher,键q在切片中具有两个元素为hello worldgopherlang=en只是lang的键,唯一的元素是切片中的en。我们使用&在URL中分离不同的查询参数。

查询参数中的值

我们甚至可以检查查询参数中的值,而无需构造循环以在密钥中找到特定值。 Values是一种将地图存储为Query方法的返回值的类型。它具有一些方便的方法:

  • Has检查键是否存在地图中(paramter作为键string并返回bool)。
  • Get要以字符串或不存在的话获取给定键的第一个值,然后返回一个空字符串(paramter作为键string并返回string)。
  • Add方法用于附加给定键的值(paramter作为键string,值为string)。
  • 如果已经存在,则使用Set方法替换给定密钥的值(paramter为键string,值为string)。
  • Del方法用于删除给定键的值(paramter作为键string)。
package main

import (
    "fmt"
    "net/url"
)

func main() {
    // a complex url with query params
    urlStr := "http://www.google.com/?q=hello+world&lang=en&q=gopher"
    parsedUrl, err := url.Parse(urlStr)
    if err != nil {
        panic(err)
    }
    fmt.Println(parsedUrl)
    fmt.Println(parsedUrl.Query())

    queryParams := parsedUrl.Query()

    fmt.Println(queryParams.Get("q"))

    fmt.Println(queryParams.Has("q"))

    if queryParams.Has("lang") {
        fmt.Println(queryParams.Get("lang"))
    }

    queryParams.Add("q", "ferris")
    fmt.Println(queryParams)

    queryParams.Set("q", "books")
    fmt.Println(queryParams)

    queryParams.Del("q")
    fmt.Println(queryParams)
}
$ go run main.go

http://www.google.com/?q=hello+world&lang=en&q=gopher
hello world
true
en
map[lang:[en] q:[hello world gopher ferris]]
map[lang:[en] q:[books]]
map[lang:[en]]

上面的代码示例演示了Values类型上几乎所有可用的方法。 Get方法用于获取给定键的第一个值,因此我们将键作为string解析为该方法,并将返回string。我们检查了q作为钥匙,然后从列表[hello world, gopher]中返回了qqueryParams中的第一个元素。 Has方法作为键作为string的键,并返回键是否存在于queryParams中,而不是作为布尔。 Add方法,我们已经用来Add具有特定值的键,我们将值ferris添加到键q中,因此它附加到列表中,而queryParams[q]的列表成为[hello world, gopher, ferris]Set方法用于覆盖具有特定值的现有密钥,在这里我们将值books设置为Key q,因此queryParams[q]的列表变为[books]。我们可以使用Del方法从queryParams中删除键,因此我们从queryParams删除q,然后queryParams根本没有键为q

解析查询参数到字符串

现在,您已经操纵了查询参数,假设您要构造查询特定的字符串表示形式或为其的URL构造。 Encode方法用于抓住queryParams,即Values对象并将其转换为编码URL的string表示。

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // a complex url with query params
    urlStr := "http://www.google.com/?q=hello+world&lang=en&q=gopher"
    parsedUrl, err := url.Parse(urlStr)
    if err != nil {
        panic(err)
    }
    queryParams := parsedUrl.Query()
    queryParams.Add("name", "ferris")

    q := queryParams.Encode()
    fmt.Println(q)
}
$ go run main.go

lang=en&name=ferris&q=hello+world&q=gopher

因此,我们可以看到Encode方法以URL编码字符串的形式给出了一个查询参数。我们首先从parsedUrl获取查询参数,该参数是Query方法的URL对象,然后我们将Add key name to ferris to queryParams。然后将其用于Encode对象回到字符串表示。这对于构建用于请求其他网站/API的查询参数可能很有用。

解析URL对象回到字符串

我们甚至可以使用URL对象上的String方法将URL对象回到字符串表示。 String方法返回URL对象的string表示。

package main

import (
    "fmt"
    "net/url"
)

func main() {
    urlStr := "http://www.google.com/?q=hello+world&lang=en&q=gopher"
    fmt.Println("URL:", urlStr)
    parsedUrl, err := url.Parse(urlStr)
    if err != nil {
        panic(err)
    }
    queryParams := parsedUrl.Query()
    queryParams.Add("name", "ferris")

    q := queryParams.Encode()
    fmt.Println(q)
    parsedUrl.RawQuery = q
    newUrl := parsedUrl.String()
    fmt.Println("New URL:", newUrl)
}
$ go run main.go

URL: http://www.google.com/?q=hello+world&lang=en&q=gopher
lang=en&name=ferris&q=hello+world&q=gopher
New URL: http://www.google.com/?lang=en&name=ferris&q=hello+world&q=gopher

在上面的示例中,我们将一个URL字符串解析为URL对象为parsedUrl,然后我们Add key name to ferris值为queryParams。然后,我们Encode URL对象回到字符串表示。但这不会更改我们要更改整个URL对象的parsedUrl对象。为此,我们将URL对象的RawQuery字段覆盖为“查询参数”字符串为qString方法返回string URL对象的表示。

解析碎片

URL中的片段通常存在于静态网站中,例如#about#contact#blog等。Fragment是一个字符串,通常是对网页或资源中特定部分或锚点的引用。当访问带有片段的URL时,Web浏览器或用户代理将滚动页面以显示片段标识的部分。

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // url with fragments
    urlStr := "https://pkg.go.dev/math/rand#Norm Float64"
    parsedUrl, err := url.Parse(urlStr)
    if err != nil {
        panic(err)
    }
    fmt.Println(parsedUrl)
    fmt.Println(parsedUrl.Fragment)
    fmt.Println(parsedUrl.RawFragment)
    fmt.Println(parsedUrl.EscapedFragment())
}
$ go run main.go

https://pkg.go.dev/math/rand#Norm Float64

Norm Float64
Norm Float64
Norm%20Float64

上面的代码用于从url https://pkg.go.dev/math/rand#NormFloat64获取#Norm Float64片段。我们可以在URL对象中使用Fragment字段来获取片段文本。有RawFragment字段用于解析片段文本,而无需试图逃脱URL中的任何特殊字符。 EscapedFragment用于通过逃脱URL中的字符来解析片段文本。

就是这一系列的第32部分,示例的所有源代码均在100 days of Golang存储库的GitHub中链接。

100-days-of-golang

GitHub logo Mr-Destructive / 100-days-of-golang

Golang系列100天的脚本和资源

100 Days of Go lang

Go lang is a programming language which is easier to write and even provides type and memory safety, garbage collection and structural typing. It is developed by the Google Team in 2009 and open sourced in 2012. It is a programming language made for Cloud native technologies and web applications. It is a general purpose programming lanugage and hence there are no restrictions on the type of programs you wish to make in it.

学习golang

的资源

一些著名的应用程序!

Web应用程序 devops工具 CLI工具
SoundCloud-音乐系统 Prometheus-监视系统和时间序列数据库 gh-cli-官方GitHub Cli
Uber-乘车共享/出租车预订webApp

结论

从Web开发部分的第一篇文章中,我们介绍了URL解析的基本原理,并有点介绍了net软件包,该软件包将大量用于使用Web的大多数核心语言功能。我们介绍了解析URL的概念,从解析对象获取URL的组件,数据库连接URL解决,解析查询参数以及其他一些与URL相关的概念。

希望,如果您有任何评论或反馈,您会发现本节有帮助,请在评论部分或我的社交手柄中告诉我。谢谢您的阅读。快乐编码:)