Go基础Http编程

Posted Ricky_0528

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go基础Http编程相关的知识,希望对你有一定的参考价值。

文章目录

1. http协议

  • http:超文本传输协议Hyper Text Transfer Protocol
  • http属于应用层协议,它在传输层用的是tcp协议
  • 无状态,对事务处理没有记忆能力(对比TCP协议里的确认号),如果要保存状态需要引用其他技术,如:cookie
  • 无连接,每次连接只处理一个请求,早期带宽和计算资源有限,这么做是为了追求传输速度快,后来通过Connection: Keep-Alive实现长连接,http1.1废弃了Keep-Alive,默认支持长连接

1.1 请求方法

请求方法解释
GET请求获取Request-URI所标识的资源
POST向URI提交数据(例如提交表单或上传数据)
HEAD类似于GET,返回的响应中没有具体的内容,用于获取报头
PUT对服务器上已存在的资源进行更新
DELETE请求服务器删除指定的页面
CONNECTHTTP/1.1预留,能够将连接改为管道方式的代理服务器
OPTIONS查看服务端性能
TRACE回显服务器收到的请求,主要用于测试或诊断
PATCH同PUT,可只对资源的一部分更新,资源不存在时会创建

GET、POST和HEAD是http1.0就有的,后面的请求方法是http1.1新增的。客户端发起一个请求时,这个请求可能要穿过防火墙、代理、网关或其他一些应用程序。每个中间节点都可能会修改原始的HTTP请求。TRACE 方法允许客户端在 最终将请求发送给服务器时,看看它变成了什么样子。TRACE请求会在目的服务器端发起一个环回诊断。行程最后一站的服务器会弹回一条TRACE响应,并在响应主体中携带它收到的原始请求报文。这样客户端就可以查看在所有中间HTTP应用程序组成的请求/响应链上,原始报文是否,以及如何被毁坏或修改过。CONNECT方法是HTTP/1.1协议预留的,能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。OPTIONS方法请求Web服务器告知其支持的各种功能。通过使用OPTIONS,客户端可以在与服务器进行交互之前,确定服务器的能力,这样它就可以更方便地与具备不同特性的代理和服务器进行互操作了。
实际中server对各种request method的处理方式可能不是按协义标准来的,比如server收到PUT请求时偏偏执行DELETE操作,同理仅用一个GET方法也能实现增删改查的全部功能。大多数浏览器只支持GET和POST。

1.2 URL

  • URI:uniform resource identifier,统一资源标识符,用来唯一的标识一个资源
  • URL: uniform resource locator,统一资源定位器,它是一种具体的URI,指明了如何locate这个资源
  • URL举例:

1.3 协议版本

现在广泛应用的协议版本是HTTP/1.1

1.4 请求头

Header解释示例
Accept指定客户端能够接收的内容类型Accept: text/plain, text/html
Accept-Charset浏览器可以接受的字符编码集Accept-Charset: iso-8859-5
Accept-Encoding指定浏览器可以支持的web服务器返回内容压缩编码类型Accept-Encoding: compress, gzip
Accept-Language浏览器可接受的语言Accept-Language: en,zh
AuthorizationHTTP授权的授权证书Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control指定请求和响应遵循的缓存机制Cache-Control: no-cache
Connection表示是否需要持久连接(HTTP 1.1默认进行持久连接)Connection: close
CookieHTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器Cookie: $Version=1; Skin=new;
Content-Length请求的内容长度Content-Length: 348
Content-Type指定正文(body)的数据格式Content-Type: application/x-www-form-urlencoded
User-Agent浏览器信息Mozilla/5.0 (Windows NT 6.1; Win64; x64)

Content-Type

  • application/x-www-form-urlencoded
    • 浏览器的原生form表单,如果不设置 Content-Type 属性,则默认以 application/x-www-form-urlencoded 方式传输数据
    • 正文例如:name=manu&message=this_is_great
  • multipart/form-data
    • 上传文件时使用multipart/form-data,支持多种文件格式
    • 正文例如: name="text"name=“file”; filename="chrome.png"Content-Type: image/png… content of chrome.png
  • application/json
    • 正文例如:“title”:“test”,“sub”:[1,2,3]
  • text/xml
    • 正文例如:<?xml version="1.0"?> examples.getStateName

1.5 请求正文

GET请求没有请求正文,POST即可以把一部分参数放在url里,也可以把一部分参数放在请求正文里,如下是一个完整的POST请求

POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

name=manu&message=this_is_great

