Skip to content

Latest commit

 

History

History
267 lines (182 loc) · 14.4 KB

File metadata and controls

267 lines (182 loc) · 14.4 KB

二、服务与路由

作为一个商业实体,网络的基石——营销和品牌推广几乎完全依赖于 URL。虽然我们还没有考虑顶级域处理,但我们需要控制 URL 及其路径(或端点)。

在本章中,我们将通过介绍多个路由和相应的处理程序来实现这一点。首先,我们将通过一个简单的平面文件服务来实现这一点,然后我们将引入复杂的混合器,通过实现一个在路由中使用正则表达式的库来更灵活地进行路由。

在本章结束时,您应该能够在 localhost 上创建一个站点,该站点可以通过任意数量的路径访问,并返回与请求的路径相关的内容。

在本章中,我们将介绍以下主题:

  • 直接提供文件
  • 基本路由
  • 对 Gorilla 使用更复杂的路由
  • 重定向请求
  • 服务基本错误

直接提供文件服务

在上一章中,我们使用fmt.Fprintln函数在浏览器中输出一些通用的 Hello,World 消息。

这显然具有有限的效用。在 Web 和 Web 服务器的早期,通过将请求定向到相应的静态文件来服务整个 Web。换句话说,如果用户请求home.html,web 服务器将查找名为home.html的文件并将其返回给用户。

这在今天看来似乎很奇怪,因为现在绝大多数 Web 都是以某种动态方式提供服务的,内容通常是通过数据库 ID 确定的,这样就可以在没有人修改单个文件的情况下生成和重新生成页面。

让我们来看看最简单的方式,我们可以以类似于 Web 的旧时代的方式来服务文件,如图所示:

package main

import (
  "net/http"
)

const (
  PORT = ":8080"
)

func main() {

  http.ListenAndServe(PORT, http.FileServer(http.Dir("/var/www")))
}

很简单吧?对该站点的任何请求都将尝试在本地/var/www目录中找到相应的文件。但是,与第 1 章介绍和设置 Go中的示例相比,这具有更实际的用途,但仍然非常有限。让我们看看扩大我们的选择有点。

基本路由

第 1 章介绍和设置时,我们生成了一个非常基本的 URL 端点,允许静态文件服务。

以下是我们为该示例制作的简单路线:

func main() {
  http.HandleFunc("/static",serveStatic)
  http.HandleFunc("/",serveDynamic)
  http.ListenAndServe(Port,nil)
}

综上所述,您可以看到两个端点,/static/,它们要么服务于单个静态文件,要么生成对http.ResponseWriter的输出。

我们可以有任意数量的路由器并排放置。然而,考虑一个场景,我们有一个基本的网站,有关于,联系,和工作人员页面,每一个都驻留在 TytT0T,Ont1,和 Ty2 T2。虽然这是一个故意愚钝的例子,但它展示了 Go 内置且未经修改的路由系统的局限性。我们不能在本地将所有请求路由到同一个目录,我们需要提供更具延展性的 URL。

使用更复杂的 Gorilla 路由

在上一节课中,我们研究了基本的路由,但这只能让我们走到目前为止,我们必须明确地定义我们的端点,然后将它们分配给处理程序。如果 URL 中有通配符或变量,会发生什么?这是 Web 和任何严肃 Web 服务器的绝对必要部分。

要调用一个非常简单的示例,请考虑为每个博客条目托管一个具有唯一标识符的博客。这可以是表示数据库 ID 条目的数字 ID,也可以是基于文本的全局唯一标识符,例如my-first-block-entry

在前面的示例中,我们希望将类似于/pages/1的 URL 路由到名为1.html的文件名。或者,在基于数据库的场景中,我们希望使用/pages/1/pages/hello-world分别映射到 GUID 为1hello-world的数据库条目。要做到这一点,我们要么需要包含一个可能的端点的详尽列表,这是非常浪费的,要么实现通配符,最好是通过正则表达式。

无论哪种情况,我们都希望能够在应用程序中直接利用 URL 中的值。这很简单,URL 参数来自GETPOST。我们可以简单地提取这些 URL,但它们在干净、分层或描述性 URL 方面并不特别优雅,而这些 URL 通常是搜索引擎优化所必需的。

内置的net/http路由系统可能在设计上相对简单。要从任何给定请求中的值中获得更复杂的内容,我们要么需要扩展路由功能,要么使用一个完成了这项工作的包。

在 Go 已经公开的几年里,社区不断发展,出现了许多 web 框架。在继续本书的过程中,我们将更深入地讨论这些问题,但其中一个特别受欢迎且非常有用:Gorilla web toolkit。

