[golang Web开发] 3.golang web开发:处理请求

6 篇文章 0 订阅
5 篇文章 2 订阅

简介

Go语音的net/http包提供了一系列用于表示HTTP报文的结构,可以使用它处理请求和发送响应,其中Request结构代表了客户端发送的请求报文,下面是Request讲解

type Request struct {
    // Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。
    Method string
    // URL在服务端表示被请求的URI,在客户端表示要访问的URL。
    //
    // 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,
    // 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。
    // (参见RFC 2616, Section 5.1.2)
    //
    // 在客户端,URL的Host字段指定了要连接的服务器,
    // 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。
    URL *url.URL
    // 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    // Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:
    //	accept-encoding: gzip, deflate
    //	Accept-Language: en-us
    //	Connection: keep-alive
    // 则:
    //	Header = map[string][]string{
    //		"Accept-Encoding": {"gzip, deflate"},
    //		"Accept-Language": {"en-us"},
    //		"Connection": {"keep-alive"},
    //	}
    // HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。
    // 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。
    Header Header
    // Body是请求的主体。
    //
    // 在客户端,如果Body是nil表示该请求没有主体买入GET请求。
    // Client的Transport字段会负责调用Body的Close方法。
    //
    // 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。
    // Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。
    Body io.ReadCloser
    // ContentLength记录相关内容的长度。
    // 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。
    // 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。
    ContentLength int64
    // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
    // 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。
    TransferEncoding []string
    // Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。
    Close bool
    // 在服务端,Host指定URL会在其上寻找资源的主机。
    // 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。
    // Host的格式可以是"host:port"。
    //
    // 在客户端,请求的Host字段(可选地)用来重写请求的Host头。
    // 如过该字段为"",Request.Write方法会使用URL字段的Host。
    Host string
    // Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。
    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
    Form url.Values
    // PostForm是解析好的POST或PUT的表单数据。
    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
    PostForm url.Values
    // MultipartForm是解析好的多部件表单,包括上传的文件。
    // 本字段只有在调用ParseMultipartForm后才有效。
    // 在客户端,会忽略请求中的本字段而使用Body替代。
    MultipartForm *multipart.Form
    // Trailer指定了会在请求主体之后发送的额外的头域。
    //
    // 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。
    // (客户端会声明哪些trailer会发送)
    // 在处理器从Body读取时,不能使用本字段。
    // 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。
    // (如果客户端发送了这些键值对),此时才可以访问本字段。
    //
    // 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)
    // ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。
    // 在开始发送请求后,Trailer可以在读取请求主体期间被修改,
    // 一旦请求主体返回EOF,调用者就不可再修改Trailer。
    //
    // 很少有HTTP客户端、服务端或代理支持HTTP trailer。
    Trailer Header
    // RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。
    // 本字段不是ReadRequest函数填写的,也没有定义格式。
    // 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。
    // 客户端会忽略请求中的RemoteAddr字段。
    RemoteAddr string
    // RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI
    // (参见RFC 2616, Section 5.1)
    // 一般应使用URI字段,在客户端设置请求的本字段会导致错误。
    RequestURI string
    // TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息
    // 本字段不是ReadRequest函数填写的。
    // 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。
    // 客户端会忽略请求中的TLS字段。
    TLS *tls.ConnectionState
}

Request类型代表一个服务端接受到的或者客户端发送出去的HTTP请求。

Request各字段的意义和用途在服务端和客户端是不同的。除了字段本身上方文档,还可参见Request.Write方法和RoundTripper接口的文档

一.获取请求url

Request结构体中的URL字段用于表示请求行中包含的URL,该字段是一个指向url.URL结构的指针,下面是URL结构讲解

import "net/url"

url包解析URL并实现了查询的逸码

 (1).Path字段

        * 获取请求的URL

        * eg:http:localhost:8080/hello?username=admin&password=123456, 通过r.URL.Path  只能得到/hello

(2).RawQuery字段

        * 获取请求的URL后面?后面的查询字符串

        * eg:http:localhost:8080/hello?username=admin&password=123456, 通过r.URL.RawQuery得到的是 username=admin&password=123456

二.获取请求头中的信息

通过Request结果中的Header字段来获取请求头中的所有信息,Header字段的类型是Header类型,而Header类型是一个map[string][]string,下面是Header类型及它的方法

三.获取请求体中的信息

请求和响应的主体都是有Request结构中的body字段表示,这个字段的类型是io.ReadCloser接口,该接口包含了Reader接口和Closer接口,Reader接口拥有Read方法,Closer接口拥有Close方法

(1).由于GET请求没有请求体,所以需要在HTML页面创建一个form表单,通过指定method="post"来发送一个POST请求

form.html提交如下:

<form action="http://localhost:8080/getBody" method="POST">
    用户名:<input type="text" name="username" value="admin"><br/>
    密码:<input type="password" name="password" value="123456"><br/>
    <input type="提交">
