# 2.01 Server详解与面试要点

## PART1. 学习路线

![学习路线](https://1407465062-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyoSgzgsmuneFp7VNWUbE%2Fuploads%2Fgit-blob-bfcff3ac7ef0e978a4f7452db89e804c0eb0590c%2F%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF.png?alt=media)

## PART2. Web框架的核心

### 2.1 Web框架的构成

在框架对比的时候,我们注意到对于一个Web框架来说,至少要提供三个抽象:

* **代表服务器的抽象**:这里我们称之为Server
* **代表上下文的抽象**:这里我们称之为Context
* **路由树**

### 2.2 Server

从前面框架对比来看,对于一个Web框架来说,我们首先要有一个**整体代表服务器的抽象**,也就是Server.

Server从特性上来说,至少要提供三部分功能:

* **生命周期控制**:即启动、关闭.如果在后期,还要考虑增加生命周期回调特性
  * TODO:这个回调是指啥?
* **路由注册接口**:提供路由注册功能
* **作为http包到Web框架的桥梁**

如果在你的代码中,没有这个**代表服务器的抽象**(换言之,你的代码里没有定义服务器这个概念,或者说没有用于表示服务器的类),那么很多功能你无法实现.例如上边说过的生命周期控制,你的代码里都没有服务器这个"实体"(这里的实体概念上有点像CRUD中的业务实体),自然也就没有控制这个实体"成往怀灭"的能力的.

同样的,路由注册功能没有Server这个抽象也无法实现.因为不同的路由可能要注册到不同的Server上.回忆一下之前讲过的多端口进程的例子.

![Server特性](https://1407465062-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyoSgzgsmuneFp7VNWUbE%2Fuploads%2Fgit-blob-d73b4c66fd65de7ae64abda53e1c15854c31da77%2FServer%E7%89%B9%E6%80%A7.png?alt=media)

## PART3. http.Handler接口

那么问题来了,怎么接入go原生的`net/http`包?

假设我们现在已经定义了一个Server结构体(当然我们现在还不知道它应该有什么成员属性和成员方法),那我们该如何让它和原生的`net/http`包交互?

先来看看原生的`net/http`包如何启动一个Server:

```go
package server

import (
	"net/http"
	"testing"
)

func TestServer(t *testing.T) {
	http.ListenAndServe(":8085", nil)
}
```

这样就已经启动了一个HTTP服务器.那么答案就很明确了:**用我们的Server替换掉代码中的nil,那我们的Server就和原生的`net/http`包交互了**.而我们要做的就是在请求与http包之间,做一个WEB框架.

## PART4. Server Interface的定义

### 4.1 我们需要什么?

```go
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
```

从`http.ListenAndServe()`方法可以看出,我们需要实现一个Handler类型:

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

### 4.2 构建Server Interface的方案:组合`http.Handler`

#### 4.2.1 组合`http.Handler`

通常而言,设计一个稍微复杂的系统,总是需要先使用接口来定义行为,而非直接写实现类的.

工程结构:

```
(base) root@yuanhong 01-composingHandlerIntoServer % tree ./
./
├── server.go
└── server_test.go

0 directories, 2 files
```

* `server.go`:定义Server接口

```go
package composingHandlerIntoServer

import "net/http"

type Server interface {
	// Handler 组合http.Handler接口
	http.Handler
}
```

* `server_test.go`:使用Server接口

```go
package composingHandlerIntoServer

import (
	"net/http"
	"testing"
)

func TestServer(t *testing.T) {
	var s Server
	http.ListenAndServe(":8085", s)
	http.ListenAndServeTLS(":443", "", "", s)
}
```

#### 4.2.2 组合`http.Handler`方案的优缺点

这样设计的优点:

* 用户在使用的时候只需要调用`http.ListenAndServe`就可以
* 和HTTPS协议完全无缝衔接
* 极简设计(相当于没设计)

这样设计的缺点:

* 难以控制生命周期,并且很难在控制生命周期的时候增加回调支持
  * 比如在端口监听之后,服务启动之前,想要做一些操作,这样的设计是不支持的.因为从端口启动开始,后续的所有工作都是http包来完成的.我们肯定不能去魔改http包
* 缺乏控制力:如果将来希望支持优雅退出的功能,将难以支持
  * 只能通过`http.Server.Shutdown()`/`http.Server.Close()`等http包里的方法去实现,没办法自己实现.因为这样的设计即使你给自定义Server接口的实现类设计了`Shutdown()`方法,也调用不到.本质上还是因为**从端口启动开始,后续的所有工作都是http包来完成的**,我们无法干涉这其中的步骤

### 4.3 构建Server Interface的方案:组合`http.Handler`并增加`Start()`方法

#### 4.3.1 增加`Start()`方法

在这个方案中,我们希望使用者调用我们的`Server.Start()`方法来启动HTTP服务器.

工程结构如下:

```
(base) root@yuanhong 02-composingHandlerAndAddStart % tree ./
./
├── httpServer.go
├── httpServer_test.go
└── serverInterface.go

0 directories, 3 files
```

* `serverInterface.go`:定义Server接口

```go
package composingHandlerAndAddStart

import "net/http"

type Server interface {
	http.Handler
	Start(addr string) error
}
```

* `httpServer.go`:Server接口的HTTP实现

```go
package composingHandlerAndAddStart

import (
	"net"
	"net/http"
)

type HTTPServer struct {
}

func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// TODO implement me
	panic("implement me")
}

func (s *HTTPServer) Start(addr string) error {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	// 在监听端口之后,启动服务之前做一些操作
	// 例如在微服务框架中,启动服务之前需要注册服务

	return http.Serve(l, s)
}
```

可以看到,这样的设计就支持注册after start回调了.大白话来说就是:相比于直接调用`http.ListenAndServe()`的方案,这种方案拆解了监听和启动服务这2个步骤,使得在这2个步骤之间可以做一些操作了.

* `httpServer_test.go`:使用HTTP Server

```go
package composingHandlerAndAddStart

import "testing"

func TestServer(t *testing.T) {
	s := &HTTPServer{}
	s.Start(":8084")
}
```

#### 4.3.2 支持HTTPS

可以使用装饰器模式,基于`HTTPServer`结构体进行再封装:

工程结构如下:

```
(base) root@yuanhong 02-composingHandlerAndAddStart % tree ./
./
├── httpServer.go
├── httpServer_test.go
├── httpsServer.go
└── serverInterface.go

0 directories, 4 files
```

* `httpsServer.go`:Server接口的HTTPS实现

```go
package composingHandlerAndAddStart

type HTTPSServer struct {
	HTTPServer
}
```

这个不是现在的重点,只是提一下可以这么去设计.

#### 4.3.3 在`Start()`方法内部创建一个原生的`http.Server`对象

* 工程结构如下:

```
(base) root@yuanhong 02-composingHandlerAndAddStart % tree ./
./
├── createHttpServerInStart.go
├── httpServer.go
├── httpServer_test.go
├── httpsServer.go
└── serverInterface.go

0 directories, 5 files
```

* `createHttpServerInStart.go`:Server接口的HTTP实现

```go
package composingHandlerAndAddStart

import (
	"net"
	"net/http"
)

type HTTPServerWithOriginHttpServer struct {
}

func (s *HTTPServerWithOriginHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// TODO implement me
	panic("implement me")
}

func (s *HTTPServerWithOriginHttpServer) Start(addr string) error {
	originServer := http.Server{
		Addr:              addr,
		Handler:           s,
	}

	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	return originServer.Serve(l)
}
```

#### 4.3.4 组合`http.Handler`并增加`Start()`方法的优缺点

这样设计的优点:

* `Server`接口的实现既可以当成普通的`http.Handler`接口的实现来使用,又可以作为一个独立的实体,拥有自己的管理生命周期的能力
* 完全的控制，可以为所欲为

这样设计的缺点:

* 如果用户不希望通过调用`http.ListenAndServeTLS()`的方式来实现HTTPS支持,那么`Server`接口需要提供HTTPS的支持(也就是HTTPS的实现)

## PART5. ServeHTTP的实现

`ServeHTTP()`方法是处理请求的入口.这里说的"入口"指的是你设计的WEB框架的入口.在这个方法中需要完成3个操作:

* **Context构建**
* **路由匹配**
* **执行业务逻辑**

## PART6. 注册路由API设计

上文说过,`ServeHTTP()`方法作为WEB框架的入口,需要完成3个操作:

* **Context构建**:还没设计了先不考虑
* **路由匹配**:首先需要把路由注册到Server上,才能考虑匹配的过程
* **执行业务逻辑**:这里也先不考虑

首先需要在接口中定义路由注册的行为.参考GIN的IRoutes接口/Iris的APIBuilder结构体/Echo的Echo结构体,他们都是把注册路由的行为定义在接口上的.

在之前的课程分析GIN框架时说过,实际上Engine结构体的`GET()`/`POST()`等HTTP动词方法,本质上调用的都是`Handle()`方法.所以注册路由的方法分为2类:

* **针对任意方法的**:如Gin和Iris的`Handle()`方法、Echo的`Add()`方法
* **针对不同HTTP方法的**:如`GET()`、`POST()`、`DELETE()`这一类方法基本上都是委托给前一类方法

所以实际上核心方法只需要有一个,例如`Handle()`方法.其它的方法都建立在这上面.

## PART7. AddRoute方法

在本例中,我们将上文的`Handle()`方法命名为`AddRoute()`方法.从命名上来讲,`Handle()`表示"处理"的含义,而实际上这个方法要完成的操作是**注册路由**,而非处理,因此用`AddRoute()`更合适.

### 7.1 定义注册路由行为

* 工程结构如下:

```
(base) root@yuanhong 03-serveHTTP % tree ./
./
├── handleFunc.go	// 定义业务逻辑函数类型
├── httpServer.go	// 定义HTTP服务器
└── serverInterface.go	// 定义HTTP服务器接口

0 directories, 3 files
```

* `handleFunc.go`:定义业务逻辑函数类型

```go
package serveHTTP

// HandleFunc 定义业务逻辑函数类型
// Tips: 该类型应与http.HandlerFunc类型一致 此处只是暂时定义一下这个类型
type HandleFunc func()
```

注:此处将其命名为`HandleFunc`而非`HandlerFunc`,是因为`handle`是动词而`handler`是名词,后边跟的`Func`很明显是名词.使用动名词的组合更加符合GO的命名风格.

* `serverInterface.go`:定义HTTP服务器接口

```go
package serveHTTP

import "net/http"

// Server WEB服务器接口
type Server interface {
	// Handler 组合http.Handler接口
	http.Handler

	// Start 启动WEB服务器
	Start(addr string) error

	// AddRoute 注册路由
	AddRoute(method string, path string, handleFunc HandleFunc)
}
```

* `httpServer.go`:定义HTTP服务器

```go
package serveHTTP

import (
	"net"
	"net/http"
)

// 为确保HTTPServer结构体为Server接口的实现而定义的变量
var _ Server = &HTTPServer{}

// HTTPServer HTTP服务器
type HTTPServer struct {
}

// ServeHTTP WEB框架入口
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// TODO implement me
	panic("implement me")
}

// Start 启动WEB服务器
func (s *HTTPServer) Start(addr string) error {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	// 在监听端口之后,启动服务之前做一些操作
	// 例如在微服务框架中,启动服务之前需要注册服务

	return http.Serve(l, s)
}

// AddRoute 注册路由
func (s *HTTPServer) AddRoute(method string, path string, handleFunc HandleFunc) {
	// TODO: implement me
	panic("implement me")
}
```

### 7.2 定义Context

其实相比7.1小节,就是增加了一个Context类型的定义,并修改了HandleFunc类型的入参

* 工程结构如下:

```go
(base) root@yuanhong 03-serveHTTP % tree ./
./
├── context.go	// 定义HandleFunc的上下文
├── handleFunc.go
├── httpServer.go
└── serverInterface.go

0 directories, 4 files
```

* `context.go`:定义HandleFunc的上下文

```go
package serveHTTP

// Context HandleFunc的上下文
type Context struct {
}
```

* `handleFunc.go`:

```go
package serveHTTP

// HandleFunc 定义业务逻辑函数类型
// Tips: 该类型应与http.HandlerFunc类型一致 此处只是暂时定义一下这个类型
type HandleFunc func(ctx Context)
```

其他2个文件没有变化.

### 7.3 思考:是否需要`AddRoutes()`方法?

#### 7.3.1 如果需要AddRoutes()方法,该如何实现?

来看GIN的`Engine.Handle()`方法:

```go
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
	if matched := regEnLetter.MatchString(httpMethod); !matched {
		panic("http method " + httpMethod + " is not valid")
	}
	return group.handle(httpMethod, relativePath, handlers)
}
```

可以看到该方法是支持**1次注册多个逻辑处理函数到路由上的**.如果我们要是也支持这个功能,该如何实现?

* 工程结构如下:

```
(base) root@yuanhong 04-addRoutes % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
└── serverInterface.go

0 directories, 4 files
```

* `serverInterface.go`:

```go
package addRoutes

import "net/http"

// Server WEB服务器接口
type Server interface {
	// Handler 组合http.Handler接口
	http.Handler

	// Start 启动WEB服务器
	Start(addr string) error

	// AddRoute 注册路由
	AddRoute(method string, path string, handleFunc HandleFunc)

	// AddRoutes 支持1个路由对应多个处理函数的注册路由
	AddRoutes(method string, path string, handles ...HandleFunc)
}
```

* `httpServer.go`:

```go
package addRoutes

import (
	"net"
	"net/http"
)

// 为确保HTTPServer结构体为Server接口的实现而定义的变量
var _ Server = &HTTPServer{}

// HTTPServer HTTP服务器
type HTTPServer struct {
}

// ServeHTTP WEB框架入口
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// TODO implement me
	panic("implement me")
}

// Start 启动WEB服务器
func (s *HTTPServer) Start(addr string) error {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	// 在监听端口之后,启动服务之前做一些操作
	// 例如在微服务框架中,启动服务之前需要注册服务

	return http.Serve(l, s)
}

// AddRoute 注册路由
func (s *HTTPServer) AddRoute(method string, path string, handleFunc HandleFunc) {
	// TODO: implement me
	panic("implement me")
}

// AddRoutes 支持1个路由对应多个处理函数的注册路由
func (s *HTTPServer) AddRoutes(method string, path string, handles ...HandleFunc) {
	// TODO: implement me
	panic("implement me")
}
```

#### 7.3.2 从使用的角度思考是否需要AddRoutes()方法

写个单元测试,演示一下如何使用`AddRoutes()`方法:

* 工程结构如下:

```
(base) root@yuanhong 04-addRoutes % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go		// 演示如何调用Server实现的单测
└── serverInterface.go

0 directories, 5 files
```

* `httpServer_test.go`:演示如何调用Server实现的单测

```go
package addRoutes

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

func TestServer(t *testing.T) {
	s := &HTTPServer{}

	// 注册多个处理函数到路由
	handleFoo := func(ctx Context) { fmt.Println("处理第1件事") }
	handleBar := func(ctx Context) { fmt.Println("处理第2件事") }

	s.AddRoutes(http.MethodGet, "/getUser", handleFoo, handleBar)

	_ = s.Start(":8080")
}
```

其他文件无变化.

#### 7.3.3 尝试使用AddRoute()方法实现同等效果

完全可以将7.3.2示例中的`handleFoo`和`handleBar`封装成1个函数,然后调用`AddRoute()`方法,其实是可以达到同等效果的.

* 工程结构如下:

```
(base) root@yuanhong 03-serveHTTP % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go		// 演示如何调用Server实现的单测
└── serverInterface.go

0 directories, 5 files
```

* `httpServer_test.go`:演示如何调用Server实现的单测

```go
package serveHTTP

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

func TestServer(t *testing.T) {
	s := &HTTPServer{}

	// 注册1个处理函数到路由
	handleFoo := func(ctx Context) { fmt.Println("处理第1件事") }
	handleBar := func(ctx Context) { fmt.Println("处理第2件事") }

	// 将2个处理函数封装为1个处理函数
	handleAssemble := func(ctx Context) {
		handleFoo(ctx)
		handleBar(ctx)
	}

	s.AddRoute(http.MethodGet, "/getUser", handleAssemble)

	_ = s.Start(":8080")
}
```

也就是说,**让使用者自行将多个处理函数封装为1个,然后交给WEB框架注册路由**.

#### 7.3.4 结论

不需要支持`AddRoutes()`方法.

理由:把"将多个函数组合成1个函数"的职责交由使用者去实现即可

而且,如果需要支持`AddRoutes()`方法,还会带来一些其他问题:

1. 如果允许注册多个函数,那么在实现的时候就要考虑,**若其中一个HandleFunc执行失败了,是否还允许继续执行后续的HandleFunc?反之,如果其中一个HandleFunc要中断后续执行,该怎么中断?**
   * 中断后续执行:例如共有5个HandleFunc,但执行完第2个之后,业务逻辑上判断后续的HandleFunc不需要执行了,即为中断后续执行
2. 站在使用者的视角上来看,由于`AddRoutes()`方法中的`handles`是不定长参数,因此使用时可能出现一个函数都不传的情况.这种情况在编译期间不会被发现

另外,Echo框架虽然不支持传入多个注册函数的功能,但它保留了传入的HandleFunc为nil的可能性.实际上Echo是**将中间件注册逻辑和路由注册逻辑合并在了一起**:

```go
// GET registers a new GET route for a path with matching handler in the router
// with optional route-level middleware.
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
	return e.Add(http.MethodGet, path, h, m...)
}
```

### 7.4 AddRoute方法的衍生方法

针对不同HTTP方法的注册API,都可以**委托给`AddRoute()`方法**.这种设计思路很常用.

在我们的设计中,我们**不认为针对HTTP动词的路由注册方法是框架的核心方法**.**保持接口简洁,但这并不意味着实现不能复杂**.

因此,我们将`GET()`/`POST()`等方法实现在`HTTPServer`结构体上.

* 工程结构如下:

```
(base) root@yuanhong 03-serveHTTP % tree ./     
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
└── serverInterface.go

0 directories, 5 files
```

* `httpServer.go`:

```go
package serveHTTP

import (
	"net"
	"net/http"
)

// 为确保HTTPServer结构体为Server接口的实现而定义的变量
var _ Server = &HTTPServer{}

// HTTPServer HTTP服务器
type HTTPServer struct {
}

// ServeHTTP WEB框架入口
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// TODO implement me
	panic("implement me")
}

// Start 启动WEB服务器
func (s *HTTPServer) Start(addr string) error {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	// 在监听端口之后,启动服务之前做一些操作
	// 例如在微服务框架中,启动服务之前需要注册服务

	return http.Serve(l, s)
}

// AddRoute 注册路由
func (s *HTTPServer) AddRoute(method string, path string, handleFunc HandleFunc) {
	// TODO: implement me
	panic("implement me")
}

// GET 注册GET请求路由
func (s *HTTPServer) GET(path string, handleFunc HandleFunc) {
	s.AddRoute(http.MethodGet, path, handleFunc)
}

// POST 注册POST请求路由
func (s *HTTPServer) POST(path string, handleFunc HandleFunc) {
	s.AddRoute(http.MethodPost, path, handleFunc)
}
```

其他文件代码无变化.

这样设计的话,就需要注意在使用时,初始化HTTPServer结构体时,不能将其类型声明为Server接口:

```go
package serveHTTP

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

func TestServer(t *testing.T) {
	// Tips: 初始化HTTPServer结构体时,不能将其类型声明为Server接口的实现,因为一些方法被我们定义在了
	// Tips: 实现上,而不是在接口上,所以不能将其类型声明为Server接口的实现.即:不能写成如下形式:
	// var s Server = &HTTPServer{}
	s := &HTTPServer{}

	// 注册1个处理函数到路由
	handleFoo := func(ctx Context) { fmt.Println("处理第1件事") }
	handleBar := func(ctx Context) { fmt.Println("处理第2件事") }

	// 将2个处理函数封装为1个处理函数
	handleAssemble := func(ctx Context) {
		handleFoo(ctx)
		handleBar(ctx)
	}

	s.AddRoute(http.MethodGet, "/getUser", handleAssemble)

	_ = s.Start(":8080")
}
```

`AddRoute()`方法最终会和路由树交互,我们后面再考虑.

![框架设计思想](https://1407465062-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FyoSgzgsmuneFp7VNWUbE%2Fuploads%2Fgit-blob-aa562307ee1fc90cea620d253667a1faf9fab459%2F%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3.png?alt=media)

如上图示,**保持核心API的方法较少,而衍生API的方法较多**,是我们这个框架的核心设计思想.因此在设计的时候,需要区分核心API和衍生API.

## PART8. `ServeHTTP()`方法

`ServeHTTP()`方法是作为`http`包与WEB框架的关联点,需要在`ServeHTTP()`方法内部完成如下操作:

* **构建起WEB框架的上下文**
* **查找路由树,并执行命中路由的代码**

说句再直白一点的话:请求进来了就执行`ServeHTTP()`方法

### 8.1 定义Context

那么问题来了,一个HandleFunc的上下文是什么?首先至少肯定有**请求**和**响应**.因此我们的Context就需要能够将**请求**和**响应**"带"到具体的HandleFunc中去.

* 工程结构如下:

```
(base) root@yuanhong 03-serveHTTP % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
└── serverInterface.go

0 directories, 5 files
```

* `context.go`:

```go
package serveHTTP

import "net/http"

// Context HandleFunc的上下文
type Context struct {
	// Req 请求
	Req *http.Request
	// Resp 响应
	Resp http.ResponseWriter
}
```

* `httpServer.go`:修改了`ServeHTTP()`方法的代码.增加了构建上下文的部分.

```go
package serveHTTP

import (
	"net"
	"net/http"
)

// 为确保HTTPServer结构体为Server接口的实现而定义的变量
var _ Server = &HTTPServer{}

// HTTPServer HTTP服务器
type HTTPServer struct {
}

// ServeHTTP WEB框架入口
func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 构建上下文
	ctx := &Context{
		Req:  r,
		Resp: w,
	}

	// 查找路由树并执行命中的业务逻辑
	s.serve(ctx)

	// TODO implement me
	panic("implement me")
}

// serve 查找路由树并执行命中的业务逻辑
func (s *HTTPServer) serve(ctx *Context)  {
	// TODO implement me
	panic("implement me")
}

// Start 启动WEB服务器
func (s *HTTPServer) Start(addr string) error {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	// 在监听端口之后,启动服务之前做一些操作
	// 例如在微服务框架中,启动服务之前需要注册服务

	return http.Serve(l, s)
}

// AddRoute 注册路由
func (s *HTTPServer) AddRoute(method string, path string, handleFunc HandleFunc) {
	// TODO: implement me
	panic("implement me")
}

// GET 注册GET请求路由
func (s *HTTPServer) GET(path string, handleFunc HandleFunc) {
	s.AddRoute(http.MethodGet, path, handleFunc)
}

// POST 注册POST请求路由
func (s *HTTPServer) POST(path string, handleFunc HandleFunc) {
	s.AddRoute(http.MethodPost, path, handleFunc)
}
```

## PART9. 面试要点

### 9.1 HTTP服务器的生命周期?

一般来说就是启动、运行和关闭.在这三个阶段的前后都可以插入生命周期回调.一般来说,面试生命周期,多半都是为了后边问生命周期回调.例如说怎么做WEB服务的服务发现?就是利用生命周期回调的启动后回调,将WEB服务注册到服务中心.

### 9.2 HTTP Server的功能?

记住在不同的框架里面有不同的叫法.比如说在Gin里面叫做Engine,它们的基本功能都是提供路由注册、生命周期控制以及作为与http包结合的桥梁.

## 附录

### 问题1. 生命周期回调究竟是个啥?
