第24篇:http编程

一、web工作流程

  • Web服务器的工作原理可以简单地归纳为
    • 客户机通过TCP/IP协议建立到服务器的TCP连接
    • 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
    • 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
    • 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果

二、HTTP协议

  • 什么是协议:协议,是指通信的双方,在通信流程或内容格式上,共同遵守的标准。
  • 什么是http协议:http协议,是互联网中最常见的网络通信标准。
  • 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议
  • HTTP协议通常承载于TCP协议之上
  • http协议的特点
    • 通信流程:
      • 断开式(无状态)
      • 断开式:http协议每次响应完成后,会断开与客户端的连接
      • 无状态:由于服务器断开了之前的连接,就无法知晓连接间的关系
    • 内容格式:消息头和消息体

HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网上应用最为广泛的一种网络协议,定义了客户端和服务端之间请求和响应的传输标准。Go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务端的具体实现。使用net/http包,我们可以很方便地编写HTTP客户端或服务端的程序。

三、Go语言的http编程

Go语言http编程的特点:

  • a. Go原生支持http,import(“net/http”)
  • b. Go的http服务性能和nginx比较接近
  • c. 几行代码就可以实现一个web服务

1. HTTP服务端

package main

import (
    "fmt"
    "net/http"
)

func main() {
    //http://127.0.0.1:8000/go
    // 单独写回调函数
    http.HandleFunc("/go", myHandler)
    //http.HandleFunc("/ungo",myHandler2 )
    //该方法用于在指定的 TCP 网络地址 addr 进行监听,然后调用服务端处理程序来处理传入的连接请求。
    //该方法有两个参数:第一个参数 addr 即监听地址;第二个参数表示服务端处理程序,通常为空
    // addr:监听的地址
    // handler:回调函数
    //第二个参数为空意味着服务端调用 http.DefaultServeMux 进行处理
    http.ListenAndServe("127.0.0.1:8000", nil)
}

//服务端编写的业务逻辑处理程序
//hander函数: 具有func(w http.ResponseWriter, r *http.Requests)签名的函数
func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println(r.RemoteAddr, "连接成功")
    // 请求方式:GET POST DELETE PUT UPDATE
    fmt.Println("method:", r.Method)
    // /go
    fmt.Println("url:", r.URL.Path)
    fmt.Println("header:", r.Header)
    fmt.Println("body:", r.Body)
    // 回复
    w.Write([]byte("www.5lmh.com"))
}

2. HTTP客户端

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
     //get方式请求一个资源
    //resp, _ := http.Get("http://www.baidu.com")
    //fmt.Println(resp)
    resp, _ := http.Get("http://127.0.0.1:8000/go")
    defer resp.Body.Close()
    // 200 OK
    fmt.Println("header = ", resp.Header)
    fmt.Printf("resp status %s\nstatusCode %d\n", resp.Status, resp.StatusCode)
    fmt.Printf("body type = %T\n", resp.Body)

    buf := make([]byte, 1024)  // 切片缓冲区
    for {
        // 接收服务端信息
        n, err := resp.Body.Read(buf)  // 读取body包内容
        if err != nil && err != io.EOF {
            fmt.Println(err)
            return
        } else {
            fmt.Println("读取完毕")
            res := string(buf[:n])
            fmt.Println(res)
            break
        }
    }
}

3. http常见请求方法

HTTP请求的方法:

HTTP/1.1协议中共定义了八种方法(有时也叫“动作”),来表明Request-URL指定的资源不同的操作方式

  • 1、OPTIONS
返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性
  • 2、HEAD
向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。
  • 3、GET
向特定的资源发出请求。它本质就是发送一个请求来取得服务器上的某一资源。资源通过一组HTTP头和呈现数据(如HTML文本,或者图片或者视频等)返回给客户端。GET请求中,永远不会包含呈现数据。
  • 4、POST
向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数:web_submit_data,web_submit_form
  • 5、PUT
向指定资源位置上传其最新内容
  • 6、DELETE
请求服务器删除Request-URL所标识的资源
  • 7、TRACE
回显服务器收到的请求,主要用于测试或诊断
  • 8、CONNECT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。

