GoWeb基础

本文详细介绍了Go语言进行Web开发的基础知识,包括创建webserver的两种方式、Handler请求处理、内置的handlers、request与response、表单处理、模版引擎的使用,以及数据库编程的初步介绍。通过实例讲解了如何解析和执行模版,如何处理HTTP请求,并讨论了Go语言中内置的HTTP响应函数。此外,文章还涵盖了路由、JSON处理、中间件、请求上下文、HTTPS和性能分析等内容,为GoWeb开发打下坚实基础。
摘要由CSDN通过智能技术生成

前言

Web是基于HTTP协议进行交互的应用网络,通过使用浏览器/APP访问各种资源

1 基础知识

1.1 简易web程序

  1. 创建好项目的空文件之后,初始化一个模块
go mod init github.com/solenovex/web

/Users/qinjianquan/goweb/go.mod //在go mod 文件中查看

module github.com/solenovex/web //初始化时声明的模块

go 1.17

2.创建一个简单的web应用程序

/Users/qinjianquan/goweb/main.go

func main() {

	//1.注册一个函数,让其可以对web请求进行响应
	//处理web请求,我们一般用http包的下列函数,第一个参数相当于路由地址,"/"是根地址,表示响应所有的请求
	//第二个参数是个函数,一个是接口:用来写响应,一个是结构体的指针:包括传入请求的所有信息
	//这个函数是个回调函数,当请求到达时,函数就会执行(函数本身是参数)
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		//接口w有write方法,所以可以调用
		w.Write([]byte("hello world"))
	})
	//2.设置web服务器,启动这个server
	//监听访问localhost:8080的请求,第二个参数默认调用Handler接口里面的方法(可以理解为一个路由器)
	//意思就是收到请求后,将所访问的路径分别匹配
	http.ListenAndServe("localhost:8080", nil) //DefaultServeMux
}

3.运行

go run main.go //终端运行
http://localhost:8080/ //web访问
hello world //结果页面

1.2 Handle请求

1.2.1 创建webserver的两种方式

在web编程中,处理请求的单词有两个Handle和Process,前者更多指的是响应,后者更多的是指处理过程

go语言使用Handler来接受web请求,每接收到一个请求,go语言就会创建一个goroutine,具体而言是由DefaultServeMux来完成的

go语言中创建web server的方式有很多中,使用http.ListenAndServe()是其中的一种,当第一个参数为空时,就是所有网络接口的80端口,第二个参数如果是nil,那就是DefaultServeMux

func main() {
	
	//第一种方式
	//http.ListenAndServe("localhost:8080", nil)
	//第二种方式:如下等同于上面的过程,但是分开写稍微更加灵活一点
	server := http.Server{
		Addr:    "localhost:8080",
		Handler: nil,
	}
	server.ListenAndServe()

}

但是上述web应用只能走http,不能走https,要走要使用配套的两个函数,我们后面再讲

server.ListenAndServeTLS()
http.ListenAndServeTLS()

1.2.2 Handler

handler

handler 是一个接口,定义如下,也就是说任何类型,只要实现了ServeHTTP(w ResponseWriter, r *Request)这个方法,它就是一个handler

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
var defaultServeMux ServeMux //DefaultServeMux其实也是一个handler
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) //它也是实现了ServeHTTP(w ResponseWriter, r *Request)这个方法

实现一个自定义的handler,所有请求都用打印“hello world”来处理

type myHandler struct{}

func (mh *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello world"))
}
func main() {

	mh := myHandler{}

	//第一种方式
	//http.ListenAndServe("localhost:8080", nil)
	//第二种方式:如下等同于上面的过程,但是分开写稍微更加灵活一点

	server := http.Server{
		Addr:    "localhost:8080",
		Handler: &mh,
	}
	server.ListenAndServe()
}

Handler函数 - http.Handle

handler函数指的是行为与handler类似的函数,也就是函数签名和ServeHTTP方法的签名一样

事实上,在一个web应用中,使用多个handler对访问不同路径的请求予以处理,才是合理的操作

