Skip to content

Files

Latest commit

00d6687 · Oct 25, 2021

History

History
712 lines (442 loc) · 31.8 KB

File metadata and controls

712 lines (442 loc) · 31.8 KB

一、我们在 Go 中的第一个 API

如果您花时间在 Web 上(或者在非 Web 上)开发应用程序,不久您就会发现自己面临与 Web 服务或 API 交互的前景。

无论是您需要的库还是您必须与之交互的另一个应用程序的沙箱,开发世界在很大程度上依赖于不协调的应用程序、语言和格式之间的合作。

毕竟,这就是为什么我们要从 API 开始,以允许任意两个给定平台之间的标准化通信。

如果你花很长时间在网络上工作,你会遇到糟糕的 API。我们指的是不全面、不符合最佳实践和标准、语义混乱或缺乏一致性的 API。您将遇到在某些地方随意使用 OAuth 或简单 HTTP 身份验证的 API,而在另一些地方则相反,或者更常见的是,忽略 HTTP 谓词的指定用途的 API(我们将在本章后面对此进行更多讨论)。

谷歌的 Go 语言特别适合服务器。凭借其内置的 HTTP 服务、XML 和 JSON 数据编码的简单方法、高可用性和并发性,它是 API 的理想平台。

在本书中,我们不仅将探讨一个健壮且干净的 API 开发,还将探讨它与其他 API 和数据源的交互,以及此类开发的最佳实践。我们将构建一个大型服务和一系列小型服务,用于独立的课程。

最重要的是,到最后,您应该能够与 Go 中的任何网络 API 进行交互,并能够自己设计和执行一个完善的 API 套件。

这本书至少需要对基于 web 的 API 有一个偶然的熟悉,并且需要具备 Go 初学者的水平,但当我们讨论新概念时,我们会做一些非常简短的介绍,如果您对 Go 或 API 的这一方面不是完全精通的话,我们会引导您了解更多信息。

我们还将讨论一下 Go 中的并发性,但如果您想了解更多信息,我们不会太详细,请查看我的著作,掌握 Go 中的并发性Packt Publishing

本章将介绍以下主题:

  • 理解需求和依赖关系
  • 介绍 HTTP 包
  • 建设我们的第一条路线
  • 通过 HTTP 设置数据
  • 将数据从数据存储区提供给客户端

理解需求和依赖关系

在我们深入阅读本书之前,最好先检查一下您需要安装的东西,以便在开发、测试和部署 API 时处理所有示例。

安装 Go

应该没有[T0]说我们需要安装 go 语言。然而,为了完成我们在本书中所做的一切,您还需要安装一些相关的项目。

Go 适用于 Mac OS X、Windows 和最常见的 Linux 变体。您可以在下载二进制文件 http://golang.org/doc/install

在 Linux 上,您通常可以通过发行版的包管理器进行访问。例如,你可以通过一个简单的apt-get install golang命令在 Ubuntu 上获取它。大多数发行版都存在类似的情况。

除了核心语言之外,我们还将与 Google App Engine 进行一些合作,使用 App Engine 进行测试的最佳方式是安装软件开发工具包SDK)。这将允许我们在部署应用程序之前在本地测试它们,并模拟许多仅在应用程序引擎上提供的功能。

App 引擎 SDK 可从下载 https://developers.google.com/appengine/downloads

虽然我们显然对 Go SDK 最感兴趣,但您也应该使用 Python SDK,因为 Go SDK 中可能没有一些次要的依赖项。

安装和使用 MySQL

我们将使用大量不同的数据库和数据存储来管理测试数据和真实数据,MySQL 将是主要的数据库和数据存储之一。

我们将使用 MySQL 作为用户的存储系统;它们的消息和它们之间的关系将存储在我们更大的应用程序中(稍后我们将对此进行更多讨论)。

MySQL 可从下载 http://dev.mysql.com/downloads/

您还可以通过 Linux/OS X 上的软件包管理器轻松获取它,如下所示:

  • Ubuntu:[T0]
  • 自制 OS X:[T0]

Redis

Redis 是两个 NoSQL 数据存储中的第一个,我们将在两个不同的演示中使用它们,包括缓存数据库中的数据以及 API 输出。