注意:

  • 1)方法名称是区分大小写的,当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Mothod Not Allowed);当服务器不认识或者不支持对应的请求方法时,应返回状态码501(Not Implemented)。
  • 2)HTTP服务器至少应该实现GET和HEAD/POST方法,其他方法都是可选的,此外除上述方法,特定的HTTP服务器支持扩展自定义的方法。

常见状态码

http.StatusContinue = 100
http.StatusOK = 200
http.StatusFound = 302
http.StatusBadRequest = 400
http.StatusUnauthorized = 401
http.StatusForbidden = 403
http.StatusNotFound = 404
http.StatusInternalServerError = 500

get 和 post区别

区别:
    get请求无消息体,只能携带少量数据
    post请求有消息体,可以携带大量数据
  携带数据的方式:
    get请求将数据放在url地址中
    post请求将数据放在消息体中
    GET请求请提交的数据放置在HTTP请求协议头中,而POST提交的数据则放在实体数据中; 
    GET方式提交的数据最多只能有1024字节,而POST则没有此限制。 

4. 表单与panic处理

package main

import (
    "io"
    "log"
    "net/http"
)

const form = `<html><body><form action="#" method="post" name="bar">
                    <input type="text" name="in"/>
                    <input type="text" name="in"/>
                     <input type="submit" value="Submit"/>
             </form></body></html>`

func SimpleServer(w http.ResponseWriter, request *http.Request) {
    io.WriteString(w, "<h1>hello, world</h1>")
    panic("test test")
}

func FormServer(w http.ResponseWriter, request *http.Request) {
    w.Header().Set("Content-Type", "text/html")
    switch request.Method {
    case "GET":
        io.WriteString(w, form)
    case "POST":
        request.ParseForm()
        io.WriteString(w, request.Form["in"][1])
        io.WriteString(w, "\n")
        io.WriteString(w, request.FormValue("in"))
    }
}
func main() {
    http.HandleFunc("/test1", logPanics(SimpleServer))
    http.HandleFunc("/test2", logPanics(FormServer))
    if err := http.ListenAndServe(":8088", nil); err != nil {
    }
}

func logPanics(handle http.HandlerFunc) http.HandlerFunc {
    return func(writer http.ResponseWriter, request *http.Request) {
        defer func() {
            if x := recover(); x != nil {
                log.Printf("[%v] caught panic: %v", request.RemoteAddr, x)
            }
        }()
        handle(writer, request)
    }
}

5. 模板

1. 替换

{{.字段名}}

- if 判断
- if 常见操作
- {{.}}
- {{with .Var}}... {{end}}
2. 循环

{{range.}}... {{end}}

3. 示例
// main.go
package main

import (
    "fmt"
    "os"
    "text/template"
)

type Person struct {
    Name  string
    Title string
    age   string
}

func main() {
    t, err := template.ParseFiles("day10/template/index.html")
    if err != nil {
        fmt.Println("parse file err:", err)
        return
    }
    p := Person{Name: "Mary", age: "31", Title: "我的个人网站"}
    if err := t.Execute(os.Stdout, p); err != nil {
        fmt.Println("There was an error:", err.Error())
    }
}

index.html

<html>
    <head>
        <title>{{.Title}}</title>
    </head>
    <body>
        <p> hello, {{.Name}}</p>
        <p> {{.}}</p>
    </body>
 </html>

渲染结果

<html>
    <head>
        <title>我的个人网站</title>
    </head>
    <body>
        <p> hello, Mary</p>
        <p> {Mary 我的个人网站 31}</p>
    </body>
</html>

示例2

// main.go
package main

import (
    "fmt"
    "html/template"
    "io"
    "net/http"
)

var myTemplate *template.Template

type Result struct {
    output string
}

func (p *Result) Write(b []byte) (n int, err error) {
    fmt.Println("called by template")
    p.output += string(b)
    return len(b), nil
}

type Person struct {
    Name  string
    Title string
    Age   int
}