GET和POST的区别:

  • get的请求参数全部在url里,参数变时url就变;post可以把参数放到请求正文里,参数变时url不变
  • 虽然http协议并没有对url和请求正文做长度限制,但在实际中浏览器对url的长度限制比请求正文要小很多,所以post可以提交的数据比get要大得多
  • get比post更容易受到攻击(源于get的参数直接暴露在url里)

1.6 http response

响应状态及话术

codephrase说明
200Ok请求成功
400Bad Request客户端有语法错误,服务端不理解
401Unauthorized请求未经授权
403Forbidden服务端拒绝提供服务
404Not Found请求资源不存在
500Internal Server Error服务器发生不可预期的错误
503Server Unavailable服务器当前有问题,过段时间可能恢复

响应头

Header解释示例
Allow对某网络资源的有效的请求行为Allow: GET, HEAD
Date原始服务器消息发出的时间Date: Tue, 15 Nov 2010 08:12:31 GMT
Content-Encoding服务器支持的返回内容压缩编码类型Content-Encoding: gzip
Content-Language响应体的语言Content-Language: en,zh
Content-Length响应体的长度Content-Length: 348
Cache-Control指定请求和响应遵循的缓存机制Cache-Control: no-cache
Content-Type返回内容的MIME类型Content-Type: text/html; charset=utf-8

响应正文可以是html、json、xml、普通文本,等等
完整http response举例:

HTTP/1.1 200 OK 
Date: Fri, 22 May 2009 06:07:21 GMT 
Content-Type: text/html; charset=UTF-8 

<html> 
	<head></head>
	 <body>
		 <!--body goes here--> 
	</body> 
</html>

1.7 https

HTTP + 加密 + 认证 + 完整性保护 = HTTPS(HTTP Secure)

2. go语言http标准库

http_server.go

import ("net/http";"fmt")
func HelloHandler(w http.ResponseWriter, r *http.Request) 
	fmt.Fprint(w, "Hello Boy") // 把返回的内容写入http.ResponseWriter

func main() 
	http.HandleFunc("/", HelloHandler) // 路由,请求要目录时去执行HelloHandler
	// ListenAndServe如果不发生error会一直阻塞,为每一个请求创建一个协程去处理
	http.ListenAndServe(":5656", nil)

http_client.go

import ("net/http";"io";"os")
func main()
	if resp, err := http.Get("http://127.0.0.1:5656"); err != nil 
		panic(err)
	 else 
	    // 注意一定要调用resp.Body.Close(),否则会协程泄漏(同时引发内存泄漏)
		defer resp.Body.Close() 
		io.Copy(os.Stdout, resp.Body) // 把resp.Body输出到标准输出流
	

3. http router

  • 安装 go get -u github.com/julienschmidt/httprouter
  • Router实现了http.Handler接口
  • 为各种request method提供了便捷的路由方式
  • 支持restful请求方式
  • 支持ServeFiles访问静态文件
  • 可以自定义捕获panic的方法
package main

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

	"github.com/julienschmidt/httprouter"
)

func handle(method string, w http.ResponseWriter, r *http.Request, params httprouter.Params) 
	fmt.Printf("request method: %s\\n", r.Method)
	fmt.Printf("request body: ")
	io.Copy(os.Stdout, r.Body) // 把r.Body流里的内容拷贝到os.Stdout流里
	fmt.Println()
	w.Write([]byte("Hi boy, you request " + method))


func get(w http.ResponseWriter, r *http.Request, params httprouter.Params) 
	handle("get", w, r, params)


func post(w http.ResponseWriter, r *http.Request, params httprouter.Params) 
	handle("post", w, r, params)


func panic(w http.ResponseWriter, r *http.Request, params httprouter.Params) 
	var arr []int
	_ = arr[1] // 数组越界panic


func main() 
	router := httprouter.New()
	router.GET("/", get)
	router.POST("/", post)

	// *只能有一个,且必须放path的末尾
	router.POST("/user/:name/:type/*addr", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) 
		fmt.Printf("name:%s, type:%s, addr:%s\\n", p.ByName("name"), p.ByName("type"), p.ByName("addr"))
	)

	// 必须以/*filepath结尾,因为要获取我们要访问的路径信息
	// 在浏览器中访问:http://127.0.0.1:5656/file/home.html
	// 或 http://127.0.0.1:5656/file/readme.md
	router.ServeFiles("/file/*filepath", http.Dir("./http/static"))

	// 通过recover捕获panic
	router.PanicHandler = func(w http.ResponseWriter, r *http.Request, err interface) 
		w.WriteHeader(http.StatusInternalServerError) // 设置response status
		fmt.Fprintf(w, "error:%s", err) // 线上环境不要把原始错误信息返回给前端,测试阶段可以这么搞
	
	router.GET("/panic", panic)

	// Router实现了ServerHTTP接口,所以它是一种http.Handler
	http.ListenAndServe(":5656", router)