如果您不熟悉 NoSQL,我们将在示例中对使用 Redis 和 Couchbase 进行结果收集做一些非常简单的介绍。如果您了解 MySQL,Redis 至少会有类似的感觉,并且您不需要完整的知识库就可以按照我们使用它的方式使用该应用程序。

Redis 可从下载 http://redis.io/download

Redis 可以通过以下方式在 Linux/OS X 上下载:

  • Ubuntu:[T0]
  • 带有自制软件的 OS X:brew install redis

泊位

如前所述,Couchbase 将是我们在各种产品中使用的第二个 NoSQL 解决方案,主要用于设置短期或短暂的密钥存储查找,以避免瓶颈,并作为内存缓存的一个实验。

与 Redis 不同,Couchbase 使用简单的 REST 命令来设置和接收数据,所有内容都以 JSON 格式存在。

couchbase can be downloaded from【t0.http://www.couchbase.com/download

  • For Ubuntu (deb), use the following command to download Couchbase:

    dpkg -i couchbase-server version.deb
  • For OS X with Homebrew use the following command to download Couchbase:

    brew install https://github.com/couchbase/homebrew/raw/stable/Library/Formula/libcouchbase.rb

NGINX

尽管 Go 提供了运行高并发、高性能 web 服务器所需的一切,但我们将尝试在结果周围包装一个反向代理。我们这样做主要是为了应对现实世界中有关可用性和速度的问题。Nginx 本机不可用于 Windows

  • For Ubuntu, use the following command to download Nginx:

    apt-get install nginx
  • For OS X with Homebrew, use the following command to download Nginx:

    brew install nginx

ApacheJMeter

我们将利用 JMeter 进行基准测试和调整 API 的性能。您在这里有一些选择,因为有几个用于模拟流量的压力测试应用程序。我们将讨论的两个是JMeter和 Apache 内置的Apache 基准AB平台。后者在基准测试方面非常可靠,但在 API 中的应用有点有限,因此首选 JMeter。

在构建一个 API 时,我们需要考虑的一个问题是它能够抵抗大量的流量(并且当它不能的时候引入一些缓解措施),所以我们需要知道我们的限制是什么。

ApacheJMeter 可以从 http://jmeter.apache.org/download_jmeter.cgi T2

使用预定义的数据集

虽然在本书的整个过程中不完全需要我们的虚拟数据集,但我们通过引入它来构建我们的社交网络时,你可以节省很多时间,因为它充满了用户、帖子和图片。

通过使用此数据集,您可以跳过创建此数据来测试 API 和 API 创建的某些方面。

我们的虚拟数据集可在下载 https://github.com/nkozyra/masteringwebservices

选择 IDE

选择集成开发环境IDE)是开发人员可以做出的最个性化的选择之一,很少有开发人员对自己喜欢的开发环境没有坚定的热情。

本书中的任何内容都不需要一个 IDE 而不是另一个 IDE;事实上,Go 在编译、格式化和测试方面的大部分优势在于命令行级别。这就是说,我们希望至少探索一些更受欢迎的编辑器和 IDE 选择,这些选择存在于 Go 中。

日食

作为任何语言都可以使用的最流行、最扩展的 IDE 之一,Eclipse 显然是第一个被提及的。大多数语言都以 Eclipse 插件的形式获得支持,Go 也不例外。

这种单一的软件有一些缺点;它在某些语言上偶尔会出现错误,对于某些自动完成函数来说速度非常慢,并且比大多数其他可用选项都要重。

然而,好处是多方面的。Eclipse 非常成熟,有一个庞大的社区,当出现问题时,您可以从中寻求支持。而且,它是免费使用的。

崇高的文本

崇高的文字是我们特别喜欢的,但它附带了一个很大的警告——这是这里列出的唯一一个不是免费的。

与繁重的 IDE 相比,它更像一个完整的代码/文本编辑器,但它包括代码完成选项和将 Go 编译器(或其他语言的编译器)直接集成到界面中的能力。

尽管 Sublime Text 的许可证价格为 70 美元,但许多开发人员发现它的优雅和速度非常值得。你可以无限期地试用这个软件,看看它是否适合你;除非您购买许可证,否则它将作为 nagware 运行。

崇高文本可从下载 http://www.sublimetext.com/2

\12304