顾名思义,Gorilla 与其说是一个框架,不如说是一套非常有用的工具,它们通常捆绑在框架中。具体而言,大猩猩包含:

  • gorilla/context:这是一个用于从请求创建全局可访问变量的包。它有助于共享 URL 中的值,而无需重复代码在应用程序中访问该值。
  • gorilla/rpc:这实现了 RPC-JSON,这是一个用于远程代码服务和通信的系统,没有实现特定的协议。这依赖于 JSON 格式来定义任何请求的意图。
  • gorilla/schema:这是一个包,允许将表单变量简单打包到struct中,这是一个繁琐的过程。
  • gorilla/securecookie:毫不奇怪,它为您的应用程序实现了经过身份验证和加密的 cookie。
  • gorilla/sessions:与 cookie 类似,它利用基于文件和/或基于 cookie 的会话系统提供独特、长期和可重复的数据存储。
  • gorilla/mux:这是为了创建灵活的路由,允许正则表达式为路由器指定可用变量。
  • 最后一个包是我们最感兴趣的包,它附带了一个名为gorilla/reverse的相关包,基本上允许您反向创建基于正则表达式的 MUX。我们将在后面的部分详细介绍这个主题。

您可以使用[T0]通过 GitHub 位置获取各个 Gorilla 软件包。例如,要获取 mux 包,请访问github.com/gorilla/mux即可,并将包带到您的GOPATH中。有关其他软件包的位置(它们相当不言自明),请访问http://www.gorillatoolkit.org/

让我们深入了解如何创建灵活的路由,并使用正则表达式将参数传递给我们的处理程序:

package main

import (
  "github.com/gorilla/mux"
  "net/http"
)

const (
  PORT = ":8080"
)

除了 Gorilla 软件包导入之外,我们的上一个代码应该对此很熟悉:

func pageHandler(w http.ResponseWriter, r *http.Request) {
  vars := mux.Vars(r)
  pageID := vars["id"]
  fileName := "files/" + pageID + ".html"
  http.ServeFile(w,r,fileName)
}

这里,我们创建了一个路由处理程序来接受响应。这里需要注意的是使用了mux.Vars,这是一种从http.Request中查找查询字符串变量并将其解析为映射的方法。然后可以通过键引用结果来访问值,在本例中为id,我们将在下一节中介绍。

func main() {
  rtr := mux.NewRouter()
  rtr.HandleFunc("/pages/{id:[0-9]+}",pageHandler)
  http.Handle("/",rtr)
  http.ListenAndServe(PORT,nil)
}

在这里,我们可以在处理程序中看到一个(非常基本的)正则表达式。我们在{id:[0-9]+}中为名为id的参数分配/pages/之后的任意位数;这是我们在pageHandler中提取的值。

通过添加两个虚拟端点,可以看到一个更简单的版本,该版本显示了如何使用该方法来描绘单独的页面:

func main() {
  rtr := mux.NewRouter()
  rtr.HandleFunc("/pages/{id:[0-9]+}", pageHandler)
  rtr.HandleFunc("/homepage", pageHandler)
  rtr.HandleFunc("/contact", pageHandler)
  http.Handle("/", rtr)
  http.ListenAndServe(PORT, nil)
}

当我们访问与此模式匹配的 URL 时,pageHandler尝试在files/子目录中查找页面,并直接返回该文件。

/pages/1的响应如下所示:

Using more complex routing with Gorilla

此时,您可能已经在问了,但是如果我们没有请求的页面怎么办?或者,如果我们移动了那个位置会发生什么?这就引出了 web 服务中返回错误响应的两种重要机制,作为其中的一部分,可能会重定向已移动的请求或具有需要向最终用户报告的其他有趣属性的请求。

重定向请求

在我们研究像 404 这样的简单且极其常见的错误之前,让我们先讨论一下重定向请求的想法,这是非常常见的。尽管并非总是因为对普通用户来说显而易见或有形的原因。

所以我们可能想要将请求重定向到另一个请求?正如 HTTP 规范所定义的,有很多原因可能导致我们在任何给定请求上实现自动重定向。以下是其中的一些及其相应的 HTTP 状态代码:

  • 非规范地址可能需要重定向到规范地址,以便进行 SEO 或更改站点架构。由301 永久移动302 发现处理。
  • 成功或不成功POST后重定向。这有助于我们防止意外地重新发布相同的表单数据。通常,这由307 临时重定向定义。
  • 页面不一定会丢失,但它现在位于另一个位置。由状态码301 永久移动处理。

net/http的基础上,执行其中任何一项都非常简单,但正如您所预料的那样,使用更健壮的框架(如 Gorilla)可以简化和改进这些操作。