4. 请求校验

跨站脚本攻击Cross-Site Scripting, XSS(为了区别于CSS),通过注入脚本获取敏感信息

<html>
<head>
	<title>留言板</title>
</head>
<body>
	<div id="board">
		<!--从数据库中读出用户的留言内容,展示在这里-->
		<script>alert("hey!you are attacked")</script>
	</div> 
</body>
</html>

CSRF(Cross-site request forgery,跨站请求伪造)

  • 登录A网站(银行网站)的个人中心:www.bank.com/my.php
  • 登录危险的B网站: <img src=http://www.bank.com/Transfer.php?toBankId=11&money=1000>,从B网站向A网站发起了转账请求(携带着A网站的认证Cookie)

jsonp

  • 主流浏览器不允许跨域访问数据(端口不同也属于跨域)
  • <script>标签的src属性不受同源策略限制
  • 通过script的src请求返回的数据,浏览器会当成js脚本去处理,所以服务端可以返回一个在客户端存在的js函数

首先安装go get github.com/go-playground/validator

type RegistRequest struct 
    UserName string `validate:"gt=0"` // >0 长度大于0
    PassWord string `validate:"min=6,max=12"` // 密码长度[6, 12]
    PassRepeat string `validate:"eqfield=PassWord"` // 跨字段相等校验
    Email string `validate:"email"` // 需要满足email的格式

范围约束

  • 对于字符串、切片、数组和map,约束其长度:len=10, min=6, max=10, gt=10
  • 对于数值,约束其取值:min, max, eq, ne, gt, gte, lt, lte, oneof=6 8

跨字段约束

  • 跨字段就在范围约束的基础上加field后缀
  • 如果还跨结构体(cross struct)就在跨字段的基础上在field前面加cs:范围约束 cs field

字符串约束

  • contains包含子串
  • containsany包含任意unicode字符, containsany=abcd
  • containsrune包含rune字符, containsrune= ☻
  • excludes不包含子串
  • excludesall不包含任意的unicode字符,excludesall=abcd
  • excludesrune不包含rune字符,excludesrune=☻
  • startswith以子串为前缀
  • endswith以子串为后缀

唯一性uniq

  • 对于数组和切片,约束没有重复的元素
  • 对于map,约束没的重复的value
  • 对于元素类型为结构体的切片,unique约束结构体对象的某个字段不重复,通过unqiue=field指定这个字段名:Friends []User `validate:“unique=Name”`

自定义约束

func validateEmail(fl validator.FieldLevel) bool 
    input := fl.Field().String()
    if pass, _ := regexp.MatchString(`^([\\w\\.\\_]2,10)@(\\w1,)\\.([a-z]2,4)$`, input); pass 
	  return true
 
 return false


// 注册一个自定义的validator
val.RegisterValidation("my_email", validateEmail)

Email string `validate:"my_email"`

5. http中间件

中间件的作用:将业务代码和非业务代码解耦。非业务代码指限流、超时控制、打日志等等
中间件的实现原理:传入一个http.Handler,外面套上一些非业务功能代码,再返回一个http.Handler;支持中间件层层嵌套;通过HandlerFunc把一个func(rw http.ResponseWriter, r *http.Request)函数转为Handler

func timeMiddleWare(next http.Handler) http.Handler 
    return http.HandlerFunc(func(rw http.ResponseWriter, r     *http.Request) 
        begin := time.Now()
        next.ServeHTTP(rw, r)
        timeElapsed := time.Since(begin)
        log.Printf("request %s use %d ms\\n", r.URL.Path, timeElapsed.Milliseconds())
    )

6. GIN

Gin是一款高性能的、简单轻巧的http Web框架
安装方式go get -u github.com/gin-gonic/gin

6.1 路由

Gin的路由是基于httprouter做的,支持GET、POST、PUT、PATCH、DELETE、OPTIONS、HEAD;支持路由分组,不用重复写上级路径

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

//你所需要的东西全都封装在了gin.Context里面,包括http.Request和ResponseWriter
func boy(c *gin.Context) 
	c.String(http.StatusOK, "hi boy") // 通过gin.Context.String返回一个text/plain类型的正文


func girl(c *gin.Context) 
	c.String(http.StatusOK, "hi girl")