LiteIDE 比这里提到的其他 IDE 年轻得多,但值得注意的是,它专注于 Go 语言。

它是跨平台的,在后台实现了许多 Go 的命令行魔法,使其真正集成。LiteIDE 还可以直接在 IDE 和健壮的包浏览器中处理代码自动完成、go fmt、构建、运行和测试。

如果你想让 Go 语言更简洁、更直接,那么它是免费的,完全值得一试。

LiteIDE 可从下载 https://code.google.com/p/golangide/

“T0”:IntelliJ 理念

与 Eclipse 一起出现的是 JetBrains 系列的 IDE,它跨越的语言数量与 Eclipse 大致相同。最终,这两种语言都是在考虑 Java 的基础上构建的,这意味着有时其他语言的支持可能是次要的。

然而,这里的 Go 集成看起来相当健壮和完整,因此如果您有许可证的话,值得一试。如果您没有许可证,可以尝试免费的社区版。

一些客户端工具

尽管我们将要讨论的绝大多数内容将集中在 Go 和 API 服务上,但我们将对客户端与 API 的交互进行一些可视化。

在这样做的过程中,我们将主要关注纯 HTML 和 JavaScript,但对于更具交互性的方面,我们还将[T0]引入 jQuery 和 AngularJS。

我们为客户端演示所做的大部分工作将在本书的 GitHub 存储库中提供 https://github.com/nkozyra/goweb 在客户机下方。

jQuery 和 AngularJS 都可以从 Google 的 CDN 动态加载,这将避免您不得不在本地下载和存储它们。GitHub 上的示例动态地调用这些。

要动态加载 AngularJS,请使用以下代码:

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>

要动态加载 jQuery,请使用以下代码:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

看看我们的申请表

在本书中,我们将构建无数的小型应用程序来演示点、函数、库和其他技术。然而,我们还将关注一个更大的项目,它模拟了一个社交网络,在这个社交网络中,我们通过 API 创建并返回用户、状态等。

虽然我们将致力于开发更大的应用程序,以此来演示拼图的每一部分,但我们还将构建和测试自包含的应用程序、API 和接口。

后一组将以一个快速打击者开头,让您知道它不是我们更大应用程序的一部分。

建立我们的数据库

如前所述,我们将设计一个几乎完全在 API 级别运行的社交网络(至少首先),作为本书中的项目。

当我们想到主要的社交网络(从过去到现在),其中有一些无处不在的概念,如下所示:

  • 创建用户和维护用户配置文件的能力
  • 能够共享消息或状态并基于它们进行对话
  • 对所述状态/信息表示高兴或不高兴的能力,以指示任何给定信息的价值

我们将在这里构建一些其他功能,但让我们从基础开始。让我们在 MySQL 中创建数据库,如下所示:

create database social_network;

这将是本书中我们社交网络产品的基础。现在,我们只需要一个users表来存储我们的个人用户及其最基本的信息。我们将对其进行修改,以包括更多功能:

CREATE TABLE users (
  user_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  user_nickname VARCHAR(32) NOT NULL,
  user_first VARCHAR(32) NOT NULL,
  user_last VARCHAR(32) NOT NULL,
  user_email VARCHAR(128) NOT NULL,
  PRIMARY KEY (user_id),
  UNIQUE INDEX user_nickname (user_nickname)
)

在这一章中我们不需要做太多,所以这应该足够了。我们将获得用户最基本的信息名称、昵称和电子邮件,其他信息就不多了。

引入 HTTP 包

我们的绝大多数 API 工作将通过 REST 来处理,因此您应该非常熟悉 Go 的http包。

除了通过 HTTP 提供服务外,http包还包括许多其他非常有用的实用程序,我们将详细介绍这些实用程序。这些包括 cookiejar、设置客户端、反向代理等等。

不过,我们现在感兴趣的主要实体是[T0]结构,它提供了所有服务器操作和参数的基础。在服务器中,我们可以设置 TCP 地址、HTTP 多路复用以路由特定请求、超时和头信息。

Go 还提供了一些快捷方式,用于在不直接初始化结构的情况下调用服务器。例如,如果您有许多默认属性,则可以使用以下代码:

Server := Server {
  Addr: ":8080",
  Handler: urlHandler,
  ReadTimeout: 1000 * time.MicroSecond,
  WriteTimeout: 1000 * time.MicroSecond,
  MaxHeaderBytes: 0,
  TLSConfig: nil
}

