Go语言学习笔记(十八)

一、创建HTTP服务器

Web服务器可提供网页、Web服务和文件,而Go语言为创建Web服务器提供了强大的支持。本篇将介绍如何创建Web服务器,使其能够响应不同的路由、不同类型的请求和不同类型的内容。使用Go语言编写Web服务器时,可使用Go语言提供的并发的方法。

1 通过Hello World Web服务器宣告您的存在

标准库中的net/http包提供了多种创建HTTP服务器的方法,他还提供了一个基本路由器。传统上通过创建一个Hello World程序来宣告您的存在。如下面所示程序是编写的最基本的HTTP服务器。

//example01.go
package main

import (
	"net/http"
)

func helloWorld(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World\n"))
}

func main() {
	http.HandleFunc("/", helloWorld)
	http.ListenAndServe(":8000", nil)
}

go run以上程序后,在浏览器中访问http://localhost:8000,我们将在服务器中看到Hello World

  1. 导入net/http包
  2. 在main函数中,使用方法HandleFunc创建了路由/。这个方法接受一个模式和一个函数,其中前者描述了路径,后者指定如何对发送到该路径的请求做出响应。
  3. 函数helloWorld接受一个http.ResponseWriter和一个指向请求的指针。这意味着在这个函数中,可查看或操作请求,再将响应返回给客户端。在这里使用了方法Write来生成响应。这个方法生成的HTTP响应包含状态、报头和响应体。[]byte声明了一个字节切片并将字符串值转换为字节。这意味着方法Write可以使用[]byte,因为这个方法将一个字节切片作为参数。
  4. 为相应客户端,使用了方法ListenAndServe来启动一个服务器,这个服务器监听localhost和端口8000。

2 查看请求和响应

为了查看发送请求和收到的响应,我们将使用工具curl
curl是一款用于发起HTTP请求的命令行工具,他几乎可在任何平台上使用。macOS系统预先安装了curl;linux系统通常也安装了curl。Windows系统没有安装curl;我们可以通过下载GIT for Windows来安装curl(GIT 自带curl)

核实工具安装
1a. 在macOS或Linux系统中,打开终端
1b. 在Windows系统中,通过"开始"菜单打开Git Bash,或者直接打开cmd。
2.输入curl并按回车
3.如果成功显示如下:
在这里插入图片描述

2.1 使用curl发出请求

安装curl后,我们就可以在开发和调试Web服务器时使用它。我们可不需要使用Web浏览器,而使用curl来向服务器发送各种请求以及查看响应,为前面的Hello World Web服务器发出请求,过程如下:

  1. go run example01.go
  2. 打开git bash,执行如下命令,-is指定打印报头,并忽略一些不感兴趣的内容。
    curl -is http://localhost:8000
    效果如下:
    在这里插入图片描述

a.这个响应使用的协议为HTTP 1.1,状态码为200
b.报头Date详细地描述了响应的发送时间
c.报头Content-Length详细地指出了响应的长度,这里是12字节
d.报头Content-Type指出了内容的类型以及使用的编码。在这里,响应的内容类型为text/plain,是使用utf-8进行编码的
e.最后输出的是响应体,这里是Hello World

2.2 详谈路由

HandleFunc用于注册对URL地址映射进行相应的而函数。简单地说,HandleFunc创建一个路由表,让HTTP服务器能够正确地做出响应。

http.HandleFunc("/",helloWorld)
http.HandleFunc("/users/",usersHandler)
http.HandLeFunc("/projects/",projectsHandler)

在这个实例中,每当用户向/发出请求时,都将调用函数helloWorld,每当用户向/users/发出请求时,都将调用函数usersHandler,以此类推。

有关路由器的行为,有以下几点需要注意。

a.路由器默认将没有指定处理程序的请求定向到/
b.路由必须完全匹配。例如,对于向/users发出的请求,将重定向到/,因为这里末尾少了斜杠。
c. 路由器不关心请求的类型,而直管将与路由匹配的请求传给相应的处理程序。

3 使用处理程序函数

在Go语言中,路由器负责将路由映射到函数,但如何处理请求以及如何向客户端返回响应,是处理程序函数定义的。很多编程语言和Web框架都采用这样的模式,即先由函数来处理请求和响应,再返回响应。在这方面,Go语言也如此。处理程序函数负责完成如下常见任务。

1.读写报头。
2.查看请求的类型。
3.从数据库中取回数据。
4.分析请求数据。
5.验证身份