func main() 
	// gin.SetMode(gin.ReleaseMode) // 发布模式,默认是Debug模式
	engine := gin.Default() // 默认的engine已自带了Logger和Recovery两个中间件
	engine.GET("/", boy)
	engine.POST("/", girl)

	// 路由分组
	oldVersion := engine.Group("/v1")
	oldVersion.GET("/student", boy) // http://localhost:5656/v1/student
	oldVersion.GET("/teacher", boy) // http://localhost:5656/v1/teacher

	newVersion := engine.Group("/v2")
	newVersion.GET("/student", girl) // http://localhost:5656/v2/student
	newVersion.GET("/teacher", girl) // http://localhost:5656/v2/teacher

	engine.Run(":5656")

6.2 参数获取

  • c.Query():从GET请求的URL中获取参数
  • c.Param():从Restful风格的url中获取参数
  • c.PostForm():从post表单中获取参数
  • c.FormFile():获取上传的文件,消息类型为form-data
  • c. MultipartForm():multipart/form-data可以上传多个form-data 并且用分隔符进行分割
// 从GET请求的URL中获取参数
func url(engine *gin.Engine) 
	engine.GET("/student", func(ctx *gin.Context) 
		name := ctx.Query("name")
		addr := ctx.DefaultQuery("addr", "China") // 如果没传addr参数,则默认为China
		ctx.String(http.StatusOK, name+" live in "+addr)
	)


// 从Restful风格的url中获取参数
func restful(engine *gin.Engine) 
	engine.GET("/student/:name/*addr", func(ctx *gin.Context) 
		name := ctx.Param("name")
		addr := ctx.Param("addr")
		ctx.String(http.StatusOK, name+" live in "+addr)
	)


// 从post表单中获取参数
func post(engine *gin.Engine) 
	engine.POST("/student", func(ctx *gin.Context) 
		name := ctx.PostForm("name")
		addr := ctx.DefaultPostForm("addr", "China") // 如果没传addr参数,则默认为China
		ctx.String(http.StatusOK, name+" live in "+addr)
	)


// 上传单个文件
func upload_file(engine *gin.Engine) 
	// 限制表单上传大小为8M,默认上限是32M
	engine.MaxMultipartMemory = 8 << 20
	engine.POST("/upload", func(ctx *gin.Context) 
		file, err := ctx.FormFile("file")
		if err != nil 
			fmt.Printf("get file error %v\\n", err)
			ctx.String(http.StatusInternalServerError, "upload file failed")
		 else 
			ctx.SaveUploadedFile(file, "./data/"+file.Filename) // 把用户上传的文件存到data目录下
			ctx.String(http.StatusOK, file.Filename)
		
	)


// 上传多个文件
func upload_multi_file(engine *gin.Engine) 
	engine.POST("/upload_files", func(ctx *gin.Context) 
		form, err := ctx.MultipartForm() // MultipartForm中不止包含多个文件
		if err != nil 
			ctx.String(http.StatusBadRequest, err.Error())
		 else 
			// 从MultipartForm中获取上传的文件
			files := form.File["files"]
			for _, file := range files 
				ctx.SaveUploadedFile(file, "./data/"+file.Filename) // 把用户上传的文件存到data目录下

			
			ctx.String(http.StatusOK, "upload "+strconv.Itoa(len(files))+" files")
		
	)

还可以定义struct,绑定到参数

type Student struct 
	Name string `form:"username" json:"name" uri:"user" xml:"user" yaml:"user" binding:"required"`
	Addr string `form:"addr" json:"addr" uri:"addr" xml:"addr" yaml:"addr" binding:"required"`


func formBind(engine *gin.Engine) 
	engine.POST("/stu/form", func(ctx *gin.Context) 
		var stu Student
		// 跟ShouldBind对应的是MustBind,MustBind内部会调用ShouldBind,如果ShouldBind发生error会直接c.AbortWithError(http.StatusBadRequest, err)
		if err := ctx.ShouldBind(&stu); err != nil 
			fmt.Println(err)
			ctx.String(http.StatusBadRequest, "parse paramter failed")
		 else 
			ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
		
	)


func jsonBind(engine *gin.Engine) 
	engine.POST("/stu/json", func(ctx *gin.Context) 
		var stu Student
		if err := ctx.ShouldBindJSON(&stu); err != nil 
			fmt.Println(err)
			ctx.String(http.StatusBadRequest, "parse paramter failed")
		 else 
			ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
		
	)


func uriBi

以上是关于Go基础Http编程的主要内容,如果未能解决你的问题,请参考以下文章

Go编程基础4-Http服务器

2.9 Go微服务实战(Go语言基础) --- Go Web编程

Go 每日一库之 net/http(基础和中间件)

GO编程基础

Go基础编程实践—— 数据库

Go语言web编程