</form>

 main.go获取客户端提交的信息,并解析如下:

func handler(w http.ResponseWriter, r *http.Request) {
    //获取内容长度
    length := r.ContentLenth
    //创建一个切片
    body := make([]byte, length)
    //读取请求体
    r.Body.Read(body)
    fmt.Fprintln(w, "请求体中的内容是:", string(body))
}

四.获取请求参数

通过net/http库中的Request结构的字段以及方法来获取请求URL后面的请求参数以及form表单提交的请求参数

1.Form字段 

(1).类型是url.Values类型,Form是解析好的表单数据,包括URL字段的query参数和POST或PUT表单数据

 包net/url下url.Values

 (2).Form字段只有在调用Request的ParseForm后才有效,在客户端,会忽略请求中的本字段而使用Body替代

代码: 

func handler(w http.ResponseWriter, r *http.Request) {
    //解析表单
    r.ParseForm()
    //获取请求参数
    fmt.Fprintln(w, "请求参数:", r.Form)
}

注意:在执行r.Form之前一定要调用r.ParseForm方法对表单进行解析 

结果:请求参数为:map[password:[123456]] username:[admin]] 

2. 如果对form表单做一些修改,在action属性的URL后面也添加相同的请求参数

如果对form表单做一些修改,在action属性的URL后面也添加相同的请求参数,如下: 

        action="http://localhost:8080/getBody?pwd=123456&username=zhangsan"

则执行结果如下:

        map[password:[123456]] pwd:[123456] username:[admin zhangsan]] 

可以发现:

        * 表单中的请求参数 username 和 URL 中的请求参数 username 都获取到了,而且表单中的请求参数的值排在 URL 请求参数值的前面

        * 如果此时只想获取表单中的请求参数该怎么办呢?

        * 那就需要使用 Request 结构中的PostForm字段,但是PostForm字段只支持 application/x-www-form-urlencoded编码,如果 form 表单的enctype属性值为 multipart/form-data,那么使用PostForm字段无法获取表单中的数据,此时需要使用MultipartForm字段

        * 说明: form 表单的enctype属性默认值为application/x-www-form-urlencoded编码.,实现上传文件时需要将该属性的值设置为multipart/form-data编码格式

 3.FormValue方法和PostFormValue方法

代码如下: 

package main

import (
   "fmt"
   "net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
   _, err := fmt.Fprintln(w, "请求地址:", r.URL.Path)
   _, err = fmt.Fprintln(w, "请求地址中后面的查询字符串:", r.URL.RawQuery)
   _, err = fmt.Fprintln(w, "请求地址中请求头数据:", r.Header)//返回map[string][][string]
   _, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的信息:", r.Header["Accept-Encoding"])   //返回map切片
   _, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的属性值:", r.Header.Get("Accept-Encoding"))//返回string

   获取请求体中内容长度
   //len := r.ContentLength
   创建byte切片
   //body := make([]byte, len)
   将请求体中的内容读取到byte切片中
   //_, err = r.Body.Read(body)
   浏览器中显示请求体中的内容
   //_, err = fmt.Fprintln(w, "浏览器中显示请求体中的内容:",string(body))

   //在解析表单r.Form之前,需执行以下方法
   //err = r.ParseForm()
   如果form表单的action属性值的地址中也有与form表单参数名相同的请求参数,那么参数值都可以得到
   并且form表单中的参数值会在url参数值的前面
   //_, err = fmt.Fprintln(w, "请求表单请求参数:", r.Form)
   //_, err = fmt.Fprintln(w, "post请求中Form表单请求参数:", r.PostForm)


   //通过直接调用FormValue,PostFormValue直接获取请求参数中的值
   _, err = fmt.Fprintln(w, "Url中username请求参数的值:", r.FormValue("username"))
   _, err = fmt.Fprintln(w, "post请求中Form表单name请求参数:", r.PostFormValue("name"))
   if err != nil {
      fmt.Println(err)
   }
}

func main()  {
   http.HandleFunc("/req", handler)
   err := http.ListenAndServe(":8080", nil)
   if err != nil {
      fmt.Println(err)
   }
}
<html>
<head>
    <meta charset="UTF-8">
</head>
</head>
<body>
<form action="http://127.0.0.1:8080/req?username=张三" method="post">
    用户名:<input type="text" name="name"/><br/>
    用密码:<input type="text" name="password"/><br/>
    <input type="submit">
</form>
</body>
</html>

五.给客户端响应

前面讲解了如何使用处理器中的*http.Request处理用户的请求,下面说一下如何使用http.ResponseWriter来给用户响应        

1.给客户端响应一个字符串

(1).处理器中的代码

func handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("你的请求服务端已收到"))
}

(2).浏览器中的结果

你的请求服务端已收到

(3).响应报文的内容

HTTP/1.1 200 OK

Date:Fri, 10 Aug 2022 01:01:01 GMT

