merge from upstream

This commit is contained in:
yetist
2012-10-30 23:19:20 +08:00
18 changed files with 392 additions and 174 deletions

24
10.2.md
View File

@@ -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 @@
//图片文件
<img src="views/{{.VV.Lang}}/images/btn.png">
这样我们在本地化视图以及资源的时候采用这种方式就可以很容易的进行扩展了。
采用这种方式来本地化视图以及资源时,我们就可以很容易的进行扩展了。
## 总结
本小节介绍了如何使用存储本地资源,本地资源有些需要通过转换函数,有通过lang设置但是最都是通过key-value的方式存储了相应的Locale对应数据然后通过转换函数通过key读取出相应Locale信息如果是文本信息就直接输出,如果是时间日期或者货币需要结合`fmt.Printf`函数处理才能转换成正确的信息展示而对于不同Locale的视图和资源是最简单的只要在路径里面增加lang就可以实现了。
本小节介绍了如何使用存储本地资源,有时需要通过转换函数来实现,有通过lang设置,但是最都是通过key-value的方式存储Locale对应数据,在需要时取出相应Locale信息,如果是文本信息就直接输出,如果是时间日期或者货币,则需要先通过`fmt.Printf`或其他格式化函数处理而对于不同Locale的视图和资源是最简单的只要在路径里面增加lang就可以实现了。
## links
* [目录](<preface.md>)

162
10.3.md
View File

@@ -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
* [目录](<preface.md>)
* 上一节: [本地化资源](<10.2.md>)

31
11.1.md Normal file
View File

@@ -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
* [目录](<preface.md>)
* 上一节: [错误处理,调试和测试](<11.md>)
* 下一节: [使用GDB调试](<11.2.md>)

6
11.2.md Normal file
View File

@@ -0,0 +1,6 @@
# 11.2 使用GDB调试
## links
* [目录](<preface.md>)
* 上一节: [错误处理](<11.1.md>)
* 下一节: [Go怎么写测试用例](<11.3.md>)

6
11.3.md Normal file
View File

@@ -0,0 +1,6 @@
# 11.3 Go怎么写测试用例
## links
* [目录](<preface.md>)
* 上一节: [使用GDB调试](<11.2.md>)
* 下一节: [小结](<11.4.md>)

6
11.4.md Normal file
View File

@@ -0,0 +1,6 @@
# 11.4 小结
## links
* [目录](<preface.md>)
* 上一节: [Go怎么写测试用例](<11.3.md>)
* 下一节: [部署与维护](<12.md>)

21
11.md Normal file
View File

@@ -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
* [目录](<preface.md>)
* 上一章: [第十章总结](<10.4.md>)
* 下一节: [错误处理](<11.1.md>)

74
2.2.md
View File

@@ -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`
这就是全部吗NoGo还支持复数。它的默认类型是`complex128`64位实数+64位虚数。如果需要小一些的也有`complex64`(32位实数+32位虚数)。复数的形式为`re + imi`,其中`re`是实数部分,`im`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子:
这就是全部吗NoGo还支持复数。它的默认类型是`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

22
2.3.md
View File

@@ -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

92
3.1.md
View File

