# 2.01 Server详解与面试要点

## PART1. 学习路线

![学习路线](/files/OuOap0BtvsNwfPy3I2jR)

## PART2. Web框架的核心

### 2.1 Web框架的构成

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

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

### 2.2 Server

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

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

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

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

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

![Server特性](/files/Qw7WVaUUtw63FVnJH6pg)

## 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()`方法最终会和路由树交互,我们后面再考虑.

![框架设计思想](/files/a62oP5ow9ludQmRNt4yl)

如上图示,**保持核心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. 生命周期回调究竟是个啥?


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://go.sai.show/part02.server/2.01-server-xiang-jie-yu-mian-shi-yao-dian.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
