From 34085caadcf90c82316daf3a614b86af02842d8a Mon Sep 17 00:00:00 2001 From: gzdaijie Date: Sun, 11 Aug 2019 02:43:39 +0800 Subject: [PATCH] add gee intro --- .gitignore | 1 + README.md | 20 ++++++++------ doc/gee.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ doc/gee/gee.jpg | Bin 0 -> 16889 bytes 4 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 .gitignore create mode 100644 doc/gee.md create mode 100644 doc/gee/gee.jpg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 9143c7a..8f2eb76 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ -# 7天用Go从零实现Web框架 +# # 7天用Go从零实现Web框架Gee -## 第一天:前置知识(http.Handler接口) -## 第二天:Tire树实现路由(Router) -## 第三天:支持模板(HTML Template) -## 第四天:JSON & RESTful API -## 第五天:支持中间件(Middleware) -## 第六天:简单鉴权(BASIC AUTH) -## 第七天:异常错误处理(Panic) \ No newline at end of file +![Gee](doc/gee/gee.jpg) + +## [目录](https://geektutu.com/post/gee.html) + +- 第一天:前置知识(http.Handler接口) +- 第二天:Tire树实现路由(Router) +- 第三天:支持模板(HTML Template) +- 第四天:JSON & RESTful API +- 第五天:支持中间件(Middleware) +- 第六天:简单鉴权(BASIC AUTH) +- 第七天:异常错误处理(Panic) \ No newline at end of file diff --git a/doc/gee.md b/doc/gee.md new file mode 100644 index 0000000..6805316 --- /dev/null +++ b/doc/gee.md @@ -0,0 +1,72 @@ +--- +title: 7天用Go从零实现Web框架Gee教程 +date: 2019-08-11 02:10:10 +description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,从零设计一个Web框架 +tags: +- Go +categories: +- 从零实现 +keywords: +- Gee教程 +- 从零实现Web框架 +- 动手写 +- from scratch +image: post/gee/gee.jpg +github: https://github.com/geektutu/7days-gee-golang-web +--- + +![gee](gee/gee.jpg) + +## 设计一个框架 + +大部分时候,我们需要实现一个 Web 应用,第一反应是应该使用哪个框架。不同的框架设计理念和提供的功能有很大的差别。比如 Python 语言的 `django`和`flask`,前者大而全,后者小而美。Go语言/golang 也是如此,新框架层出不穷,比如`Beego`,`Gin`,`Iris`等。那为什么不直接使用标准库,而必须使用框架呢?在设计一个框架之前,我们需要回答框架核心为我们解决了什么问题。只有理解了这一点,才能想明白我们需要在框架中实现什么功能。 + +我们先看看标准库`net/http`如何处理一个请求。 + +```go +func main() { + http.HandleFunc("/", handler) + http.HandleFunc("/count", counter) + log.Fatal(http.ListenAndServe("localhost:8000", nil)) +} + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) +} +``` + +`net/http`提供了基础的Web功能,即监听端口,映射静态路由,解析HTTP报文。一些Web开发中简单的需求并不支持,需要手工实现。 + +- 动态路由:例如`hello/:name`,`hello/*`这类的规则。 +- 鉴权:没有分组/统一鉴权的能力,需要在每个路由映射的handler中实现。 +- 模板:没有统一简化的HTML机制。 +- ... + +当我们离开框架,使用基础库时,需要频繁手工处理的地方,就是框架的价值所在。但并不是每一个频繁处理的地方都适合在框架中完成。Python有一个很著名的Web框架,名叫[`bottle`](https://github.com/bottlepy/bottle),整个框架由`bottle.py`一个文件构成,共4400行,可以说是一个微框架。那么理解这个微框架提供的特性,可以帮助我们理解框架的核心能力。 + +- 路由(Routing):将请求映射到函数,支持动态路由。例如`'/hello/:name`。 +- 模板(Templates):使用内置模板引擎提供模板渲染机制。 +- 工具集(Utilites):提供对 cookies,headers 等处理机制。 +- 插件(Plugin):Bottle本身功能有限,但提供了插件机制。可以选择安装到全局,也可以只针对某几个路由生效。 +- ... + +## Gee 框架 + +这个教程将使用 Go 语言实现一个简单的 Web 框架,起名叫做`Gee`,[`geektutu.com`](https://geektutu.com)的前三个字母。我第一次接触的 Go 语言的 Web 框架是`Gin`,`Gin`的代码总共是14K,其中测试代码9K,也就是说实际代码量只有**5K**。`Gin`也是我非常喜欢的一个框架,与Python中的`Flask`很像,小而美。 + +`7天实现Gee框架`这个教程的很多设计,包括源码,参考了`Gin`,大家可以看到很多`Gin`的影子。 + +时间关系,同时为了尽可能地简洁明了,这个框架中的很多部分实现的功能都很简单,但是尽可能地体现一个框架核心的设计原则。例如`Router`的设计,虽然支持的动态路由规则有限,但为了性能考虑匹配算法是用`Trie树`实现的,`Router`最重要的指标之一便是性能。 + +希望这个教程能够对你有所启发,如果对 Gee 有任何好的建议,欢迎提[issues - Github](https://github.com/geektutu/7days-gee-golang-web/issues) 和 PR。教程中的任何问题,可以直接在文章末尾评论。 + +## 目录 + +- 第一天:前置知识(http.Handler接口) +- 第二天:Tire树实现路由(Router) +- 第三天:支持模板(HTML Template) +- 第四天:JSON & RESTful API +- 第五天:支持中间件(Middleware) +- 第六天:简单鉴权(BASIC AUTH) +- 第七天:异常错误处理(Panic) + diff --git a/doc/gee/gee.jpg b/doc/gee/gee.jpg new file mode 100644 index 0000000000000000000000000000000000000000..63a884d3fcbf2d0cb55d9dedeab4afaccc080fee GIT binary patch literal 16889 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 R`bn(3MUS?_dj)SR{|j03^V