func userInfo(w http.ResponseWriter, r *http.Request) {
    fmt.Println("handle hello")
    //fmt.Fprintf(w, "hello ")
    var arr []Person
    p := Person{Name: "Mary001", Age: 10, Title: "我的个人网站"}
    p1 := Person{Name: "Mary002", Age: 10, Title: "我的个人网站"}
    p2 := Person{Name: "Mary003", Age: 10, Title: "我的个人网站"}
    arr = append(arr, p)
    arr = append(arr, p1)
    arr = append(arr, p2)

    resultWriter := &Result{}
    io.WriteString(resultWriter, "hello world")
    err := myTemplate.Execute(w, arr)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println("template render data:", resultWriter.output)
    //myTemplate.Execute(w, p)
    //myTemplate.Execute(os.Stdout, p)
    //file, err := os.OpenFile("C:/test.log", os.O_CREATE|os.O_WRONLY, 0755)
    //if err != nil {
    //    fmt.Println("open failed err:", err)
    //    return
    //}
}

func initTemplate(filename string) (err error) {
    myTemplate, err = template.ParseFiles(filename)
    if err != nil {
        fmt.Println("parse file err:", err)
        return
    }
    return
}

func main() {
    initTemplate("day10/template_http/index.html")
    http.HandleFunc("/user/info", userInfo)
    err := http.ListenAndServe("0.0.0.0:8880", nil)
    if err != nil {
        fmt.Println("http listen failed")
    }
}

index.html

<html>
    <head>
    </head>
    <body>
        <p>hello world</p>
        <table border="1">
        {{range .}}
            <tr>
                <td>{{.Name}}</td> <td>{{.Age}}</td><td>{{.Title}}</td>
            </tr>
         {{end}}
        </table>
    </body>
</html>

浏览器访问

四、http请求

1. 请求的结构

HTTP的交互以请求和响应的应答模式。go的请求我们早就见过了,handler函数的第二个参数http.Requests。其结构为:

type Request struct {
    Method string

    URL *url.URL
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    Header Header
    Body io.ReadCloser
    ContentLength int64
    TransferEncoding []string
    Close bool
    Host string
    Form url.Values
    PostForm url.Values
    MultipartForm *multipart.Form
  ....
    ctx context.Context
}

从request结构可以看到,http请求的基本信息都囊括了。对于请求而言,主要关注一下请求的URL,Method,Header,Body这些结构。

2. URL

HTTP的url请求格式为scheme://[userinfo@]host/path[?query][#fragment], go的提供了一个URL结构,用来映射HTTP的请求URL。

type URL struct {
  Scheme   string
  Opaque   string
  User     *Userinfo
  Host     string
  Path     string
  RawQuery string
  Fragment string
}

URL的格式比较明确,其实更好的名词应该是URI,统一资源定位。url中比较重要的是查询字符串query。通常作为get请求的参数。query是一些使用&符号分割的key1=value1&key2=value2键值对,由于url编码是ASSIC码,因此query需要进行urlencode。go可以通过request.URI.RawQuery读取query

func indexHandler(w http.ResponseWriter, r *http.Request) {
    info := fmt.Sprintln("URL", r.URL, "HOST", r.Host, "Method", r.Method, "RequestURL", r.RequestURI, "RawQuery", r.URL.RawQuery)
    fmt.Fprintln(w, info)
}
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
URL /?lang=zh&version=1.1.0 HOST 127.0.0.1:8000 Method POST RequestURL /?lang=zh&version=1.1.0 RawQuery lang=zh&version=1.1.0

3. header

header也是HTTP中重要的组成部分。Request结构中就有Header结构,Header本质上是一个map(map[string][]string)。将http协议的header的key-value进行映射成一个图:

Host: example.com
accept-encoding: gzip, deflate
Accept-Language: en-us
fOO: Bar
foo: two


Header = map[string][]string{
"Accept-Encoding": {"gzip, deflate"},
"Accept-Language": {"en-us"},
"Foo": {"Bar", "two"},
}

header中的字段包含了很多通信的设置,很多时候请求都需要指定Content-Type

func indexHandler(w http.ResponseWriter, r *http.Request) {

    info := fmt.Sprintln(r.Header.Get("Content-Type"))
    fmt.Fprintln(w, info)
}
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/x-www-form-urlencoded

