所谓Web开发,也就是我们通常说的网站后端开发。与其他语言相比,Go的Web开发具有简单易学,并发效率高,原生标准库支持等特点。即使是Python Web开发,也没有Go的简单。
学习Go的Web,是可以不需要安装任何第三方库的,标准库即支持,且底层已经使用Go协程封装了并发请求,因此Go不需要任何所谓的服务器容器的软件,例如Java开发需要Tomcat服务器,Python需要Gunicorn,uWSGI之类的服务器,而Go语言,直接上手撸API即可,可以说Go语言是为Web而生的,最适合后端开发。
学习Web开发,应当具备HTTP协议的基础,请先阅读另一篇文章 Web基础(一) HTTP详解[1]
package main
import (
"fmt"
"net/http"
)
//定义一个名为 handler 的处理器函数
func handler(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintf(writer, "Hello, %s!", request.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
http://localhost:8080/Mary
这时浏览器显示Hello, Mary!
handler
函数:事件被触发之后,负责对事件进行处理的回调函数。该处理器函数接受两个参数,第一个参数类型为ResponseWriter
接口 , 第二个参数为指向Request
结构的指针。
handler
函数会从 Request 结构中提取相关的信息,然后创建一个HTTP响应, 最后再通过ResponseWriter
接口将响应返回给客户端。
handler
函数中的Fprintf
函数在被调用时,需要传入一个ResponseWriter
接口实例 ,第二个参数是带有格式化占位符%s
的字符串,第三参数就是占位符需要替换的内容,这里则是将Request
结构里带的URL路径截取后作为参数
流程图
示例代码
package main
import (
"net/http"
"time"
"fmt"
)
func main() {
fmt.Println("*** 服务器启动,监听端口:8080 ***")
mux := http.NewServeMux()
// 处理静态资源文件
files := http.FileServer(http.Dir("./public"))
mux.Handle("/static/", http.StripPrefix("/static/", files))
mux.HandleFunc("/", index)
// 配置服务器
server := &http.Server{
Addr: "0.0.0.0:8080",
Handler: mux, // 设置多路复用器
ReadTimeout: time.Duration(10 * int64(time.Second)),
WriteTimeout: time.Duration(200 * int64(time.Second)),
MaxHeaderBytes: 1 << 20, // 左移运算,等同:1*2^20,高性能乘法运算
}
server.ListenAndServe()
}
func index(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "Hello, world!")
}
public
文件夹public
中分别创建home.html
、note.txt
文件<html>
<head>
<title>
这是主页
</title>
</head>
<body style="background:Pink">
<h1>Home</h1>
<p style="color:red">
Welcome to the Go homepage!
</p>
</body>
</html>
现在,让我们在浏览器分别访问http://127.0.0.1:8080/
、http://127.0.0.1:8080/static/home.html
和http://127.0.0.1:8080/static/note.txt
可以发现,http://127.0.0.1:8080/static/home.html
和http://127.0.0.1:8080/static/note.txt
的结果是不一样的,这是因为浏览器能识别html
标记语言,关于前端html
标记语言本文不做说明,请自行学习前端相关知识。
通过
NewServeMux
函数来创建一个默认的多路复用器,调用HandleFunc
函数将发送至根URL的请求重定向到对应的处理器。因为所有处理器都接受一个ResponseWriter
实例和一个指向Request
结构的指针作为参数,并且所有请求参数都可以通过访问Request
结构得到,所以程序并不需要向处理器显式地传入任何请求参数。
多路复用器主要负责接收 HTTP 请求,并根据请求中的 URL 将请求重定向到正确的处理器。注意,所有引入了 net/http
标准库的程序都可以使用一个默认的多路复用器实例,当没有为 Server
结构指定处理器时,服务器就会使用 DefaultServeMux
。
实际上,所谓多路复用器,也就是我们在开发中常说的路由的概念,根据不同的URL,调用不同的函数去处理。
使用
FileServer
函数创建了一个处理器,它能够处理指定目录中的静态文件。最后将这个处理器传递给多路复用器的Handle
函数
如示例代码,当服务器接收到一个以/static/
开头的 URL 请求时,以上将URL中的/static/
路径映射到public
目录中,然后查找被请求的文件。
例如,当服务器接收到一个针对文件 http://127.0.0.1:8080/static/note.txt
的请求时,它将会在public
目录中查找note.txt
文件。这样做的好处是可以将服务器上的真实文件目录隐藏。
处理器函数实际上就是一个接受
ResponseWriter
和Request
指针作为参数的 Go 函数。通常该函数负责生成HTML内容并将其写人ResponseWriter
中。
func index(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "Hello, world!")
}
除了可以通过
ListenAndServe
的参数对服务器的网络地址和处理器进行配置之外,还可以通过 Server 结构对服务器进行更详细的配置,如上面的例子,其中包括为请求读取操作设置超时时间、为响应写入操作设置超时时间,以及为 Server 结构设置错误日志记录器等
最简配置
package main
import (
"net/http"
)
func main() {
server := http.Server{
Addr : "127.0.0.1:8080" ,
Handler : nil,
}
server.ListenAndServe()
}
Server
结构详细字段
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
TLSConfig *tls.Config // optional TLS config, used by ServeTLS and ListenAndServeTLS
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
inShutdown int32 // accessed atomically (non-zero means we're in Shutdown)
nextProtoOnce sync.Once // guards setupHTTP2_* init
nextProtoErr error // result of http2.ConfigureServer if used
mu sync.Mutex
listeners map[net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
处理器 是一个拥有
ServeHTTP
方法的接口,这个ServeHTTP
方法需要接受两个参数,第一个参数是一个ResponseWriter
接口实例,而第二个参数则是一个指向Request
结构的指针。即任何拥有ServeHTTP
方法的接口,且该方法的签名为ServeHTTP(http.ResponseWriter, *http.Request)
,那么这个接口就是一个处理器。 处理器函数 实际上是与处理器拥有相同行为的函数,这个函数与ServeHTTP
方法拥有相同的签名,即接受ResponseWriter
和指向Request
结构的指针作为参数。通过使用HandlerFunc
可以把一个带有正确签名的函数f
转换成一个带有方法f
的处理器实例,这个方法会与DefaultServeMux
进行绑定。因此,处理器函数只不过是创建处理器的一种便利方法而已。
虽然处理器函数能够完成跟处理器一样的工作,并且使用处理器函数的代码比使用处理器的代码更为简洁,但是处理器函数并不能完全代替处理器。这是因为在某些情况下,代码可能已经包含了某个接口或者某种类型,这时我们只需要为它们添加 ServeHTTP
方法就可以将它们转变为处理器,这种转变更灵活也有助于构建出更为模块化的 Web 应用。
package main
import (
"fmt"
"net/http"
)
// 声明一个结构体
type MyHandler struct{
}
// 让结构体实现 ServeHTTP 函数
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
func main() {
handler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &handler, //指定处理器
}
server.ListenAndServe()
}
不用在
Server
结构的Handler
字段中指定处理器,而是让服务器使用默认的DefaultServeMux
作为处理器, 然后通过http.Handle
函数将处理器绑定至DefaultServeMux
。
需要说明的是,虽然Handle
函数来源于http
包,但它实际上是ServeMux
结构的方法,这些函数是为了操作便利而创建的函数,调用它们等同于调用DefaultServeMux
的方法。比如调用http.Handle
实际上就是在调用DefaultServeMux
的Handle
方法
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct{}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}
type WorldHandler struct{}
func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "World!")
}
func main() {
hello := HelloHandler{}
world := WorldHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.Handle("/hello", &hello)
http.Handle("/world", &world)
server.ListenAndServe()
}
ServeMux
是一个HTTP请求多路复用器,它负责接收HTTP请求并根据请求中的 URL将请求重定向到正确的处理器。ServeMux
结构包含了一个映射,这个映射会将URL映射至相应的处理器。因为ServeMux
结构也实现了ServeHTTP
方法,所以它也是一个处理器
在这里插入图片描述
ServeMux
是一个结构体而不是一个接口,因此DefaultServeMux
是ServeMux
的一个实例而不ServeMux
的实现。
前面提到的http.ListenAndServe(":8080", nil)
函数接受的第二个参数是一个处理器,但它的默认值却是一个多路复用器DefaultServeMux
,这是因为DefaultServeMux
多路复用器是ServeMux
结构的一个实例,而ServeMux
也拥有ServeHTTP
方法,也就是说DefaultServeMux
既是ServeMux
结构的实例,也是Handler
结构的实例。
ServeMux
的一个缺陷是无法使用变量实现URL模式匹配。例如在浏览器请求/threads
的时候,ServeMux
可以很好地获取并显示所有帖子,但当浏览器请求的是/threads/123
时,那么要获取并显示ID为123的帖子就会变得非常困难。
创建自定义的多路复用器来代替net/http
包中的ServeMux
是可行的,并且目前市面上已经出现了很多第三方的多路复用器可供使用,而HttpRouter
就是一个功能强大的轻量级第三方多路复用器。
执行以下命令安装 HttpRouter
go get github.corn/julienschrnidt/httprouter
这表示从GitHub上下载HttpRouter包源码,并将其保存到GOPATH/src目录中
使用示例
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", p.ByName("name"))
}
func main() {
//创建多路复用器
mux := httprouter.New()
//将处理器函数与给定的HTTP方法进行绑定
mux.GET("/hello/:name", hello)
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
}
server.ListenAndServe()
}
以上代码不再使用HandleFunc
绑定处理器函数,而是直接把处理器函数与给定的 HTTP方法进行绑定。当浏览器向这个URL发送GET请求时,hello
函数就会被调用,但当浏览器向这个URL发送其他请求时,hello
函数不会被调用。
可以看到被绑定的URL包含了具名参数(named parameter),这些具名参数会被 URL中的具体值所代替,并且程序可以在处理器里面获取这些值。此时的处理器函数hello
接收三个参数,而第三个参数Params
就包含了具名参数,其值可以 通过ByName
方法获取。
如,运行程序后,浏览器输入localhost:8080/hello/fox
,则显示 hello,fox,p.ByName
成功获取到URL中的fox字段。
编程之路从0到1
[1]
Web基础(一) HTTP详解: https://blog.csdn.net/yingshukun/article/details/83863152