百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Go HTTP

bigegpt 2024-08-24 23:09 2 浏览

作者:JunChow520

出处:https://www.jianshu.com/p/844ba023eac7

由于Web服务是HTTP协议的一个服务,Golang提供完善的 net/http 包,通过 net/http 包可以很方便地搭建一个可以运行的Web服务,同时 net/http 包能够简单地对Web的路由、静态资源、模板、Cookie等数据进行设置和操作。

net/http

Golang标准库内置 net/http 包涵盖了HTTP客户端和服务端的具体实现,使用 net/http 包可方便地编写HTTP客户端和服务端的程序。

Golang为了实现高并发和高性能,使用 goroutine 来处理连接的读写事件,以保证每个请求独立且互不阻塞以高效地响应网络事件。

c,err := srv.newConn(rw)
if err!=nil {
  continue
}
go c.serve()

Golang在等待客户端请求对连接处理时,客户端每次请求都会创建一个 Conn 连接对象,这个 Conn 连接对象中保存了本次请求的信息,然后再传递到对应的处理器,处理器可以方便地读取到相应地HTTP头信息,如此这般保证了每次请求的独立性。

服务端

基于HTTP构建的服务标准模型包括客户端和服务端,HTTP请求从客户端发出,服务端接收到请求后进行处理,然后将响应返回给客户端。HTTP服务器核心工作是如何接收来自客户端的请求,并向客户端返回响应。

HTTP服务器处理流程

当HTTP服务器接收到客户端请求时,首先会进入路由模块,路由又称为服务复用器(Multiplexer),路由的工作在于请求找到对应的处理器(Handler),处理器对接收到的请求进行对应处理后,构建响应并返回给客户端。

client -> Request -> Multiplexer(router)->handler ->Response -> client

运行流程

运行流程

  1. 创建Listen Socket监听指定端口,等待客户端请求到来。
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

首先初始化 Server 对象,然后调用其 ListenAndServe() 方法。

func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
        return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(ln)
}

调用 Server 实例的 ListenAndServe() 方法会调用底层的 net.Listen("tcp", addr) 方法,即基于TCP协议创建Listen Socket,通过传入的主机地址和端口号,在指定端口上监听客户端请求。

  1. Listen Socket接受客户端请求并建立连接以获取Client Socket,通过Client Socket与客户端通信。

创建Listen Socket成功后会调用 Server 实例的 Serve(net.Listener) 方法,该方法用于接受并处理客户端请求。

Serve(net.Listener) 方法内部会开启一个 for 死循环,在循环体内通过 net.Listener (即Listen Socket)实例的 Accept 方法来接受客户端请求,接收到请求后根据请求会创建 net.Conn 连接实例(即Client Socket)。为了处理并发请求,会单独为每个连接实例开启一个 goroutine 去服务,请求的具体逻辑处理都会在 serve() 方法内完成。

  1. 处理客户端请求并返回响应

客户端请求的处理集中在 conn 连接实例的 serve() 方法内, serve() 方法主要实现将HTTP请求分配给指定的处理器函数来进行处理。

首先从Client Socket中读取HTTP请求的协议头,判断请求方法若是POST则需读取客户端提交的数据,然后交给对应的Handler来处理请求,Handler处理完毕后准备后客户端所需数据,再通过Client Socket写给客户端。

连接实例通过 readRequest() 方法解析请求,然后再通过 serverHandler{c.server}.ServeHTTP(w, w.req) 中的 ServeHTTP() 方法获取请求对应的处理器。

创建服务

创建HTTP服务需经过两个阶段,首先需注册路由即提供URL模式和Handler处理函数的映射,然后是实例化 Server 对象并开启对客户端的监听。

例如:使用 net/http 包搭建Web服务

mux:= http.NewServeMux()
mux.Handle("/", http.RedirectHandler("http://www.baidu.com", 307))

server := &http.Server{Addr: ":3000", Handler: mux}
server.ListenAndServe()
  1. 注册路由,即注册一个到 ServeMux 的处理器函数。

