Skip to main content

http

go语言在http领域还是占有很大的份额的,现在很多php写的老项目都在用go重写

1,概述

编写web语言:

  1. java
  2. php,现在都在尝试使用go语言重写
  3. python, 豆瓣
  4. go语言 ==> beego,gin两个主流的web框架

https协议:我们使用浏览器访问的时候发送的就是http请求

  1. http是应用层的协议,底层还是依赖传输层:tcp(端连接), 网络层(ip)
  2. 无状态的,没一次请求都是独立的,下次请求需要重新建立连接
  3. https:
    1. http是标准协议 ===> 明文传输 不安全
    2. https不是标注协议 ===> https = http + ssl (非对称加密,数字证书)
    3. 现在所有网站都会尽量要求使用https开发:安全

2,http请求报文格式

一个http请求可以分为4部分:

  1. 请求行:包含3部分
    1. 格式: 方法 + URL + 协议版本号
    2. 实例: POST + /api/user + HTTP/1.1
    3. 请求方法:
      1. GET:获取数据
      2. POST:上传数据(表单格式,json格式)
      3. PUT:修改数据
      4. DELETE:删除数据
  2. 请求头
    1. 格式:key: value
    2. 可以有多个键值对(包含协议自带,也包含用户自定义的)
    3. 常见重要头:
      1. Accept:接收数据的格式
      2. User-Agent:描述用户浏览器信息
      3. Connection:Keep-Alive(长连接) Close(短连接)
      4. Accept-Encoding:gzip,xxx,描述可以接受的编码
      5. Cookie:由服务器设置的key=value数据,客户端下次请求的时候可以携带出来
      6. Content-Type:
        1. application/-from(表示上传的数据时表单)
        2. appication/json(表示body的数据时json格式)
      7. 用户可以自定义的
        1. name: scott
        2. age: 18
  3. 空行
    1. 告诉服务器,请求头结束了,用于分隔
  4. 请求体(可选的)
    1. 一般在POST方法时,可配套提供BODY
    2. 在GET的时候也可以提供BODY,但是这样容易让人混淆,建议不要这样使用
    3. 上传数据的格式:
      1. 表单:姓名,性别,年龄
      2. json数据格式

tcp属于http的下游,属于网络层的协议,因此它也可以用于接收http请求,只是它没有http那样专门解析的工具,帮你去解析头,解析体

用tcp的库去解析http就很难解析http的细节 但是数据还是可以抓的到的

我们来启动tcp的server

接着用postman去请求:http://127.0.0.1:8848

来看看goland:

我们用get携带数据,然后再发送请求:

之前我们说过请求头是可以自定义数据的,我们来自定义几个数据看看:

来看看goland:

在前面添加header的基础上 我们再去添加body

来看看goland:

在前面的基础上 我们再去添加Params

来看看goland:

多个params用 url用&连接:

来看看goland:

这里有个细节:当url中带有空格或者中文字符的时候会转义,后端接收的话 就不准确,我们需要用func QueryUnescape(s string) (string, error)去解析, 这里有个我博客前台中文关键字搜索的示例代码:

import(
"net/url"
)

func GetSearchPageInfo(c *gin.Context) {
var title = c.Query("title")
escapeTitle, _ := url.QueryUnescape(title)
pg, _ := db.GetSearchPageInfo(escapeTitle)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"pages": int(pg),
})
}
总结: 前端与后台传输数据的方法
  1. 放在请求头中
  2. 放在请求包体中
  3. 放在url中: GET /?score=98&id=10086&school=Beijing%20Middle&phone=%E4%B8%AD%E5%9B%BD%E7%A7%BB%E5%8A%A8
    1. ?分隔参数和url
    2. 多个参数之间使用&分隔,每一个参数数据都是一个键值对

3,http响应消息格式

http响应格式也分为4部分:

  1. 第一部分:状态行
    1. 协议格式:协议版本号 + 状态码 + 状态描述
    2. 实例1: HTTP/1.1 + 200 + OK
    3. 实例2: HTTP/1.1 + 404 + not found
    4. 常用状态码:
      1. 1xx => 客户端可以继续发送请求(一般感知不到)
      2. 2xx => 正常访问 200
      3. 3xx => 重定向
      4. 4xx
        1. 400 => bad request
        2. 401 => 未授权 not authorized
        3. 403 => forbidden
        4. 404 => not found
      5. 5xx =>
        1. 501 => internal error (服务器内部错误)
  2. 第二部分: 响应头
    1. Connection: keep-alive
    2. Content-Length: 487
    3. Content-Type: application/json; charset=utf-8
    4. Date: Wed, 30 Nov 2022 13:00:40 GMT
    5. Server: nginx/1.19.6
  3. 第三部分:空行
    1. 用于分隔,表示下面没有响应头了
  4. 第四部分:响应包体
    1. 通常是返回json数据

4,http server

package main

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

func main() {
//注册路由
// xxxx/user ===》 func1
// xxxx/name ===》 func2
// xxxx/id ===》 func3

//http://127.0.0.1:8898/user func是回调函数 用于路由的响应,这个回调函数的原型是固定的
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
//request ===》包含客户端发来的数据
fmt.Println("用户请求详情:")
fmt.Println("request:",r)

//writer ==>通过writer将数据返回给客户端
_,_ = io.WriteString(w,"这是/user请求返回的数据")
})

