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页面,其中包含/usersURL路径的用户表。
在本教程中,我们使用Go创建了简单的HTTP服务器。
列出所有Go教程。