注册路由即提供URL模式和Handler处理函数的映射

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    rw.Write([]byte(time.Now().Format(time.RFC3339)))
})
http.ListenAndServe(":3000", nil)

net/http 包提供了注册路由的API, http.HandleFunc 方法默认会采用 DefaultServeMux 作为服务复用器, DefaultServeMux 实际是 ServeMux 的一个实例。

func http.HandleFunc(pattern string, handler func(ResponseWriter, *Request)){
  DefaultServeMux.HandleFunc(pattern, handler)
}

net/http 包也提供了 NewServeMux() 方法来创建一个自定义的 ServeMux 实例,默认则创建一个 DefaultServeMux

mux := http.NewServeMux()
mux.Handle("/", http.RedirectHandler("http://www.baidu.com", 307))

http.ListenAndServe(":3000", mux)
  1. 监听启动,设置监听的TCP地址并启动服务

监听启动实际上是实例化一个Server对象,并开启对客户端的监听。

func http.ListenAndServe(addr string, handler Handler) error

net/http 提供的 http.ListenAndServe(addr string, handler Handler) 用于在指定的TCP网络地址进行监听,然后调用服务端处理程序来处理传入的请求。

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    n, err := rw.Write([]byte(rq.RemoteAddr))
    if err != nil || n <= 0 {
        panic(err)
    }
})

err := http.ListenAndServe(":3000", nil)
if err != nil {
    panic(err)
}

处理器默认为 nil 表示服务端会调用包变量 http.DefaultServeMux 作为默认处理器,服务端编写的业务逻辑处理程序 http.Handler()http.HandleFunc() 会默认注入 http.DefaultServeMux 中。

若不想采用默认的的 http.DefaultServeMux 可使用 net/http 包中提供的 NewServeMux() 创建自定义的 ServeMux

func NewServeMux() *ServeMux

http.ListenAndSerTLS() 方法用于处理HTTPS请求

func http.ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error

注册路由

理解Golang中的HTTP服务最重要的是理解Multiplexer多路转接器和Handler处理器,Multiplexer基于 ServeMux 结构同时实现了 Handler 接口。

  • ServeMux 本质上是一个HTTP请求路由器,又称为多路转接器(Multiplexor),它会将接收到的请求与一组预先定义的URL路径列表做对比,然后匹配路径时调用关联的处理器(Handler)。
  • Handler 处理器负责输出HTTP响应的头和正文,任何满足 http.Handler 接口的对象都可以作为一个处理器。

多路转接器

HTTP请求的多路转接器(即路由)会负责将每个请求的URL与注册模式列表进行匹配,并调用和URL最佳匹配模式的处理器。多路转换器内部使用一个 map 映射来保存所有处理器。

type ServeMux struct {
    mu    sync.RWMutex//读写互斥锁,并发请求需锁机制
    m     map[string]muxEntry//路由规则,一个路由表达式对应一个复用器实体
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // 是否在任意规则中携带主机信息
}

虽然 ServeMux 也实现了 ServerHTTP 方法算得上是一个处理器,但 ServeMuxServeHTTP 方法并非用来处理请求和响应,而是用来查找注册路由对应的处理器。

DefaultServeMux 是默认的 ServeMux ,随着 net/http 包初始化而被自动初始化。

快捷函数

net/http 包提供了一组快捷函数 http.Handlehttp.HandleFunc 来配置 DefaultServeMux ,快捷函数会将处理器注册到 DefaultServeMux 。当 ListenAndServe 在没有提供其他处理器的情况下,即 handlernil 时内部会使用 DefaultServeMux

默认多路转接器

package main

import (
    "net/http"
)

func defaultHandler(rw http.ResponseWriter, rq *http.Request) {
    rw.Write([]byte(rq.RemoteAddr))
}

func main() {
    http.Handle("/", http.HandlerFunc(defaultHandler))
    http.ListenAndServe(":3000", nil)
}

