通过示例学习-Go-语言-2023-十四-

通过示例学习 Go 语言 2023(十四)

在 Go (Golang) 中,从传入的 HTTP 请求中获取客户端的用户代理。

来源:golangbyexample.com/user-agent-http-golang/

目录

  • 概述

  • 示例

概述

在传入的 HTTP 请求中,用户代理存在于请求的头部。在 Go 中,传入的 HTTP 请求由 http.Request 结构体表示。

golang.org/src/net/http/request.go

http.Request 结构体提供了获取请求用户代理的以下方法。

func (r *Request) UserAgent() string

此方法需要在请求对象上调用。

示例

让我们看看相应的程序。

package main

import (
	"fmt"
	"net/http"
)

func main() {
	handler := http.HandlerFunc(handleRequest)
	http.Handle("/example", handler)
	http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {

	userAgent := r.UserAgent()
	fmt.Printf("UserAgent:: %s", userAgent)
}

运行上述程序并进行以下 API 调用。同时传入用户代理头部。

curl -v -X POST http://localhost:8080/example -H "user-agent: Mozialla -1.0"

输出

UserAgent:: Mozialla -1.0

在上述程序中,我们在请求结构体对象上调用 UserAgent() 函数,然后打印出来。它正确地打印了用户代理。

获取不同时区的当前时间和日期在 Go (Golang) 中

来源:golangbyexample.com/get-current-time-and-date-of-different-timezones-golang/

time 包有一个 LoadLocation(name string) 函数,可以用来加载任何地方的位置。一旦你有了位置,In 函数可以用来改变与特定 time.Time 对象关联的位置。

对于 LoadLocation(locationName) 函数

  • 如果 locationName 是 “” 或 “UTC”,则返回 UTC

  • 如果 locationName 是本地的,它会返回本地位置

  • 否则,位置名称必须对应于 IANA 时区数据库进行指定(参考维基 - en.wikipedia.org/wiki/List_of_tz_database_time_zones)。例如:

    • 欧洲/柏林

    • 美国/纽约

    • 亚洲/迪拜

让我们来看一个例子:

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	loc, _ := time.LoadLocation("UTC")
	fmt.Printf("UTC Time: %s\n", now.In(loc))

	loc, _ = time.LoadLocation("Local")
	fmt.Printf("Local Time: %s\n", now.In(loc))

	loc, _ = time.LoadLocation("Europe/Berlin")
	fmt.Printf("Berlin Time: %s\n", now.In(loc))

	loc, _ = time.LoadLocation("America/New_York")
	fmt.Printf("New York Time: %s\n", now.In(loc))

	loc, _ = time.LoadLocation("Asia/Dubai")
	fmt.Printf("Dubai Time: %s\n", now.In(loc))

} 

输出

UTC Time: 2020-01-31 17:36:26.481946 +0000 UTC
Local Time: 2020-02-01 01:36:26.481946 +0800 +08
Berlin Time: 2020-01-31 18:36:26.481946 +0100 CET
New York Time: 2020-01-31 12:36:26.481946 -0500 EST
Dubai Time: 2020-01-31 21:36:26.481946 +0400 +04

获取当前用户的主目录(Go/Golang)

来源:golangbyexample.com/get-current-user-home-directory-go/

概述

‘os/user’ 包可用于获取当前用户的主目录

让我们看看一个工作示例

代码

package main

import (
    "fmt"
    "log"
    "os/user"
)

func main() {
    user, err := user.Current()
    if err != nil {
        log.Fatalf(err.Error())
    }
    homeDirectory := user.HomeDir
    fmt.Printf("Home Directory: %s\n", homeDirectory)
}

输出

Home Directory: "some_home_directory"

在 Go (Golang) 中获取当前用户名

来源:golangbyexample.com/get-current-username-golang/

概述

‘os/user’ 包可用于获取当前用户名。

让我们看一段可运行的代码

代码

package main

import (
	"fmt"
	"log"
	"os/user"
)

func main() {

	user, err := user.Current()
	if err != nil {
		log.Fatalf(err.Error())
	}

	username := user.Username

	fmt.Printf("Username: %s\n", username)
} 

输出

Username: "someusername"

在 Go (Golang) 中获取当前工作目录

来源:golangbyexample.com/current-working-directory-golang/

os.Getwd() 用于获取当前工作目录。输出将类似于 linux 上的 pwd 命令

package main
import (
    "fmt"
    "log"
    "os"
)

func main() {
    currentWorkingDirectory, err := os.Getwd()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Current Wroking Direcoty: %s", currentWorkingDirectory)
}

输出:

当前工作目录:<将是机器上的当前工作目录>

在 Go(Golang)中获取文件名、大小、权限位、模式、修改时间

来源:golangbyexample.com/file-info-golang/

目录

  • 概述

  • 代码

概述

os.Stat()函数可用于获取 Go 中文件的信息。该函数返回可用于获取的信息统计数据。

  • 文件的名称

  • 文件的大小(以字节为单位)

  • 文件的修改时间

  • 文件的权限位或模式

以下是函数的签名。它接受指定的文件并返回FileInfo结构体,该结构体定义了获取上述信息的实用方法。

func Stat(name string) (FileInfo, error)

代码

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    //Create a file
    file, err := os.Create("temp.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    //Write something to the file
    file.WriteString("some sample text" + "\n")

    //Gets stats of the file
    stats, err := os.Stat("temp.txt")
    if err != nil {
        log.Fatal(err)
    }

    //Prints stats of the file
    fmt.Printf("Permission: %s\n", stats.Mode())
    fmt.Printf("Name: %s\n", stats.Name())
    fmt.Printf("Size: %d\n", stats.Size())
    fmt.Printf("Modification Time: %s\n", stats.ModTime())
}

输出:

Permission: -rwxrwxrwx
Name: temp.txt
Size: 17
Modification Time: 2020-04-16 22:26:47.080128602 +0530 IST

在 Go(Golang)中获取 URL 的完整主机名及端口。

来源:golangbyexample.com/hostname-port-url-golang/

目录

** 概览

  • 程序

概览

golang 的 net/url 包包含一个解析函数,可以用来解析给定的 URL 并返回 URL 结构体的实例。golang.org/pkg/net/url/#URL

一旦给定的 URL 正确解析,将返回 URI 对象。我们可以从 URI 中访问以下信息。

  • 协议

  • 用户信息

  • 主机名

  • 端口

  • 路径名

  • 查询参数

  • 片段

一旦我们拥有所有部分,就可以将它们连接起来,以获取完整的主机名及端口。我们将解析以下 URL。

https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history