//http://127.0.0.1:8898/name
http.HandleFunc("/name", func(w http.ResponseWriter, r *http.Request) {
_,_ = io.WriteString(w,"这是/name请求返回的数据")
})

//http://127.0.0.1:8898/id
http.HandleFunc("/id", func(w http.ResponseWriter, r *http.Request) {
_,_ = io.WriteString(w,"这是/id请求返回的数据")
})

fmt.Println("http server started")
//函数原型:func ListenAndServe(addr string, handler Handler) error
//传入nil则使用默认的handler
if err:=http.ListenAndServe("127.0.0.1:8898",nil);err!=nil{
fmt.Println("http start failed, err:",err)
return
}
}

访问: http://127.0.0.1:8898/user

来看看服务端响应:

5,json

{
"json": [
"rigid",
"better for data interchange"
],
"yaml": [
"slim and flexible",
"better for configuration"
],
"object": {
"key": "value",
"array": [
{
"null_value": null
},
{
"boolean": true
},
{
"integer": 1
},
{
"alias": "aliases are like variables"
},
{
"alias": "aliases are like variables"
}
]
},
"paragraph": "Blank lines denote\nparagraph breaks\n",
"content": "Or we\ncan auto\nconvert line breaks\nto save space",
"alias": {
"bar": "baz"
},
"alias_reuse": {
"bar": "baz"
}
}

记住,json语法要求最后一个元素后面不能加,

json编解码

在网络传输的时候,把结构体编码成json字符串,传输 ===> 结构体 ===> 字符串 ===> 编码

接收字符串,需要将字符串转换成结构体,然后操作 ===> 字符串 ===> 结构体 ====> 解密

package main

import (
"encoding/json"
"fmt"
)

type Student struct {
Id int
Name string
Age int
//注意,这里的gender 故意小写 小写字母开头的在json编解码时会被忽略掉
gender string
}

func main() {
scott:=Student{
Id: 01,
Name: "scott",
Age: 18,
gender: "boy",
}

//编码(也叫序列化): 结构体 ==》字符串
//func Marshal(v interface{}) ([]byte, error)
encodeinfo, err:= json.Marshal(&scott)
if err!=nil{
fmt.Println("json.Marshal err:",err)
return
}
fmt.Println(string(encodeinfo))

//对端接收到数据
//反序列化(解码): 字符串 ===》 结构体
var stu Student
//func Unmarshal(data []byte, v interface{}) error
if err:=json.Unmarshal(encodeinfo,&stu);err!=nil{
//注意这里的err和上面的err不一样,它是属于这个作用域的
fmt.Println("json.Unmarshal err:",err)
return
}

fmt.Println("id:",stu.Id)
fmt.Println("name:",stu.Name)
fmt.Println("age:",stu.Age)
fmt.Println("gender:",stu.gender)

}

结构体标签

结构体加json标签主要是为了json编解码用的

通过上面的示例可以看出,结构体编码之后,key都是大写的,但是在实际开发中,key通常都是小写的,引入json tag的目的就是起引导作用--将key编程小写

package main

import (
"encoding/json"
"fmt"
)

type Teacher struct {
Name string `json:"-"` //===>在遇到json编码时,这个编码不参与
Age int `json:"age"`

//注意 height,string之间不可以加空格 否则height还是转化为int
Height int `json:"height,string"` //在json编码时,age转化为string类型

Subject string `json:"Subject_name"`// 定义别名,在编码时,这个字段会编码成 Subject_name

Address string `json:"address,omitempty"`//在json编码时,如果这个字段是空的则不参与编码
Salary int `json:"slary,omitempty"`//在json编码时,如果这个字段是空的则不参与编码

//注意,这里的gender 故意小写 小写字母开头的在json编解码时会被忽略掉
gender string
}

func main() {
t1 := Teacher{
Name: "Judy",
Age: 20,
Height: 165,
Subject: "English",
Address: "",
Salary: 8848,
gender: "Woman",
}

fmt.Println("t1:",t1)

encodeInfo,_:=json.Marshal(&t1)
fmt.Println("encodeInfo:",string(encodeInfo))

var t2 Teacher
_=json.Unmarshal(encodeInfo,&t2)
fmt.Println("t2.Name:",t2.Name)
fmt.Println("t2.Age:",t2.Age)
fmt.Println("t2.Height:",t2.Height)
fmt.Println("t2.Subject:",t2.Subject)
fmt.Println("t2.Address:",t2.Address)
fmt.Println("t2.Salary:",t2.Salary)
fmt.Println("t2.gender:",t2.gender)
}

总结
  1. 对结构体进行json编码时:字段首字母必须大写,否则无法编码
  2. 如果json格式要求key小写,那么可以通过标签(tag)来解决
  3. tag细节:
type Teacher struct {
Name string `json:"-"` //===>在遇到json编码时,这个编码不参与
Age int `json:"age"`

//注意 height,string之间不可以加空格 否则height还是转化为int
Height int `json:"height,string"`
//在json编码时,age转化为string类型,一定要2个字段:名字,类型 中间不能有空格

Subject string `json:"Subject_name"`// 定义别名,在编码时,这个字段会编码成 Subject_name

Address string `json:"address,omitempty"`//在json编码时,如果这个字段是空的则不参与编码
Salary int `json:"slary,omitempty"`//在json编码时,如果这个字段是空的则不参与编码

//注意,这里的gender 故意小写 小写字母开头的在json编解码时会被忽略掉
gender string
}