前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言入门——实践篇(五)

Go语言入门——实践篇(五)

作者头像
arcticfox
发布2019-09-09 17:55:19
5670
发布2019-09-09 17:55:19
举报
  • Web开发基础
    • 最简示例
    • Go Web工作流程
      • 创建多路复用器
      • 处理静态文件
      • 创建处理器函数
    • Go Web 应用基础
      • 简单配置
      • 处理器与处理器函数
        • 创建处理器
        • 创建多个处理器
      • 多路复用器
        • 第三方多路复用器

Web开发基础

所谓Web开发,也就是我们通常说的网站后端开发。与其他语言相比,Go的Web开发具有简单易学,并发效率高,原生标准库支持等特点。即使是Python Web开发,也没有Go的简单。

学习Go的Web,是可以不需要安装任何第三方库的,标准库即支持,且底层已经使用Go协程封装了并发请求,因此Go不需要任何所谓的服务器容器的软件,例如Java开发需要Tomcat服务器,Python需要Gunicorn,uWSGI之类的服务器,而Go语言,直接上手撸API即可,可以说Go语言是为Web而生的,最适合后端开发。

学习Web开发,应当具备HTTP协议的基础,请先阅读另一篇文章 Web基础(一) HTTP详解[1]

最简示例

  1. 运行以下代码
代码语言:javascript
复制
package main

import (
    "fmt"
    "net/http"
)