然后完整的主机名及端口将为

https://golangbyexample.com:8000

程序

以下是相应的程序。

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	input_url := "https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history"
	u, err := url.Parse(input_url)
	if err != nil {
		log.Fatal(err)
	}

	fullHostname := u.Scheme + "://" + u.Host

	fmt.Println(fullHostname)
}

输出

https://golangbyexample.com:8000:8000

在 Go (Golang)中获取主机名

来源:golangbyexample.com/get-hostname-golang/

目录

  • 概述

  • 代码

概述

golang 的‘os’包提供了一个Hostname函数,可用于获取内核报告的主机名

下面是此方法的签名。如果无法成功获取主机名,则返回错误

func Hostname() (name string, err error)

让我们来看一个工作代码

代码

package main

import (
	"fmt"
	"os"
)

func main() {
	hostname, err := os.Hostname()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Printf("Hostname: %s", hostname)
}

输出

Hostname: <hostname of="" your="" machine=""></hostname>

在 Go (Golang) 中获取浮点数的整数值

来源:golangbyexample.com/integer-value-of-float/

目录

  • 概述

  • 代码

概述

GO 的 math 包提供了一个 Trunc 方法,可以用来获取浮点数的整数值

以下是该函数的签名。它接受一个浮点数作为输入,并返回一个浮点数。

func Trunc(x float64) float64

Trunc 函数的一些特殊情况是

  • Trunc(±0) = ±0

  • Trunc(±Inf) = ±Inf

  • Trunc(NaN) = NaN

代码

package main

import (
    "fmt"
    "math"
)

func main() {
    res := math.Trunc(1.6)
    fmt.Println(res)

    res = math.Trunc(-1.6)
    fmt.Println(res)

    res = math.Trunc(1)
    fmt.Println(res)
}

输出:

1
-1
1

从 HTTP 请求中获取 JSON 请求体(Golang)

来源:golangbyexample.com/json-request-body-golang-http/

目录

  • 概述

  • 示例

概述

json/encoding包包含可以用于将传入 HTTP 请求的请求体转换为 Go 语言结构体的方法。在开始之前,先说一句关于请求体的话。HTTP 请求的请求体是一系列字节。请求体的内容类型表示这些字节的表示格式,并且意味着要被读取。对于 JSON 请求体,内容类型是

application/json

另外还有两件关于 Go 语言结构体你需要知道的事。

  • 仅导出的结构体字段对外部库可见。因此,只有导出的结构体字段才能从传入的 HTTP 请求中解析。需要注意的是,Go 语言中大写的结构体字段是导出的。

  • 结构体字段有一个元信息部分,包含关于该字段的附加信息。这些元字段在将传入的 JSON 请求体解析为结构体时使用。例如,假设我们有以下结构体:

type employee struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

注意与每个标记为‘json’的字段相关联的元标签。这些元字段用于将 JSON 中的键映射到结构体的字段。例如,如果我们有以下 JSON:

{
  "name" : "John",
  "age"  : 21
}

然后上述 JSON 的name键将映射到employee结构体的Name字段,JSON 中的age键将映射到结构体的Age字段。假设我们有以下结构体和 JSON:

type employee struct {
	Name string `json:"n"`
	Age  int    `json:"ag"`
}

{
  "n" : "John",
  "age"  : 21
}

然后 JSON 中的‘n’键将映射到结构体的Name字段,JSON 中的‘ag’键将映射到结构体的Age字段。

可以使用json/encoding包的以下两个方法获取传入请求的 JSON 请求体。

  • json.Unmarshal([]byte)

  • json.NewDecoder(io.Reader).Decode(interface{})

第二种方法是获取 JSON 请求体的优选方式,原因有两个。

  • 传入 HTTP 请求的请求体是一个 io 流。json.Unmarshal将首先读取请求体的全部内容,然后进行解组。它不会对请求体进行验证,而是直接处理。相比之下,Decode 方法如果 JSON 无效会立即给出解析错误。这对于客户端发送的大型无效请求体非常有用。Unmarshal 将在整个大请求体读取完毕后发现问题,而 Decode 在一开始就会抛出错误。

  • json.Decode包含DisallowUnknownFields方法,如果传入的 JSON 包含与任何字段不匹配的键,则会引发错误。

  1. 导出和

  2. 未被忽略的结构体字段。

在解析传入的 JSON 请求体时,可能需要处理几个问题。

  • 请求体可能不是有效的 JSON。

  • 这是一个有效的 JSON,但包含额外的字段或没有任何结构体中预期的字段。

  • JSON 请求体太大。

  • 该字段是预期的,但它包含不同的类型。例如,对于上面的名称字段,传入的 HTTP 请求 JSON 主体有一个数字,而预期是一个字符串。

  • 请求体包含额外字段。

Decode 方法可以捕获所有这些问题,并能够返回适当的错误消息,除了在出现解组错误的情况下。让我们看看在所有情况下解码方法返回的错误。

想象一下,传入的 JSON 需要转换成我们上面提到的员工结构。

问题

  • 传入的 JSON 中有一个额外的字段。例如,假设我们有一个额外的性别字段。
'{"Name":"John", "Age": 21, "Gender": "M"}'

解码函数返回的错误将是

unknown field "Gender"
  • 请求体不是有效的 JSON
'{"Name": "John", "Age":}'

解码函数返回的错误将是

invalid character '}' looking for beginning of value
  • 请求体为空
''

解码函数返回的错误将是

EOF
  • 某个字段的类型与预期不同。例如,发送一个字符串值的年龄,而预期是 int。
'{"Name":"John", "Age": "21"}'

解码函数返回的错误将是

json: cannot unmarshal string into Go struct field employee.age of type int

在这种情况下,解码函数返回的错误包含内部信息,这不是应返回给客户端的适当错误。也可以捕获这种类型的错误并返回适当的错误给客户端。这正是我们在下面的程序中所做的。只需检查是否是解组错误,然后返回自定义错误消息。

在下面的代码中,我们还设置了解码器的禁止未知字段选项,因此传入 JSON 主体中的任何额外字段都会导致错误。

decore.DisallowUnknownFields()

示例

下面是完整的程序示例。

package main

import (
	"encoding/json"
	"errors"
	"net/http"
)

type employee struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	createEmployeeHanlder := http.HandlerFunc(createEmployee)
	http.Handle("/employee", createEmployeeHanlder)
	http.ListenAndServe(":8080", nil)
}