处理程序函数能够访问请求和响应,因此一种常见的模式是,先完成对请求的所有处理,在将响应返回给客户端。

响应生成后,就不能在对其做进一步的处理了。在下面的实例中,使用了方法Write来发送请求,接下来的一行设置了响应的一个报头。鉴于响应已经发送,这行代码不会有任何作用,但能够通过编译。这里要说的是发送响应是最后一步。

func helloWorld(w http.ResponseWriter,r *http.Request){
	w.Write([]byte("Hello World\n"))
	// This has no effect, as the response is already written
	w.Header().Set("X-My-Header","I am setting a header!")
}

4 处理404错误

默认路由器的行为是将所有没有指定处理程序的请求都定向到/。回到第一个示例,如果用户请求的页面不存在,将调用给/指定的处理程序,从而返回响应Hello World和状态码200
在这里插入图片描述
然而,鉴于请求的路由不存在,原本应返回404错误(页面未找到)。为此,可在处理默认路由的函数中检查路径,如果路径不为/,就返回404错误,如下所示:

//example02.go
package main
import (
	"net/http"
)
func helloWorld(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	w.Write([]byte("Hello World\n"))
}
func main() {
	http.HandleFunc("/", helloWorld)
	http.ListenAndServe(":8000", nil)
}

运行结果如下:
在这里插入图片描述

相比于原来的Hello World Web 服务器,这里所做的修改如下:

  1. 在处理程序函数hello World中,检查路径是否是/
  2. 如果不是,就调用http包中的方法NotFound,并将响应和请求传递给它。这将向客户端返回一个404响应
  3. 如果路径与/匹配,则if语句将被忽略,进而发送响应Hello World

5 设置报头

创建HTTP服务器时,经常需要设置响应的报头。在创建、读取、更新和删除报头方面,Go语言提供了强大的支持。在下面的示例中,假设服务器将发送一些JSON数据。通过设置Content-Type报头,服务器可告诉客户端,发送的是JSON数据。处理程序函数可使用ResponseWriter来添加报头,如下:

w.Header().Set("Content-Type","application/json;charaset=utf-8")

只要这行代码是在响应被发送给客户端之前执行的,这个报头就会被添加到响应中。如下例(注意此时的数据虽然是一个字符串但json数据往往是从别的地方读取的,并编码为json格式):

//example03.go
package main
import (
	"net/http"
)
func helloWorld(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Write([]byte(`{"hello": "world"}`))
}
func main() {
	http.HandleFunc("/", helloWorld)
	http.ListenAndServe(":8000", nil)
}

效果如下:
在这里插入图片描述

6 响应以不同类型的内容

响应客户端时,HTTP服务器通常提供多种类型的内容。一些常用的内容类型包括text/plain、text/html、application/json和application/xml。如果服务器支持多种类型的内容,客户端可使用Accept报头请求指定类型的内容。这意味着同一个URL可能向浏览器提供HTML,而向API提供JSON。只需对本章的示例稍作修改,就可以让它查看客户端发送的Accept报头,如下:

//example04.go
package main
import (
	"net/http"
)
func helloWorld(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	switch r.Header.Get("Accept") {
	case "application/json":
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.Write([]byte(`{"message": "Hello World"}`))
	case "application/xml":
		w.Header().Set("Content-Type", "application/xml; charset=utf-8")
		w.Write([]byte(`<?xml version="1.0" encoding="utf-8"?><Message>Hello World</Message>`))
	default:
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.Write([]byte("Hello World\n"))
	}
}
func main() {
	http.HandleFunc("/", helloWorld)
	http.ListenAndServe(":8000", nil)
}

执行效果如下:
在这里插入图片描述

在这里使用-H参数,设置请求的报头

7 响应不同类型的请求

除响应以不同类型的内容外,HTTP服务器通常也需要能够响应不同类型的请求。客户端可发送的请求类型是HTTP规范中定义的,包括GET、POST、PUT和DELETE。要使用Go语言创建出响应不同请求类型的HTTP服务器,可采用类似于提供多种类型内容的方法,如下:在这里我们使用switch来检查请求类型:

//example05.go
package main
import (
	"net/http"
)
func helloWorld(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	switch r.Method {
	case "GET":
		w.Write([]byte("Received a GET request\n"))
	case "POST":
		w.Write([]byte("Received a POST request\n"))
	default:
		w.WriteHeader(http.StatusNotImplemented)
		w.Write([]byte(http.StatusText(http.StatusNotImplemented) + "\n"))
	}
}
func main() {
	http.HandleFunc("/", helloWorld)
	http.ListenAndServe(":8000", nil)
}