//定义一个名为 handler 的处理器函数
func handler(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintf(writer, "Hello, %s!", request.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}
  1. 在浏览器输入http://localhost:8080/Mary这时浏览器显示Hello, Mary!

handler函数:事件被触发之后,负责对事件进行处理的回调函数。该处理器函数接受两个参数,第一个参数类型为ResponseWriter接口 , 第二个参数为指向Request结构的指针。

handler函数会从 Request 结构中提取相关的信息,然后创建一个HTTP响应, 最后再通过ResponseWriter接口将响应返回给客户端。

handler函数中的Fprintf函数在被调用时,需要传入一个ResponseWriter接口实例 ,第二个参数是带有格式化占位符%s的字符串,第三参数就是占位符需要替换的内容,这里则是将Request结构里带的URL路径截取后作为参数

Go Web工作流程

流程图

示例代码

代码语言:javascript
复制
package main

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

func main() {
	fmt.Println("*** 服务器启动,监听端口:8080 ***")
    mux := http.NewServeMux()
    // 处理静态资源文件
    files := http.FileServer(http.Dir("./public"))
    mux.Handle("/static/", http.StripPrefix("/static/", files))
    mux.HandleFunc("/", index)

    // 配置服务器
    server := &http.Server{
        Addr:           "0.0.0.0:8080",
        Handler:        mux,     // 设置多路复用器
        ReadTimeout:    time.Duration(10 * int64(time.Second)),
        WriteTimeout:   time.Duration(200 * int64(time.Second)),
        MaxHeaderBytes: 1 << 20, // 左移运算,等同:1*2^20,高性能乘法运算
    }
    server.ListenAndServe()
}

func index(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintln(writer, "Hello, world!")
}
  1. 创建以上代码
  2. 在代码所在目录创建public文件夹
  3. public中分别创建home.htmlnote.txt文件
  4. 使用编辑器打开创建的两个文件,分别将以下内容复制粘贴到文件中保存
代码语言:javascript
复制
<html>
    <head>
        <title>
        这是主页
        </title>
    </head>
<body style="background:Pink">
   <h1>Home</h1>
   <p style="color:red">
      Welcome to the Go homepage!
   </p>
</body>
</html>

现在,让我们在浏览器分别访问http://127.0.0.1:8080/http://127.0.0.1:8080/static/home.htmlhttp://127.0.0.1:8080/static/note.txt

可以发现,http://127.0.0.1:8080/static/home.htmlhttp://127.0.0.1:8080/static/note.txt的结果是不一样的,这是因为浏览器能识别html标记语言,关于前端html标记语言本文不做说明,请自行学习前端相关知识。

创建多路复用器

通过NewServeMux函数来创建一个默认的多路复用器,调用HandleFunc函数将发送至根URL的请求重定向到对应的处理器。因为所有处理器都接受一个 ResponseWriter 实例和一个指向 Request 结构的指针作为参数,并且所有请求参数都可以通过访问 Request 结构得到,所以程序并不需要向处理器显式地传入任何请求参数。

多路复用器主要负责接收 HTTP 请求,并根据请求中的 URL 将请求重定向到正确的处理器。注意,所有引入了 net/http 标准库的程序都可以使用一个默认的多路复用器实例,当没有为 Server 结构指定处理器时,服务器就会使用 DefaultServeMux

实际上,所谓多路复用器,也就是我们在开发中常说的路由的概念,根据不同的URL,调用不同的函数去处理。

处理静态文件

使用FileServer函数创建了一个处理器,它能够处理指定目录中的静态文件。最后将这个处理器传递给多路复用器的Handle函数

如示例代码,当服务器接收到一个以/static/开头的 URL 请求时,以上将URL中的/static/路径映射到public目录中,然后查找被请求的文件。

例如,当服务器接收到一个针对文件 http://127.0.0.1:8080/static/note.txt的请求时,它将会在public目录中查找note.txt文件。这样做的好处是可以将服务器上的真实文件目录隐藏。

创建处理器函数

处理器函数实际上就是一个接受ResponseWriterRequest指针作为参数的 Go 函数。通常该函数负责生成HTML内容并将其写人ResponseWriter中。

代码语言:javascript
复制
func index(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintln(writer, "Hello, world!")
}

Go Web 应用基础

简单配置

除了可以通过ListenAndServe的参数对服务器的网络地址和处理器进行配置之外,还可以通过 Server 结构对服务器进行更详细的配置,如上面的例子,其中包括为请求读取操作设置超时时间、为响应写入操作设置超时时间,以及为 Server 结构设置错误日志记录器等

最简配置

代码语言:javascript
复制
package  main

import  (
    "net/http"
)

func  main()  {
    server := http.Server{
        Addr : "127.0.0.1:8080" ,
        Handler :  nil,
    }
    server.ListenAndServe()
}

Server结构详细字段

代码语言:javascript
复制
type Server struct {
    Addr      string   // TCP address to listen on, ":http" if empty
    Handler   Handler  // handler to invoke, http.DefaultServeMux if nil
    TLSConfig *tls.Config // optional TLS config, used by ServeTLS and ListenAndServeTLS

    ReadHeaderTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    MaxHeaderBytes int
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger

    disableKeepAlives int32     // accessed atomically.
    inShutdown        int32     // accessed atomically (non-zero means we're in Shutdown)
    nextProtoOnce     sync.Once // guards setupHTTP2_* init
    nextProtoErr      error     // result of http2.ConfigureServer if used

    mu         sync.Mutex
    listeners  map[net.Listener]struct{}
    activeConn map[*conn]struct{}
    doneChan   chan struct{}
    onShutdown []func()
}

处理器与处理器函数

处理器 是一个拥有ServeHTTP方法的接口,这个ServeHTTP方法需要接受两个参数,第一个参数是一个ResponseWriter接口实例,而第二个参数则是一个指向Request结构的指针。即任何拥有ServeHTTP方法的接口,且该方法的签名为ServeHTTP(http.ResponseWriter, *http.Request),那么这个接口就是一个处理器。 处理器函数 实际上是与处理器拥有相同行为的函数,这个函数与ServeHTTP方法拥有相同的签名,即接受ResponseWriter和指向Request结构的指针作为参数。通过使用HandlerFunc可以把一个带有正确签名的函数f转换成一个带有方法 f的处理器实例,这个方法会与DefaultServeMux进行绑定。因此,处理器函数只不过是创建处理器的一种便利方法而已。

虽然处理器函数能够完成跟处理器一样的工作,并且使用处理器函数的代码比使用处理器的代码更为简洁,但是处理器函数并不能完全代替处理器。这是因为在某些情况下,代码可能已经包含了某个接口或者某种类型,这时我们只需要为它们添加 ServeHTTP方法就可以将它们转变为处理器,这种转变更灵活也有助于构建出更为模块化的 Web 应用。

创建处理器
代码语言:javascript
复制
package main

import (
    "fmt"
    "net/http"
)

// 声明一个结构体
type MyHandler struct{
}

// 让结构体实现 ServeHTTP 函数
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World!")
}