func createEmployee(w http.ResponseWriter, r *http.Request) {
	headerContentTtype := r.Header.Get("Content-Type")
	if headerContentTtype != "application/json" {
		errorResponse(w, "Content Type is not application/json", http.StatusUnsupportedMediaType)
		return
	}
	var e employee
	var unmarshalErr *json.UnmarshalTypeError

	decoder := json.NewDecoder(r.Body)
	decoder.DisallowUnknownFields()
	err := decoder.Decode(&e)
	if err != nil {
		if errors.As(err, &unmarshalErr) {
			errorResponse(w, "Bad Request. Wrong Type provided for field "+unmarshalErr.Field, http.StatusBadRequest)
		} else {
			errorResponse(w, "Bad Request "+err.Error(), http.StatusBadRequest)
		}
		return
	}
	errorResponse(w, "Success", http.StatusOK)
	return
}

func errorResponse(w http.ResponseWriter, message string, httpStatusCode int) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(httpStatusCode)
	resp := make(map[string]string)
	resp["message"] = message
	jsonResp, _ := json.Marshal(resp)
	w.Write(jsonResp)
}

运行上述文件。这将触发一个服务器,监听端口 8080。在服务器运行后,让我们对我们讨论过的一些场景进行 API 调用。

  • 正确的请求
curl -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name":"John", "Age": 21}'

输出

Response Code: 200
Response Body: {"message":"Success"}
  • 传入的 JSON 中有一个额外的字段
curl -v -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name":"John", "Age": 21, "Gender": "M"}'

输出

Response Code: 400
Response Body: {"message":"Bad Request json: unknown field \"Gender\""}
  • 请求体不是有效的 JSON
curl -v -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name": "John", "Age":}'

输出

Response Code: 400
Response Body: {"message":"Bad Request invalid character '}' looking for beginning of value"}
  • 请求体为空
curl -v -X POST -H "content-type: application/json" http://localhost:8080/employee

输出

Response Code: 400
Response Body: {"message":"Bad Request EOF"}
  • 某个字段的类型与预期不同。例如,发送一个字符串值的年龄,而预期是 int
curl -v -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name":"John", "Age": "21"}'

输出

Response Code: 400
Response Body: {"message":"Bad Request. Wrong Type provided for field age"}
  • 未提供内容类型或内容类型不是 application/json
curl -X POST -H "content-type: application/json" http://localhost:8080/employee -d '{"Name":"John", "Age": 21}'

输出

Response Code: 415 Unsupported Media Type
Response Body: {"message":"Content Type is not application/json"}

获取当前运行/活动的 goroutine 数量

来源:golangbyexample.com/number-currently-running-active-goroutines/

NumGoroutine 函数来自 runtime 包,可用于了解当前运行/活动的 goroutine 数量。

golang.org/pkg/runtime/#NumGoroutine

以下是该函数的签名

func NumGoroutine() int

工作代码:

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    for i := 0; i < 20; i++ {
        go execute()
    }
    fmt.Println(runtime.NumGoroutine())
}

func execute() {
    time.Sleep(time.Second * 10)
}

输出:

21

在 Go(Golang)中获取或提取 URL 的查询参数。

来源:golangbyexample.com/query-params-url-golang/

目录

  • 概述

  • 程序

概述

URL 实例的查询函数可用于获取 URL 中存在的查询参数。

pkg.go.dev/net/url#URL.Query

请注意,在 Golang 中,查询参数表示如下。

pkg.go.dev/net/url#Values

这是

map[string][]string

这实际上意味着特定的查询参数键值可以有一个或多个值。

示例

http://localhost:8080/products?filters=color&filters=price&order=asc

在此 URL 中,filters键有两个值——colorprice,而order键只有一个值asc

程序

让我们来看一个工作示例。

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	input_url := "http://localhost:8080/products?filters=color&filters=price&order=asc"
	u, err := url.Parse(input_url)
	if err != nil {
		log.Fatal(err)
	}

	queryParams := u.Query()

	fmt.Println(queryParams)

	fmt.Println(queryParams["filters"])

	fmt.Println(queryParams["order"])

}

输出

map[filters:[color price] order:[asc]]
[color price]
[asc]

注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们努力涵盖所有概念及示例。本教程适合希望获得 Golang 专业知识和扎实理解的人——Golang 高级教程

如果你有兴趣了解如何在 Golang 中实现所有设计模式。如果是,那么这篇文章适合你——所有设计模式 Golang

在 Go (Golang) 中从传入的 HTTP 请求获取请求头

来源:golangbyexample.com/headers-http-request-golang/

注意:相关帖子

现在我们来看看如何在传入的 HTTP 请求中获取请求头

目录

  • 概述

  • 使用 Header.Values 方法

  • 使用 Header.Get 方法

  • 直接访问 Header 结构

  • 示例

概述

以下是 Go 中头部的表示格式。

type Header map[string][]string

因此,头部是一个键值对

  • 键以规范形式表示。规范形式意味着第一个字符和破折号后面的任何字符都为大写。其他所有字符均为小写。规范形式的示例包括
Content-Type
Accept-Encoding
  • 值表示为字符串切片。为什么是字符串数组?因为在请求中有两个具有相同键和不同值的头部是完全可以接受的。这两个值将被收集在切片中。

例如,如果在传入请求中有以下头部

content-type: applcation/json
foo: bar1
foo: bar2

然后在服务器上,头部看起来像

map[string][]string {
  "Content-Type": {"application/json"},
  "Foo": {"bar1" , "bar2"}
}

注意:

  • content-type 被转换为规范形式 Content-Type

  • content-type 头部表示为一个包含 1 个元素 application/json 的字符串数组

  • foo 头部表示为一个包含两个元素 bar1bar2 的字符串数组

现在我们已经看到请求中的头部是如何表示的。让我们看看如何根据键获取头部值。假设我们有以下头部的键值对

content-type: applcation/json
foo: bar1In the below example let's assume that variable r of type *http.Request type. Now let's see different ways of getting a header value.
foo: bar2

Headerhttp.Request 结构中如下所示。

type Request struct {
    Method string
    Header Header
    ...
}

在下面的示例中,假设变量 r 的类型为 *http.Request。现在让我们看看根据键获取头部值的不同方式

使用 Header.Values 方法

用于访问给定键的头部的所有值,使用 Values 方法。下面是该方法的格式

func (h Header) Values(key string) []string

注意返回类型是字符串切片。它将返回与给定头部键相关的所有值。同时,在获取值之前,给定的键会首先转换为规范形式。可以如下调用

r.Header.Values(key)

例如对于

  • r.Header.Values("content-type") 将输出 “application/json”

  • r.Header.Values(“foo”)将输出[“bar1”, “bar2”]

使用 Header.Get 方法

