GoHTTP服务器教程展示了如何在Golang中创建简单的HTTP服务器。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
HTTP
超文本传输协议(HTTP)是分布式、协作、超媒体信息系统的应用协议。HTTP协议是万维网数据通信的基础。
去http
在Go中,我们使用http
包来创建GET和POST请求。该包提供了HTTP客户端和服务器实现。
Gohttp类型
客户端向服务器发送请求以接收资源。
type Request struct
Request
表示由服务器接收、由客户端发送的HTTP请求。
type Response struct
Response表示来自HTTP请求的响应。
type ResponseWriter interface
HTTP处理程序使用ResponseWriter
接口来构造HTTP响应。
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
Handler
响应HTTP请求。ServeHTTP
将回复标头和数据写入ResponseWriter
,然后返回。
type HandlerFunc func(ResponseWriter, *Request)
HandlerFunc
类型是一个适配器,它允许将普通函数用作HTTP处理程序。
去HTTP服务器句柄
Handle
函数为给定的URL注册一个处理程序。处理程序的目标是创建对客户端请求的回复。
package main import ( "fmt" "net/http" ) type CounterHandler struct { counter int } func (ct *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Println(ct.counter) ct.counter++ fmt.Fprintln(w, "Counter:", ct.counter) } func main() { th := &CounterHandler{counter: 0} http.Handle("/count", th) http.ListenAndServe(":8080", nil) }
每次访问URL时,计数器都会递增并返回值。
type CounterHandler struct { counter int } func (ct *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Println(ct.counter) ct.counter++ fmt.Fprintln(w, "Counter:", ct.counter) }
CounterHandler
包含counter
变量和ServeHTTP
函数的实现。该函数递增计数器并将消息写入http.ResponseWriter
。
th := &CounterHandler{counter: 0} http.Handle("/count", th)
CounterHandler
创建并注册到Handle
。
$ curl localhost:8080/count Counter: 1 $ curl localhost:8080/count Counter: 2 $ curl localhost:8080/count Counter: 3 $ curl localhost:8080/count Counter: 4
去HTTP服务器HandleFunc
使用HandleFunc
函数,我们为给定的URL模式注册一个处理函数。HandleFunc
函数是创建处理程序的便捷方式。
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", HelloHandler) log.Println("Listening...") log.Fatal(http.ListenAndServe(":8080", nil)) } func HelloHandler(w http.ResponseWriter, _ *http.Request) { fmt.Fprintf(w, "Hello, there\n") }
该示例创建了一个侦听端口8080的简单HTTP服务器。在发送请求后,服务器会以“Hello,there”消息进行响应。
http.HandleFunc("/", HelloHandler)
我们使用HandleFunc
将/
模式映射到HelloHandler
。
log.Fatal(http.ListenAndServe(":8080", nil))
ListenAndServe
侦听TCP网络地址,然后处理传入连接的请求。
func HelloHandler(w http.ResponseWriter, _ *http.Request) { fmt.Fprintf(w, "Hello, there\n") }
处理程序响应HTTP请求。它有两个参数:响应编写器和请求对象。
$ go run main.go
我们启动服务器。
$ curl localhost:8080/ Hello, there
使用curl
工具,我们生成一个请求。
GoHTTP状态码
HTTP响应状态代码指示特定HTTP请求是否已成功完成。
响应分为五类:
- 信息响应(100-199)
- 成功响应(200-299)
- 重定向(300-399)
- 客户端错误(400-499)
- 服务器错误(500-599)
响应的状态码是用WriteHeader
函数写入的。
package main import ( "log" "net/http" ) func main() { http.HandleFunc("/status", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) log.Println("Listening...") log.Fatal(http.ListenAndServe(":8080", nil)) }
我们为/status
路径发送http.StatusOK
。
$ curl -I localhost:8080/status HTTP/1.1 200 OK Date: Sat, 23 Apr 2022 12:59:52 GMT
GoHTTPServer未找到处理程序
如果找不到服务器资源,将向客户端返回404错误代码。
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/about", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, "about page") }) http.HandleFunc("/news", func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, "news page") }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { w.WriteHeader(404) w.Write([]byte("404 - not found\n")) return } fmt.Fprintln(w, "home page") }) log.Println("Listening...") log.Fatal(http.ListenAndServe(":8080", nil)) }
在示例中,我们有三个端点。对于这三个端点之外的任何其他端点,我们将返回404错误消息。
$ curl localhost:8080/about about page $ curl localhost:8080/ home page $ curl localhost:8080/contact 404 - not found
去HTTP服务器获取标头
HTTP标头让客户端和服务器通过HTTP请求或响应传递附加信息。HTTP标头是一个名称/值对,由冒号字符分隔。
User-Agent请求标头是一个字符串,可让服务器和网络对等方识别请求用户代理的应用程序、操作系统、供应商和/或版本。
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/ua", func(w http.ResponseWriter, r *http.Request) { ua := r.Header.Get("User-Agent") fmt.Fprintf(w, "User agent: %s\n", ua) }) log.Println("Listening...") log.Fatal(http.ListenAndServe(":8080", nil)) }
从Header
字段中,我们获取User-Agent
标头并将其返回给调用者。
package main import ( "fmt" "io/ioutil" "log" "net/http" ) func main() { resp, err := http.Get("http://localhost:8080/ua") if err != nil { log.Fatal(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } fmt.Println(string(body)) }
该示例在Go中创建了一个简单的客户端,它生成一个GET请求到/ua
路径。
GoURL路径参数
我们可以在URL中向服务器发送数据。
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", HelloServer) fmt.Println("Server started at port 8080") log.Fatal(http.ListenAndServe(":8080", nil)) } func HelloServer(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s!\n", r.URL.Path[1:]) }
在代码示例中,我们使用r.URL.Path[1:]
获取URL路径值,并使用数据构建消息。
$ curl localhost:8080/John Hello, John!
我们在URL路径中发送名称;服务器回复问候语。
Go查询参数
查询字符串是统一资源定位符(URL)的一部分,它为指定的参数赋值。
通用URL具有以下形式:
scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
查询参数以?
字符开头。多个查询参数用&
字符分隔。
https://example.com/path/page?name=John&occupation=teacher
这是一个带有两个查询参数的URL示例。
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/", handler) log.Println("Listening...") log.Fatal(http.ListenAndServe(":8080", nil)) } func handler(w http.ResponseWriter, r *http.Request) { keys, ok := r.URL.Query()["name"] name := "guest" if ok { name = keys[0] } fmt.Fprintf(w, "Hello %s!\n", name) }
在代码示例中,我们接受一个name
参数。我们使用r.URL.Query()["name"]
获取参数。
$ curl localhost:8080/?name=Peter Hello Peter!
我们将名称作为查询参数发送;服务器响应消息。
Go文件服务器
使用http.FileServer
,我们将文件发送到客户端。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>About page</title> </head> <body> <p> About page </p> </body> </html>
这是about.html
页面。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Home page</title> </head> <body> <p> Home page </p> </body> </html>
这是主页。
package main import ( "fmt" "io" "log" "net/http" ) func main() { fileServer := http.FileServer(http.Dir("./public")) http.Handle("/", fileServer) http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "Hello there!\n") }) log.Println("Listening...") log.Fatal(http.ListenAndServe(":8080", nil)) }
在代码示例中,我们有一个文件服务器和一个简单的hello处理程序。
fileServer := http.FileServer(http.Dir("./public")) http.Handle("/", fileServer)
文件服务器已注册到Handle
;它提供来自public
目录的文件。
$ curl localhost:8080 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Home page</title> </head> <body> <p> Home page </p> </body> </html> $ curl localhost:8080/about.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>About page</title> </head> <body> <p> About page </p> </body> </html>
我们请求主页和关于页面。
去处理GET/POST请求
在下面的示例中,服务器处理来自客户端的GET和POST请求。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Form</title> </head> <body> <form method="POST" action="/"> <div> <label>Name:</label><input name="name" type="text"> </div> <div> <label>Occupation:</label><input name="occupation" type="text"> </div> <button type="submit" value="submit">Submit</button> </form> </body> </html>
HTML页面呈现一个简单的表单。
package main import ( "fmt" "log" "net/http" ) func process(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.Error(w, "404 not found.", http.StatusNotFound) return } switch r.Method { case "GET": http.ServeFile(w, r, "form.html") case "POST": if err := r.ParseForm(); err != nil { fmt.Fprintf(w, "ParseForm() err: %v", err) return } name := r.FormValue("name") occupation := r.FormValue("occupation") fmt.Fprintf(w, "%s is a %s\n", name, occupation) default: fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.") } } func main() { http.HandleFunc("/", process) log.Println("Listening...") log.Fatal(http.ListenAndServe(":8080", nil)) }
对于GET请求,我们发送带有表单的网页。对于POST请求,我们处理来自表单的数据。
case "GET": http.ServeFile(w, r, "form.html")
如果请求是GET请求,我们将form.html
发送给客户端。
case "POST": if err := r.ParseForm(); err != nil { fmt.Fprintf(w, "ParseForm() err: %v", err) return }
如果是POST请求,我们调用ParseForm
函数;它解析来自URL的原始查询并更新r.Form
。
name := r.FormValue("name") occupation := r.FormValue("occupation") fmt.Fprintf(w, "%s is a %s\n", name, occupation)
我们使用FormValue
获取表单值并构建一条消息。
转到HTTP服务图像
在下面的示例中,我们提供一张图片。
package main import ( "fmt" "io/ioutil" "log" "net/http" ) func main() { handler := http.HandlerFunc(handleRequest) http.Handle("/image", handler) fmt.Println("Server started at port 8080") http.ListenAndServe(":8080", nil) } func handleRequest(w http.ResponseWriter, r *http.Request) { buf, err := ioutil.ReadFile("sid.png") if err != nil { log.Fatal(err) } w.Header().Set("Content-Type", "image/png") w.Write(buf) }
在代码示例中,我们创建了一个向客户端发送图像的简单Web服务器。该图像位于当前工作目录中。
handler := http.HandlerFunc(handleRequest) http.Handle("/image", handler)
我们将处理程序映射到/image
路径。
func handleRequest(w http.ResponseWriter, r *http.Request) { ...
处理函数接受两个参数:http.ResponseWriter
和http.Request
。
buf, err := ioutil.ReadFile("sid.png")
我们将图像读入缓冲区。
w.Header().Set("Content-Type", "image/png")
我们设置标题。Content-Type
内容类型用于PNGimage。
w.Write(buf)
图像数据通过Write
写入响应体。
GoHTTP服务器模板
Go有一个内置的模板包,用于生成动态HTML内容。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Users</title> </head> <body> <table> <thead> <tr> <th>Name</th> <th>Occupation</th> </tr> </thead> <tbody> {{ range .Users}} <tr> <td>{{.Name}}</td> <td>{{.Occupation}}</td> </tr> {{ end }} </tbody> </table> </body> </html>
输出是一个HTML文件。数据被插入到HTML表格中。
package main import ( "html/template" "log" "net/http" ) type User struct { Name string Occupation string } type Data struct { Users []User } func main() { tmp := template.Must(template.ParseFiles("layout.html")) http.HandleFunc("/users", func(w http.ResponseWriter, _ *http.Request) { data := Data{ Users: []User{ {Name: "John Doe", Occupation: "gardener"}, {Name: "Roger Roe", Occupation: "driver"}, {Name: "Peter Smith", Occupation: "teacher"}, }, } tmp.Execute(w, data) }) log.Println("Listening...") http.ListenAndServe(":8080", nil) }
Web服务器返回一个HTML页面,其中包含/users
URL路径的用户表。
在本教程中,我们使用Go创建了简单的HTTP服务器。
列出所有Go教程。