百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Go HTTP(干呕后头疼缓解)

toyiye 2024-07-03 02:01 21 浏览 0 评论

作者: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

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码