@@ -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协议/协议版本
Hostwww.iana.org //服务端的主机名
User-AgentMozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 //浏览器信息
Accepttext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 //客户端能接收的mine
Accept-Encodinggzip,deflate,sdch //是否支持流压缩
Accept-CharsetUTF-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 //主体内容长度
//空行 用来分割消息头和主体
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... //消息体
Content-Type服务器发送信息的类型
Date发送时间
Server服务器类型
- 消息体:服务器发送给客户端的页面内容
Response包中的第一行叫做状态行由HTTP协议版本号 状态码, 状态消息 三部分组成。
Response 消息中的第一行叫做状态行由HTTP协议版本号 状态码, 状态消息 三部分组成。
状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response。HTTP/1.1协议中定义了5类状态码 状态码由三位数字组成,第一个数字定义了响应的类别
状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response.
HTTP/1.1中定义了5类状态码 状态码由三位数字组成,第一个数字定义了响应的类别
- 1XX 提示信息 - 表示请求已被成功接收,继续处理
- 2XX 成功 - 表示请求已被成功接收,理解,接受
- 3XX 重定向 - 要完成请求必须进行更进一步的处理
- 4XX 客户端错误 - 请求有语法错误或请求无法实现
- 5XX 服务器端错误 - 服务器未能实现合法的请求
- 1XX 提示信息 - 表示请求已被成功接收,继续处理
- 2XX 成功 - 表示请求已被成功接收,理解,接受
- 3XX 重定向 - 要完成请求必须进行更进一步的处理
- 4XX 客户端错误 - 请求有语法错误或请求无法实现
- 5XX 服务器端错误 - 服务器未能实现合法的请求
我们看下面这个图展示了详细的返回信息左边可以看到有很多的资源返回码200是常用的表示正常信息302表示跳转。response header里面展示了详细的信息。
@@ -126,11 +124,11 @@ HTTP/1.1中定义了5类状态码 状态码由三位数字组成,第一个
### HTTP协议是无状态的和Connection: keep-alive的区别
无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。
HTTP是一个无状态的面向连接的协议无状态不代表HTTP不能保持TCP连接更不能代表HTTP使用的是UDP协议无连接
HTTP是一个无状态的面向连接的协议无状态不代表HTTP不能保持TCP连接更不能代表HTTP使用的是UDP协议面对无连接)。
从HTTP/1.1起默认都开启了Keep-Alive保持连接特性简单地说当一个网页打开完成后客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭如果客户端再次访问这个服务器上的网页会继续使用这一条已经建立的连接。
从HTTP/1.1起默认都开启了Keep-Alive保持连接特性简单地说当一个网页打开完成后客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭如果客户端再次访问这个服务器上的网页会继续使用这一条已经建立的TCP连接。
Keep-Alive不会永久保持连接它有一个保持时间可以在不同服务器软件如Apache中设这个时间
Keep-Alive不会永久保持连接它有一个保持时间可以在不同服务器软件如Apache中设这个时间
## 请求实例
@@ -138,9 +136,9 @@ Keep-Alive不会永久保持连接它有一个保持时间可以在不同
上面这张图我们可以了解到整个的通讯过程同时细心的读者是否注意到了一点一个URL请求但是左边栏里面为什么会有那么多的资源请求(这些都是静态文件go对于静态文件有专门的处理方式)。
这个就是浏览器的功能第一次请求url服务器端返回的是html页面然后浏览器开始渲染html当解析到src里面的img资源、css资源、js资源浏览器就会自动发起http请求把需要的资源从服务器请求回来,然后浏览器就会宣布渲染出来,这样就是完整展现在我们面前的一个网页了
这个就是浏览器的一个功能第一次请求url服务器端返回的是html页面然后浏览器开始渲染HTML当解析到HTML DOM里面的图片连接css脚本和js脚本的链接浏览器就会自动发起一个请求静态资源的HTTP请求获取相对应的静态资源,然后浏览器就会渲染出来,最终将所有资源整合、渲染,完整展现在我们面前的屏幕上
>网页优化方面有一项是减少http请求次数就是把尽量多的css和js合并在一起尽量做到少的http请求
>网页优化方面有一项措施是减少HTTP请求次数就是把尽量多的css和js资源合并在一起,目的是尽量减少网页请求静态资源的次数,提高网页加载速度,同时减缓服务器的压力
## links
* [目录](<preface.md>)

8
3.2.md
View File

@@ -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
* [目录](<preface.md>)

22
3.3.md
View File

@@ -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为"/"的请求到函数sayhelloNameDefaultServeMux会调用ServeHTTP方法这个方法内部其实就是调用sayhelloName本身最后通过写入response的信息反馈到客户端。
那么如何具体分配到相应的函数来处理请求呢conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数我们前面例子传递的是nil也就是为空那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则,当请求uri为"/",路由就会转到函数sayhelloNameDefaultServeMux会调用ServeHTTP方法这个方法内部其实就是调用sayhelloName本身最后通过写入response的信息反馈到客户端。
详细的整个流程如下图所示:
![](images/3.3.illustrator.png?raw=true)
至此我们的三个问题已经全部得到了解答你现在对于Go如何让Web跑起来的是否已经大概清楚了呢?
至此我们的三个问题已经全部得到了解答你现在对于Go如何让Web跑起来的是否已经基本了解呢?
## links

2
6.2.md
View File

@@ -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函数中初始化

2
8.1.md
View File

@@ -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)

78
9.6.md
View File

@@ -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`接口,这个接口实现了三个功能:

0
genepub.sh Executable file → Normal file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 141 KiB

View File

@@ -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 网站错误处理