用于根据键访问头部的第一个值。这是最常用的方法,因为通常只有单个值与头部关联。下面是该方法的格式。

func (h Header) Get(key string) string

请注意,返回类型只是一个字符串。它将返回与给定键关联的第一个值。此外,给定键在获取值之前会先转换为规范形式。可以如下调用。

r.Header.Get(key)

例如

  • r.Header.Get(“content-type”)将输出“application/json”

  • r.Header.Get(“foo”)将输出“bar1”。只会返回第一个值。

直接访问 Header 结构

头部映射也可以直接访问,存在于*http.Request中。

例如

  • r.Header将输出一个映射
map[Accept:[*/*] Content-Type:[application/json] Foo:[bar1 bar2]
  • r.Header[“Content-Type”]将输出
[application/json]
  • r.Header[“Foo”]将输出
[bar1 bar2]

请注意,当直接访问头部映射时,我们需要仅以规范形式提供键。

示例

让我们看看一个演示上述所有要点的程序。

package main

import (
	"fmt"
	"net/http"
)

func main() {
	handler := http.HandlerFunc(handleRequest)
	http.Handle("/example", handler)
	http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {

	fooVAlues := r.Header.Values("foo")
	fmt.Printf("r.Header.Values(\"foo\"):: %s\n\n", fooVAlues)

	contentType := r.Header.Get("content-type")
	fmt.Printf("r.Header.Get(\"content-type\"):: %s\n\n", contentType)

	fooFirstValue := r.Header.Get("foo")
	fmt.Printf("r.Header.Get(\"foo\"):: %s\n\n", fooFirstValue)

	headers := r.Header
	fmt.Printf("r.Headers:: %s\n\n", headers)
	fmt.Printf("r.Headers[\"Content-Type\"]:: %s\n\n", headers["Content-Type"])
	fmt.Printf("r.Headers[\"Foo\"]:: %s", headers["Foo"])
}

在上述程序中,我们启动了一个监听在 8080 端口的服务器。我们还在该端点定义了一个 URL。运行此服务器并进行以下 API 调用。

curl -v -X POST http://localhost:8080/example -H "content-type: application/json" -H "foo: bar1" -H "foo: bar2"

运行此 API 后,请检查终端中的输出。它将输出。你可以检查输出,它与我们讨论的完全一致。

r.Header.Values("foo"):: [bar1 bar2]

r.Header.Get("content-type"):: application/json

r.Header.Get("foo"):: bar1

r.Headers:: map[Accept:[*/*] Content-Type:[application/json] Foo:[bar1 bar2] User-Agent:[curl/7.54.0]]

r.Headers["Content-Type"]:: [application/json]

r.Headers["Foo"]:: [bar1 bar2]

获取 Go(Golang)中发出的 HTTP 请求的响应头。

来源:golangbyexample.com/get-response-headers-making-go/

注意:相关帖子

现在让我们看看在进行 HTTP 请求时如何获取头部信息。

目录

  • 概述

  • 使用 Header.Values 方法

    • 使用 Header.Get 方法
  • 直接访问 Header 结构

  • 示例

概述

以下是 Go 中表示头部的格式。

type Header map[string][]string

所以头部是一个键值对,

  • 键以规范形式表示。规范形式意味着第一个字符和任何在连字符后面的字符都是大写。其他所有字符都是小写。规范形式的示例包括
Content-Type
Accept-Encoding

值表示为字符串切片。为什么是字符串数组?因为在请求中允许有两个相同键但不同值的头部。这两个值会被收集到切片中。

HTTP 请求中的响应由http.Response结构表示。

golang.org/src/net/http/response.go

http.Response结构中定义了一个Header字段,如下所示。它包含与响应相关的响应头。

type Response interface {
    Header() Header
    ...
}

在下面的示例中,假设变量w的类型是http.Response。现在让我们看看获取响应头的不同方法。此外,假设我们在发出的请求中目前有以下响应头。

User-Agent: application/json
Foo: bar1
Foo: bar2

使用 Header.Values 方法

用于访问给定键的头部的所有值,使用 Values 方法。下面是该方法的格式。

func (h Header) Values(key string) []string

请注意,返回类型是字符串切片。它将返回与给定头部键相关的所有值。此外,在获取值之前,给定键会先转换为规范形式。可以如下调用

w.Header.Values(key)

例如

  • w.Header.Values(“content-type”) 将输出 [“application/json” ]

  • w.Header.Values(“foo”) 将输出 [“bar1”, “bar2”]

使用 Header.Get 方法

用于根据给定键访问头部的第一个值。这是最常用的方法,因为通常与头部关联的值只有一个。以下是该方法的格式。

func (h Header) Get(key string) string

注意返回类型只是一个字符串。它将返回与给定键关联的第一个值。此外,在获取值之前,给定的键首先会转换为规范形式。可以如下调用:

w.Header.Get(key)

例如对于

  • w.Header.Get(“content-type”) 将输出 “application/json”

  • w.Header.Get(“foo”) 将输出 “bar1”。只会返回第一个值。

直接访问 Header 结构

Header 映射也可以直接访问,它存在于 http.Response 中。

例如

  • w.Header 将输出一个映射