任何具有 func(http.ResponseWriter, *http.Request) 签名的函数都能转换成为一个 http.HandlerFunc 类型的对象,因为 HandlerFunc 对象内置了 ServeHTTP() 方法。

自定义快捷函数

实际上将函数转换为 HandlerFunc 后注册到 ServeMux 是很普遍的用法,当显式地使用 ServeMux 时,Golang提供了更为方便地 ServeMux.HandleFunc 函数。

package main

import (
    "net/http"
)

func defaultHandler(rw http.ResponseWriter, rq *http.Request) {
    rw.Write([]byte(rq.RemoteAddr))
}

func main() {
    http.HandleFunc("/", defaultHandler)
    http.ListenAndServe(":3000", nil)
}

此时若需要从 main() 函数中传递参数到处理器,应该如何实现呢?一种优雅的方式是将处理器放入闭包中,将参数传入。

package main

import (
    "net/http"
    "time"
)

func defaultHandler(format string) http.Handler {
    return http.HandlerFunc(func(rw http.ResponseWriter, rq *http.Request) {
        rw.Write([]byte(time.Now().Format(format)))
    })
}

func main() {
    handler := defaultHandler(time.RFC3339)
    http.Handle("/", handler)
    http.ListenAndServe(":3000", nil)
}

这里 defaultHandler() 函数除了将函数封装成为 Handler 外还会返回一个处理器。

也可在返回时使用一个到 http.HandlerFunc 类型的隐式转换

package main

import (
    "net/http"
    "time"
)

func defaultHandler(format string) http.HandlerFunc {
    return func(rw http.ResponseWriter, rq *http.Request) {
        rw.Write([]byte(time.Now().Format(format)))
    }
}

func main() {
    handler := defaultHandler(time.RFC3339)
    http.Handle("/", handler)
    http.ListenAndServe(":3000", nil)
}

处理器

Golang中没有继承、多态,可通过接口来实现。而接口则是定义声明的函数签名,任何结构体只要实现与接口函数签名相同的方法,即等同于实现了对应的接口。

Golang的 net/http 包实现的HTTP服务都是基于 http.Handler 接口进行处理的

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

任何结构体只要实现了 ServeHTTP 方法即可称之为处理器对象, http.ServeMux 多路转接器会使用处理器对象并调用其 ServeHTTP 方法来处理请求并返回响应。

处理器函数的实现实际上调用默认 ServeMuxHandleFunc() 方法

func http.HandleFunc(pattern string, handler func(ResponseWriter, *Request)){
  DefaultServeMux.HandleFunc(pattern, handler)
}

若使用 http.Handle() 方法则第二个参数需实现 Handler 接口,实现 Handler 接口需实现其 ServeHTTP() 方法。换句话说,只要具有如下签名的 ServeHTTP 方法即可作为处理器。

ServeHTTP(http.ResponseWriter, *http.Request)

ServeHTTP

  • handler 函数表示具有 func(ResponseWriter, *Request) 签名的函数
  • handler 处理器函数表示经过 http.HandlerFunc 结构包装的 handler 函数, http.HandlerFunc 结构实现了 ServeHTTP 接口,因此调用 handler 处理器的 ServeHTTP() 方法时,也就是在调用 handler 函数本身。
  • handler 对象表示实现了 http.Handler 接口中 ServerHTTP() 方法的结构实例

自定义处理器

package main

import (
    "net/http"
    "time"
)

type TestHandler struct {
    format string
}

func (t *TestHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request) {
    rw.Write([]byte(time.Now().Format(t.format)))
}

func main() {
    mux := http.NewServeMux()

    th := &TestHandler{format: time.RFC3339}
    mux.Handle("/", th)

    http.ListenAndServe(":3000", mux)
}

Golang中 net/http 包中自带处理程序包括 FileServerNotFoundHandlerRedirectHandler 等。

适配器

type HandlerFunc func(ResponseWriter, *Request)