如何实现这一点?

首先不指定Server struct里面的Handler 字段值,这样就可以使用DefaultServeMux

接着使用http.Handle将某个Handler附加到DefaultServeMux

DefaultServeMux是ServeMux的指针变量,ServeMux有一个Handle方法,http包还有一个Handle函数,调用它实际上就是调用的是Handle方法

func Handle(pattern string, handler Handler)
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

先让http.Serve参数中handler 为nil

接着使用http.Handle向DefaultServeMux注册

//先自定义一个类型
type helloHandler struct{}

//为它实现ServeHTTP方法后就会将它变成一个handler
func (mh *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
}

//再自定义一个类型
type aboutHandler struct{}

//同样的,实现ServeHTTP方法
func (mh *aboutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("About!"))
}

func main() {

	mh := helloHandler{} //初始化变量
	a := aboutHandler{}  //初始化变量

	//第一种方式
	//http.ListenAndServe("localhost:8080", nil)
	//第二种方式:如下等同于上面的过程,但是分开写稍微更加灵活一点

	server := http.Server{
		Addr:    "localhost:8080",
		Handler: nil,
	}
	//向DefaultServeMux注册,此时再过来符合条件的请求就会经由DefaultServeMux分发给该注册过的Handle
	//其实就是把我们自定义的handler加入到DefaultServeMux分配的备选handler集合之中
	http.Handle("/hello", &mh) //注册
	http.Handle("/about", &a)  //同上也是注册

	server.ListenAndServe()
}

现在我们就能在浏览器访问指定的路径(http://localhost:8080/about 和 http://localhost:8080/hello),并可以获取写入的内容

Handler函数 - http.HandleFunc

http.HandleFunc 原理:它是一个函数类型,它可以将某个具有适当签名的函数f适配成一个Handler,而这个Handler具有方法f

//先自定义一个类型
type helloHandler struct{}

//为它实现ServeHTTP方法后就会将它变成一个handler
func (mh *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
}

//再自定义一个类型
type aboutHandler struct{}

//同样的,实现ServeHTTP方法
func (mh *aboutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("About!"))
}

func welcome(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Welcome!"))
}

func main() {

	mh := helloHandler{} //初始化变量
	a := aboutHandler{}  //初始化变量

	//第一种方式
	//http.ListenAndServe("localhost:8080", nil)
	//第二种方式:如下等同于上面的过程,但是分开写稍微更加灵活一点

	server := http.Server{
		Addr:    "localhost:8080",
		Handler: nil,
	}
	//1.使用Handle向DefaultServeMux注册,此时再过来符合条件的请求就会经由DefaultServeMux分发给该注册过的Handle
	//其实就是把我们自定义的handler加入到DefaultServeMux分配的备选handler集合之中
	http.Handle("/hello", &mh) //注册
	http.Handle("/about", &a)  //同上也是注册

	//2.使用HandleFunc向DefaultServeMux注册
	//在签名中定义func
	http.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Home!"))
	})
	//在外面定义,直接传函数名
	http.HandleFunc("/welcome", welcome)
	
	//3.也可以使用http.HandlerFunc()将函数转换为为一个handler,其实就是将函数名称变为ServeHTTP
	//type HandlerFunc func(ResponseWriter, *Request)
	//
	 ServeHTTP calls f(w, r).
	//func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	//	f(w, r)
	//}
	http.Handle("/welcome", http.HandlerFunc(welcome))

	server.ListenAndServe()
}

为什么要使用两种注册方式呢?因为我们看到这样就不用修改原来的函数了,直接把它转换为接口里面的方法

1.3 内置的handlers

Go语言可以自定义handler以及将现有符合条件的函数转换为handler来处里访问特定路径的web请求。另外Go语言也内置了五种类型的handler