Golng 提供了不少打印函数,基本上分为三类三种。即 PrintPrintlnPrintf
Print比较简单,打印输出到标准输出流,Println则也一样不同在于多打印一个换行符。至于Printf则是打印格式化字符串,三个方法都返回打印的bytes数。SprintSprinlnSprintf则返回打印的字符串,不会输出到标准流中。FprintFprintfFprinln则把输出的结果打印输出到io.Writer接口中,http中则是http.ReponseWriter这个对象中,返回打印的bytes数。

4. Body

http中数据通信,主要通过body传输。go把body封装成Request的Body,它是一个ReadCloser接口。接口方法Reader也是一个接口,后者有一个Read(p []byte) (n int, err error)方法,因此body可以通过读取byte数组获取请求的数据。

func indexHandler(w http.ResponseWriter, r *http.Request) {

    info := fmt.Sprintln(r.Header.Get("Content-Type"))
    len := r.ContentLength
    body := make([]byte, len)
    r.Body.Read(body)
    fmt.Fprintln(w, info, string(body))
}
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/x-www-form-urlencoded
 name=vanyar&age=27

可见,当请求的content-type为application/x-www-form-urlencoded, body也是和query一样的格式,key-value的键值对。换成json的请求方式则如下:

curl -X POST -H "Content-Type: application/json" -d '{name: "vanyar", age: 27}' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/json
 {name: "vanyar", age: 27}

multipart/form-data的格式用来上传图片,请求的body如下:

curl -X POST -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "name=vanyar" -F "age=27" "http://127.0.0.1:8000?lang=zh&version=1.1.0"
multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW; boundary=------------------------d07972c7800e4c23
 --------------------------d07972c7800e4c23
Content-Disposition: form-data; name="name"

vanyar
--------------------------d07972c7800e4c23
Content-Disposition: form-data; name="age"

27
--------------------------d07972c7800e4c23--

5. 表单

解析body可以读取客户端请求的数据。而这个数据是无论是键值对还是form-data数据,都比较原始。直接读取解析还是挺麻烦的。这些body数据通常也是表单提供。因此go提供处理这些表单数据的方法。

Form

go提供了ParseForm方法用来解析表单提供的数据,即content-type 为 x-www-form-urlencode的数据。

func indexHandler(w http.ResponseWriter, r *http.Request) {

    contentType := fmt.Sprintln(r.Header.Get("Content-Type"))

    r.ParseForm()
    fromData := fmt.Sprintf("%#v", r.Form)
    fmt.Fprintf(w, contentType, fromData)

}
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/x-www-form-urlencoded
%!(EXTRA string=url.Values{"name":[]string{"vanyar"}, "age":[]string{"27"}, "lang":[]string{"zh"}, "version":[]string{"1.1.0"}})%

用来读取数据的结构和方法大致有下面几个:

fmt.Println(r.Form["lang"])
fmt.Println(r.PostForm["lang"])
fmt.Println(r.FormValue("lang"))
fmt.Println(r.PostFormValue("lang"))

其中r.Formr.PostForm必须在调用ParseForm之后,才会有数据,否则则是空数组。
r.FormValuer.PostFormValue("lang")无需ParseForm的调用就能读取数据。

此外r.Form和r.PostForm都是数组结构,对于body和url都存在的同名参数,r.Form会有两个值,即 [“en”, “zh”],而带POST前缀的数组和方法,都只能读取body的数据。

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'name=vanyar&age=27&lang=en' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
application/x-www-form-urlencoded
%!(EXTRA string=url.Values{"version":[]string{"1.1.0"}, "name":[]string{"vanyar"}, "age":[]string{"27"}, "lang":[]string{"en", "zh"}})%

此时可以看到,lang参数不仅url的query提供了,post的body也提供了,go默认以body的数据优先,两者的数据都有,并不会覆盖。

如果不想读取url的参数,调用PostFormPostFormValue读取字段的值即可。

r.PostForm["lang"][0]
r.PostFormValue["lang"]