HandlerFunc 适配器实现了 ServeHTTP 接口,因此它也是一个处理器。

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

HandlerFunc 适配器的作用是将自定义的函数转换为 Handler 处理器类型,当调用 HandlerFunc(f) 后会强制将 f 函数类型转换为 HandlerFunc 类型,这样 f 函数就具有了 ServeHTTP 方法,同时也就转换成为了一个处理器。

请求

Web服务最基本的工作是接受请求返回响应, net/http 包封装了 http.Request 结构体,用于获取一次HTTP请求的所有信息。

type Request struct {
    Method string//请求方法
    URL *url.URL//请求地址
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    Header Header//请求头
    Body io.ReadCloser//请求体
    GetBody func() (io.ReadCloser, error)//获取请求体
    ContentLength int64//内容长度
    TransferEncoding []string//传输编码
    Close bool//连接是否关闭
    Host string//服务器主机地址
    Form url.Values//GET表单
    PostForm url.Values//POST表单
    MultipartForm *multipart.Form//上传表单
    Trailer Header
    RemoteAddr string//远程客户端地址
    RequestURI string//请求URI
    TLS *tls.ConnectionState//HTTPS
    Cancel <-chan struct{}
    Response *Response//响应
    ctx context.Context//上下文对象
}
http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    //rw.Write([]byte(rq.RemoteAddr))
    fmt.Printf("protocol: %v\n", rq.Proto)
    fmt.Printf("method: %v\n", rq.Method)
    fmt.Printf("content length: %v\n", rq.ContentLength)
    fmt.Printf("url: %v\n", rq.URL)
    fmt.Printf("uri: %v\n", rq.RequestURI)
    fmt.Printf("remoteAddr: %v\n", rq.RemoteAddr)
    fmt.Printf("host: %v\n", rq.Host)
})
http.ListenAndServe(":3000", nil)

响应

net/http 包中提供了访问Web服务的函数,比如 http.Get()http.Post()http.Head() 等,用于读取请求数据。服务端发送的响应报文会被保存在 http.Response 结构体中,响应包体会被存放在 ResponseBody 字段中。程序使用完响应必须关闭回复主体。

服务端

package main

import (
    "fmt"
    "net/http"
)

func server() {
    http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
        fmt.Printf("client %v %v %v\n", rq.RemoteAddr, rq.Method, rq.URL)
        rw.Write([]byte(rq.RemoteAddr))
    })
    http.ListenAndServe(":3000", nil)
}

func main() {
    server()
}

GET

客户端发送不带参数的GET请求

package main

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

func client() {
    url := "http://127.0.0.1:3000?id=1"
    fmt.Printf("request: GET %v\n", url)

    rp, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    defer rp.Body.Close()

    fmt.Printf("response: status=%v, code=%v\n", rp.Status, rp.StatusCode)

    body, err := ioutil.ReadAll(rp.Body)
    if err != nil {
        panic(err)
    }
    fmt.Printf("response: body=%v\n", string(body))
}

func main() {
    client()
}

运行测试

客户端发送带参数的GET请求

//请求参数
params := url.Values{}
params.Set("id", "1")
params.Set("pid", "0")
//设置URL
rawURL := "http://127.0.0.1:3000"
reqURL, err := url.ParseRequestURI(rawURL)
if err != nil {
    panic(err)
}
//整合参数
reqURL.RawQuery = params.Encode()
fmt.Printf("request: GET %v\n", reqURL.String())
//发送请求
rp, err := http.Get(reqURL.String())
if err != nil {
    panic(err)
}
//延迟关闭响应包体
defer rp.Body.Close()
//解析响应
fmt.Printf("response: status=%v, code=%v\n", rp.Status, rp.StatusCode)
body, err := ioutil.ReadAll(rp.Body)//一次性读取响应包体内容
if err != nil {
    panic(err)
}
fmt.Printf("response: body=%v\n", string(body))
request: GET http://127.0.0.1:3000?id=1&pid=0
response: status=200 OK, code=200
response: body=127.0.0.1:3000: id=1, pid=0