Content-Length: 27

Content-Type: text/plain; charset=utf-8 

2.给客户端响应一个HTML页面 

(1).处理器中的代码

func handler(w http.ResponseWriter, r *http.Request) {
    html := `<html>
        <head>
            <title>测试响应内容为网页</title>    
            <meta charset="utf-8"/>
        </head>
        <body>
            以网页形式响应
        </body>
</html>`
    w.Write([]byte(html))
}

(2).浏览器中的结果

 以网页形式响应

通过在浏览器中右键->查看网页代码可以发现:有一个html页面

(3).响应报文中的内容

HTTP/1.1 200 OK

Date:Fri, 10 Aug 2022 01:01:01 GMT

Content-Length: 199

Content-Type: text/html; charset=utf-8 

3.给客户端响应JSON格式的数据

(1).处理器中的代码

func handler(w http.ResponseWriter, r *http.Request) {
    //设置响应头中的内容类型
    w.Header().Set("Content-Type", "application/json")
    user := User{
        ID: 1,
        Username: "张三",
        Password: "123456",    
    }
    //将user转换成JSON格式
    json, _ := json.Marshal(user)
    w.Write(json)
}

(2).浏览器中的结果

{"ID":1, "Username":"张三","Password":"123456"}

(3).响应报文中的内容

HTTP/1.1 200 OK

Date:Fri, 10 Aug 2022 01:01:01 GMT

Content-Length: 199

Content-Type: application/json 

4.让客户端重定向

(1).处理器中的代码

func handler(w http.ResponseWriter, r *http.Request) {
    //以下操作必须在WriteHeader之前进行
    w.Header().Set("Location", "https://www.baidu.com")
    w.WriteHeader(302)
}

(2).响应报文中的内容

HTTP/1.1 200 OK

Location: https://www.baidu.com

Date:Fri, 10 Aug 2022 01:01:01 GMT

Content-Length:  0

Content-Type: text/plain; charset=utf-8

以上案例完整代码如下:

package main

import (
   "encoding/json"
   "fmt"
   "go_code/web_app/sql/model"
   "net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
   _, err := fmt.Fprintln(w, "请求地址:", r.URL.Path)
   _, err = fmt.Fprintln(w, "请求地址中后面的查询字符串:", r.URL.RawQuery)
   _, err = fmt.Fprintln(w, "请求地址中请求头数据:", r.Header)//返回map[string][][string]
   _, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的信息:", r.Header["Accept-Encoding"])   //返回map切片
   _, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的属性值:", r.Header.Get("Accept-Encoding"))//返回string

   获取请求体中内容长度
   //len := r.ContentLength
   创建byte切片
   //body := make([]byte, len)
   将请求体中的内容读取到byte切片中
   //_, err = r.Body.Read(body)
   浏览器中显示请求体中的内容
   //_, err = fmt.Fprintln(w, "浏览器中显示请求体中的内容:",string(body))

   //在解析表单r.Form之前,需执行以下方法
   //err = r.ParseForm()
   如果form表单的action属性值的地址中也有与form表单参数名相同的请求参数,那么参数值都可以得到
   并且form表单中的参数值会在url参数值的前面
   //_, err = fmt.Fprintln(w, "请求表单请求参数:", r.Form)
   //_, err = fmt.Fprintln(w, "post请求中Form表单请求参数:", r.PostForm)
   //

   //通过直接调用FOrmValue,PostFormValue直接获取请求参数中的值
   _, err = fmt.Fprintln(w, "Url中username请求参数的值:", r.FormValue("username"))
   _, err = fmt.Fprintln(w, "post请求中Form表单name请求参数:", r.PostFormValue("name"))
   if err != nil {
      fmt.Println(err)
   }
}

func handlerJson(w http.ResponseWriter, r *http.Request)  {
   //设置响应内容头
   w.Header().Set("Content-Text", "application/json")
   //创建User
   user := &model.User{
      ID: 3,
      Name: "李四",
      Email: "lisi@qqlcom",
   }
   //转换成json格式
   data, _ := json.Marshal(user)
   //将json格式数据响应给客户端
   _, err := w.Write(data)
   if err != nil {
      fmt.Println(err)
   }
}


func handlerRedirect(w http.ResponseWriter, r *http.Request)  {
   //设置响应头中的Location
   w.Header().Set("Location", "https://www.baidu.com")
   //设置响应状态码
   w.WriteHeader(302)
}

func main()  {
   http.HandleFunc("/req", handler)
   http.HandleFunc("/res/json", handlerJson)
   http.HandleFunc("/res/redirect", handlerRedirect)
   err := http.ListenAndServe(":8080", nil)
   if err != nil {
      fmt.Println(err)
   }
}

[上一节][golang Web开发] 2.golang web开发:操作数据库,增删改查,单元测试

[下一节][golang Web开发] 4.golang web开发:模板引擎 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值