Golang 中的 Web 服务器开发 - 从简单到复杂

Golang 中的 Web 服务器开发 - 从简单到复杂

五年前我开始 开发 Gophish,这就提供了一个学习Golang的机会。 我意识到 Go 是一门强大的语言,有许多库作为补充。 Go 具有多种用途:特别是,它可以毫无问题地用于开发服务器端应用程序。

本文是关于用 Go 编写服务器的。 让我们从“Hello world!”之类的简单内容开始,以具有以下功能的应用程序结束:

- 使用 Let's Encrypt 进行 HTTPS。
— 作为 API 路由器工作。
— 使用中间件。
— 静态文件的处理。
— 正确关闭。

技能箱推荐: 实践课程 《从零开始的Python开发》.

我们提醒: 对于“Habr”的所有读者 - 使用“Habr”促销代码注册任何 Skillbox 课程可享受 10 卢布的折扣。

您好,世界!

您可以非常快速地在 Go 中创建一个 Web 服务器。 下面是一个使用处理程序返回上面承诺的“Hello, world!”的示例。

package main
 
import (
"fmt"
"net/http"
)
 
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":80", nil)
}

之后,如果您运行应用程序并打开页面 本地,然后你会立即看到文字“Hello, world!” (当然,如果一切正常的话)。

稍后我们将多次使用该处理程序,但首先让我们了解一切是如何工作的。

网络/http

该示例使用了包 net/http,它是 Go 中用于开发服务器和 HTTP 客户端的主要工具。 为了理解代码,我们先来理解三个重要元素的含义:http.Handler、http.ServeMux 和 http.Server。

HTTP 处理程序

当我们收到请求时,处理程序会分析它并生成响应。 Go 中的处理程序实现如下:

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

第一个示例使用 http.HandleFunc 辅助函数。 它包装了另一个函数,该函数又将 http.ResponseWriter 和 http.Request 放入 ServeHTTP 中。

换句话说,Golang 中的处理程序呈现在单个界面中,这为程序员提供了很多选择。 例如,中间件是使用处理程序实现的,其中 ServeHTTP 首先执行某些操作,然后调用另一个处理程序的 ServeHTTP 方法。

如上所述,处理程序只是生成对请求的响应。 但是在特定的时间点应该使用哪个特定的处理程序?

请求路由

要做出正确的选择,请使用 HTTP 多路复用器。 在许多库中,它被称为复用器或路由器,但它们都是相同的东西。 多路复用器的作用是分析请求路径并选择合适的处理程序。

如果您需要支持复杂的路由,那么最好使用第三方库。 一些最先进的—— 大猩猩/多路复用器 и 戈吉,这些库使得可以毫无问题地实现中间处理。 在他们的帮助下,您可以配置通配符路由并执行许多其他任务。 它们的优点是与标准 HTTP 处理程序兼容。 因此,您可以编写将来可以修改的简单代码。

在正常情况下使用复杂的框架将需要非标准解决方案,这使得默认处理程序的使用变得非常复杂。 要创建绝大多数应用程序,默认库和简单路由器的组合就足够了。

查询处理

此外,我们需要一个组件来“侦听”传入连接并将所有请求重定向到正确的处理程序。 http.Server 可以轻松处理这个任务。

下面显示服务器负责与连接处理相关的所有任务。 例如,这可以使用 TLS 协议进行工作。 为了实现 http.ListenAndServer 调用,需要使用标准 HTTP 服务器。

现在让我们看一下更复杂的示例。

添加让我们加密

默认情况下,我们的应用程序运行在HTTP协议上,但建议使用HTTPS协议。 这在 Go 中可以毫无问题地完成。 如果您已收到证书和私钥,那么使用正确的证书和密钥文件注册 ListenAndServeTLS 就足够了。

http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)

你总是可以做得更好。

让我们加密 提供免费证书并自动续订。 为了使用该服务,您需要一个包 autocert.

最简单的配置方法是将 autocert.NewListener 方法与 http.Serve 结合使用。 该方法允许您在 HTTP 服务器处理请求时获取和更新 TLS 证书:

http.Serve(autocert.NewListener("example.com"), nil)

如果我们在浏览器中打开 example.com,我们将收到 HTTPS 响应“Hello, world!”

如果您需要更详细的配置,那么您应该使用 autocert.Manager 管理器。 然后我们创建自己的 http.Server 实例(到目前为止我们默认使用它)并将管理器添加到 TLSConfig 服务器:

m := &autocert.Manager{
Cache:      autocert.DirCache("golang-autocert"),
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
}
server := &http.Server{
    Addr:      ":443",
    TLSConfig: m.TLSConfig(),
}
server.ListenAndServeTLS("", "")

这是通过自动证书更新实现完整 HTTPS 支持的简单方法。

添加自定义路线

标准库中包含的默认路由器很好,但它非常基础。 大多数应用程序需要更复杂的路由,包括嵌套和通配符路由,或设置路径模式和参数的过程。

在这种情况下,值得使用包 大猩猩/多路复用器 и 戈吉。 我们将学习如何使用后者 - 下面显示了一个示例。

给出的文件 api/v1/api.go 包含我们的 API 的路由:

/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
 
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
 
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}

我们在主文件中设置路由的 api/vq 前缀。

然后我们可以将其安装到我们主应用程序中 api/v1/ 前缀下的主路由器:

// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
    router.Mount("/api/v1/", v1.NewRouter())
    return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())

Go 可以轻松处理复杂的路由,从而可以简化大型复杂应用程序的构建和维护。

使用中间件

分段涉及将一个 HTTP 处理程序与另一个处理程序包装在一起,从而可以快速执行身份验证、压缩、日志记录和其他一些功能。

作为示例,让我们看一下 http.Handler 接口;我们将使用它来编写一个对服务用户进行身份验证的处理程序。

func RequireAuthentication(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isAuthenticated(r) {
            http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
            return
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

有第三方路由器(例如 chi)允许您扩展中间件功能。

处理静态文件

Go 标准库包含处理静态内容的功能,包括图像、JavaScript 和 CSS 文件。 可以通过 http.FileServer 函数访问它们。 它返回一个处理程序,为特定目录中的文件提供服务。

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Настройка раздачи статических файлов
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

绝对值得记住的是,如果 http.Dir 不包含主 index.html 文件,则它会显示目录的内容。 在这种情况下,为了防止目录被破坏,您应该使用该包 unindexed.

正确关机

Go 还有一个称为 HTTP 服务器正常关闭的功能。 这可以使用 Shutdown() 方法来完成。 服务器在goroutine中启动,然后监听channel接收中断信号。 一旦收到信号,服务器就会关闭,但不是立即关闭,而是在几秒钟后关闭。

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

作为结论

Go 是一种功能强大的语言,拥有几乎通用的标准库。 它的默认功能非常广泛,并且可以使用接口进行增强 - 这使您可以开发真正可靠的 HTTP 服务器。

技能箱推荐:

来源: habr.com

添加评论