服务端接收GET请求并解析参数

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    //延迟关闭请求包体
    defer rq.Body.Close()
    fmt.Printf("client %v %v %v\n", rq.RemoteAddr, rq.Method, rq.URL)
    //获取GET请求参数
    val := rq.URL.Query()
    id := val.Get("id")
    pid := val.Get("pid")
    //返回响应
    msg := fmt.Sprintf("%v: id=%v, pid=%v", rq.Host, id, pid)
    rw.Write([]byte(msg))
})
http.ListenAndServe(":3000", nil)

POST

application/x-www-form-urlencoded

客户端发送POST请求

url := "http://127.0.0.1:3000"
//内容类型
contentType := "application/x-www-form-urlencoded"
//对应内容类型的数据样式
id := 10
pid := 1
data := fmt.Sprintf("id=%v&pid=%v", id, pid)
body := strings.NewReader(data)
//发送请求
rp, err := http.Post(url, contentType, body)
if err != nil {
    panic(err)
}
defer rp.Body.Close() //延迟关闭响应包体
//解析响应包体
arr, err := ioutil.ReadAll(rp.Body) //一次性读取完毕
if err != nil {
    panic(err)
}
msg := string(arr)
fmt.Println(msg)

服务端解析POST参数

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    defer rq.Body.Close()
    //解析表单
    rq.ParseForm()
    //获取表单字段
    id := rq.PostForm.Get("id")
    pid := rq.PostForm.Get("pid")
    fmt.Printf("%v %v %v id=%v pid=%v\n", rq.RemoteAddr, rq.Method, rq.Proto, id, pid)
    //返回响应
    msg := fmt.Sprintf("%v %v %v", rq.Host, rq.Method, rq.Proto)
    rw.Write([]byte(msg))
})

application/json

客户端发送POST JSON数据

url := "http://127.0.0.1:3000"
//内容类型
contentType := "application/json"
//对应内容类型的数据样式
id := 10
pid := 1
data := fmt.Sprintf(`{"id":%v, "pid":%v}`, id, pid)
body := strings.NewReader(data)
//发送请求
rp, err := http.Post(url, contentType, body)
if err != nil {
    panic(err)
}
defer rp.Body.Close() //延迟关闭响应包体
//解析响应包体
arr, err := ioutil.ReadAll(rp.Body) //一次性读取完毕
if err != nil {
    panic(err)
}
msg := string(arr)
fmt.Println(msg)

服务端解析POST JSON

http.HandleFunc("/", func(rw http.ResponseWriter, rq *http.Request) {
    defer rq.Body.Close()
    //读取数据
    arr, err := ioutil.ReadAll(rq.Body)
    if err != nil {
        panic(err)
    }
    str := string(arr)
    //获取表单字段
    fmt.Printf("%v %v %v %v\n", rq.RemoteAddr, rq.Method, rq.Proto, str)
    //返回响应
    msg := fmt.Sprintf(`{"code":%v, "message":%v}`, 1, "success")
    rw.Write([]byte(msg))
})

POST JSON

作者:JunChow520

出处:https://www.jianshu.com/p/844ba023eac7

相关推荐

AI「自我复制」能力曝光,RepliBench警示:大模型正在学会伪造身份

科幻中AI自我复制失控场景,正成为现实世界严肃的研究课题。英国AISI推出RepliBench基准,分解并评估AI自主复制所需的四大核心能力。测试显示,当前AI尚不具备完全自主复制能力,但在获取资源...

【Python第三方库安装】介绍8种情况,这里最全看这里就够了!

**本图文作品主要解决CMD或pycharm终端下载安装第三方库可能出错的问题**本作品介绍了8种安装方法,这里最全的python第三方库安装教程,简单易上手,满满干货!希望大家能愉快地写代码,而不要...

pyvips,一个神奇的 Python 库!(pythonvip视频)