这里对Web服务器所做的拓展如下。

1.服务器鄙视根据内容类型来生成响应,而是根据请求方法来生成响应。
2.switch语句根据请求类型发送不同的响应
3.在这个示例中,发送一个plain/text响应来指出请求的类型
4.如果请求方法不是GET或者POST,就执行default子句,发送501(Not Implemented HTTP)响应。代码501意味着服务器不明白或不支持客户机使用的HTTP请求方法

运行效果如下(要修改请求类型可使用-X参数):
在这里插入图片描述

8 获取GET和POST请求中的数据

HTTP客户端可在HTTP请求中向HTTP服务器发送数据,这样的典型示例包括以下几点。

  1. 提交表单
  2. 设置有关要返回的数据的选项
  3. 通过API管理数据

在Go语言中,获取客户端请求中的数据很简单,但获取方式遂请求类型而异。对于GET请求,其中的数通常是通过查询字符串设置的。一个通过GET请求发送数据的例子是,在Google网站上搜索。在这个示例中,URL包含以查询字符串的方式指定的搜索关键字。

https://www.google.com/?q=golang

Web服务器可能读取查询字符串中的数据,并使用它来做些事情,如从数据库获取相应的数据,再将其返回给客户端。在Go语言中,以字符串映射的方式提供了请求中的查询字符串参数,我们可使用range子句来遍历它们

func queryParams(w http.ResponseWriter, r *http.Request){
	for k,v := range r.URL.Query(){
		fmt.Printf("%s:	%s\n",k,v)
	}
}

在POST请求中,数据通常是在请求体重发送的。要读取并使用这些数据,可像下面这样做。

func queryParams(w http.ResponseWriter,r *http.Request){
	reqBody,err:=ioutil.ReadAll(r.Body)
	if err!=nil{
		log.Fatal(err)
	}
	fmt.Printf("%s",reqBody)
}

在这里插入图片描述
下面是一个完整的代码示例,演示如何处理不同请求的数据:

//example06.go
package main
import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)
func helloWorld(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	switch r.Method {
	case "GET":
		for k, v := range r.URL.Query() {
			fmt.Printf("%s: %s\n", k, v)
		}
		w.Write([]byte("Received a GET request\n"))
	case "POST":
		reqBody, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("%s\n", reqBody)
		w.Write([]byte("Received a POST request\n"))
	default:
		w.WriteHeader(http.StatusNotImplemented)
		w.Write([]byte(http.StatusText(http.StatusNotImplemented)))
	}
}
func main() {
	http.HandleFunc("/", helloWorld)
	http.ListenAndServe(":8000", nil)
}

运行结果如下:
示例一
在这里插入图片描述
在这里插入图片描述
示例二
使用-X-d修改请求的类型和对应的内容
在这里插入图片描述
在这里插入图片描述

9 相关问题:

9.1 在路由模式中,可使用变量么?如/proucts/:id这样的模式,其中:id是一个变量。

http包默认使用ServeMux来处理路由,因此不支持变量也不支持正则表达式。社区创建的一些流行的路由器支持变量以及请求和内容类型等特性。一班而言,他们能够与http包集成起来

9.2 Go语言有创建服务器的框架库么?

Go语言同样存在Go语言框架库。在大多数情况下,http包提供了我们需要的所有功能

9.3 如何创建HTTPS服务器?

http包提供了方法ListenAndServeTLS,可用来创建HTTPS(TLS)服务器。这个方法的工作原理和ListenAndServe相同,但必须向它传递证书和密钥文件。

9.4 HTTP GET请求和POST请求有何不同?

GET请求向指定资源请求数据,而POST请求向指定资源提交数据。GET请求可能通过查询字符串参数发送数据,而POST请求通过消息题向服务器发送数据。

9.5 在处理程序函数中,w.Write在处理程序响应方面做了什么(其中一个w是ResponseWriter)?

对ResponseWriter调用Write导致响应被发送给客户端,这包括报头和响应体的内容。响应一旦发送,就无法对其进行修改。

9.6 该信任用户提交给HTTP服务器的数据么?

不应该,绝对不要信任客户端提交给服务器的数据,应确定没问题后在使用。

参考书籍
 [1]: 【Go语言入门经典】[英] 乔治·奥尔波 著 张海燕 译
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是兔不是秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值