对于form-data的格式的数据,ParseForm的方法只会解析url中的参数,并不会解析body中的参数。

curl -X POST -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "name=vanyar" -F "age=27" -F "lang=en" "http://127.0.0.1:8000?lang=zh&version=1.1.0"
multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW; boundary=------------------------5f87d5bfa764488d
%!(EXTRA string=url.Values{"lang":[]string{"zh"}, "version":[]string{"1.1.0"}}
)%

因此当请求的content-type为form-data的时候,ParseFrom则需要改成 MutilpartFrom,否则r.From是读取不到body的内容,只能读取到query string中的内容。

MutilpartFrom

ParseMutilpartFrom方法需要提供一个读取数据长度的参数,然后使用同样的方法读取表单数据,MutilpartFrom只会读取body的数据,不会读取url的query数据。

func indexHandler(w http.ResponseWriter, r *http.Request) {
    r.ParseMultipartForm(1024)

    fmt.Println(r.Form["lang"])
    fmt.Println(r.PostForm["lang"])
    fmt.Println(r.FormValue("lang"))
    fmt.Println(r.PostFormValue("lang"))
    fmt.Println(r.MultipartForm.Value["lang"])

    fmt.Fprintln(w, r.MultipartForm.Value)
}

可以看到请求之后返回 map[name:[vanyar] age:[27] lang:[en]]。即r.MultipartForm.Value并没有url中的参数。

总结一下,读取urlencode的编码方式,只需要ParseForm即可,读取form-data编码需要使用ParseMultipartForm方法。如果参数中既有url,又有body,From和FromValue方法都能读取。而带Post前缀的方法,只能读取body的数据内容。其中MultipartForm的数据通过r.MultipartForm.Value访问得到。

6. 文件上传

form-data格式用得最多方式就是在图片上传的时候。r.MultipartForm.Value是post的body字段数据,r.MultipartForm.File则包含了图片数据:

func indexHandler(w http.ResponseWriter, r *http.Request) {

    r.ParseMultipartForm(1024)
    fileHeader := r.MultipartForm.File["file"][0]
    fmt.Println(fileHeader)
    file, err := fileHeader.Open()
    if err == nil{
        data, err := ioutil.ReadAll(file)
        if err == nil{
            fmt.Println(len(data))
            fmt.Fprintln(w, string(data))
        }
    }
    fmt.Println(err)
}

发出请求之后,可以看见返回了图片。当然,go提供了更好的工具函数r.FormFile,直接读取上传文件数据。而不需要再使用 ParseMultipartForm 方法。

file, _, err := r.FormFile("file")

if err == nil{
    data, err := ioutil.ReadAll(file)
    if err == nil{
        fmt.Println(len(data))
        fmt.Fprintln(w, string(data))
    }
}
fmt.Println(err)

这种情况只适用于出了文件字段没有其他字段的时候,如果仍然需要读取lang参数,还是需要加上 ParseMultipartForm 调用的。读取到了上传文件,接下来就是很普通的写文件的io操作了。

7. JSON

现在流行前后端分离,客户端兴起了一些框架,angular,vue,react等提交的数据,通常习惯为json的格式。对于json格式,body就是原生的json字串。也就是go解密json为go的数据结构。

type Person struct {
    Name string
    Age int
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    decode := json.NewDecoder(r.Body)
    var p Person
    err := decode.Decode(&p)
    if err != nil{
        log.Fatalln(err)
    }
    info := fmt.Sprintf("%T\n%#v\n", p, p)
    fmt.Fprintln(w, info)
}
☁  ~  curl -X POST -H "Content-Type: application/json"  -d '{"name": "vanyar", "age": 27 }' "http://127.0.0.1:8000?lang=zh&version=1.1.0"
main.Person
main.Person{Name:"vanyar", Age:27}

更多关于json的细节,以后再做讨论。访问官网文档获取更多的信息。

五、http响应

1. Response

请求和响应是http的孪生兄弟,不仅它们的报文格式类似,相关的处理和构造也类似。go构造响应的结构是ResponseWriter接口。

type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(int)
}