大家好,今天为大家分享一个神奇的Python库-pyvips。在图像处理领域,高效和快速的图像处理工具对于开发者来说至关重要。pyvips是一个强大的Python库,基于libvips...

mac 安装tesseract、pytesseract以及简单使用

一.tesseract-OCR的介绍1.tesseract-OCR是一个开源的OCR引擎,能识别100多种语言,专门用于对图片文字进行识别,并获取文本。但是它的缺点是对手写的识别能力比较差。2.用te...

实测o3/o4-mini:3分钟解决欧拉问题,OpenAI最强模型名副其实!

号称“OpenAI迄今为止最强模型”,o3/o4-mini真实能力究竟如何?就在发布后的几小时内,网友们的第一波实测已新鲜出炉。最强推理模型o3,即使遇上首位全职提示词工程师RileyGoodsid...

使用Python将图片转换为字符画并保存到文件

字符画(ASCIIArt)是将图片转换为由字符组成的艺术作品。利用Python,我们可以轻松实现图片转字符画的功能。本教程将带你一步步实现这个功能,并详细解释每一步的代码和实现原理。环境准备首先,你...

5分钟-python包管理器pip安装(python pip安装包)

pip是一个现代的,通用、普遍的Python包管理工具。提供了对Python包的查找、下载、安装、卸载的功能,是Python开发的基础。第一步:PC端打开网址:选择gz后缀的文件下载第二步:...

网络问题快速排查,你也能当好自己家的网络攻城狮

前面写了一篇关于网络基础和常见故障排查的,只列举了工具。没具体排查方式。这篇重点把几个常用工具的组合讲解一下。先有请今天的主角:nslookup及dig,traceroute,httping,teln...

终于把TCP/IP 协议讲的明明白白了,再也不怕被问三次握手了

文:涤生_Woo下周就开始和大家成体系的讲hadoop了,里面的每一个模块的技术细节我都会涉及到,希望大家会喜欢。当然了你也可以评论或者留言自己喜欢的技术,还是那句话,希望咱们一起进步。今天周五,讲讲...

记一次工控触摸屏故障的处理(工控触摸屏维修)

先说明一下,虽然我是自动化专业毕业,但已经很多年不从事现场一线的工控工作了。但自己在单位做的工作也牵涉到信息化与自动化的整合,所以平时也略有关注。上一周一个朋友接到一个活,一家光伏企业用于启动机组的触...

19、90秒快速“读懂”路由、交换命令行基础

命令行视图VRP分层的命令结构定义了很多命令行视图,每条命令只能在特定的视图中执行。本例介绍了常见的命令行视图。每个命令都注册在一个或多个命令视图下,用户只有先进入这个命令所在的视图,才能运行相应的命...

摄像头没图像的几个检查方法(摄像头没图像怎么修复)

背景描述:安防监控项目上,用户的摄像头运行了一段时间有部分摄像头不能进行预览,需要针对不能预览的摄像头进行排查,下面列出几个常见的排查方法。问题解决:一般情况为网络、供电、设备配置等情况。一,网络检查...

小谈:必需脂肪酸(必需脂肪酸主要包括)

必需脂肪酸是指机体生命活动必不可少,但机体自身又不能合成,必需由食物供给的多不饱和脂肪酸(PUFA)。必需脂肪酸主要包括两种,一种是ω-3系列的α-亚麻酸(18:3),一种是ω-6系列的亚油酸(18:...

期刊推荐:15本sci四区易发表的机械类期刊

  虽然,Sci四区期刊相比收录在sci一区、二区、三区的期刊来说要求不是那么高,投稿起来也相对容易一些。但,sci四区所收录的期刊中每本期刊的投稿难易程度也是不一样的。为方便大家投稿,本文给大家推荐...

be sick of 用法考察(be in lack of的用法)

besick表示病了,做谓语.本身是形容词,有多种意思.最通常的是:生病,恶心,呕吐,不适,晕,厌烦,无法忍受asickchild生病的孩子Hermother'sverysi...