func main() {
	//1. func NotFoundHandler() Handler,给每个请求都返回 "404 page not found"
	http.NotFoundHandler()
	//2. func RedirectHandler(url string, code int) Handler,将要访问的url页面通过跳转码跳转到另一个url页面
	//常见的有StatusMovedPermanently, StatusFound or StatusSeeOther,分别是301,302,303
	http.RedirectHandler("/welcome", 301)
	//3. func StripPrefix(prefix string, h Handler) Handler,将请求的url去掉指定的前缀,然后调用另一个handler,如果不符合,则404
	//有点像中间件,修饰了另外一个handler
	http.StripPrefix("/welcome", http.NotFoundHandler())
	//4.func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
	//time.Duration 实际上是int64的一个别名,这个handler也相当于一个修饰器
	http.TimeoutHandler(http.NotFoundHandler(), 3, "timeout")
	//5. func FileServer(root FileSystem) Handler,参数是一个接口,得传入一个实现了接口的类型
	http.FileServer(http.Dir("/welcome"))
}

使用web server测试http.FileServer(),先准备一份html和css文件放入独立文件夹但与main函数放在同一目录下

func main() {

	//第一种方式
	//http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
	//	http.ServeFile(w, r, "index"+r.URL.Path)
	//})
	//
	//http.ListenAndServe(":8080", nil)
	//第二种方式,使用 http.FileServer()
	http.ListenAndServe(":8080", http.FileServer(http.Dir("index")))
}

1.4 request

本节讲述对http请求的处理过程

1.4.1 HTTP消息

HTTP消息

HTTP Request和HTTP Response,结构相同

请求(响应)行

0个或者多个header

空行

可选的消息体
1.请求行
格式:
请求方式  请求资源  请求协议/版本
GET  虚拟路径/login.html HTTP/1.1

	请求方式:
	 HTTP中目前有9种请求方式,常用的有2种
		 GET:
			1.请求参数在请求行中,在URL后面
			2.请求的url长度有限制的
			3.不太安全
		 POST:
		 	1.请求参数封装在请求体中
		 	2.请求的url长度没有限制
		 	3.相对安全

2.请求头
	这部分的作用就是浏览器告诉服务器自身的一些信息
	Host:表示请求的主机
	User-Agent:浏览器告诉服务器我访问你使用的浏览器版本信息 
		可以在服务器端获取该头的信息,解决浏览器的兼容性问题
	Referer:告诉服务器我(当前的请求)从哪 里来的
			用来防盗链和做统计工作

3.请求空行
	就是用于分割POST请求的请求头和请求体的

4.请求体(真正的的内容所在)
	封装POST请求消息的请求参数的

Request

go语言中,net/http包提供了用于表示HTTP消息的结构

type Request struct {
	//几个重要的字段
	URL //通用形式:scheme://[userinfo@]host/path[?query][#fragment] //[?query]查询字符串[#fragment]
	Header
	Body
	Form,PsotForm,MultipartForm
	...
}
另外,还可以使用Request的一些方法访问请求中的cookie,URL,User Agent等信息
request既可以用在服务端,也可以用在客户端

URL Fragment

URL Fragment
如果请求从浏览器发出,则无法从URL中提取Fragment字段值,因为浏览器会在发送时去掉它
func main() {

	server := http.Server{
		Addr: "localhost:8080",
	}

	http.HandleFunc("/url", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, r.URL.Fragment)
	})

	server.ListenAndServe()

}
访问http://localhost:8080/url#fragement无返回值
curl localhost:8080/url#frag //使用curl也无返回值
//curl 是一种命令行工具,作用是发出网络请求,然后获取数据,显示在"标准输出"(stdout)上面

Request Header

Request Header
header 是一个map,用来表述HTTP Header 里面的Key-Value对
key string,value []string,value里面第一个元素就是新的header值
使用append为指定的key增加一个header值

获取header
r.Header 返回的是map
r.Header["Accept-Encoding"] //返回的是[]string
r.Header.Get("Accept-Encoding") //返回的是string,使用逗号隔开
func main() {

	server := http.Server{
		Addr: "localhost:8080",
	}

	//http.HandleFunc("/url", func(w http.ResponseWriter, r *http.Request) {
	//	fmt.Fprintln(w, r.URL.Fragment)
	//})
	http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
		//fmt.Fprintln(w, r.URL.Fragment)
		fmt.Fprintln(w, r.Header)
		fmt.Fprintln(w, r.Header["Accept-Encoding"])
		fmt.Fprintln(w, r.Header.Get("Accept-Encoding"))

	})

	server.ListenAndServe()
}

