http
go语言在http领域还是占有很大的份额的,现在很多php写的老项目都在用go重写
1,概述
编写web语言:
- java
- php,现在都在尝试使用go语言重写
- python, 豆瓣
- go语言 ==> beego,gin两个主流的web框架
https协议:我们使用浏览器访问的时候发送的就是http请求
- http是应用层的协议,底层还是依赖传输层:tcp(端连接), 网络层(ip)
- 无状态的,没一次请求都是独立的,下次请求需要重新建立连接
- https:
- http是标准协议 ===> 明文传输 不安全
- https不是标注协议 ===> https = http + ssl (非对称加密,数字证书)
- 现在所有网站都会尽量要求使用https开发:安全
2,http请求报文格式
一个http请求可以分为4部分:
- 请求行:包含3部分
- 格式: 方法 + URL + 协议版本号
- 实例: POST + /api/user + HTTP/1.1
- 请求方法:
- GET:获取数据
- POST:上传数据(表单格式,json格式)
- PUT:修改数据
- DELETE:删除数据
- 请求头
- 格式:
key: value
- 可以有多个键值对(包含协议自带,也包含用户自定义的)
- 常见重要头:
- Accept:接收数据的格式
- User-Agent:描述用户浏览器信息
- Connection:Keep-Alive(长连接) Close(短连接)
- Accept-Encoding:gzip,xxx,描述可以接受的编码
- Cookie:由服务器设置的key=value数据,客户端下次请求的时候可以携带出来
- Content-Type:
- application/-from(表示上传的数据时表单)
- appication/json(表示body的数据时json格式)
- 用户可以自定义的
- name: scott
- age: 18
- 格式:
- 空行
- 告诉服务器,请求头结束了,用于分隔
- 请求体(可选的)
- 一般在POST方法时,可配套提供BODY
- 在GET的时候也可以提供BODY,但是这样容易让人混淆,建议不要这样使用
- 上传数据的格式:
- 表单:姓名,性别,年龄
- 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),
})
}
- 放在请求头中
- 放在请求包体中
- 放在url中: GET /?score=98&id=10086&school=Beijing%20Middle&phone=%E4%B8%AD%E5%9B%BD%E7%A7%BB%E5%8A%A8
?
分隔参数和url- 多个参数之间使用
&
分隔,每一个参数数据都是一个键值对
3,http响应消息格式
http响应格式也分为4部分:
- 第一部分:状态行
- 协议格式:协议版本号 + 状态码 + 状态描述
- 实例1: HTTP/1.1 + 200 + OK
- 实例2: HTTP/1.1 + 404 + not found
- 常用状态码:
- 1xx => 客户端可以继续发送请求(一般感知不到)
- 2xx => 正常访问 200
- 3xx => 重定向
- 4xx
- 400 => bad request
- 401 => 未授权 not authorized
- 403 => forbidden
- 404 => not found
- 5xx =>
- 501 => internal error (服务器内部错误)
- 第二部分: 响应头
- Connection: keep-alive
- Content-Length: 487
- Content-Type: application/json; charset=utf-8
- Date: Wed, 30 Nov 2022 13:00:40 GMT
- Server: nginx/1.19.6
- 第三部分:空行
- 用于分隔,表示下面没有响应头了
- 第四部分:响应包体
- 通常是返回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)
}
- 对结构体进行json编码时:字段首字母必须大写,否则无法编码
- 如果json格式要求key小写,那么可以通过标签(tag)来解决
- 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
}