您只需使用以下代码执行即可:

http.ListenAndServe(":8080", nil)

这将为您调用一个服务器结构,并在其中仅设置AddrHandler属性。

当然,有时我们希望对服务器进行更精细的控制,但就目前而言,这样做还可以。让我们采用这个概念,第一次通过 HTTP 输出一些 JSON 数据。

快速打击者-通过 API 向世界问好

正如本章前面提到的,我们将偏离正轨,做一些工作,我们将以quick hitter作为开场白,表明这与我们的大项目无关。

在本例中,我们只是希望加快http包的速度,并向浏览器提供一些 JSON。不出所料,我们只会向全世界输出令人不快的信息。

让我们用我们所需的软件包和导入进行设置:

package main

import
(
  "net/http"
  "encoding/json"
  "fmt"
)

这是通过 HTTP 以 JSON 格式输出简单字符串所需的最低要求。编组 JSON 数据可能比我们在这里看到的要复杂一些,因此,如果消息的结构没有立即发挥作用,请不要担心。

这是我们的响应结构,其中包含我们希望在从 API 获取数据后发送给客户端的所有数据:

type API struct {
  Message string "json:message"
}

显然,这里还没有很多。我们所要设置的只是一个明显命名为[T0]变量中的单个消息字符串。

最后,我们需要设置我们的主要功能(如下所示),以响应路由并交付封送 JSON 响应:

func main() {

  http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {

    message := API{"Hello, world!"}

    output, err := json.Marshal(message)

    if err != nil {
      fmt.Println("Something went wrong!")
    }

    fmt.Fprintf(w, string(output))

  })

  http.ListenAndServe(":8080", nil)
}

进入main()后,我们设置了一个路由处理函数来响应/api处的请求,该函数使用Hello, world!初始化 API 结构,然后将其封送到 JSON 字节数组output,并在将该消息发送到iowriter类(在本例中为http.ResponseWriter值)后,将其转换为字符串。

最后一步是通过一个需要字符串的函数发送字节数组的一种快速而肮脏的方法,但是这样做不会有太多错误。

Go 通过将类型作为目标变量旁边的函数应用,非常简单地处理类型转换。换句话说,我们可以通过简单地使用int(OurInt64)函数将int64值转换为整数。这种类型有一些不能直接强制转换的例外情况和一些其他陷阱,但这是一般的想法。在可能的例外情况中,有些类型不能直接转换为其他类型,有些类型需要像strconv这样的包来管理类型转换。

如果我们转到浏览器并调用localhost:8080/api(如以下屏幕截图所示),假设一切正常,您应该得到我们所期望的结果:

Quick hitter – saying Hello, World via API

打造我们的第一条路线

当我们在 Go 术语中讨论路由时,我们更准确地讨论了多路复用器或mux。在这种情况下,多路复用器指的是获取 URL 或 URL 模式并将其转换为内部函数。

您可以将其视为从请求到函数(或处理程序)的简单映射。你可以这样写:

/api/user  func apiUser
/api/message  func apiMessage
/api/status  func apiStatus

net/http包提供的内置 mux/路由器存在一些限制。例如,不能向路由提供通配符或正则表达式。

您可能希望能够执行以下代码段中讨论的操作:

  http.HandleFunc("/api/user/\d+", func(w http.ResponseWriter, r *http.Request) {

    // react dynamically to an ID as supplied in the URL

  })

但是,这会导致解析错误。

如果您认真研究过任何成熟的 web API,您就会知道这是行不通的。我们需要能够对动态和不可预测的请求做出反应。我们的意思是,预测每个数字用户是站不住脚的,因为它涉及到映射到一个函数。我们需要能够接受和使用模式。

这个问题有几种解决办法。第一种是使用第三方平台,该平台内置了这种健壮的路由。有一些非常好的平台可供选择,因此我们现在将快速了解这些平台。

大猩猩

Gorilla 是一个包罗万象的 web 框架,我们将在本书中大量使用它。它正好有我们需要的类 URL 路由包(在其gorilla/mux包中),并且它还提供一些其他非常有用的工具,如 JSON-RPC、安全 cookie 和全局会话数据。