不同方式获取header得到的结果,如下是使用vs code插件 REST Client发送的请求

map[Accept-Encoding:[gzip, deflate] Connection:[close] User-Agent:[vscode-restclient]]
[gzip, deflate]
gzip, deflate

Request Body

获取body得到的结果,如下是使用vs code插件 REST Client发送的请求

Request Body

Body io.ReadCloser body类型是一个接口,接口里面也是两个接口,使用Read方法读取body内容

type ReadCloser interface {
    Reader
    Closer
}
type Reader interface {
    Read(p []byte) (n int, err error)
}
type Closer interface {
    Close() error
}
HTTP/1.1 200 OK
Date: Thu, 21 Apr 2022 05:30:07 GMT
Content-Length: 70
Content-Type: text/plain; charset=utf-8
Connection: close

{
  "name": "Sample",
  "time": "Wed,21 Oct 2015 18:27:50 UTC"
}

URL Query

URL Query 

RawQuery 会提供实际查询的字符串,在问号后面表示,有两对值,如:id=123&thread_id=456
例如:http://www.example.com/post?id=123&thread_id=456
还可以通过Request的Form字段得到key-value对

http://www.example.com/post?id=123&thread_id=456
在URL中使用查询字符串向后端发送数据,这种通常是GET请求
r.URL.RawQuery 会提供实际查询的原始字符串,如id=123&thread_id=456
r.URL.Query会提供查询字符串对应的map[string][]string
func main() {

	server := http.Server{
		Addr: "localhost:8080",
	}

	//1.#fragment
	//http.HandleFunc("/url", func(w http.ResponseWriter, r *http.Request) {
	//	fmt.Fprintln(w, r.URL.Fragment)
	//})

	//2.Header
	// http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
	// 	//fmt.Fprintln(w, r.URL.Fragment)
	// 	fmt.Fprintln(w, r.Header)
	// 	fmt.Fprintln(w, r.Header["Accept-Encoding"])
	// 	fmt.Fprintln(w, r.Header.Get("Accept-Encoding"))

	// })

	//3.Body
	//http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
	//	length := r.ContentLength //获取内容的长度
	//	body := make([]byte, length)
	//	r.Body.Read(body) //获取body,  并将其读到上述bytes中
	//
	//	fmt.Fprintln(w, string(body))
	//})

	//4.URL
	http.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
		url := r.URL
		query := url.Query() //返回的是一个map
		fmt.Println(query)

		id := query["id"] //返回的是id []string
		log.Println(id)

		name := query.Get("name") //返回的是[]string中的第一个元素
		log.Println(name)
	})

	server.ListenAndServe()
}
http://localhost:8080/home?id=123&name=Dave&id=456&name=Nick

1.5 Form

1. 使用表单发送POST请求

HTML表单
<form action = "/process" method = "post"> //action对应的是处理的路径,method 对应的是请求类型
    <input type = "text" name="first_name"/> //input 是输入框
    <input type = "text" name="last_name"/> //输入的数据是以name-value对形式存放的,name是现有值,value是输入值
    <input type = "sumit"/> //当我们点击提交按钮之后,这个HTML表单里面的数据(两对数据)就会以name-value对的形式以post请求发送出去
    //数据内容放在POST请求的Body里面
</form>

name-value对在Body里面的格式

通过POST发送的name-value数据对的格式可以通过表单的Content Type来指定,也就是enctype属性

enctype 属性的默认值:application/x-www-form-urlencoded

<form action = "/process" method = "post" enctype="application/x-www-form-urlencoded"> //action对应的是处理的路径,method 对应的是请求类型
    <input type = "text" name="first_name"/> //input 是输入框
    <
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值