map[Accept:[*/*] Content-Type:[application/json] Foo:[bar1 bar2]
  • w.Header[“Content-Type”] 将输出
[application/json]
  • w.Header[“Foo”] 将输出
[bar1 bar2]

请注意,直接访问 Header 映射时,我们需要仅以规范形式提供键。

示例

让我们看一个展示以上所有要点的程序

package main

import (
	"fmt"
	"net/http"
	"time"
)

func main() {
	call("https://google.com", "GET")
}

func call(url, method string) error {
	client := &http.Client{
		Timeout: time.Second * 10,
	}
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return fmt.Errorf("Got error %s", err.Error())
	}
	res, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("Got error %s", err.Error())
	}
	defer res.Body.Close()

	contentTypeValues := res.Header.Values("content-type")
	fmt.Printf("res.Header.Values(\"content-type\"):: %s\n\n", contentTypeValues)

	contentType := res.Header.Get("content-type")
	fmt.Printf("res.Header.Get(\"content-type\"):: %s\n\n", contentType)

	headers := res.Header
	fmt.Printf("res.Headers:: %s\n\n", headers)
	fmt.Printf("res.Headers[\"Content-Type\"]:: %s\n\n", headers["Content-Type"])
	return nil

}

运行上述程序后,检查终端中的输出。结果与我们讨论的完全一致。以下是输出:

res.Header.Values("content-type"):: 

res.Header.Get("content-type"):: text/html; charset=ISO-8859-1

res.Headers:: map[Alt-Svc:[h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"] Cache-Control:[private, max-age=0] Content-Type: Date:[Thu, 10 Dec 2020 16:38:03 GMT] Expires:[-1] P3p:[CP="This is not a P3P policy! See g.co/p3phelp for more info."] Server:[gws] Set-Cookie:[1P_JAR=2020-12-10-16; expires=Sat, 09-Jan-2021 16:38:03 GMT; path=/; domain=.google.com; Secure NID=204=w6zf-xFyVywx7QaClDZuQ5N-Yc-4HqKWBS-JXWp2Tat9kmq0BRsanM35PJHiM2iEn4TbP2HcTUd0KkIuMuIW7xFewD5un2_mc0O4fm2IXzrQyRmPWHJSeQJUUVb0-_lIfJgSnGmJm2MptRsd2egrPsbZJQBZWd97o7KlFBI3CIE; expires=Fri, 11-Jun-2021 16:38:03 GMT; path=/; domain=.google.com; HttpOnly] X-Frame-Options:[SAMEORIGIN] X-Xss-Protection:[0]]

res.Headers["Content-Type"]:: 

注意

res.Header.Values("content-type"):

输出一个数组

go`while res.Header.Get("content-type") go outputs text/html; charset=ISO-8859-1 go Also res.Headers go outputs the entire header map. Notice that each key is in the canonical format map[Alt-Svc:[h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"] Cache-Control:[private, max-age=0] Content-Type: Date:[Thu, 10 Dec 2020 16:38:03 GMT] Expires:[-1] P3p:[CP="This is not a P3P policy! See g.co/p3phelp for more info."] Server:[gws] Set-Cookie:[1P_JAR=2020-12-10-16; expires=Sat, 09-Jan-2021 16:38:03 GMT; path=/; domain=.google.com; Secure NID=204=w6zf-xFyVywx7QaClDZuQ5N-Yc-4HqKWBS-JXWp2Tat9kmq0BRsanM35PJHiM2iEn4TbP2HcTUd0KkIuMuIW7xFewD5un2_mc0O4fm2IXzrQyRmPWHJSeQJUUVb0-_lIfJgSnGmJm2MptRsd2egrPsbZJQBZWd97o7KlFBI3CIE; expires=Fri, 11-Jun-2021 16:38:03 GMT; path=/; domain=.google.com; HttpOnly] X-Frame-Options:[SAMEORIGIN] X-Xss-Protection:[0]] ```go * golang````*

从传入的 HTTP 请求中获取 IP 地址。

来源:golangbyexample.com/golang-ip-address-http-request/

目录

  • 概述

  • 代码

概述

在这篇文章中,我们将使用传入的 HTTP 请求获取客户端的 IP 地址。

  • X-REAL-IP 头部。

  • 如果 X-REAL-IP 为空,我们将退回到 X-FORWARDED-FOR 头部。

  • 如果 X-FORWARDED-FOR 为空,我们将退回到 http.Request 结构的 RemoteAddr

请注意,

  • X-REAL-IP 头部仅包含客户端机器的一个 IP 地址。一些代理服务器,如 Nginx,如果该头部为空,会根据请求之前遇到的可信代理填充此头部。同时请注意,客户端也可以轻易伪造该头部。

  • X-FORWARDED-FOR 是一个 IP 地址列表——代理链。将其视为请求跳跃日志。因此,如果请求源自 IP 为 ip1 的客户端,然后经过 IP 为 ip2 的代理服务器,再经过 IP 为 ip3 的负载均衡器,那么 X-FORWARDED-FOR 的值将是 “ip1,ip2,ip3”。因此,按 “,” 分割是个好主意。此外,请注意,客户端也可以轻易伪造它。只有在你控制设置该头部的代理时,才应该使用该头部。

  • RemoteAddr 包含客户端的真实 IP 地址。它是网络服务器接收连接的实际物理 IP 地址,响应将发送到该地址。但如果客户端通过代理连接,它将给出代理的 IP 地址。如果使用负载均衡器或反向代理服务器,则将提供它们的地址。RemoteAddr 代表 IP 端口组合。

有关 X-REAL-IPX-FORWARDED-FOR 头部的更多信息,请参考这篇文章。

distinctplace.com/2014/04/23/story-behind-x-forwarded-for-and-x-real-ip-headers/

在上述三个值中,RemoteAddr 是最可靠的,但如果客户端位于代理后面,或使用负载均衡器或反向代理服务器,它将永远无法提供正确的 IP 地址,因此顺序为 X-REAL-IP,然后是 X-FORWARDED-FOR,最后是 RemoteAddr。请注意,恶意用户仍然可以伪造 X-REAL-IPX-FORWARDED-FOR 头部。

代码

让我们看看在 GOLANG 中的工作代码。

package main

import (
    "fmt"
    "net"
    "net/http"
    "strings"
)

func main() {
    handlerFunc := http.HandlerFunc(handleRequest)
    http.Handle("/getIp", handlerFunc)
    http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
    ip, err := getIP(r)
    if err != nil {
        w.WriteHeader(400)
        w.Write([]byte("No valid ip"))
    }
    w.WriteHeader(200)
    w.Write([]byte(ip))
}

func getIP(r *http.Request) (string, error) {
    //Get IP from the X-REAL-IP header
    ip := r.Header.Get("X-REAL-IP")
    netIP := net.ParseIP(ip)
    if netIP != nil {
        return ip, nil
    }

    //Get IP from X-FORWARDED-FOR header
    ips := r.Header.Get("X-FORWARDED-FOR")
    splitIps := strings.Split(ips, ",")
    for _, ip := range splitIps {
        netIP := net.ParseIP(ip)
        if netIP != nil {
            return ip, nil
        }
    }

    //Get IP from RemoteAddr
    ip, _, err := net.SplitHostPort(r.RemoteAddr)
    if err != nil {
        return "", err
    }
    netIP = net.ParseIP(ip)
    if netIP != nil {
        return ip, nil
    }
    return "", fmt.Errorf("No valid ip found")
}

发起一个 curl 调用。

curl -v -X  GET http://localhost:8080/getIp

输出:

On my machine it outputs ::1 which is actually loopback address for IPV6

在你的机器上,它将输出 IPV4 的 127.0.0.1 或 IPV6 的 ::1。

en.wikipedia.org/wiki/Localhost

从错误或 Go(Golang)中的错误断言获取底层类型。

来源:golangbyexample.com/error-assertion-golang/

目录。

** 概述。

  • 代码

概述

获取底层类型有两种方式。

  • 使用.({type})断言。

如果断言成功,它将返回相应的错误,否则将引发 panic。以下是语法。

err := err.({type})

最好使用ok变量以防断言失败而导致 panic。以下是语法:如果错误的底层类型正确,ok变量将被设置为 true。

err, ok := err.({type})

使用errors包的As函数 - golang.org/pkg/errors/。使用As函数比使用.({type})断言更可取,因为它通过顺序解包第一个错误并在每一步解包时与目标错误进行匹配。以下是 Is 函数的语法。

func As(err error, target interface{}) bool

As函数将在第一个参数中找到第一个可以匹配目标的错误。一旦找到匹配项,它将把目标设置为该错误值。

代码

让我们来看一个例子。

package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {

	err := openFile("non-existing.txt")

	if e, ok := err.(*os.PathError); ok {
		fmt.Printf("Using Assert: Error e is of type path error. Path: %v\n", e.Path)
	} else {
		fmt.Println("Using Assert: Error not of type path error")
	}

	var pathError *os.PathError
	if errors.As(err, &pathError) {
		fmt.Printf("Using As function: Error e is of type path error. Path: %v\n", pathError.Path)
	}
}

func openFile(fileName string) error {
	_, err := os.Open("non-existing.txt")
	if err != nil {
		return err
	}
	return nil
}

输出:

Using Assert: Error e is of type path error. Path: non-existing.txt
Using As function: Error e is of type path error. Path: non-existing.txt

在上面的程序中,我们有一个 openFile 函数,其中我们尝试打开一个不存在的类型,因此会引发错误。然后我们以两种方式对错误进行断言。

  • 使用.assert 运算符。如果底层错误类型是*os.PathError,则 ok 变量将被设置为 true,否则将被设置为 false。
e,ok := err.(*os.PathError); ok
  • 使用 errors 包的As函数。
errors.As(err, &pathError)

两种方法都正确地断言错误是类型为os.PathError,因为openFile函数返回的错误是类型为os.PathError

我们上面提到,使用As函数比使用.({type})断言更可取,因为它通过顺序解包第一个错误并在每一步解包时与目标错误进行匹配。让我们看看一个例子来理解这一点。

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	var pathError *os.PathError
	err := openFile("non-existing.txt")

	if e, ok := err.(*os.PathError); ok {
		fmt.Printf("Using Assert: Error e is of type path error. Error: %v\n", e)
	} else {
		fmt.Println("Using Assert: Error not of type path error")
	}

	if errors.As(err, &pathError) {
		fmt.Printf("Using As function: Error e is of type path error. Error: %v\n", pathError)
	}
}

func openFile(fileName string) error {
	_, err := os.Open("non-existing.txt")
	if err != nil {
		return fmt.Errorf("Error opening: %w", err)
	}
	return nil
}

输出:

Using Assert: Error not of type path error
Using As function: Error e is of type path error. Error: open non-existing.txt: no such file or directory

上面的程序几乎与之前的程序相同,唯一的区别是我们在 openFile 函数中也包装了错误。

return fmt.Errorf("Error opening: %w", err)
  • .assert 的输出。
Using Assert: Error not of type path error
  • 当 As 函数输出时。
Using As function: Error e is of type path error. Error: open non-existing.txt: no such file or directory

这是因为openFile函数返回的错误包装了os.Patherror错误,而点(‘.’)断言没有捕获到该错误,但As*函数捕获到了。

Go(Golang)中的全局和局部常量。

来源:golangbyexample.com/global-local-constant-golang/

目录。

** 概述**。

  • 示例

概述

与其他变量一样,如果常量在文件顶部的任何函数范围外声明,则它在包内是全局的。例如,在下面的程序中,name 将是一个全局常量,可以在主包的任何函数中使用。请注意,const name 在主包外不可用。要使其在主包外可用,必须以大写字母开头。请查看下面的代码。它还显示了包内局部常量的示例。

示例

package main

import "fmt"

const name = "test"

func main() {
    const a = 8
    fmt.Println(a)
    testGlobal()
}

func testGlobal() {
    fmt.Println(name)
    //The below line will give compiler error as a is a local constant
    //fmt.Println(a)
}

输出

8
test

让我们看一个示例,尝试从不同的包访问全局常量。假设。

  • main.go 位于 $GOPATH/src/github.com/learn

  • data.go 位于 $GOPATH/src/github.com/learn/data

main.go

package main

import (
	"fmt"

	"github.com/learn/data"
)

func main() {
	val := data.PI
	fmt.Println(val)
}

data.go

package data

const PI = 3.142

前往 learn 目录并运行命令。

go run main.go

输出

3.142

在上述程序中,我们尝试从主包访问在 data.go 中定义的常量 PI。该程序正常工作,并成功打印出值,因为 const PI 以大写字母开头,因此它在其包外可见。

如果将常量名称从 PI 更改为 pi,则上述程序将引发编译错误。

cannot refer to unexported name data.pi
./main.go:11:7: undefined: data.pi

让我们看一个程序来说明这一点。

main.go

package main

import (
	"fmt"

	"github.com/learn/data"
)

func main() {
	val := data.pi
	fmt.Println(val)
}

data.go

package data

const pi = 3.142

前往 learn 目录并运行命令。

go run main.go

输出

cannot refer to unexported name data.pi
./main.go:11:7: undefined: data.pi

Go : 检查类型是否实现接口

来源:golangbyexample.com/go-check-if-type-implements-interface/

有时可能会出现需要知道你的类型是否满足某个接口的场景。这可以通过使用空标识符轻松实现。

package main

type shape interface {
    getNumSides() int
    getArea() int
}

type square struct {
    len int
}

func (s square) getNumSides() int {
    return 4
}

// func (s square) getArea() int {
//  return s.len * 2
// }

func main() {
    //Verify that *square implement shape
    var _ shape = square{}
}

在上面的程序中,我们注释掉了正方形的getArea()函数。在“go build”中,上述程序会给出错误

main.go:25:6: cannot use square literal (type square) as type shape in assignment:
        square does not implement shape (missing getArea method)

取消注释getArea()方法后,错误消失了。要检查该类型的指针是否实现接口,可以如下进行

var _ shape = &square{}

或者

var _ shape = (*square)(nil)

第一个会导致错误

main.go:25:6: cannot use (*square)(nil) (type *square) as type shape in assignment:
        *square does not implement shape (missing getArea method)

而后者会导致错误

main.go:25:6: cannot use &square literal (type *square) as type shape in assignment:
        *square does not implement shape (missing getArea method)

Go: 遍历数组和切片的不同方式

来源:golangbyexample.com/go-different-ways-iterating-array-slice/

Go 提供了许多遍历数组的不同方式。以下所有示例也适用于切片。

让我们先定义一个字母的数组

letters := []string{"a", "b", "c", "d", "e"}

目录

** 使用 range 操作符

  • 仅使用 For 操作符

使用 range 操作符

  • 带索引和值
for i, letter := range letters {
   fmt.Printf("%d %s\n", i, letter)
}
  • 仅值
for _, letter := range letters {
   fmt.Println(letter)
}
  • 仅索引
for i := range letters {
   fmt.Println(i)
}
  • 无值和索引。仅打印数组值
i := 0
for range letters {
  fmt.Println(i)
  i++
}

仅使用 For 操作符

  • 单一初始化和后置
len := len(letters)
for i := 0; i < len; i++ {
  fmt.Println(letters[i])
}
  • 多重初始化和后置语句
len := len(letters)
for i, j := 0, len; i < j; i, j = i+1, j-1 {
  fmt.Println(letters[i])
}

GO 安装

来源:golangbyexample.com/golang-installation/

这是 Golang 综合教程系列的第二章。请参考此链接了解系列的其他章节 – Golang 综合教程系列

下一个教程设置 GO 工作空间和 Hello World 程序

前一个教程关于 Golang

现在让我们查看当前的教程。以下是当前教程的目录。

目录

  • 概述

  • 在 MAC 上安装

    • 使用.pkg 安装程序

    • 使用归档

    • 使用 brew

  • 在 Linux 和 Free BSD 上安装

    • 下载归档
  • 在 Windows 上安装

    • 使用.msi 安装程序
  • 结论

概述

GO 可以在 Windows、Mac 和 Linux 平台上安装。你可以从 GO 的官方下载页面下载最新 GO 版本的二进制文件 – golang.org/dl/

GO 可以在 Windows、Mac 和 Linux 平台上安装。

在 MAC 上安装

GO 可以在 MAC 上通过三种方式安装。

  • 使用归档

  • 使用 brew

  • 使用.pkg 安装程序

让我们看看这三种方式。

使用.pkg 安装程序

安装

  • 从这里下载 MAC pkg 安装程序 – golang.org/dl/。双击.pkg 文件并按照屏幕上的说明操作。完成后,GO 将安装在以下目录中。
/usr/local/go
  • 安装程序还会将‘/usr/local/go/bin’添加到你的环境 PATH 变量中。这是 GO 二进制文件所在的目录。重新启动终端以使更改生效。

测试安装:

  • 重新启动终端并在终端中输入命令‘which go’。它会输出/usr/local/go/bin/go。这是 go 二进制文件的位置。

  • 尝试运行‘go version’命令。它会输出当前的 GO 版本。

卸载

卸载请执行以下两个步骤:

rm -rf /usr/local/go      //Will require sudo permission
rm -rf /etc/paths.do/go   //Will require sudo permission. This action deletes will remove /usr/local/go/bin from PATH env

使用归档

安装

  • 从这里下载最新版本 GO 的归档 – golang.org/dl/。下载后在/usr/local位置解压。你可以运行以下命令进行解压。
tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz
  • 解压后,以下路径将包含 go 二进制文件‘/usr/local/go/bin’。你必须将此位置添加到你的.bashrc。打开你的.bashrc并做以下条目。
export PATH=$PATH:/usr/local/go/bin

测试安装

  • 重新启动终端并在终端中输入命令 ‘which go’。它会输出 /usr/local/go/bin/go。 这是 GO 二进制文件的位置。

  • 尝试运行 ‘go version’ 命令。它会输出当前的 GO 版本。

  • 也尝试运行 ‘go’ 命令。它会输出

Go is a tool for managing Go source code.

Usage:

go <command></command> [arguments]
.....

卸载

要卸载,请执行以下两个步骤。

  • 运行以下命令以删除文件。这将需要 sudo 权限。
rm -rf /usr/local/go 
  • .bashrc 文件中删除以下条目。
export PATH=$PATH:/usr/local/go/bin

使用 brew

安装

在 MAC 上安装 GO 最简单的方法是使用 brew。

brew install go

测试安装

  • 重新启动终端并在终端中输入命令 ‘which go’。它会输出 /usr/local/go/bin/go. 这是 GO 二进制文件的位置。

  • 尝试运行 ‘go version’ 命令。它会输出当前的 GO 版本。

卸载

要卸载,只需运行命令。

brew uninstall go

在 Linux 和 Free BSD 上安装

下载归档文件

安装

  • 从这里下载最新版本的 GO 归档文件 – golang.org/dl/。下载后在 /usr/local 位置解压。你也可以运行以下命令进行解压。

  • 解压后,以下路径包含 GO 二进制文件 ‘/usr/local/go/bin’。你必须将此位置添加到你的 .bashrc 中。打开你的 .bashrc,添加以下条目。如果文件尚不存在,请创建它。

测试安装:

  • 重新启动终端并在终端中输入命令 ‘which go’。它会输出 /usr/local/go/bin/go. 这是 GO 二进制文件的位置。

  • 尝试运行 ‘go version’ 命令。它会输出当前的 GO 版本。

  • 也尝试运行 ‘go’ 命令。

卸载

要卸载,请执行以下两个步骤。

  • 运行以下命令以删除文件。这将需要 sudo 权限。

  • 从 .bashrc 文件中删除以下条目。

export PATH=$PATH:/usr/local/go/bin

在 Windows 上安装

使用 .msi 安装程序

安装

  • 从这里下载 .msi 安装程序 – golang.org/dl/。双击 .msi 文件并按照屏幕上的说明进行操作。完成后,GO 将安装在以下目录中。
c:\Go

  • 安装程序还会将 ‘c:\Go\bin’ 目录添加到你的 PATH 环境变量中。这是 GO 二进制文件所在的目录。你必须重新启动命令提示符以使更改生效。

测试安装

  • 尝试运行 ‘go version’ 命令。它会输出当前的 GO 版本。

  • 也尝试运行 ‘go’ 命令。

卸载

要卸载,请执行以下两个步骤。

结论

以上就是关于 golang 安装的所有内容。希望你喜欢这篇文章。请在评论中分享反馈或错误或改进意见。

下一个教程设置 Go 工作区和 hello world 程序

前一个教程关于 Golang

Go 日志轮换

来源:golangbyexample.com/go-logger-rotation/

本文讨论的是如何在 go 应用程序中轮换日志文件,而无需担心磁盘空间不足的警报。

我们将使用github.com/lestrrat/go-file-rotatelogs进行日志轮换。

示例:在下面的示例中,我们正在设置

  • MaxAge设置为 10 秒。这表示在日志文件被从文件系统中清除之前的最大年龄。也就是说,文件将在 10 秒后被删除。见
rotatelogs.WithMaxAge(time.Second*10)
  • RotationTime是 1 秒它设置了文件轮换的时间。所以文件将每秒轮换一次。见
rotatelogs.WithRotationTime(time.Second*1)
  • Location是/var/log/service/。见
path := "/var/log/service/"
  • 文件的Pattern是“old.UTC..2019-11-30.22:40:10”。见
 fmt.Sprintf("%s.%s", path, "%Y-%m-%d.%H:%M:%S")
  • Link Path是/var/log/service/current。这个文件将是实际文件的符号链接。见
rotatelogs.WithLinkName("/var/log/service/current")

代码:

package main

import (
    "fmt"
    "log"
    "time"
    rotatelogs "github.com/lestrrat/go-file-rotatelogs"
    "github.com/sirupsen/logrus"
)

func initiLogger() {
    path := "/var/log/service/old.UTC."
    writer, err := rotatelogs.New(
        fmt.Sprintf("%s.%s", path, "%Y-%m-%d.%H:%M:%S"),
        rotatelogs.WithLinkName("/var/log/service/current"),
        rotatelogs.WithMaxAge(time.Second*10),
        rotatelogs.WithRotationTime(time.Second*1),
    )
    if err != nil {
        log.Fatalf("Failed to Initialize Log File %s", err)
    }
    log.SetOutput(writer)
    return
}

func main() {
    initiLogger()
    for i := 0; i < 100; i++ {
        time.Sleep(time.Second * 1)
        log.Printf("Hello, World!")
    }
    fmt.Scanln()
}

前往位置/var/log/service/。你会注意到

  • 文件每秒轮换一次。

  • 文件的最大年龄是 10 秒。因此,每个文件将在 10 秒后被删除。

Golang:Redis 客户端示例

来源:golangbyexample.com/golang-redis-client-example/

首先,我们将编写 Redis 层,该层将具有一个初始化 Redis 客户端的功能。Redis 客户端只会被创建一次,并在整个过程中使用。在下面的代码中,初始化函数将初始化 Redis。

package main

import (
	"encoding/json"
	"time"
	"github.com/go-redis/redis"
)

var (
	client = &redisClient{}
)

type redisClient struct {
	c *redis.Client
}

//GetClient get the redis client
func initialize() *redisClient {
	c := redis.NewClient(&redis.Options{
		Addr: "127.0.0.1:6379",
	})

	if err := c.Ping().Err(); err != nil {
		panic("Unable to connect to redis " + err.Error())
	}
	client.c = c
	return client
}

//GetKey get key
func (client *redisClient) getKey(key string, src interface{}) error {
	val, err := client.c.Get(key).Result()
	if err == redis.Nil || err != nil {
		return err
	}
	err = json.Unmarshal([]byte(val), &src)
	if err != nil {
		return err
	}
	return nil
}

//SetKey set key
func (client *redisClient) setKey(key string, value interface{}, expiration time.Duration) error {
	cacheEntry, err := json.Marshal(value)
	if err != nil {
		return err
	}
	err = client.c.Set(key, cacheEntry, expiration).Err()
	if err != nil {
		return err
	}
	return nil
}

测试 Redis 客户端层

package main

import (
	"log"
	"time"
)

type valueEx struct {
	Name  string
	Email string
}

func main() {
	redisClient := initialize()
	key1 := "sampleKey"
	value1 := &valueEx{Name: "someName", Email: "someemail@abc.com"}
	err := redisClient.setKey(key1, value1, time.Minute*1)
	if err != nil {
		log.Fatalf("Error: %v", err.Error())
	}

	value2 := &valueEx{}
	err = redisClient.getKey(key1, value2)
	if err != nil {
		log.Fatalf("Error: %v", err.Error())
	}

	log.Printf("Name: %s", value2.Name)
	log.Printf("Email: %s", value2.Email)
} 

输出:

Name: someName
Email: someemail@abc.com 

Golang: Redis 集群客户端示例

来源:golangbyexample.com/golang-redis-cluster-client-example/

首先,我们将编写 Redis 集群层,该层将具有初始化 Redis 集群客户端的功能。Redis 集群客户端只会创建一次,并在整个过程中使用。在下面的代码中,初始化函数将初始化 Redis。

client.go

package main

import (
    "encoding/json"
    "strings"
    "time"
    "github.com/go-redis/redis"
)

var (
    client = &redisCluterClient{}
)

//RedisClusterClient struct
type redisCluterClient struct {
    c *redis.ClusterClient
}

//GetClient get the redis client
func initialize(hostnames string) *redisCluterClient {
    addr := strings.Split(hostnames, ",")
    c := redis.NewClusterClient(&redis.ClusterOptions{
        Addrs: addr,
    })
    if err := c.Ping().Err(); err != nil {
        panic("Unable to connect to redis " + err.Error())
    }
    client.c = c
    return client
}

//GetKey get key
func (client *redisCluterClient) getKey(key string, src interface{}) error {
    val, err := client.c.Get(key).Result()
    if err == redis.Nil || err != nil {
        return err
    }
    err = json.Unmarshal([]byte(val), &src)
    if err != nil {
        return err
    }
    return nil
}

//SetKey set key
func (client *redisCluterClient) setKey(key string, value interface{}, expiration time.Duration) error {
    cacheEntry, err := json.Marshal(value)
    if err != nil {
        return err
    }
    err = client.c.Set(key, cacheEntry, expiration).Err()
    if err != nil {
        return err
    }
    return nil
}

main.go

package main

import (
    "log"
    "time"
)

type valueEx struct {
    Name  string
    Email string
}

func main() {
    //Use your actually ip address here
    redisCluterClient := initialize("10.9.8.122:6379,10.9.8.123:6379,10.9.8.123:6379,")
    key1 := "sampleKey"
    value1 := &valueEx{Name: "someName", Email: "someemail@abc.com"}
    err := redisCluterClient.setKey(key1, value1, time.Minute*1)
    if err != nil {
        log.Fatalf("Error: %v", err.Error())
    }
    value2 := &valueEx{}
    err = redisCluterClient.getKey(key1, value2)
    if err != nil {
        log.Fatalf("Error: %v", err.Error())
    }
    log.Printf("Name: %s", value2.Name)
    log.Printf("Email: %s", value2.Email)
}

输出:

Name: someName 
Email: someemail@abc.com
posted @   绝不原创的飞龙  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示