diff --git a/10.2.md b/10.2.md index 15fee775..af6a67f8 100644 --- a/10.2.md +++ b/10.2.md @@ -1,7 +1,7 @@ # 10.2 本地化资源 前面小节我们介绍了如何设置Locale,设置好Locale之后我们需要解决的问题就是如何存储相应的Locale对应的信息呢?这里面的信息包括:文本信息、时间和日期、货币值、图片、包含文件以及视图等资源。那么接下来我们讲对这些信息一一进行介绍,Go语言中我们把这些格式信息存储在JSON中,然后通过合适的方式展现出来。(接下来以中文和英文两种语言对比举例,存储格式文件en.json和zh-CN.json) ## 本地化文本消息 -文本信息是我们编写Web应用中最长用到的,也是本地化资源中最多的信息,你想要以适合本地的语言显示文本信息,那么就需要建立相应的map来维护一个key-value的关系,在打印消息之前从map中去获取相应的字符串,例如下面这个例如,一个包含英文和中文的食品名称的简单的map,以及一个从该map中抽取单词的函数。 +本信息是编写Web应用中最常用到的,也是本地化资源中最多的信息,想要以适合本地语言的方式来显示文本信息,可行的一种方案是:建立需要的语言相应的map来维护一个key-value的关系,在输出之前按需从适合的map中去获取相应的文本,如下是一个简单的示例: package main @@ -34,24 +34,24 @@ } -上面实现了简单的不同locale的文本翻译,实现了中文和英文对于同一个key显示不同的语言,上面实现了中文语言的文本消息,如果想切换到英文版本,只需要把lang设置问en即可。 +上面示例演示了不同locale的文本翻译,实现了中文和英文对于同一个key显示不同语言的实现,上面实现了中文的文本消息,如果想切换到英文版本,只需要把lang设置为en即可。 -但是有些时候并不是简单的key-value就能实现所有的文本翻译的,例如"I am 30 years old",中文表达是"我今年30岁了",而这个30是一个变量,这个时候我们可以结合`fmt.Printf`函数来实现,请看下面的代码: +有些时候仅是key-value替换是不能满足需要的,例如"I am 30 years old",中文表达是"我今年30岁了",而此处的30是一个变量,该怎么办呢?这个时候,我们可以结合`fmt.Printf`函数来实现,请看下面的代码: en["how old"] ="I am %d years old" cn["how old"] ="我今年%d岁了" fmt.Printf(msg(lang, "how old"), 30) -上面的示例代码主要是为了演示我们内部实现的思路,其实最后数据是存储在JSON里面的,map我们可以通过`json.Unmarshal`获取对应的数据。 +上面的示例代码仅用以演示内部的实现方案,而实际数据是存储在JSON里面的,所以我们可以通过`json.Unmarshal`来为相应的map填充数据。 ## 本地化日期和时间 -时间和日期对于不同的地区会显示不同的时间点,而且相应的显示时间格式也是不同的,例如中文环境下可能显示:`2012年10月24日 星期三 23时11分13秒 CST`,而在英文环境下可能显示:`Wed Oct 24 23:11:13 CST 2012`。这里面我们需要解决两点: +因为时区的关系,同一时刻,在不同的地区,表示是不一样的,而且因为Locale的关系,时间格式也不尽相同,例如中文环境下可能显示:`2012年10月24日 星期三 23时11分13秒 CST`,而在英文环境下可能显示:`Wed Oct 24 23:11:13 CST 2012`。这里面我们需要解决两点: 1. 时区问题 2. 格式问题 -时区问题我们可以通过`time.Now`返回当前locale的时间,但是我们可以通过`time.LoadLocation(name string)`获取相应地区的时区,如果中文地区,那么`name=Asia/Shanghai`,如果在美国那么可以设置`name=America/Chicago`,你可以在`$GOROOT/lib/time`包中找到time.zip,里面有每个地区的时区定义。详细的请看下面的例子: +$GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为了获得对应于当前locale的时间,我们应首先使用`time.LoadLocation(name string)`获取相应于地区的locale,比如`Asia/Shanghai`或`America/Chicago`对应的时区信息,然后再利用此信息与调用`time.Now`获得的Time对象协作来获得最终的时间。详细的请看下面的例子(该例子采用上面例子的一些变量): en["time_zone"]="America/Chicago" cn["time_zone"]="Asia/Shanghai" @@ -61,7 +61,7 @@ t = t.In(loc) fmt.Println(t.Format(time.RFC3339)) -格式问题我们可以通过类似上面文本信息的方式解决这个问题,就如上面刚开始显示的时间格式一样,中文和英文的显示信息完全不同,那我们就可以通过如下方式来实现 +我们可以通过类似处理文本格式的方式来解决时间格式的问题,举例如下: en["date_format"]="%Y-%m-%d %H:%M:%S" cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒" @@ -78,7 +78,7 @@ } ## 本地化货币值 -各个地区的货币表示也不一样,我们想要根据不同的Locale显示不同的货币金额需要实现如上时间格式的处理函数,详细的实现请看下面代码: +各个地区的货币表示也不一样,处理方式也与日期差不多,细节请看下面代码: en["money"] ="USD %d" cn["money"] ="¥%d元" @@ -91,7 +91,7 @@ ## 本地化视图和资源 -我们在展现不同Locale的时候可能会根据不同的Locale采用不同的视图,这些视图里面包含不同的图片、css、js等各种静态资源。那么我们如何来处理这些信息呢?首先我们需要组织对应的locale文件信息,请看下面的文件目录安排: +我们可能会根据Locale的不同来展示视图,这些视图包含不同的图片、css、js等各种静态资源。那么应如何来处理这些信息呢?首先我们应按locale来组织文件信息,请看下面的文件目录安排: views |--en //英文模板 @@ -107,7 +107,7 @@ index.tpl login.tpl -有了这个文件安排之后我们就可以在渲染的地方这样来实现代码 +有了这个目录结构后我们就可以在渲染的地方这样来实现代码: s1, _ := template.ParseFiles("views"+lang+"index.tpl") @@ -123,10 +123,10 @@ //图片文件 -这样我们在本地化视图以及资源的时候采用这种方式就可以很容易的进行扩展了。 +采用这种方式来本地化视图以及资源时,我们就可以很容易的进行扩展了。 ## 总结 -本小节介绍了如何使用存储本地资源,本地资源有些需要通过转换函数,有些通过lang设置,但是最后都是通过key-value的方式存储了相应的Locale对应数据,然后通过转换函数通过key读取出来相应的Locale信息,如果是文本信息就直接输出了,如果是时间日期或者货币需要结合`fmt.Printf`函数的处理才能转换成正确的信息展示,而对于不同Locale的视图和资源是最简单的,只要在路径里面增加lang就可以实现了。 +本小节介绍了如何使用及存储本地资源,有时需要通过转换函数来实现,有时通过lang来设置,但是最终都是通过key-value的方式来存储Locale对应的数据,在需要时取出相应于Locale的信息后,如果是文本信息就直接输出,如果是时间日期或者货币,则需要先通过`fmt.Printf`或其他格式化函数来处理,而对于不同Locale的视图和资源则是最简单的,只要在路径里面增加lang就可以实现了。 ## links * [目录]() diff --git a/10.3.md b/10.3.md index 0f9d8a70..416e80d4 100644 --- a/10.3.md +++ b/10.3.md @@ -1,36 +1,178 @@ # 10.3 国际化站点 前面小节介绍了如何处理本地化资源,即Locale一个相应的配置文件,那么如果处理多个的本地化资源呢?而对于一些我们经常用到的例如:简单的文本翻译、时间日期、数字等如果处理呢?本小节将一一解决这些问题。 - ## 管理多个本地包 -在我们开发一个应用的时候,首先我们应该知道这个Web应用要支持多少个语言,例如首先这个Web应用需要支持中文和英文,也许以后会支持其他语言,但是结构已经有了,所以扩展非常容易。那么我们设计如下:Locale文件夹在config/locales下,假设你要支持中文和英语,那么你需要在这个文件夹下放置en.json和zh.json。大概的内容如下所示: +在开发一个应用的时候,首先我们要决定是只支持一种语言,还是多种语言,如果要支持多种语言,我们则需要制定一个组织结构,以方便将来更多语言的添加。在此我们设计如下:Locale有关的文件放置在config/locales下,假设你要支持中文和英文,那么你需要在这个文件夹下放置en.json和zh.json。大概的内容如下所示: # zh.json - zh: - submit: '提交' - create: '创建' + { + "zh": { + "submit": "提交", + "create": "创建" + } + } #en.json - en: - submit: 'Submit' - create: 'Create' + { + "en": { + "submit": "Submit", + "create": "Create" + } + } -然后调用go-i18n包里面注册一下这个目录,这样Go会加载config/locales目录下的locale文件: +为了支持国际化,在此我们使用了一个国际化相关的包——go-i18n(https://github.com/astaxie/go-i18n),首先我们向go-i18n包注册config/locales这个目录,以加载所有的locale文件 Tr:=i18n.NewLocale() Tr.LoadPath("config/locales") +这个包使用起来很简单,你可以通过下面的方式进行测试: + + fmt.Println(Tr.Translate("submit")) + //输出Submit + Tr.SetLocale("zn") + fmt.Println(Tr.Translate("submit")) + //输出“递交” ## 自动加载本地包 +上面我们介绍了如何自动加载自定义语言包,其实go-i18n库已经预加载了很多默认的格式信息,例如时间格式、货币格式,用户可以在自定义配置时改写这些默认配置,请看下面的处理过程: + + + //加载默认配置文件,这些文件都放在go-i18n/locales下面 + + //文件命名zh.json、en-json、en-US.json等,可以不断的扩展支持更多的语言 + + func (il *IL) loadDefaultTranslations(dirPath string) error { + dir, err := os.Open(dirPath) + if err != nil { + return err + } + defer dir.Close() + + names, err := dir.Readdirnames(-1) + if err != nil { + return err + } + + for _, name := range names { + fullPath := path.Join(dirPath, name) + + fi, err := os.Stat(fullPath) + if err != nil { + return err + } + + if fi.IsDir() { + if err := il.loadTranslations(fullPath); err != nil { + return err + } + } else if locale := il.matchingLocaleFromFileName(name); locale != "" { + file, err := os.Open(fullPath) + if err != nil { + return err + } + defer file.Close() + + if err := il.loadTranslation(file, locale); err != nil { + return err + } + } + } + + return nil + } + +通过上面的方法加载配置信息到默认的文件,这样我们就可以在我们没有自定义时间信息的时候执行如下的代码获取对应的信息: + + //locale=zh的情况下,执行如下代码: + + fmt.Println(Tr.Time(time.Now())) + //输出:2009年1月08日 星期四 20:37:58 CST + + fmt.Println(Tr.Time(time.Now(),"long")) + //输出:2009年1月08日 + + fmt.Println(Tr.Money(11.11)) + //输出:¥11.11 ## template mapfunc +上面我们实现了多个语言包的管理和加载,而一些函数的实现是基于逻辑层的,例如:"Tr.Translate"、"Tr.Time"、"Tr.Money"等,虽然我们在逻辑层可以利用这些函数把需要的参数进行转换后在模板层渲染的时候直接输出,但是如果我们想在模版层直接使用这些函数该怎么实现呢?不知你是否还记得,在前面介绍模板的时候说过:Go语言的模板支持自定义模板函数,下面是我们实现的方便操作的mapfunc: 1. 文本信息 + + 文本信息调用`Tr.Translate`来实现相应的信息转换,mapFunc的实现如下: + + func I18nT(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Translate(s) + } + + 注册函数如下: + + t.Funcs(template.FuncMap{"T": I18nT}) + + 模板中使用如下: + + {{.V.Submit | T}} + + 2. 时间日期 -3. 数字 + + 时间日期调用`Tr.Time`函数来实现相应的时间转换,mapFunc的实现如下: + + func I18nTimeDate(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Time(s) + } + + 注册函数如下: + + t.Funcs(template.FuncMap{"TD": I18nTimeDate}) + + 模板中使用如下: + + {{.V.Now | TD}} +3. 货币信息 + + 货币调用`Tr.Money`函数来实现相应的时间转换,mapFunc的实现如下: + + func I18nMoney(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Money(s) + } + + 注册函数如下: + + t.Funcs(template.FuncMap{"M": I18nMoney}) + + 模板中使用如下: + + {{.V.Money | M}} ## 总结 +通过这小节我们知道了如何实现一个多语言包的Web应用,通过自定义语言包我们可以方便的实现多语言,而且通过配置文件能够非常方便的扩充多语言,默认情况下,go-i18n会自定加载一些公共的配置信息,例如时间、货币等,我们就可以非常方便的使用,同时为了支持在模板中使用这些函数,也实现了相应的模板函数,这样就允许我们在开发Web应用的时候直接在模板中通过pipeline的方式来操作多语言包。 + ## links * [目录]() * 上一节: [本地化资源](<10.2.md>) diff --git a/11.1.md b/11.1.md new file mode 100644 index 00000000..f068429e --- /dev/null +++ b/11.1.md @@ -0,0 +1,31 @@ +# 11.1 错误处理 +Go语言设计的时候主要的特点是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐式的东西,Go在设计错误的时候也是一样。我们知道C语言里面返回错误是使用-1或者nil之类的返回信息表示错误,但是对于使用者来说这个返回值根本不知道什么意思,而Go里面当发生异常时返回一个Error类型,通过前面编写的代码,我们发现在Go语言里面有很多地方都使用了Error类型,很多函数都有这个Error返回。例如`os.Open`函数当打开文件失败时返回一个不为nil的error + + func Open(name string) (file *File, err error) + +下面这个例子通过`os.Open`打开一个文件,如果出错那么会执行`log.Fatal`打印出来错误信息: + + f, err := os.Open("filename.ext") + if err != nil { + log.Fatal(err) + } + +其实这样的error返回在Go语言的很多内置包里面有很多,我们这个小节将详细的介绍这些error是怎么设计的,以及在我们设计的Web应用如何更好的处理error。 +## Error类型 +error类型是一个接口类型,这是他的定义: + + type error interface { + Error() string + } + + +## 自定义Error + +## Error检测 + +## 总结 + +## links + * [目录]() + * 上一节: [错误处理,调试和测试](<11.md>) + * 下一节: [使用GDB调试](<11.2.md>) \ No newline at end of file diff --git a/11.2.md b/11.2.md new file mode 100644 index 00000000..8a7c3a03 --- /dev/null +++ b/11.2.md @@ -0,0 +1,6 @@ +# 11.2 使用GDB调试 + +## links + * [目录]() + * 上一节: [错误处理](<11.1.md>) + * 下一节: [Go怎么写测试用例](<11.3.md>) \ No newline at end of file diff --git a/11.3.md b/11.3.md new file mode 100644 index 00000000..92557aeb --- /dev/null +++ b/11.3.md @@ -0,0 +1,6 @@ +# 11.3 Go怎么写测试用例 + +## links + * [目录]() + * 上一节: [使用GDB调试](<11.2.md>) + * 下一节: [小结](<11.4.md>) \ No newline at end of file diff --git a/11.4.md b/11.4.md new file mode 100644 index 00000000..8ab0f766 --- /dev/null +++ b/11.4.md @@ -0,0 +1,6 @@ +# 11.4 小结 + +## links + * [目录]() + * 上一节: [Go怎么写测试用例](<11.3.md>) + * 下一节: [部署与维护](<12.md>) \ No newline at end of file diff --git a/11.md b/11.md new file mode 100644 index 00000000..79f911c3 --- /dev/null +++ b/11.md @@ -0,0 +1,21 @@ +# 11 错误处理,调试和测试 +我们经常会看到很多程序员大部分的"编程"时间都花费在检查bug和修复bug上。无论你是在编写修改代码还是重构系统,几乎都是花费大量的时间在进行故障排除和测试,外界都觉得我们程序员是设计师,能够把一个系统从无做到有,是一项很伟大的工作,而且是相当有趣的工作,但事实上我们每天都是徘回在排错、调试、测试之间。当然如果你有良好的习惯和技术方案来直面这些问题,那么你就有可能将排错时间减到最少,而尽可能的将时间花费在更有价值的事情上。 + +但是遗憾的是很多程序员不愿意在错误处理、调试和测试能力上下工夫,导致后面应用上线之后查找错误、定位问题花费更多的时间。所以我们在设计应用之前就做好错误处理规划、测试用例等,那么将来修改代码、升级系统都讲变得简单。 + +开发Web应用过程中,错误自然难免,那么如何更好的找到错误原因,解决问题呢?11.1小节将介绍Go语言中如何处理错误,如何设计自己的包、函数的错误处理,11.2小节将介绍如何使用GDB来调试我们的程序,动态运行情况下各种变量信息,运行情况的监控和调试。 + +11.3小节将对Go语言中的单元测试进行深入的探讨,并示例如何来编写单元测试,Go的单元测试规则规范如何定义,以保证以后升级修改运行相应的测试代码就可以进行最小化的测试。 + +长期以来,培养良好的调试、测试习惯一直是很多程序员逃避的事情,所以现在你不要再逃避了,就从你现在的项目开发,从学习Go Web开发开始养成良好的习惯。 + +## 目录 + * 1 [错误处理](11.1.md) + * 2 [使用GDB调试](11.2.md) + * 3 [Go怎么写测试用例](11.3.md) + * 4 [小结](11.4.md) + +## links + * [目录]() + * 上一章: [第十章总结](<10.4.md>) + * 下一节: [错误处理](<11.1.md>) \ No newline at end of file diff --git a/2.2.md b/2.2.md index cc773217..69f09297 100644 --- a/2.2.md +++ b/2.2.md @@ -1,12 +1,12 @@ # 2.2 Go基础 -这小节我们将要介绍如何定义变量、常量、Go内置类型以及一些Go程序设计中的技巧。 +这小节我们将要介绍如何定义变量、常量、Go内置类型以及Go程序设计中的一些技巧。 ## 定义变量 Go语言里面定义变量有多种方式。 -使用`var`关键字定义变量是最基本的,与C语言定义变量不同的是,Go语言变量名在变量类型前面: +使用`var`关键字是Go最基本的定义变量方式,与C语言不同的是Go把变量类型放在变量名后面: //定义一个名称为“variableName”,类型为"type"的变量 var variableName type @@ -29,7 +29,7 @@ Go语言里面定义变量有多种方式。 */ var vname1, vname2, vname3 type= v1, v2, v3 -你是不是觉得上面这样的定义有点复杂?没关系,因为Go语言的设计者也发现了,我们来让它变得简单一点。我们可以直接忽略类型声明,那么上面的东西变成这样了: +你是不是觉得上面这样的定义有点繁琐?没关系,因为Go语言的设计者也发现了,有一种写法可以让它变得简单一点。我们可以直接忽略类型声明,那么上面的代码变成这样了: /* 定义三个变量,它们分别初始化相应的值 @@ -38,7 +38,7 @@ Go语言里面定义变量有多种方式。 */ var vname1, vname2, vname3 = v1, v2, v3 -你还是觉得上面的有些复杂?好吧,我觉得也是。让我们继续简化: +你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化: /* 定义三个变量,它们分别初始化相应的值 @@ -47,7 +47,7 @@ Go语言里面定义变量有多种方式。 */ vname1, vname2, vname3 := v1, v2, v3 -现在是不是看上去非常的简单了?`:=`这个符号直接取代了`var`和`type`,这样的代码是不是很简洁?这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,比如说用它来定义全局变量。 +现在是不是看上去非常简洁了?`:=`这个符号直接取代了`var`和`type`,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用`var`方式来定义全局变量。 `_`(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,我们将值`35`赋予`b`,并同时丢弃`34`: @@ -63,29 +63,27 @@ Go对于已声明但未使用的变量会在编译阶段报错,比如下面的 ## 常量 -所谓常量,也就是在编译阶段就确定下来的值,而程序在运行时则无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。 +所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时则无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。 它的语法如下: const constantName = value + //如果需要,也可以明确指定常量的类型: + const Pi float32 = 3.1415926 下面是一些常量声明的例子: const Pi = 3.1415926 const i = 10000 const MaxThread = 10 - const prefix = 'astaxie_' - -当然如果需要,也可以明确指定常量的类型: - - const Pi float32 = 3.1415926 + const prefix = "astaxie_" ## 内置基础类型 ### Boolean -在Go中,布尔值的类型为`bool`,可用的值是`true`或`false`,默认为`false`。 +在Go中,布尔值的类型为`bool`,值是`true`或`false`,默认为`false`。 //示例代码 var isActive bool // 全局变量声明 @@ -99,30 +97,30 @@ Go对于已声明但未使用的变量会在编译阶段报错,比如下面的 ### 数值类型 -整数类型有无符号和带符号两种。Go同时支持`int`和`uint`,这两种类型的长度相同,但具体长度取决于编译器的实现。当前的gc和gccgo编译器在32位和64位平台上都使用32位来表示`int`和`uint`,但未来在64位平台上可能增加到64位。Go里面也有直接定义好位数的类型:`rune`, `int8`, `int16`, `int32`, `int64`和`byte`, `uint8`, `uint16`, `uint32`, `uint64`。`rune`是`int32`的别称,`byte`是`uint8`的别称。 +整数类型有无符号和带符号两种。Go同时支持`int`和`uint`,这两种类型的长度相同,但具体长度取决于不同编译器的实现。当前的gcc和gccgo编译器在32位和64位平台上都使用32位来表示`int`和`uint`,但未来在64位平台上可能增加到64位。Go里面也有直接定义好位数的类型:`rune`, `int8`, `int16`, `int32`, `int64`和`byte`, `uint8`, `uint16`, `uint32`, `uint64`。其中`rune`是`int32`的别称,`byte`是`uint8`的别称。 >需要注意的一点是,这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。 > >如下的代码会产生错误 > -> var a int8 -> var b int32 -> c:=a + b +>> var a int8 +>> var b int32 +>> c:=a + b +> >另外,尽管int的长度是32 bit, 但int 与 int32并不可以互用。 浮点数的类型有`float32`和`float64`两种(没有`float`类型),默认是`float64`。 -这就是全部吗?No!Go还支持复数。它的默认类型是`complex128`(64位实数+64位虚数)。如果需要小一些的,也有`complex64`(32位实数+32位虚数)。复数的形式为`re + imi`,其中`re`是实数部分,`im`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子: +这就是全部吗?No!Go还支持复数。它的默认类型是`complex128`(64位实数+64位虚数)。如果需要小一些的,也有`complex64`(32位实数+32位虚数)。复数的形式为`RE + IMi`,其中`RE`是实数部分,`IM`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子: var c complex64 = 5+5i - + //output: (5+5i) fmt.Printf("Value is: %v", c) -它会打印:(5+5i) ### 字符串 -我们在上一节中讲过,Go中的字符串都是用`UTF-8`的形式编码的。字符串通过用一对双引号(`""`)或反引号(`` ` ```` ` ``)括起来定义,它的类型是`string`。 +我们在上一节中讲过,Go中的字符串都是采用`UTF-8`字符集编码。字符串是用一对双引号(`""`)或反引号(`` ` `` `` ` ``)括起来定义,它的类型是`string`。 //示例代码 var frenchHello string // 声明变量为字符串的一般方法 @@ -148,7 +146,7 @@ Go对于已声明但未使用的变量会在编译阶段报错,比如下面的 fmt.Printf("%s\n", s2) -Go中可以使用`+`来链接两个字符串: +Go中可以使用`+`操作符来连接两个字符串: s := "hello," m := " world" @@ -157,7 +155,7 @@ Go中可以使用`+`来链接两个字符串: 修改字符串也可写为: - s := "hello" + s := "hello" s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作 fmt.Printf("%s\n", s) @@ -237,8 +235,8 @@ Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采 ### Go程序设计的一些规则 Go之所以会那么简洁,是因为它有一些默认的行为: -- 大写字母开头的变量是已导出的,也就是其它包可以读取的,类似`class`中`public`的概念;小写字母开头的就是未导出的 -- 大写字母开头的函数也是一样,相当于`public`的函数;小写字母开头的就是类似`private` +- 大写字母开头的变量是可导出的,也就是其它包可以读取的,是公用变量;小写字母开头的就是不可导出的,是私有变量。 +- 大写字母开头的函数也是一样,相当于`class`中的带`public`关键词的公有函数;小写字母开头的就是有`private`关键词的私有函数。 ## array、slice、map @@ -252,7 +250,8 @@ Go之所以会那么简洁,是因为它有一些默认的行为: var arr [10]int // 声明了一个int类型的数组 arr[0] = 42 // 数组下标是从0开始的 arr[1] = 13 // 赋值操作 - fmt.Printf("The first element is %d\n", arr[0]) // 获取数据 + fmt.Printf("The first element is %d\n", arr[0]) // 获取数据,返回42 + fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0 由于长度也是数组类型的一部分,因此`[3]int`与`[4]int`是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的`slice`类型了。 @@ -264,7 +263,7 @@ Go之所以会那么简洁,是因为它有一些默认的行为: c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度 -也许你会说,我想数组里面还是数组,能实现吗?当然咯,Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组: +也许你会说,我想数组里面的值还是数组,能实现吗?当然咯,Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组: // 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素 doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} @@ -279,9 +278,9 @@ Go之所以会那么简洁,是因为它有一些默认的行为: ### slice -在很多应用场景中,数组并不能满足我们的需求。在刚开始时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫`slice` +在很多应用场景中,数组并不能满足我们的需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”。在Go里面这种数据结构叫`slice` -`slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向底层的一个`array`,`slice`的声明也可以像`array`一样,只是不需要长度。 +`slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向一个底层`array`,`slice`的声明也可以像`array`一样,只是不需要长度。 // 和声明array一样,只是少了长度 var fslice []int @@ -306,7 +305,7 @@ Go之所以会那么简洁,是因为它有一些默认的行为: b = ar[3:5] // b的元素是:ar[3]和ar[4] ->注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明``slice``时,方括号内没有任何字符。 +>注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明`slice`时,方括号内没有任何字符。 它们的数据结构如下所示 @@ -316,7 +315,7 @@ slice有一些简便的操作 - `slice`的默认开始位置是0,`ar[:n]`等价于`ar[0:n]` - `slice`的第二个序列默认是数组的长度,`ar[n:]`等价于`ar[n:len(ar)]` - - `slice`如果从一个数组里面直接获取,可以这样`ar[:]`,因为默认第一个序列是0,第二个是数组的长度,即等价于`ar[0:len(ar)]` + - 如果从一个数组里面直接获取`slice`,可以这样`ar[:]`,因为默认第一个序列是0,第二个是数组的长度,即等价于`ar[0:len(ar)]` 下面这个例子展示了更多关于`slice`的操作: @@ -365,7 +364,7 @@ slice有一些简便的操作 `map`也就是Python中字典的概念,它的格式为`map[keyType]valueType` -我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`只有`int`的`key`,而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。 +我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`的`key`只能是`int`类型,而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。 // 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化 var numbers map[string] int @@ -376,8 +375,7 @@ slice有一些简便的操作 numbers["three"] = 3 fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据 - // 打印出来如下: - // 第三个数字是: 3 + // 打印出来如:第三个数字是: 3 这个`map`就像我们平常看到的表格一样,左边列是`key`,右边列是值 @@ -416,15 +414,19 @@ slice有一些简便的操作 `make`用于内建类型(`map`、`slice` 和`channel`)的内存分配。`new`用于各种类型的内存分配。 -内建函数`new`本质上说跟其它语言中的同名函数功能一样:`new(T)`分配了零值填充的`T`类型的内存空间,并且返回其地址,即一个`*T`类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型`T`的零值。有一点非常重要:`new`返回指针。 +内建函数`new`本质上说跟其它语言中的同名函数功能一样:`new(T)`分配了零值填充的`T`类型的内存空间,并且返回其地址,即一个`*T`类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型`T`的零值。有一点非常重要: -内建函数`make(T, args)`与`new(T)`有着不同的功能,它只能创建`slice`、`map`和`channel`,并且返回一个有初始值(非零)的`T`类型,而不是`*T`。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个`slice`,是一个包含指向数据(内部`array`)的指针、长度和容量的三项描述符;在这些项目被初始化之前,`slice`为`nil`。对于`slice`、`map`和`channel`来说,`make`初始化了内部的数据结构,填充适当的值。`make`返回初始化后的(非零)值。 +>`new`返回指针。 + +内建函数`make(T, args)`与`new(T)`有着不同的功能,make只能创建`slice`、`map`和`channel`,并且返回一个有初始值(非零)的`T`类型,而不是`*T`。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个`slice`,是一个包含指向数据(内部`array`)的指针、长度和容量的三项描述符;在这些项目被初始化之前,`slice`为`nil`。对于`slice`、`map`和`channel`来说,`make`初始化了内部的数据结构,填充适当的值。 + +>`make`返回初始化后的(非零)值。 下面这个图详细的解释了`new`和`make`之间的区别。 ![](images/2.2.makenew.png?raw=true) -关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值。通常为 0。 +关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为0。 此处罗列 部分类型 的 “零值” int 0 diff --git a/2.3.md b/2.3.md index 53f42dbf..60bef660 100644 --- a/2.3.md +++ b/2.3.md @@ -1,11 +1,11 @@ # 2.3 流程和函数 这小节我们要介绍Go里面的流程控制以及函数操作 ## 流程控制 -流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的描述来表达很复杂的事情。 +流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑。流程控制包含分三大类:条件判断,循环控制和无条件跳转。 ### if -`if`也许是所有语言中最常见的了,它的语法概括起来就是:`如果满足条件就做某事,否则做另一件事` +`if`也许是各种编程语言中最常见的了,它的语法概括起来就是:如果满足条件就做某事,否则做另一件事。 -Go里面`if`条件语法中不需要括号,如下代码所示 +Go里面`if`条件判断语句中不需要括号,如下代码所示 if x > 10 { fmt.Println("x is greater than 10") @@ -13,7 +13,7 @@ Go里面`if`条件语法中不需要括号,如下代码所示 fmt.Println("x is less than 10") } -Go的`if`还有一个强大的地方就是条件里面允许声明一个变量,这个变量的作用域只能在该条件中,出了这个条件就不起作用了,如下所示 +Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示 // 计算获取值x,然后根据x返回的大小,判断是否大于10。 if x := computedValue(); x > 10 { @@ -37,7 +37,7 @@ Go的`if`还有一个强大的地方就是条件里面允许声明一个变量 ### goto -Go有`goto`语句——请明智地使用它。用`goto`跳转到一定是当前函数内定义的标签。例如假设这样一个循环: +Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前函数内定义的标签。例如假设这样一个循环: func myFunc() { i := 0 @@ -47,7 +47,7 @@ Go有`goto`语句——请明智地使用它。用`goto`跳转到一定是当前 goto Here //跳转到Here去 } -标签名是大小写敏感的。 +>标签名是大小写敏感的。 ### for Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下: @@ -56,7 +56,7 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 //... } -`expression1`、`expression2`和`expression3`都是表达式,其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的,`expression2`是条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。 +`expression1`、`expression2`和`expression3`都是表达式,其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的,`expression2`是用来条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。 一个例子比上面讲那么多更有用,那么我们看看下面的例子吧: @@ -224,7 +224,7 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 上面这个里面我们可以看到`max`函数有两个参数,它们的类型都是`int`,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int),默认为离它最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。 ### 多个返回值 -Go语言和C相比,更先进的地方,其中一点就是能够返回多个值,也许这个思想来源于Python。 +Go语言比C更先进的特性,其中一点就是函数能够返回多个值。 我们直接上代码看例子 @@ -246,7 +246,7 @@ Go语言和C相比,更先进的地方,其中一点就是能够返回多个 fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) } -上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议,最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档不易读。 +上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。 func SumAndProduct(A, B int) (add int, Multiplied int) { add = A+B @@ -265,7 +265,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 } ### 传值与传指针 -当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为我们作用在了copy上面。 +当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。 为了验证我们上面的说法,我们来看一个例子 @@ -320,7 +320,7 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢? - 传指针使得多个函数能操作同一个对象。 -- 传指针比较轻量级 (8 bytes)只是传内存地址,我们可以通过指针高效的传递大的结构体。如果传值的话,那么每次传递, 在copy上面就会花费大量的时间和内存。所以记住了,当你要传递大的结构体的时候,用指针是一个明智的选择。 +- 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。 - Go语言中`string`,`slice`,`map`这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变`slice`的长度,则仍需要取地址传递指针) ### defer diff --git a/3.1.md b/3.1.md index 7c526e3c..8998288b 100644 --- a/3.1.md +++ b/3.1.md @@ -1,21 +1,21 @@ # 3.1 Web工作方式 -我们平时浏览网页的时候,打开浏览器,输入网址,按下回车键,然后就出来了内容。在这个看似简单的行为背后,到底隐藏了些什么呢? +我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢? -上面这个普通的浏览过程,底层其实是这样做的:浏览器是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP和服务器建立socket连接,发送http请求信息,服务器接收到请求信息之后,处理相应的请求,返回http response信息,客户端接收到http信息之后开始渲染这些http response里面的content信息,断开和服务器的socket链接。 +对于普通的上网过程,系统其实是这样做的:浏览器本身是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。 ![](images/3.1.web2.png?raw=true) - 一个Web服务器也被称为HTTP服务器,它通过HTTP协议与客户端通信。这个客户端通常指的是Web浏览器(手机端客户端其实内部也是浏览器实现)。 + 一个Web服务器也被称为HTTP服务器,它通过HTTP协议与客户端通信。这个客户端通常指的是Web浏览器(其实手机端客户端内部也是浏览器实现的)。 -Web服务器的工作原理简单的可以归纳为: +Web服务器的工作原理可以简单地归纳为: -- 客户机通过socket建立到服务器的连接 -- 客户端向服务器发送请求,http协议包,请求转化成服务器对应的文档 -- 服务器向客户机发送应答,http协议包,请求的如果包含有动态语言的部分,那么动态语言把相应的数据结果返回给客户端 -- 客户机与服务器断开。客户端解释HTML文档,在客户端屏幕上显示结果 +- 客户机通过TCP/IP协议建立到服务器的TCP连接 +- 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档 +- 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端 +- 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果 -一个简单的事务处理事件就是这样实现的,看起来很复杂,做起来其实是挺简单的,需要注意的是客户机与服务器之间的通信是非连接的,也就是当服务器发送了应答后就与客户机断开连接,等待下一次请求。 +一个简单的HTTP事务就是这样实现的,看起来很复杂,原理其实是挺简单的。需要注意的是客户机与服务器之间的通信是非持久连接的,也就是当服务器发送了应答后就与客户机断开连接,等待下一次请求。 ## URL和DNS解析 我们浏览网页都是通过URL访问的,那么URL到底是怎么样的呢? @@ -62,24 +62,22 @@ HTTP协议是Web工作的核心,所以要了解清楚Web的工作方式就需 HTTP是一种让Web服务器与浏览器(客户端)通过Internet发送与接收数据的协议,它建立在TCP协议之上,一般采用TCP的80端口。它是一个请求、响应协议--客户端发出一个请求,服务器响应这个请求。在HTTP中,客户端总是通过建立一个连接与发送一个HTTP请求来发起一个事务。服务器不能主动去与客户端联系,也不能给客户端发出一个回调连接。客户端与服务器端都可以提前中断一个连接。例如,当浏览器下载一个文件时,你可以通过点击“停止”键来中断文件的下载,关闭与服务器的HTTP连接。 -HTTP协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系,对HTTP服务器来说,它并不知道这两个请求来自同一个客户端。 为了解决这个问题, Web程序引入了Cookie机制来维护状态。 +HTTP协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系,对HTTP服务器来说,它并不知道这两个请求是否来自同一个客户端。为了解决这个问题, Web程序引入了Cookie机制来维护连接的可持续状态。 >HTTP协议是建立在TCP协议之上的,因此TCP攻击一样会影响HTTP的通讯,例如比较常见的一些攻击:SYN Flood是当前最流行的DoS(拒绝服务攻击)与DdoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。 -### HTTP请求信息(浏览器信息) +### HTTP请求包(浏览器信息) -我们先来看看Request消息的结构, Request 消息分为3部分,第一部分叫Request line, 第二部分叫Request header,第三部分是body。header和body之间有个空行,详细的如下所示 +我们先来看看Request包的结构, Request包分为3部分,第一部分叫Request line(请求行), 第二部分叫Request header(请求头),第三部分是body(主体)。header和body之间有个空行,请求包的例子所示: - - 请求行:GET/POST(流的组织(请求)方式) URL(地址+目录) 版本号 - - 请求头: - - Host:客户端IP和端口 - User-Agent:浏览器信息 - Accept:客户端能接收的数据类型 - Accept-encoding:是否支持压缩的流 - Accept-charset:客户端字符编码集 - - 空行:分割请求头和消息体 - - 消息体:请求的参数 + GET /domains/example/ HTTP/1.1 //请求行: 请求方法 请求URI HTTP协议/协议版本 + Host:www.iana.org //服务端的主机名 + User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 //浏览器信息 + Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 //客户端能接收的mine + Accept-Encoding:gzip,deflate,sdch //是否支持流压缩 + Accept-Charset:UTF-8,*;q=0.5 //客户端字符编码集 + //空行,用于分割请求头和消息体 + //消息体,请求资源参数,例如POST传递的参数 我们通过fiddler抓包可以看到如下请求信息 @@ -87,36 +85,36 @@ HTTP协议是无状态的,同一个客户端的这次请求和上次请求是 ![](images/3.1.httpPOST.png?raw=true) -我们可以看到GET请求消息体为空,POST请求带有消息体。 +**我们可以看到GET请求消息体为空,POST请求带有消息体**。 -Http协议定义了很多与服务器交互的方法,最基本的有4种,分别是GET,POST,PUT,DELETE. 一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,改,增,删4个操作。 我们最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息. +HTTP协议定义了很多与服务器交互的请求方法,最基本的有4种,分别是GET,POST,PUT,DELETE. 一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,改,增,删4个操作。 我们最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息. 我们看看GET和POST的区别 1. GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的Body中. 2. GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制. 3. GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码。 -### HTTP响应信息(服务器信息) -我们再来看看HTTP的response信息,他的结构如下: +### HTTP响应包(服务器信息) +我们再来看看HTTP的response包,他的结构如下: - - 状态行:HTTP版本 服务器状态(比如:404找不到...) 描述信息 - - 响应头 + HTTP/1.1 200 OK //状态行 + Server: nginx/1.0.8 //服务器使用的WEB软件名及版本 + Date:Date: Tue, 30 Oct 2012 04:14:25 GMT //发送时间 + Content-Type: text/html //服务器发送信息的类型 + Transfer-Encoding: chunked //表示发送HTTP包是分段发的 + Connection: keep-alive //保持连接状态 + Content-Length: 90 //主体内容长度 + //空行 用来分割消息头和主体 + 网页优化方面有一项是减少http请求次数,就是把尽量多的css和js合并在一起,尽量做到少的http请求。 +>网页优化方面有一项措施是减少HTTP请求次数,就是把尽量多的css和js资源合并在一起,目的是尽量减少网页请求静态资源的次数,提高网页加载速度,同时减缓服务器的压力。 ## links * [目录]() diff --git a/3.2.md b/3.2.md index 0ea4397e..9f0f6ea3 100644 --- a/3.2.md +++ b/3.2.md @@ -50,13 +50,13 @@ 我们看到上面的代码,要编写一个web服务器很简单,只要调用http包的两个函数就可以了。 ->- 如果你以前是PHP程序员,那你也许就会问,我们的nginx、apache服务器不需要吗?Go就是不需要这些,因为他直接就监听tcp端口了,做了nginx做的事情,然后sayhelloName这个其实就是我们写的逻辑函数了,也就是php里面的执行逻辑类似。 +>如果你以前是PHP程序员,那你也许就会问,我们的nginx、apache服务器不需要吗?Go就是不需要这些,因为他直接就监听tcp端口了,做了nginx做的事情,然后sayhelloName这个其实就是我们写的逻辑函数了,也就是php里面的控制层(controller)函数类似。 ->- 如果你以前是python程序员,那么你一定听说过tornado,这个代码和他是不是很像,对,没错,go就是拥有类似python这样动态语言的特性,写web应用很方便。 +>如果你以前是python程序员,那么你一定听说过tornado,这个代码和他是不是很像,对,没错,go就是拥有类似python这样动态语言的特性,写web应用很方便。 ->- 如果你以前是ruby程序员,那么和ROR的/script/server启动有点类似。 +>如果你以前是ruby程序员,那么和ROR的/script/server启动有点类似。 -我们看到Go通过简单的几行代码就已经运行起来一个web服务了,而且这个Web服务内部已经支持了高并发的特性,我将会在接下来的两个小节里面详细的讲解一下go是如何实现Web高并发的。 +我们看到Go通过简单的几行代码就已经运行起来一个web服务了,而且这个Web服务内部有支持高并发的特性,我将会在接下来的两个小节里面详细的讲解一下go是如何实现Web高并发的。 ## links * [目录]() diff --git a/3.3.md b/3.3.md index 197b7365..1cb06a15 100644 --- a/3.3.md +++ b/3.3.md @@ -1,9 +1,9 @@ # 3.3 Go如何使得Web工作 -前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单的应用了一个net/http包就方便的搭建起来了。那么他底层到底是怎么做的呢?万变不离其宗,Go也离不开我们第一小节介绍的Web工作方式。 +前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单应用一个net/http包就方便的搭建起来了。那么Go在底层到底是怎么做的呢?万变不离其宗,Go的Web服务工作也离不开我们第一小节介绍的Web工作方式。 -## 对应web工作方式的几个概念 +## web工作方式的几个概念 -以下均是服务器端的相应概念 +以下均是服务器端的几个概念 Request:用户请求的信息,用来解析用户的请求信息,包括post、get、cookie、url等信息 @@ -15,15 +15,15 @@ Handler:处理请求和生成返回信息的处理逻辑 ## 分析http包运行机制 -如下图所示,是Go实现Web工作模式的流程图 +如下图所示,是Go实现Web服务的工作模式的流程图 ![](images/3.3.http.png?raw=true) -(1) 创建listen socket, 在指定的端口监听, 等待客户端请求的到来。 +1. 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。 -(2) listen socket接受客户端的请求, 得到client socket, 接下来通过client socket与客户端通信。 +2. Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信。 -(3) 处理客户端的请求, 首先从client socket读取http请求的协议头, 如果是post协议, 还可能要读取客户端上传的数据, 然后扔给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过client socket写给客户端。 +3. 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过Client Socket写给客户端。 这整个的过程里面我们只要了解清楚下面三个问题,也就知道Go是如何让Web运行起来了 @@ -31,17 +31,17 @@ Handler:处理请求和生成返回信息的处理逻辑 - 如何接收客户端请求? - 如何分配handler? -前面小节的代码里面我们可以看到,Go是通过一个函数来操作这个事情的`ListenAndServe`来监听起来的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层起的是TCP协议,然后监控了我们设置的端口。 +前面小节的代码里面我们可以看到,Go是通过一个函数来操作这个事情的`ListenAndServe`来监听起来的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。 -监控之后如何接收客户端的请求呢?上面的监控端口之后,就调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次起一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户来的请求都是goroutine去服务,相互不影响。 +监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Sere(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。 -那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来把url匹配到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个就是注册了相应的路由,url为"/"的请求到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。 +那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。 详细的整个流程如下图所示: ![](images/3.3.illustrator.png?raw=true) -至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经大概清楚了呢? +至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经基本了解呢? ## links diff --git a/6.2.md b/6.2.md index e5055948..36f5d5d9 100644 --- a/6.2.md +++ b/6.2.md @@ -48,7 +48,7 @@ session的基本原理是由服务器为每个会话维护一份信息数据, return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil } -Go实现整个的流程应该也是这样的,在main包中创建一个全部的session管理器 +Go实现整个的流程应该也是这样的,在main包中创建一个全局的session管理器 var globalSessions *session.Manager //然后在init函数中初始化 diff --git a/8.1.md b/8.1.md index c16550b4..b821fc3b 100644 --- a/8.1.md +++ b/8.1.md @@ -215,7 +215,7 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 conn.Write([]byte(daytime)) // don't care about return value // we're finished with this client } - func checkError(err os.Error) { + func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) diff --git a/9.6.md b/9.6.md index 504b2d7d..c506d8d1 100644 --- a/9.6.md +++ b/9.6.md @@ -11,23 +11,17 @@ "fmt" ) - const ( - base64Table = "123QRSTUabcdVWXYZHijKLAWDCABDstEFGuvwxyzGHIJklmnopqr234560178912" - ) - - var coder = base64.NewEncoding(base64Table) - func base64Encode(src []byte) []byte { - return []byte(coder.EncodeToString(src)) + return []byte(base64.StdEncoding.EncodeToString(src)) } func base64Decode(src []byte) ([]byte, error) { - return coder.DecodeString(string(src)) + return base64.StdEncoding.DecodeString(string(src)) } func main() { // encode - hello := "hello world" + hello := "你好,世界! hello world" debyte := base64Encode([]byte(hello)) fmt.Println(debyte) // decode @@ -43,6 +37,7 @@ fmt.Println(string(enbyte)) } + ## 高级加解密 Go语言的`crypto`里面支持对称加密的高级加解密包有: @@ -54,40 +49,51 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有: package main - import ( - "crypto/aes" - . "fmt" - "os" - ) + import ( + "crypto/aes" + "crypto/cipher" + "fmt" + "os" + ) - func main() { - msg := "My name is Astaxie" - // some key, 16 Byte long - key := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} + var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} - Println("len of message: ", len(msg)) - Println("len of key: ", len(key)) - // create the new cipher - c, err := aes.NewCipher(key) - if err != nil { - Println("Error: NewCipher(%d bytes) = %s", len(key), err) - os.Exit(-1) - } + func main() { + //需要去加密的字符串 + plaintext := []byte("My name is Astaxie") + //如果传入加密串的话,plaint就是传入的字符串 + if len(os.Args) > 1 { + plaintext = []byte(os.Args[1]) + } - out := make([]byte, len(msg)) + //aes的加密字符串 + key_text := "astaxie12798akljzmknm.ahkjkljl;k" + if len(os.Args) > 2 { + key_text = os.Args[2] + } - c.Encrypt(out, []byte(msg)) // encrypt the first half - //c.Encrypt(msgbuf[16:32], out[16:32]) // encrypt the second half + fmt.Println(len(key_text)) - Println("len of encrypted: ", len(out)) - Println(">> ", out) + // 创建加密算法aes + c, err := aes.NewCipher([]byte(key_text)) + if err != nil { + fmt.Printf("Error: NewCipher(%d bytes) = %s", len(key_text), err) + os.Exit(-1) + } - // // now we decrypt our encrypted text - plain := make([]byte, len(out)) - c.Decrypt(plain, out) + //加密字符串 + cfb := cipher.NewCFBEncrypter(c, commonIV) + ciphertext := make([]byte, len(plaintext)) + cfb.XORKeyStream(ciphertext, plaintext) + fmt.Printf("%s=>%x\n", plaintext, ciphertext) + + // 解密字符串 + cfbdec := cipher.NewCFBDecrypter(c, commonIV) + plaintextCopy := make([]byte, len(plaintext)) + cfbdec.XORKeyStream(plaintextCopy, ciphertext) + fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy) + } - Println("msg: ", string(plain)) - } 上面通过调用函数`aes.NewCipher`(参数key必须是16、24或者32位的[]byte,分别对应AES-128, AES-192或AES-256算法),返回了一个`cipher.Block`接口,这个接口实现了三个功能: diff --git a/genepub.sh b/genepub.sh old mode 100755 new mode 100644 diff --git a/images/2.2.makenew.png b/images/2.2.makenew.png index 11a9baa3..00f74179 100644 Binary files a/images/2.2.makenew.png and b/images/2.2.makenew.png differ diff --git a/preface.md b/preface.md index 184d2399..cb49f263 100644 --- a/preface.md +++ b/preface.md @@ -65,11 +65,11 @@ - 10.2 [本地化资源](10.2.md) - 10.3 [国际化站点](10.3.md) - 10.4 [小结](10.4.md) -* 11.错误处理,故障排除和测试 - - 11.1 错误处理 - - 11.2 使用GDB调试 - - 11.3 Go怎么写测试用例 - - 11.4 小结 +* 11.[错误处理,调试和测试](11.md) + - 11.1 [错误处理](11.1.md) + - 11.2 [使用GDB调试](11.2.md) + - 11.3 [Go怎么写测试用例](11.3.md) + - 11.4 [小结](11.4.md) * 12.部署与维护 - 12.1 应用日志 - 12.2 网站错误处理