goWeb学习笔记完整版

背景

整理一下上个月学习用go语言搭建服务器的笔记。

本文基于go1.14,文本编辑器采用vscode1.43.2,如果对go语言不甚了解,可以参见我的博客go语言基础学习笔记完整版

监听端口号,响应客户端请求

package main

import (
    "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/main
PS D:\develop\Microsoft VS Code\workspace> .\web.exe

然后打开localhost:8080,进行验证

访问根目录

访问/next

也可以通过实现ServeHttp方法的方式,来处理请求,方法如下

package main

import (
    "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方法的请求参数
}

浏览器输入 http://localhost:8080/index?user=szc&pwd=123后,浏览器输出如下

 可见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路径发出请求

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <form action="http://localhost:8080/info" method="POST">
            用户名:<input type="text" name="username"/><br/>
            密码:<input type="password" name="password"/><br/>
            <input type="submit"/>
        </form>
    </body>
</html>

用浏览器打开,输入以下内容

提交后的输出如下所示

获取表单信息

 

    err = r.ParseForm() // 必须先调用ParseForm()方法
    if err != nil {
        fmt.Println("Parse form error:", err)
        return
    }
    fmt.Println(r.Form) // 表单 + url中参数,如果有同名,表单中的值靠前
    fmt.Println(r.PostForm) // 仅表单

对应html如下

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <form action="http://localhost:8080/info?k1=v1&k2=v2" method="POST">
            用户名:<input type="text" name="username"/><br/>
            密码:<input type="password" name="password"/><br/>
            <input type="submit"/>
        </form>
    </body>
</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"))

输出如下

v1
user

响应客户端请求

直接响应请求

主要是向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内容如下,{{}}是模板动作,来接收响应数据,.表示所有数据

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:{{.}}
    </body>
</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内容如下

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:{{.}} (html1)
    </body>
</html>

浏览器输出如下

模板动作

以下是一些模板引擎中的动作语句

分支选择

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:
        {{if .}}
        select1
        {{else}}
        select 2
        {{end}} (html)
    </body>
</html>

后台输出测试如下

func (my_handler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t, _ := template.ParseFiles("output.html")
    t.Execute(w, 2 > 1)
}

浏览器输出如下

遍历动作

遍历并输出某个元素的指定字段

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:
        {{range .}}
            {{if .}} <!-- 如果当前元素不为空 -->
                <br/>当前元素:{{.Username}}
            {{end}}
        {{else}}
            nothing
        {{end}}
        <br/>(html)
    </body>
</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_)
}

浏览器输出如下

遍历元素时,也可以输出整个元素,如下

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:
        {{range .}}
            {{if .}} <!-- 如果当前元素不为空 -->
                <br/>当前元素:{{.}}
            {{end}}
        {{else}} <!-- 没有遍历到 -->
            nothing
        {{end}}
        <br/>(html)
    </body>
</html>

浏览器输出如下

设置动作

设置动作用with就可以,但是如果with传入的字符串为空,就不会进行设置

<html>
    <head>
        <meta charset="UTF-8"/>
    </head>
    <body>
        后台传来的数据:{{.}}
        
        <div>尝试改变数据
            {{with "newData"}}
                新数据:{{.}}
            {{end}}
        </div><br/>
        
        <div>
            {{with ""}}
                传入""后的结果:{{.}}
            {{else}}
                空字符串没有修改: {{.}}
            {{end}}
        </div>
        <br/>(html)
    </body>
</html>

后台代码如下

func handler(w http.ResponseWriter, r *http.Request) {
    t, _ := template.ParseFiles("output.html")
    t.Execute(w, "后台数据")
}

浏览器的输出如下

包含动作

使用template动作把另一个模板文件里的内容拷贝进来。

output.html模板内容如下

<html>
    <head>
        <meta charset="UTF-8"/>
    </head>
    <body>
        后台传来的数据:{{.}}
        
        <div>output1.html里的数据:<br/>{{template "output_1.html"}}</div><bt/> <!-- 仅输出模板 -->
        <div>{{template "output_1.html" .}}</div> <!-- 输出模板和数据 -->
        <br/>(html)
    </body>
</html>

output_1.html模板内容如下

<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:{{.}} (html1)
    </body>
</html>

后台处理逻辑如下

func handler(w http.ResponseWriter, r *http.Request) {
    t, _ := template.ParseFiles("output.html", "output_1.html")
    t.Execute(w, "后台数据")
}

浏览器输出如下

可见output.html中的两个模板动作里,上面的仅仅拷贝了模板,下面连同数据也引入了

定义动作

可以使用define去定义一个新动作

{{define "model"}}
<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:{{.}} (html1)
        <br/>
        {{template "content"}} <!-- 引用自定义动作 -->
    </body>
</html>
{{end}}


{{define "content"}}
    <a href="https://www.baidu.com">百度</a>
{{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", "")
}

浏览器输入 http://localhost:9999/_1,浏览器输出如下

http://localhost:9999/的输出如下

块动作

当后台执行模板时,模板没有对应的动作,就可以使用block定义默认的动作

output.html内容如下,当没有给"content"指定动作时,使用block进行默认的输出

{{define "model"}}
<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:{{.}} (html1)
        <br/>
        {{block "content" .}}
            block
        {{end}}
    </body>
</html>
{{end}}

而"content"动作定义在output_1.html中

{{define "model"}}
<html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        后台传来的数据:{{.}} (html1)
        <br/>
        {{template "content"}}
    </body>
</html>
{{end}}

{{define "content"}}
    <a href="https://www.baidu.com">百度</a>
{{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语言搭建服务器还是很简单的,不用安装任何其他包,就可以进行端口的监听、请求的响应等操作。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值