服务基本错误

在这一点上,谈论一些错误是有意义的。很可能,您在使用我们的基本平面文件服务服务器时已经遇到了一个问题,特别是当您超过两到三页时。

我们的示例代码包括四个用于平面服务的示例 HTML 文件,编号为1.html2.html等等。当你到达/pages/5终点时会发生什么?幸运的是,http包将自动处理文件未找到的错误,就像大多数常见的 web 服务器一样。

此外,与大多数常见的 web 服务器类似,错误页面本身很小、平淡且难以描述。在下面的部分中,您可以看到我们从 Go 得到的404 页面未找到状态响应:

Serving basic errors

如前所述,这是一个非常基本和不伦不类的页面。通常情况下,这是一件好事,错误页面包含的信息或天赋比必要的多,可能会产生负面影响。

把这个 T1 错误作为例子来考虑。如果我们包含对存在于同一服务器上的图像和样式表的引用,如果这些资产也丢失了会发生什么?

简言之,您可能很快就会出现递归错误,每个404页面都会调用一个图像和样式表,触发404响应,并重复该循环。即使 web 服务器足够聪明,能够阻止这种情况,而且很多人都是如此,它也会在日志中产生一个噩梦场景,使日志充满噪音,变得毫无用处。

让我们来看一些代码,我们可以使用这些代码为/files目录中的任何缺失文件实现一个全面的404页面:

package main

import (
  "github.com/gorilla/mux"
  "net/http"
  "os"
)

const (
  PORT = ":8080"
)

func pageHandler(w http.ResponseWriter, r *http.Request) {
  vars := mux.Vars(r)
  pageID := vars["id"]
  fileName := "files/" + pageID + ".html"_, 
  err := os.Stat(fileName)
    if err != nil {
      fileName = "files/404.html"
    }

  http.ServeFile(w,r,fileName)
}

在这里,您可以看到,我们首先尝试使用os.Stat(及其潜在错误)检查文件,并输出我们自己的404响应:

func main() {
  rtr := mux.NewRouter()
  rtr.HandleFunc("/pages/{id:[0-9]+}",pageHandler)
  http.Handle("/",rtr)
  http.ListenAndServe(PORT,nil)
}

现在,如果我们看一下404.html页面,我们将看到我们已经创建了一个自定义 HTML 文件,该文件生成的内容比我们之前调用的默认Go page Not Found消息更加用户友好。

让我们来看看这是什么样子,但请记住,它可以看你喜欢的任何方式:

<!DOCTYPE html>
<html>
<head>
<title>Page not found!</title>
<style type="text/css">
body {
  font-family: Helvetica, Arial;
  background-color: #cceeff;
  color: #333;
  text-align: center;
}
</style>
<link rel="stylesheet" type="text/css" media="screen" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"></link>
</head>

<body>
<h1><i class="ion-android-warning"></i> 404, Page not found!</h1>
<div>Look, we feel terrible about this, but at least we're offering a non-basic 404 page</div>
</body>

</html>

另外,请注意,虽然我们将404.html文件与其他文件保持在同一目录中,但这只是为了简单起见。

实际上,在大多数具有自定义错误页面的生产环境中,我们更希望它存在于自己的目录中,理想情况下,该目录不在我们网站的公共可用部分。毕竟,您现在可以通过访问http://localhost:8080/pages/404以一种实际上不是错误的方式访问错误页面。这将返回错误消息,但实际情况是,在本例中找到了文件,而我们只是返回它。

让我们通过访问 OutT1 席来查看我们的新的更漂亮的 To.T0.页面,它指定了一个不存在于文件系统中的静态文件:

Serving basic errors

通过显示更友好的错误消息,我们可以为遇到错误的用户提供更有用的操作。考虑一些可能从更富表现力的错误页面获益的其他常见错误。

总结

现在,我们不仅可以使用net/http包生成基本路线,还可以使用 Gorilla 工具包生成更复杂的路线。通过利用 Gorilla,我们现在可以创建正则表达式并实现基于模式的路由,并为路由模式提供更大的灵活性。

随着灵活性的提高,我们现在也必须注意错误,因此我们已经研究了如何处理基于错误的重定向和消息,包括定制的[T0]404,Page not found[T1]消息,以生成更多定制的错误消息。

现在我们已经掌握了创建端点、路由和处理程序的基本知识;我们需要开始做一些非平凡的数据服务。

第三章连接到数据中,我们将开始从数据库中获取动态信息,这样我们可以更智能、更可靠地管理数据。通过连接到两个不同的常用数据库,我们将能够构建健壮、动态和可伸缩的 web 应用程序。