本文共 10231 字,大约阅读时间需要 34 分钟。
背景
整理一下上个月学习用go语言搭建服务器的笔记。
本文基于go1.14,文本编辑器采用vscode1.43.2,如果对go语言不甚了解,可以参见我的博客
监听端口号,响应客户端请求
package mainimport ( "fmt" "net/http")func handler(w http.ResponseWriter, r *http.Request) { // 处理请求函数,函数名任意,参数固定 fmt.Fprintln(w, "Hi everyone", r.URL.Path) // 向w输入后面的参数}func main() { http.HandleFunc("/", handler) // 注册处理函数,传入路径(根目录)和对应处理器 http.ListenAndServe(":8080", nil) // 创建路由,传入端口号和多路复用器(nil表示默认)}
ListenAndServe()函数的第二个参数为传入的多路复用器,多路复用器接收用户请求根据url,来判断用哪个处理器来处理请求。
编译运行
PS D:\develop\Microsoft VS Code\workspace> go build -o web.exe go_code/go_web/mainPS D:\develop\Microsoft VS Code\workspace> .\web.exe
然后打开localhost:8080,进行验证
访问根目录
访问/next
也可以通过实现ServeHttp方法的方式,来处理请求,方法如下
package mainimport ( "fmt" "net/http")type myHandler struct{}func (my_handler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hi from struct server", r.URL.Path)}func main() { http.Handle("/", &myHandler{}) // 注册处理函数,传入路径(根目录)和对应处理器 http.ListenAndServe(":8080", nil) // 创建路由,传入端口号和多路复用器(nil表示默认)}
还可以通过实例化http.Server的方式进行初始化服务器
func main() { server := http.Server{ Addr: ":8080", Handler: &myHandler{}, ReadHeaderTimeout: 2 * time.Second, } server.ListenAndServe()}
导入time包即可
import ( "fmt" "net/http" "time")
使用自己的多路复用器,用的很少
mux := http.NewServeMux() mux.Handle("/", &myHandler{}) http.ListenAndServe(":8080", mux)
获取客户端请求相关信息
func (my_handler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hi from struct server", r.URL.Path) fmt.Fprintln(w, "header", r.Header) //头部信息 fmt.Fprintln(w, "method:", r.Method) // 请求方法 fmt.Fprintln(w, "RawQuery:", r.URL.RawQuery) // GET方法的请求参数}
浏览器输入 后,浏览器输出如下
可见header是一个map[string][]string类型,可以根据映射取值方式来获取想要的内容
fmt.Println("header中的accept-encoding:", r.Header["Accept-Encoding"]) fmt.Println("header中的浏览器:", r.Header["User-Agent"])
输出如下
header中的accept-encoding: [gzip, deflate, br]header中的浏览器: [Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36]
获取POST方法的请求体时,要先构造byte切片,再通过r.Body.Read()方法进行获取
body := make([]byte, r.ContentLength) r.Body.Read(body) fmt.Fprintln(w, "body:", string(body))
构造以下html文件,向localhost:8080/info路径发出请求
用浏览器打开,输入以下内容
提交后的输出如下所示
获取表单信息
err = r.ParseForm() // 必须先调用ParseForm()方法 if err != nil { fmt.Println("Parse form error:", err) return } fmt.Println(r.Form) // 表单 + url中参数,如果有同名,表单中的值靠前 fmt.Println(r.PostForm) // 仅表单
对应html如下
用浏览器打开,输入以下内容
提交后控制台输出如下
map[k1:[v1] k2:[v2] password:[pass] username:[user]]map[password:[pass] username:[user]]
注意,如果之前调用了r.Body().Read(),那么这里只能获取空映射
如果form的enctype是multipart/form-data的话,就要调用MultipartForm()来获取映射了
也可以调用FormValue()和PostFormValue()直接获取表单中的值,这两个方法必要时会调用ParseForm()方法
fmt.Println(r.FormValue("k1")) fmt.Println(r.PostFormValue("username"))
输出如下
v1user
响应客户端请求
直接响应请求
主要是向w里写数据,之前的fmt.FPrintln()就可以。如果要向客户端写入json数据,就要先导入encoding/json包,然后给w设置header,最后写入json字节切片
import ( "encoding/json" ...)type User struct { Id int `json:"id"` Username string `json:"username"` Password string `json:"password"`}func (my_handler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ... w.Header().Set("Content-Type", "application/json") // 设置header user := User{ Id: 1, Username: "szc", Password: "pwd", } json, _ := json.Marshal(&user) // 解析json w.Write(json)}
输出结果如下
如果要在响应中重定向到其他网址,可以使用以下代码,注意不要再重定向前进行其他响应
func (my_handler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", "https://www.baidu.com") w.WriteHeader(302)}
在浏览器输入localhost:8080后,就会跳转到百度首页,并且在浏览器控制台,会找到我们的响应报文
利用模板响应请求
从模板中响应请求
import ( ... "html/template" ...)func (my_handler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("output.html") // 解析模板文件 t.Execute(w, "Response from template") // 执行模板引擎,传入数据}
模板文件output.html内容如下,{
{}}是模板动作,来接收响应数据,.表示所有数据后台传来的数据:{ {.}}
浏览器输出如下
如果要传入多个模板文件,就要调用ExecuteTemplate()方法,传入指定输出的模板文件名以及要输出的数据
t, _ := template.ParseFiles("output.html", "output_1.html") // 解析多个模板文件 t.ExecuteTemplate(w, "output_1.html", "Response from template to html2") // 参数列表:writer、输出模板文件、输出数据
模板文件output_1.html内容如下
后台传来的数据:{ {.}} (html1)
浏览器输出如下
模板动作
以下是一些模板引擎中的动作语句
分支选择
后台传来的数据: { {if .}} select1 { {else}} select 2 { {end}} (html)
后台输出测试如下
func (my_handler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("output.html") t.Execute(w, 2 > 1)}
浏览器输出如下
遍历动作
遍历并输出某个元素的指定字段
后台传来的数据: { {range .}} { {if .}} 当前元素:{ {.Username}} { {end}} { {else}} nothing { {end}} (html)
后台输出测试如下
func (my_handler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("output.html") slice_ := make([]*User, 5) slice_ = append(slice_, &User{Id: 0, Username: "user0", Password: "pwd0"}) slice_ = append(slice_, &User{Id: 1, Username: "user1", Password: "pwd1"}) slice_ = append(slice_, &User{Id: 2, Username: "user2", Password: "pwd2"}) slice_ = append(slice_, &User{Id: 3, Username: "user3", Password: "pwd3"}) slice_ = append(slice_, &User{Id: 4, Username: "user4", Password: "pwd4"}) t.Execute(w, slice_)}
浏览器输出如下
遍历元素时,也可以输出整个元素,如下
后台传来的数据: { {range .}} { {if .}} 当前元素:{ {.}} { {end}} { {else}} nothing { {end}} (html)
浏览器输出如下
设置动作
设置动作用with就可以,但是如果with传入的字符串为空,就不会进行设置
后台传来的数据:{ {.}}尝试改变数据 { {with "newData"}} 新数据:{ {.}} { {end}}{ {with ""}} 传入""后的结果:{ {.}} { {else}} 空字符串没有修改: { {.}} { {end}}(html)
后台代码如下
func handler(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("output.html") t.Execute(w, "后台数据")}
浏览器的输出如下
包含动作
使用template动作把另一个模板文件里的内容拷贝进来。
output.html模板内容如下
后台传来的数据:{ {.}}output1.html里的数据: { {template "output_1.html"}} { {template "output_1.html" .}}(html)
output_1.html模板内容如下
后台传来的数据:{ {.}} (html1)
后台处理逻辑如下
func handler(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("output.html", "output_1.html") t.Execute(w, "后台数据")}
浏览器输出如下
可见output.html中的两个模板动作里,上面的仅仅拷贝了模板,下面连同数据也引入了
定义动作
可以使用define去定义一个新动作
{ {define "model"}} 后台传来的数据:{ {.}} (html1) { {template "content"}} { {end}}{ {define "content"}} 百度{ {end}}
后台使用ExecuteTemplate()方法指定加载的动作和参数
func handler(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("output_1.html") t.ExecuteTemplate(w, "model", "")}
浏览器输出如下
我们还可以根据不同的情况加载不同的模板文件,从而加载不同的动作,哪怕重名。
现有output.html和output_1.html,唯一不同的是前者把百度改成了新浪,故而只贴出后台逻辑
func handler(w http.ResponseWriter, r *http.Request) { var t *template.Template if strings.Contains(r.URL.Path, "_1") { t, _ = template.ParseFiles("output_1.html") } else { t, _ = template.ParseFiles("output.html") } t.ExecuteTemplate(w, "model", "")}
浏览器输入 ,浏览器输出如下
而的输出如下
块动作
当后台执行模板时,模板没有对应的动作,就可以使用block定义默认的动作
output.html内容如下,当没有给"content"指定动作时,使用block进行默认的输出
{ {define "model"}} 后台传来的数据:{ {.}} (html1) { {block "content" .}} block { {end}} { {end}}
而"content"动作定义在output_1.html中
{ {define "model"}} 后台传来的数据:{ {.}} (html1) { {template "content"}} { {end}}{ {define "content"}} 百度{ {end}}
后台处理逻辑如下
func handler(w http.ResponseWriter, r *http.Request) { var t *template.Template if strings.Contains(r.URL.Path, "_1") { t, _ = template.ParseFiles("output.html", "output_1.html") } else { t, _ = template.ParseFiles("output.html") } t.ExecuteTemplate(w, "model", "")}
当输入url包含_1时,加载两个模板文件,这时"content"的定义在output_1.html中被找到,所以会加载出相应内容
当输入url不含_1是,只加载一个模板文件,没有"content"的定义,所以加载block中默认的内容
处理静态资源
go语言不能直接访问css、js等静态资源,需要我们为这些资源的请求注册处理函数
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(utils.Static_res)))) // 处理静态资源,入参:请求路径,处理器。 http.Handle("/pages/", http.StripPrefix("/pages/", http.FileServer(http.Dir(utils.Pages))))
Handle()方法里的/static/表示要处理的请求相对路径,也就是静态css和js所在的目录,后面的http.FileServer()返回一个处理器,这里用来访问静态资源所在目录。
http.FileServer的入参就是目录的路径,这里使用自定义常量来表示
var ( Project_src = "D:/develop/Go/workspace/src/go_code/book_store/src/" Index_html = Project_src + "views/index.html" Pages = Project_src + "views/pages/" Static_res = Project_src + "views/static/")
cookie
设置cookie时,可以使用Set或Add方法
func setCookie(w http.ResponseWriter, r *http.Request) { cookie := http.Cookie{ Name: "user2", Value: "admin2", } http.SetCookie(w, &cookie)}
或者
func setCookie(w http.ResponseWriter, r *http.Request) { cookie := http.Cookie{ Name: "user2", Value: "admin2", } w.Header().Set("Set-Cookie", cookie.String()) w.Header().Add("Set-Cookie", cookie.String()) }
所有Set方法都会覆盖老的,Add方法则不会
除了键值对,也可以为Cookie指定有效期限,单位是秒
cookie2 := http.Cookie{ Name: "user", Value: "admin2", MaxAge: 60, }
获取cookie时,可以从头部信息获取全部的cookie,也可以获取指定的cookie
获取header中的所有cookie,返回的是一个string切片
cookies := r.Header["Cookie"]
获取指定cookie,返回的是一个Cookie指针
cookie_, _ := r.Cookie("user") cookie_.String()
结语
go语言搭建服务器还是很简单的,不用安装任何其他包,就可以进行端口的监听、请求的响应等操作。
转载地址:https://blog.csdn.net/qq_37475168/article/details/105454110 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!