里面的方法也很简单,Header方法返回一个header的map结构。WriteHeader则会返回响应的状态码。Write返回给客户端的数据。

我们已经使用了fmt.Fprintln 方法,直接向w写入响应的数据。也可以调用Write方法返回的字符。

func indexHandler(w http.ResponseWriter, r *http.Request) {
    str := `<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>`
    w.Write([]byte(str))
}
curl -i http://127.0.0.1:8000/
HTTP/1.1 200 OK
Date: Wed, 07 Dec 2016 09:13:04 GMT
Content-Length: 95
Content-Type: text/html; charset=utf-8

Go Web Programming

Hello World

%

go根据返回的字符,自动修改成了text/htmlContent-Type格式。返回数据自定义通常需要修改header相关信息。

func indexHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(501)
    fmt.Fprintln(w, "No such service, try next door")
}
curl -i http://127.0.0.1:8000/
HTTP/1.1 501 Not Implemented
Date: Wed, 07 Dec 2016 09:14:58 GMT
Content-Length: 31
Content-Type: text/plain; charset=utf-8

No such service, try next door

2. 重定向

重定向的功能可以更加设置header的location和http状态码实现。

func indexHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Location", "https://google.com")
    w.WriteHeader(302)
}
curl -i http://127.0.0.1:8000/
HTTP/1.1 302 Found
Location: https://google.com
Date: Wed, 07 Dec 2016 09:20:19 GMT
Content-Length: 31
Content-Type: text/plain; charset=utf-8

重定向是常用的功能,因此go也提供了工具方法,http.Redirect(w, r, "https://google.com", http.StatusFound)

与请求的Header结构一样,w.Header也有几个方法用来设置headers

func (h Header) Add(key, value string) {
    textproto.MIMEHeader(h).Add(key, value)
}

func (h Header) Set(key, value string) {
    textproto.MIMEHeader(h).Set(key, value)
}

func (h MIMEHeader) Add(key, value string) {
    key = CanonicalMIMEHeaderKey(key)
    h[key] = append(h[key], value)
}

func (h MIMEHeader) Set(key, value string) {
    h[CanonicalMIMEHeaderKey(key)] = []string{value}
}

Set和Add方法都可以设置headers,对于已经存在的key,Add会追加一个值value的数组中,,set则是直接替换value的值。即 append和赋值的差别。

3. Json

请求发送的数据可以是JSON,同样响应的数据也可以是json。restful风格的api也是返回json格式的数据。对于请求是解码json字串,响应则是编码json字串,go提供了标准库 encoding/json

type Post struct {
    User string
    Threads []string
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    post := &Post{
        User: "vanyar",
        Threads: []string{"first", "second", "third"},
    }
    json, _ := json.Marshal(post)
    w.Write(json)
}
curl -i http://127.0.0.1:8000/
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 08 Dec 2016 06:45:17 GMT
Content-Length: 54

{"User":"vanyar","Threads":["first","second","third"]}%

当然,更多的json处理细节稍后再做介绍。

本节小结

对于web应用程式,处理请求,返回响应是基本的内容。golang很好的封装了Request和ReponseWriter给开发者。无论是请求还是响应,都是针对url,header和body相关数据的处理。也是http协议的基本内容。


文章作者: 张亚飞
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 张亚飞 !
评论
 上一篇
第25篇:单元测试 第25篇:单元测试
一、如何写好单元测试单元测试(Unit Tests, UT) 是一个优秀项目不可或缺的一部分,特别是在一些频繁变动和多人合作开发的项目中尤为重要。你或多或少都会有因为自己的提交,导致应用挂掉或服务宕机的经历。如果这个时候你的修改导致测试用例
2021-01-08
下一篇 
第1篇:初识pandas与Series 第1篇:初识pandas与Series
pandas是Python的核心数据分析支持库,提供了快速、灵活、明确的数据结构,旨在简单、直观地处理关系型、标记型数据。pandas 的目标是成为 Python 数据分析实践与实战的必备高级工具,其长远目标是成为最强大、最灵活、可以支持任何语言的开源数据分析工具。经过多年不懈的努力,pandas 离这个目标已经越来越近了。
  目录