Gorilla 的mux包允许我们使用正则表达式,但它也有一些速记表达式,让我们可以定义我们期望的请求字符串类型,而不必写出完整的表达式。

例如,如果我们有一个像/api/users/309这样的请求,我们可以在 Gorilla 中按如下方式简单路由它:

gorillaRoute := mux.NewRouter()
gorillaRoute.HandleFunc("/api/{user}", UserHandler)

然而,这样做有一个明显的风险,因为这是开放的,我们有可能得到一些数据验证问题。如果此函数接受任何内容作为参数,并且我们只需要数字或文本,那么这将导致底层应用程序出现问题。

因此,Gorilla 允许我们用正则表达式来澄清这一点,如下所示:

r := mux.NewRouter()
r.HandleFunc("/products/{user:\d+}", ProductHandler)

现在,我们只能得到我们期望的基于数字的请求参数。让我们用这个概念修改前面的示例来演示:

package main

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

type API struct {
  Message string "json:message"
}

func Hello(w http.ResponseWriter, r *http.Request) {

  urlParams := mux.Vars(r)
  name := urlParams["user"]
  HelloMessage := "Hello, " + name

  message := API{HelloMessage}
  output, err := json.Marshal(message)

  if err != nil {
    fmt.Println("Something went wrong!")
  }

  fmt.Fprintf(w, string(output))

}

func main() {

  gorillaRoute := mux.NewRouter()
  gorillaRoute.HandleFunc("/api/{user:[0-9]+}", Hello)
  http.Handle("/", gorillaRoute)
  http.ListenAndServe(":8080", nil)
}

提示

下载示例代码

您可以下载您在账户购买的所有 Packt 书籍的示例代码文件 http://www.packtpub.com 。如果您在其他地方购买了本书,您可以访问http://www.packtpub.com/support 并注册,将文件直接通过电子邮件发送给您。

有了这段代码,我们在路由级别上进行了一些验证。对/api/44的有效请求将给我们一个正确的响应,如以下屏幕截图所示:

Gorilla

向发出类似/api/nkozyra的无效请求将给我们 404 响应。

路线

来自drone.io的路由明确且仅是 Go 的路由包。这使得它比 Gorilla web 工具包更加专注。

在大多数情况下,URL 路由在较小的应用程序中不会成为瓶颈,但在应用程序扩展时应该考虑这一点。就我们的目的而言,大猩猩和路线之间的速度差异可以忽略不计。

在 routes 中定义您的mux包非常简洁。下面是响应 URL 参数的Hello world消息的变体:

func Hello(w http.ResponseWriter, r *http.Request) {

  urlParams := r.URL.Query()
  name := urlParams.Get(":name")
  HelloMessage := "Hello, " + name
  message := API{HelloMessage}
  output, err := json.Marshal(message)

  if err != nil {
    fmt.Println("Something went wrong!")
  }

  fmt.Fprintf(w, string(output))

}

func main() {

  mux := routes.New()
  mux.Get("/api/:name", Hello)
  http.Handle("/", mux)
  http.ListenAndServe(":8080", nil)
}

这里的主要区别(与 Gorilla 一样)是我们将routes多路复用器传递给http,而不是使用内部多路复用器。与 Gorilla 一样,我们现在可以使用可变 URL 模式更改输出,如下所示:

Routes

您可以在上阅读更多关于路由和如何安装路由的 https://github.com/drone/routes

运行以下命令以安装路由:

go get github.com/drone/routes

通过 HTTP 设置数据

既然我们已经研究了如何处理路由,那么我们就可以直接从损坏端点向我们的 Tyt0 数据库中注入数据。

在本例中,我们将专门研究POST请求方法,因为在大多数情况下,当大量数据可以传输时,您希望避免GET请求施加的长度限制。

提示

从技术上讲,PUT请求是在创建-读取-更新-删除CRUD)概念中用于创建数据的请求所使用的语义正确的方法,但多年的使用已基本上将PUT降级为历史脚注。最近,一些人支持将PUT(和DELETE)恢复到适当的位置。Go(和 Gorilla)将很乐意允许您将请求降级到其中一个,随着我们的前进,我们将朝着更有效的协议语义迈进。

连接 MySQL