func main() {
    handler := MyHandler{}
    
    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: &handler,  //指定处理器
    }
    server.ListenAndServe()
}
创建多个处理器

不用在Server结构的Handler字段中指定处理器,而是让服务器使用默认的 DefaultServeMux作为处理器, 然后通过http.Handle函数将处理器绑定至 DefaultServeMux

需要说明的是,虽然Handle函数来源于http包,但它实际上是ServeMux结构的方法,这些函数是为了操作便利而创建的函数,调用它们等同于调用DefaultServeMux的方法。比如调用http.Handle实际上就是在调用DefaultServeMuxHandle方法

代码语言:javascript
复制
package main

import (
    "fmt"
    "net/http"
)

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!")
}

type WorldHandler struct{}

func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "World!")
}

func main() {
    hello := HelloHandler{}
    world := WorldHandler{}

    server := http.Server{
        Addr: "127.0.0.1:8080",
    }

    http.Handle("/hello", &hello)
    http.Handle("/world", &world)

    server.ListenAndServe()
}

多路复用器

ServeMux是一个HTTP请求多路复用器,它负责接收HTTP请求并根据请求中的 URL将请求重定向到正确的处理器。ServeMux结构包含了一个映射,这个映射会将URL映射至相应的处理器。因为ServeMux结构也实现了ServeHTTP方法,所以它也是一个处理器

在这里插入图片描述

ServeMux是一个结构体而不是一个接口,因此DefaultServeMuxServeMux的一个实例而不ServeMux的实现。

前面提到的http.ListenAndServe(":8080", nil)函数接受的第二个参数是一个处理器,但它的默认值却是一个多路复用器DefaultServeMux,这是因为DefaultServeMux多路复用器是ServeMux结构的一个实例,而ServeMux也拥有ServeHTTP方法,也就是说DefaultServeMux既是ServeMux结构的实例,也是Handler结构的实例。

第三方多路复用器

ServeMux的一个缺陷是无法使用变量实现URL模式匹配。例如在浏览器请求/threads的时候,ServeMux可以很好地获取并显示所有帖子,但当浏览器请求的是/threads/123时,那么要获取并显示ID为123的帖子就会变得非常困难。

创建自定义的多路复用器来代替net/http包中的ServeMux是可行的,并且目前市面上已经出现了很多第三方的多路复用器可供使用,而HttpRouter就是一个功能强大的轻量级第三方多路复用器。

执行以下命令安装 HttpRouter

代码语言:javascript
复制
go get github.corn/julienschrnidt/httprouter

这表示从GitHub上下载HttpRouter包源码,并将其保存到GOPATH/src目录中

使用示例

代码语言:javascript
复制
package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "net/http"
)

func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", p.ByName("name"))
}

func main() {
    //创建多路复用器
    mux := httprouter.New()
    //将处理器函数与给定的HTTP方法进行绑定
    mux.GET("/hello/:name", hello)

    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: mux,
    }
    server.ListenAndServe()
}

以上代码不再使用HandleFunc绑定处理器函数,而是直接把处理器函数与给定的 HTTP方法进行绑定。当浏览器向这个URL发送GET请求时,hello函数就会被调用,但当浏览器向这个URL发送其他请求时,hello函数不会被调用。

可以看到被绑定的URL包含了具名参数(named parameter),这些具名参数会被 URL中的具体值所代替,并且程序可以在处理器里面获取这些值。此时的处理器函数hello接收三个参数,而第三个参数Params就包含了具名参数,其值可以 通过ByName方法获取。

如,运行程序后,浏览器输入localhost:8080/hello/fox,则显示 hello,foxp.ByName成功获取到URL中的fox字段。

欢迎关注我的公众号:编程之路从0到1

编程之路从0到1

参考资料

[1]

Web基础(一) HTTP详解: https://blog.csdn.net/yingshukun/article/details/83863152

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程之路从0到1 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Web开发基础
    • 最简示例
      • Go Web工作流程
        • 创建多路复用器
        • 处理静态文件
        • 创建处理器函数
      • Go Web 应用基础
        • 简单配置
        • 处理器与处理器函数
        • 多路复用器
        • 参考资料
    • 欢迎关注我的公众号:编程之路从0到1
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档