GoWeb学习笔记
学习的资料来自杨旭老师在B站的视频
文章目录
创建第一个Web程序(网页输出HelloWorld)
在go语言中创建一个网页中输出的HelloWorld,需要先创建一个访问的函数,然后指定相应的监听和服务
HadleFunc源码
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
使用HadleFunc,并创建内置函数的形式创建访问函数
//HandleFunc一共有两个参数,第一个参数是访问路径,第二个参数是路由函数
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("Hello World!"))
})
使用HadleFunc,并调用外部函数的形式创建访问函数
//自定义的外部函数
func MyHandleFunc(w http.ResponseWriter,r *http.Request){
w.Write([]byte("MyHandleFunc"))
}
func main(){
//调用函数
http.HandleFunc("/myHandleFunc",MyHandleFunc)
}
创建访问监听和服务
创建访问监听和服务有两种方式,一个是调用http.ListenAndServe方法,需要配置两个参数,另一个是调用http.Server,这种方式需要自定http.Server提供的参数,相对于http.ListenAndServe,这种方式更加灵活
http.ListenAndServe源码
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
http.Server源码(去除了源码中的注释)
type Server struct {
Addr string
Handler Handler
TLSConfig *tls.Config
ReadTimeout time.Duration
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
BaseContext func(net.Listener) context.Context
ConnContext func(ctx context.Context, c net.Conn) context.Context
inShutdown atomicBool
disableKeepAlives int32
nextProtoOnce sync.Once
nextProtoErr error
mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
使用http.ListenAndServe创建监听和服务
//使用nil相当与使用了go语言内置的http.DefaultServeMux(多路复用器)
//需要传入两个参数,分别是访问的地址和使用的访问函数
//当使用localhost的时候可以写为http.ListenAndServe(":8080",nil)
http.ListenAndServe("localhost:8080",nil)
使用http.Server创建监听和服务
//等同于http.ListenAndServe,但是使用这种方式配置更加灵活,因为可以设置更多参数值
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}
server.ListenAndServe()
整合实现
直接创建
//访问路径为localhost:8080
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("Hello World!"))
})
http.ListenAndServe("localhost:8080",nil)
通过外部函数和使用http.Server实现
//为了方便,写了一个监听的函数
func Listen(){
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}
server.ListenAndServe()
}
//自定义的访问函数
func MyHandleFunc(w http.ResponseWriter,r *http.Request){
//因为Write()函数的源码为Write([]byte) (int, error),所以在输出string类型的时候需要转换
w.Write([]byte("Hello World"))
}
func main(){
http.HandleFunc("/myHandleFunc",MyHandleFunc)
Listen()
}
http.Handle源码
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
使用http.Handle创建一个Hello World
有http.Handle的源码可以看出传入的第二个参数为Handler,源码如下:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler是一个ServeHTTP类型的Type,所以传入Handle的第二个参数也得是ServeHTTP类型的访问函数
//自定义路由
type HelloHandler struct {}
//自定义路由
type AboutHandler struct {}
//定义访问控制器
func (m *HelloHandler) ServeHTTP(w http.ResponseWriter ,r *http.Request) {
w.Write([]byte("Hello World!"))
}
//定义访问控制器
func (m *AboutHandler) ServeHTTP(w http.ResponseWriter ,r *http.Request) {
w.Write([]byte("About!"))
}
func MyListen(){
mh := HelloHandler{}
ma := AboutHandler{}
server := http.Server{
Addr: ":8080",
//不指定访问路由器,从而达成通过访问不同的路由参数而访问不同的自定义路由器
Handler: nil,
}
http.Handle("/hello",&mh)
http.Handle("/about",&ma)
server.ListenAndServe()
}
注意
在上面使用Handle函数的时候是使用创建好的ServeHttp,我们也可以使用HandlerFunc来将创建的没有继承ServeHTTP的 路由转化
//使用HandlerFunc将自定义的访问控制转换为一个Handler
//HandlerFunc可以将某个具有适当签名的函数f适配成为一个Handler
http.Handle("/my", http.HandlerFunc(MyHandleFunc))
func MyHandleFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("MyHandleFunc"))
}
Go语言的五个内置Handler
1.NotFoundHandler
返回一个handler,每个请求的相应都是404 page not found
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
2.RedirectHandler
返回一个handler,把每一个请求按照状态码跳转到指定的URL
常见的:StatusMovedPermanently、StatusFound、StatusSeeOther
func RedirectHandler(url string, code int) Handler {
return &redirectHandler{url, code}
}
3.StripPrefix
去前缀,返回一个handler,在指定的url中去掉前缀,然后调用另一个handler
func StripPrefix(prefix string, h Handler) Handler {
if prefix == "" {
return h
}
return HandlerFunc(func(w ResponseWriter, r *Request) {
if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
r2 := new(Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p
h.ServeHTTP(w, r2)
} else {
NotFound(w, r)
}
})
}
4.TimeoutHandler
返回一个handler,在指定时间内运行传入的handler
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {
return &timeoutHandler{
handler: h,
body: msg,
dt: dt,
}
}
5.FileServer
返回一个handler,使用基于root的文件系统来相应请求
func FileServer(root FileSystem) Handler {
return &fileHandler{root}
}
type FileSystem interface {
Open(name string) (File, error)
}
在使用时需要用到操作系统的文件系统,所以一般交给下面的函数来用
type Dir string
func (d Dir) Open(name string) (File, error) {
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
return nil, errors.New("http: invalid character in file path")
}
dir := string(d)
if dir == "" {
dir = "."
}
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
f, err := os.Open(fullName)
if err != nil {
return nil, mapDirOpenError(err, fullName)
}
return f, nil
}
HTTP消息
HTTP Request 和HTTP Response,他俩具有相同的结构,都有请求行,0个或者多个url,空行以及可选的消息体(body)
Request(请求)
在GO语言中Request是一个struct,代表了客户端发送的HTTP请求消息(既可以代表客户端的请求,也可以代表服务端的请求),可以通过Request的方法访问请求中的Cookie、URL、User Agent等信息,源码如下:
type Request struct {
Method string
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
Header Header
Body io.ReadCloser
GetBody func() (io.ReadCloser, error)
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chan struct{}
Response *Response
ctx context.Context
}
其中几个重要的字段
1.URL
Request的URL字段就代表了请求行(请求信息第一行)里面的部分内容,URL字段是指向url.URL类型的一个指针,url.URL是一个struct源码如下:
type URL struct {
Scheme string
Opaque string
User *Userinfo
Host string
Path string
RawPath string
ForceQuery bool
RawQuery string
Fragment string
RawFragment string
}
URL的通用格式为:
scheme://[userinfo@]host/path[?query][#fragment]
不以斜杠开头的URL被解释为:
scheme:opaque[?query][#fragment]
URL Query
- RawQuery提供实际查询的字符串
- 通过Request的Form字段
r.URL.Query()
该方法会提供查询字符串对应的map[string] [] string
URL Fragment
就是URL格式中的#后面的部分
当请求从浏览器发出时,无法获取到Fragment的值,因为在浏览器发送请求的时候会把Fragment去掉
部分客户端工具发出的请求可以获取到Fragment的值,例如HTTP客户端包
2.Handler
请求和相应的headers是通过Header类型来描述的,它是一个map类型,用来描述HTTP header里的Key-Value对。
Header map的key是string类型,value是[]string
设置key时会创建一个空的[]string作为value,value里面第一个元素就是新的header的值
如果是为指定的key添加一个新的header值的话,执行append操作即可
3.Body
请求和相应的bodies都是使用Body字段来表示的
Body是一个io.ReadCloser接口,一个Reader接口和一个Closer接口
Reader接口定义了一个Open方法,参数[]byte,返回byte的数量、可选的错误
Closer接口定义了一个Close方法:没有参数,返回可选的错误
读取body的内容,调用Body的Read方法
func getQuery(){
http.HandleFunc("/query", func(writer http.ResponseWriter, request *http.Request) {
//获取URL
url := request.URL
//调用query方法
query := url.Query()
//根据传入的key值查询相应的数据,返回全部值
id := query["id"]
//以日志的形式打印在控制台
log.Println(id)
//根据传入的key值返回第一个值
name := query.Get("name")
log.Println(name)
})
}
4.Form、PostForm、MultipartForm
通过表单发送post请求
<form action="/index" method="post">
用户名<input type="text" name="name" />
密码<input type="password" name="password" />
<input type="submit" />
</form>
action是发送请求对应的服务器路径,method是发送请求的方式,有post和get两种,html表单里的数据会以name-value对的方式通过post请求发送出去。
name-value
通过psot发送的name-value数据对的格式通过表单的Content Type来指定,也就是表单里面的enctype属性,在form表单中enctype的默认属性为application/x-www-form-urlencoded。
1.application/x-www-form-urlencoded
在这个属性下,浏览器将会将表单数据编码到查询字符串里面,简单的文本格式使用这种方式
2.multipart/form-data
在这种属性下每一个name-value对都会被转化为一个mime消息部分
每一个部分都有自己的Content Type和Content Disposition,在上传文件的时候选用这个方式
3.text/plain
POST & GET
表单的method属性可以设置post和get两种属性
1.GET
get请求没有body,所有的数据都通过URL的name-value对来发送
2.POST
FORM字段
Resquest上的函数允许我们通过url或/和body中提取数据,form里面的数据是key-value对
通常的做法是先调用ParseForm或ParseMultipartForm来解析Request,然后相应的访问Form、PostForm或MultipartForm字段
PostForm
PostForm只支持application/x-www-form-urlencoded,
func getForm(){
http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {
request.ParseForm()
//输出到页面
fmt.Fprintln(writer,request.Form)
//以日志的形式打印到控制台
log.Println(request.Form)
})
}
前端代码:
<form action="http://localhost:8080/process" method="post" >
用户名<input type="text" name="name" />
密码<input type="password" name="password" />
<input type="submit" />
</form>
MultipartForm
使用MultipartForm的时候需要先调用ParseMultipartForm,ParseMultipartForm会在必要的时候调用ParseForm,里面需要传入一个参数(读取的数据长度,单位为字节),MultipartForm只包含表单的key-value对,返回类型是一个struct而不是map,这个struct里面包含两个map,一个是你表单里面的数据,另一个是文件
func getMultipart(){
http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {
//参数为字节,是上传数据的长度
request.ParseMultipartForm(1024)
fmt.Fprintln(writer,request.MultipartForm)
log.Println(request.MultipartForm)
})
}
FormValue&PostFormValue
FormValue方法会返回form字段指定key对应的第一个value,无需调用ParseForm和ParseMultipartForm
PostFormValue方法只能读取PostForm
这两种该方法都会调用ParseMultipartForm
当你表单的enctype设置为multipart/form-data的时候,上面两种方法无法获取到表单的数据
上传文件
首先form里面的enctype类型要设置为multipart/form-data
在GO语言中处理上传文件的时候:
1.调用ParseMultiparForm方法
2.从file字段获得FileHeadler,调用Open方法来获得文件
3.可以使用ioutil.ReadAll函数将文件内容读取到byte切片里
func getFile(){
http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {
request.ParseMultipartForm(1024)
//因为file是一个map,也就是允许多个文件上传,这里可以指定获取那个文件,0代表第一个
fileHeader := request.MultipartForm.File["uploaded"][0]
file,err := fileHeader.Open()
if err == nil {
data,err := ioutil.ReadAll(file)
if err != nil {
fmt.Println(err)
}
fmt.Fprintln(writer,string(data))
}
})
}
方法2
func getFile2(){
http.HandleFunc("/process", func(writer http.ResponseWriter, request *http.Request) {
//返回第一个文件,当只上传一个文件的时候,这种方式更快
file,_,err := request.FormFile("uploaded")
if err == nil {
data,err := ioutil.ReadAll(file)
if err != nil {
fmt.Println(err)
}
fmt.Fprintln(writer,string(data))
}
})
}
MultipartReader()
源码如下:
func (r *Request) MultipartReader() (*multipart.Reader, error) {
if r.MultipartForm == multipartByReader {
return nil, errors.New("http: MultipartReader called twice")
}
if r.MultipartForm != nil {
return nil, errors.New("http: multipart handled by ParseMultipartForm")
}
r.MultipartForm = multipartByReader
如果是multipart/form-data或multipart混合的POST请求,MultipartReader会返回一个MIME multipart reader,否则这返回一个error和nil
在使用中可以使用MultipartReader代替ParseMultipartForm来把请求的body作为stream进行处理,它在处理的时候不是一次性处理整个表单数据,而是检查来自表单的值,然后每次处理一个
POST请求-JSON BODY
不是所有的post请求都来自form
在不同的客户端框架下会以不同的方式对post请求编码
例如jQuery通常使用application/x-www-form-urlencoded
Augular则是application/json,但是ParseForm方法无法处理application/json
ResponseWriter
从服务器向客户端返回相应需要使用ResponseWriter
ResponseWriter是一个接口,handler用它来返回相应,真正支撑ResponseWriter的幕后struct是一个非导出的http.response
写入到ResponseWriter
ResponseWriter在底层实现的时候其实也是实现了一个指针
在ResponseWriter中,write方法接收一个byte切片作为参数,然后把他写入到HTTP相应的Body里面。
如果Write方法被调用时,header里面没有设定content type,那么数据的前512字节就会被用来监测content type
func writeExample(w http.ResponseWriter,r *http.Request) {
str := `<html><head><title>Go Web</title></head>
<body><h1>Hello World</h1></body>
</html>`
w.Write([]byte(str))
}
WriteHeader
WriteHeader方法接收一个整数类型(HTTP状态码)作为参数,并把它作为HTTP响应的状态码返回,如果这个方法没有被显示的调用,那么在第一次调用Write方法前会隐式的调用
当WriteHeader被调用完后,仍然可以写入到ResponseWriter,但是不能再修改header
func writeHeader(w http.ResponseWriter,r *http.Request) {
w.WriteHeader(501)
fmt.Fprintln(w,"66666666666666")
}
Header
Header方法返回Headers的map。可以进行修改,修改后的headers将会体现再返回给客户端的HTTP响应里
func headerExample(w http.ResponseWriter,r * http.Request) {
//重定向访问的请求
w.Header().Set("location","http://www.baidu.com")
//访问请求的状态码
w.WriteHeader(302)
}
传入json数据
type Post struct {
User string
Threads []string
}
func jsonExample(w http.ResponseWriter , r *http.Request) {
w.Header().Set("Content-Type","application/json")
post := &Post{
User: "张三",
Threads: []string{"666","777","888"},
}
json,_:= json2.Marshal(post)
w.Write(json)
}
内置的Response
1.NotFound函数,包装一个404状态码和一个额外的信息
2.ServeFile函数,从文件系统提供文件,返回给请求者
3.ServeContent函数,它可以把实现了io.ReadSeeker接口的任何东西里面的内容返回给请求者,同时它还可以处理Range请求(范围请求),如果只请求了资源的一部分内容,那么ServeContent就可以如此响应。而ServeFile或io.Copy则不行
4.Redirect函数,告诉客户端重定向到另一个URL
tp://www.baidu.com")
//访问请求的状态码
w.WriteHeader(302)
}
传入json数据
```go
type Post struct {
User string
Threads []string
}
func jsonExample(w http.ResponseWriter , r *http.Request) {
w.Header().Set("Content-Type","application/json")
post := &Post{
User: "张三",
Threads: []string{"666","777","888"},
}
json,_:= json2.Marshal(post)
w.Write(json)
}
内置的Response
1.NotFound函数,包装一个404状态码和一个额外的信息
2.ServeFile函数,从文件系统提供文件,返回给请求者
3.ServeContent函数,它可以把实现了io.ReadSeeker接口的任何东西里面的内容返回给请求者,同时它还可以处理Range请求(范围请求),如果只请求了资源的一部分内容,那么ServeContent就可以如此响应。而ServeFile或io.Copy则不行
4.Redirect函数,告诉客户端重定向到另一个URL