Go 有一个很大程度上内置的不可知数据库连接工具,大多数第三方数据库连接包都依附于它。Go 的默认 SQL 包是database/sql,它允许更通用的数据库连接,并具有一定的标准化。

但是,我们不会使用自己的 MySQL 连接(至少现在是这样),而是使用第三方附加库。有两个这样的库可用,但我们将使用Go-MySQL-Driver

您可以使用以下命令安装Go-MySQL-Driver(需要 Git):

go get github.com/go-sql-driver/mysql

在本例中,我们假设 MySQL 在[T0]标准端口上与本地主机一起运行。如果未运行,请在示例中进行相应的调整。为了清晰起见,这里的示例还将使用无密码根帐户。

我们的导入将保持基本不变,但有两个明显的增加:sql包(database/sql和前面提到的 MySQL 驱动程序,该驱动程序仅为副作用而导入,在其前面加了下划线:

package main

import
(
  "database/sql"
  _ "github.com/go-sql-driver/mysql"
  "encoding/json"
  "fmt"
  "github.com/gorilla/mux"
  "net/http"
)

我们将使用 Gorilla 设置一个新端点。您可能还记得,当我们打算设置或创建数据时,我们通常会推送一个PUTPOST动词,但就本演示而言,附加 URL 参数是推送数据的最简单方法。下面是我们如何设置这条新路线的:

  routes := mux.NewRouter()
  routes.HandleFunc("/api/user/create", CreateUser).Methods("GET")

请注意,我们正在指定此请求将接受的谓词。在实际使用中,建议将其用于GET请求。

我们的CreateUser功能将接受多个参数-useremailfirstlastUser表示一个简短的用户名,其余的应该是不言自明的。我们将在代码之前先定义一个[T6]结构,如下所示:

type User struct {
  ID int "json:id"
  Name  string "json:username"
  Email string "json:email"
  First string "json:first"
  Last  string "json:last"
}

现在让我们来看一下函数本身:

func CreateUser(w http.ResponseWriter, r *http.Request) {

  NewUser := User{}
  NewUser.Name = r.FormValue("user")
  NewUser.Email = r.FormValue("email")
  NewUser.First = r.FormValue("first")
  NewUser.Last = r.FormValue("last")
  output, err := json.Marshal(NewUser)
  fmt.Println(string(output))
  if err != nil {
    fmt.Println("Something went wrong!")
  }

  sql := "INSERT INTO users set user_nickname='" + NewUser.Name + "', user_first='" + NewUser.First + "', user_last='" + NewUser.Last + "', user_email='" + NewUser.Email + "'"
  q, err := database.Exec(sql)
  if err != nil {
    fmt.Println(err)
  }
  fmt.Println(q)
}

当我们运行此操作时,我们的路由 API 端点应该在localhost:8080/api/user/create处可用。但是如果您查看调用本身,您会注意到我们需要传递 URL 参数来创建用户。我们还没有对输入进行任何健全性检查,也没有确定输入是否干净/已转义,但我们将按如下方式点击 URL:[T1]。

然后,我们将在users表中创建一个用户,如下所示:

Connecting to MySQL

从数据存储向客户端提供数据

显然,如果我们开始通过 API 端点设置数据(尽管很粗糙),我们还希望通过另一个 API 端点检索数据。我们可以使用以下代码轻松修改当前通话,以包括通过请求返回数据的新路由:

func GetUser(w http.ResponseWriter, r *http.Request) {

  urlParams   := mux.Vars(r)
  id       := urlParams["id"]
  ReadUser := User{}
  err := database.QueryRow("select * from users where user_id=?",id).Scan(&ReadUser.ID, &ReadUser.Name, &ReadUser.First, &ReadUser.Last, &ReadUser.Email )
  switch {
      case err == sql.ErrNoRows:
              fmt.Fprintf(w,"No such user")
      case err != nil:
              log.Fatal(err)
  fmt.Fprintf(w, "Error")
      default:
        output, _ := json.Marshal(ReadUser)
        fmt.Fprintf(w,string(output))
  }
}

我们正在做一些新的和值得注意的事情。首先,我们使用的是QueryRow()方法,而不是Exec()。Go 的默认数据库界面提供了两种不同的查询机制,它们的功能略有不同。详情如下:

  • Exec():此方法主要用于不返回行的查询(INSERTUPDATEDELETE)。
  • Query():此方法用于返回一行或多行的查询。这通常用于SELECT查询。
  • QueryRow():此方法类似于Query(),但它只期望一个结果。这通常是一个基于行的请求,类似于我们在上一个示例中遇到的[T4]。然后,我们可以在该行上运行Scan()方法,将返回的值注入到结构的属性中。

因为我们正在将返回的数据扫描到结构中,所以没有得到返回值。使用err值,我们运行一个开关来确定如何向用户或使用我们 API 的应用程序传递响应。

如果没有行,则可能是请求中存在错误,我们会让收件人知道存在错误。

但是,如果有 SQL 错误,那么我们暂时保持沉默。向公众公开内部错误是不好的做法。然而,我们应该回应,有些地方出了问题,但不要说得太具体。

最后,如果请求有效并且我们得到一条记录,我们将把它封送到 JSON 响应中,并在返回它之前将其转换为字符串。我们的以下结果与有效请求的预期结果类似:

Serving data from the datastore to the client

然后,如果我们从用户表中请求一个实际不存在的特定记录,它会适当地返回一个错误(如下面的屏幕截图所示):

Serving data from the datastore to the client

设置标题为客户端添加细节

[T0]在我们继续讨论的时候,会有更多的想法,即使用 HTTP 头来传递有关我们通过 API 发送或接收的数据的重要信息。

通过对 API 运行curl请求,我们现在可以快速查看通过 API 发送的头文件。当我们这样做的时候,我们会看到这样的情况:

curl --head http://localhost:8080/api/user/read/1111
HTTP/1.1 200 OK
Date: Wed, 18 Jun 2014 14:09:30 GMT
Content-Length: 12
Content-Type: text/plain; charset=utf-8

默认情况下,这是由 Go 发送的一组非常小的头文件。接下来,我们可能希望附加更多的信息头,告诉收件人服务如何处理或缓存数据。

让我们简单地尝试设置一些标题,并使用http包将它们应用到我们的请求中。我们将从一个更基本的响应头开始,并设置一个 Pragma。这是我们的结果集上的一个[T1]杂注,告诉接受我们 API 的用户或服务总是从我们的数据库请求一个新版本。

最终,考虑到我们正在处理的数据,这在本例中是不必要的,但却是证明这种行为的最简单方法。我们可能会发现端点缓存有助于提高性能,但它可能无法为我们提供最新的数据。

http包本身有一个非常简单的方法来设置响应头和获取请求头。让我们修改GetUser函数,告诉其他服务不应该缓存此数据:

func GetUser(w http.ResponseWriter, r *http.Request) {

  w.Header().Set("Pragma","no-cache")

Header()方法返回iowriterHeader结构,我们可以直接使用Set()进行添加,也可以使用Get()进行取值。

现在我们已经完成了,让我们看看我们的输出是如何变化的:

curl --head http://localhost:8080/api/user/read/1111
HTTP/1.1 200 OK
Pragma: no-cache
Date: Wed, 18 Jun 2014 14:15:35 GMT
Content-Length: 12
Content-Type: text/plain; charset=utf-8

正如我们所期望的,我们现在直接在 CURL 的头信息中看到我们的值,它正确地返回不应该缓存这个结果。

当然,我们可以通过 web 服务和 API 发送更有价值的响应头,但这是一个良好的开端。随着我们的前进,我们将利用更多的信息,包括Content-EncodingAccess-Control-Allow-Origin和更多的标题,允许我们指定数据是什么,谁可以访问数据,以及在格式和编码方面他们应该期望什么。

总结

我们已经接触了在 Go 中开发简单 web 服务界面的基础知识。诚然,这个特定的版本非常有限,并且容易受到攻击,但它展示了我们可以用来生成可由其他服务接收的可用、形式化输出的基本机制。

在这一点上,您应该有基本的工具供您使用,这些工具是开始完善这个过程和整个应用程序所必需的。随着我们的推进,我们将继续对 API 应用更全面的设计,因为两个随机选择的 API 端点显然对我们没有多大帮助。

在下一章中,我们将深入了解 API 规划和设计、RESTful 服务的本质,并了解如何将逻辑与输出分离。我们将在第 3 章路由和自举中简要介绍一些逻辑/视图分离概念,并转向更健壮的端点和方法。